HGAME 2025

web

Level 24 Pacman

进去是个吃豆人游戏,game,game!!!!!!!!!!!!!!!!!!!!!!!,达到1w分给flag,这种一般是前端网页游戏,可以去f12的网络栏验证,如果你没有寄了还没有网络请求就是纯前端,这里就是

然后先寄一次,爆了个一眼base64的假flag,这里直接去前端的2个js文件里面查找这个字符串,然后成功找到,分析其所在的函数,虽然被混淆,但是还是能看懂是结算判定,同时还有1w的16进制在语句中,所以真flag一定在这里

再仔细看发现了_SOCRE这个参数,直接去控制台修改,然后再寄掉就是真flag了

_SOCRE=10000

然后就是解码flag了,首先肯定是base64解码,得到一个有点像flag的字符串,观察发现hgame前缀是有的,不过被拆开了,跳着读发现规律,其实就是栅栏密码,即将字符串分成2栏,然后竖着读,解码得到flag

Level 47 BandBomb

页面是个文件上传功能,查看源代码后发现除了上传就是修改名字,一开始先试了试一句话木马,然后发现读取不了uploads路径,但是背景图在的静态资源是能查看的,就尝试能不能用../来将文件重命名的同时修改其在的位置,发现可行,但是还是不能运行,静态文件夹里面不解读,于是去问问R1,发现还有路径遍历覆盖EJS模板(RCE)的方法

我们可以上传一个包含恶意EJS代码的文件,然后通过../覆盖本来的ejs文件,让调用ejs模版时能够运行我们的恶意代码,这里先find命令没找到,然后猜测可能在环境变量里面,于是直接读取环境变量

<%- process.mainModule.require('child_process').execSync('env') %>

然后再用rename页面的重命名功能进行覆盖即可,然后访问页面找到flag

64a98410e0cfd70c343d0a9a6fc1ffde.png

Level 69 MysteryMessageBoard

这个题后面偷偷给了源码,发现的时候现在已经开不了环境了,不悲

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main

import (
"context"
"fmt"
"github.com/chromedp/chromedp"
"github.com/gin-gonic/gin"
"github.com/gorilla/sessions"
"log"
"net/http"
"sync"
"time"
)

var (
store = sessions.NewCookieStore([]byte("fake_key"))
users = map[string]string{
"shallot": "fake_password",
"admin": "fake_password"}
comments []string
flag = "FLAG{this_is_a_fake_flag}"
lock sync.Mutex
)

func loginHandler(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
if storedPassword, ok := users[username]; ok && storedPassword == password {
session, _ := store.Get(c.Request, "session")
session.Values["username"] = username
session.Options = &sessions.Options{
Path: "/",
MaxAge: 3600,
HttpOnly: false,
Secure: false,
}
session.Save(c.Request, c.Writer)
c.String(http.StatusOK, "success")
return
}
log.Printf("Login failed for user: %s\n", username)
c.String(http.StatusUnauthorized, "error")
}
func logoutHandler(c *gin.Context) {
session, _ := store.Get(c.Request, "session")
delete(session.Values, "username")
session.Save(c.Request, c.Writer)
c.Redirect(http.StatusFound, "/login")
}
func indexHandler(c *gin.Context) {
session, _ := store.Get(c.Request, "session")
username, ok := session.Values["username"].(string)
if !ok {
log.Println("User not logged in, redirecting to login")
c.Redirect(http.StatusFound, "/login")
return
}
if c.Request.Method == http.MethodPost {
comment := c.PostForm("comment")
log.Printf("New comment submitted: %s\n", comment)
comments = append(comments, comment)
}
htmlContent := fmt.Sprintf(`<html>
<body>
<h1>留言板</h1>
<p>欢迎,%s,试着写点有意思的东西吧,admin才不会来看你!自恋的笨蛋!</p>
<form method="post">
<textarea name="comment" required></textarea><br>
<input type="submit" value="提交评论">
</form>
<h3>留言:</h3>
<ul>`, username)
for _, comment := range comments {
htmlContent += "<li>" + comment + "</li>"
}
htmlContent += `</ul>
<p><a href="/logout">退出</a></p>
</body>
</html>`
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(htmlContent))
}
func adminHandler(c *gin.Context) {
htmlContent := `<html><body>
<p>好吧好吧你都这么求我了~admin只好勉为其难的来看看你写了什么~才不是人家想看呢!</p>
</body></html>`
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(htmlContent))
//无头浏览器模拟登录admin,并以admin身份访问/路由
go func() {
lock.Lock()
defer lock.Unlock()
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
ctx, _ = context.WithTimeout(ctx, 20*time.Second)
if err := chromedp.Run(ctx, myTasks()); err != nil {
log.Println("Chromedp error:", err)
return
}
}()
}

// 无头浏览器操作
func myTasks() chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate("/login"),
chromedp.WaitVisible(`input[name="username"]`),
chromedp.SendKeys(`input[name="username"]`, "admin"),
chromedp.SendKeys(`input[name="password"]`, "fake_password"),
chromedp.Click(`input[type="submit"]`),
chromedp.Navigate("/"),
chromedp.Sleep(5 * time.Second),
}
}

func flagHandler(c *gin.Context) {
log.Println("Handling flag request")
session, err := store.Get(c.Request, "session")
if err != nil {
c.String(http.StatusInternalServerError, "无法获取会话")
return
}
username, ok := session.Values["username"].(string)
if !ok || username != "admin" {
c.String(http.StatusForbidden, "只有admin才可以访问哦")
return
}
log.Println("Admin accessed the flag")
c.String(http.StatusOK, flag)
}
func main() {
r := gin.Default()
r.GET("/login", loginHandler)
r.POST("/login", loginHandler)
r.GET("/logout", logoutHandler)
r.GET("/", indexHandler)
r.GET("/admin", adminHandler)
r.GET("/flag", flagHandler)
log.Println("Server started at :8888")
log.Fatal(r.Run(":8888"))
}

首先就是登录,这里一开始被ai带偏了,把fake_key当秘钥就开始伪造,后面尝试之后发现直接爆破就好了,bp自带的纯粹的fvv,用网上找的一开始没爆破出来,后来一气之下开始让bp直接慢慢跑最大的字典

6541543a-4d9f-41ba-8ab6-c75f5703cff8.png

然后进去之后是个留言板,源代码里面很明显的xss漏洞,应该是得到admin的权限就可以访问/flag得到flag了

Level 25 双面人派对

没研究太久,主要是记录一下逆向脱壳

ExeinfoPe来查壳,查到为upx的壳,然后去找怎么脱壳,下载upx

upx -d 文件