Headline
CVE-2017-2831: TALOS-2017-0332 || Cisco Talos Intelligence Group
An exploitable 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. An attacker can simply send an HTTP request to the device to trigger this vulnerability.
Summary
An exploitable 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. 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.1 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
CWE
CWE-120: Buffer Copy without Checking Size of Input (‘Classic 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 the “CGIProxy.fcgi” service is started, the service will first enter the FCGX_Init function. After initializing the FastCGI service and then register a couple of message callbacks, the service will initialize the request. This request will be used in a loop in order to read requests that are submitted by the http daemon that is hosting this FastCGI server.
.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
After registering the message handlers and initializing the request, the service will enter the following loop to handle each request that was forwarded by the http daemon. This loop will receive each request, decode it, and then determine how each request should be handled.
.text:00009F6C loop_outer_1f6c
.text:00009F6C 10 01 9F E5 LDR R0, =gv_theRequest_10b74
.text:00009F70 3F FC FF EB BL FCGX_Accept_r
.text:00009F70
.text:00009F74 00 40 50 E2 SUBS R4, R0, #0
.text:00009F78 3A 00 00 1A BNE return(0)_2068
...
.text:0000A048 continue_outer_2048
.text:0000A048 34 00 9F E5 LDR R0, =gv_theRequest_10b74
.text:0000A04C F3 FB FF EB BL FCGX_Finish_r
.text:0000A050
.text:0000A050 48 20 9F E5 LDR R2, =byte_13274
.text:0000A054 00 30 A0 E3 MOV R3, #0
.text:0000A058 00 30 C2 E5 STRB R3, [R2]
.text:0000A05C
.text:0000A05C 40 20 9F E5 LDR R2, =byte_13275
.text:0000A060 00 30 C2 E5 STRB R3, [R2]
.text:0000A064 C0 FF FF EA B loop_outer_1f6c
Once calling FCGX_Accept, the function at [1] will be called. This function will determine what type of http request is being made. This can be a GET request, a POST request, or an unknown. If a GET request is made, this function will return 0 and then proceed to call the function at [2]. The function at [2] will also pass a global variable [3] which contains a number of buffers that can each be overflown.
.text:00009F7C 0C 01 9F E5 LDR R0, =gv_globalContent_280
.text:00009F80 73 FC FF EB BL nullsub_1
.text:00009F80
.text:00009F84 04 01 9F E5 LDR R0, =gv_globalContent_280
.text:00009F88 DC FC FF EB BL sub_9300 ; [1] return request type
.text:00009F8C
.text:00009F8C 01 00 50 E3 CMP R0, #1
.text:00009F90 00 50 A0 E1 MOV R5, R0
.text:00009F94 2B 00 00 8A BHI continue_outer_2048
.text:00009F98
.text:00009F98 F0 00 9F E5 LDR R0, =gv_globalContent_280 ; [3] buffer that's overwritten.
.text:00009F9C 36 FE FF EB BL sub_987C ; [2]
Inside the function, the service will use FCGX_GetParam [4] to fetch the query string as provided by the FastCGI protocol. If the query string exists, then it will calculate the strlen at [5], and then pass it through a URL decoder [6] to process any encoded characters. Immediately afterwards, the function call at [7] will check to see that the query string is no larger than 0x400 bytes. Afterwards, the service will copy the decoded string into the global buffer at [8]. The structure of the global sets aside only 0x400 bytes of space for the query. Later, this buffer will be appended to, causing a buffer overflow.
.text:0000987C sub_987C
.text:0000987C
.text:0000987C F0 40 2D E9 STMFD SP!, {R4-R7,LR}
.text:00009880 7C 71 9F E5 LDR R7, =gv_theRequest_10b74
.text:00009884 24 D0 4D E2 SUB SP, SP, #$ F987C.lv_pidString_24+0x10
.text:00009884
.text:00009888 00 40 A0 E1 MOV R4, R0 ; global content argument from caller
.text:0000988C 14 10 97 E5 LDR R1, [R7,#(dword_18B88 - 0x18B74)]
.text:00009890 70 01 9F E5 LDR R0, =str.QUERY_STRING
.text:00009894 B1 FD FF EB BL FCGX_GetParam ; [4] fetch query string
.text:00009894
.text:00009898 00 60 50 E2 SUBS R6, R0, #0
.text:0000989C 0C 00 00 1A BNE hasQueryString_18d4
...
.text:000098D4 hasQueryString_18d4
.text:000098D4 7A FD FF EB BL strlen ; [5] calculate strlen
.text:000098D8
.text:000098D8 06 10 A0 E1 MOV R1, R6
.text:000098DC 00 20 A0 E1 MOV R2, R0
.text:000098E0 04 00 A0 E1 MOV R0, R4 ; global content argument
.text:000098E4 9A FF FF EB BL sub_9754 ; [6] url decode
.text:000098E8
.text:000098E8 04 00 A0 E1 MOV R0, R4
.text:000098EC 06 10 A0 E1 MOV R1, R6
.text:000098F0 18 FE FF EB BL sub_9158 ; [7] check length
.text:000098F4
.text:000098F4 00 00 50 E3 CMP R0, #0
.text:000098F8 00 50 A0 13 MOVNE R5, #0
.text:000098FC 3D 00 00 1A BNE return(@r5)_19f8
...
.text:00009900 04 50 84 E2 ADD R5, R4, #globalContent.v_queryBuffer_0+4
.text:00009904 06 10 A0 E1 MOV R1, R6
.text:00009908 05 00 A0 E1 MOV R0, R5
.text:0000990C DE FD FF EB BL strcpy ; [8] strcpy into buffer
After the buffer has been written into the global, the following code will be executed. This code will first grab the “REMOTE_ADDR” parameter [9] and then append the “remoteIp=” string to the buffer [10]. Due to the buffer being only 0x400 bytes, this will write the string outside the bounds of the global buffer. Afterwards, the service will search for the “remoteP2P=” string in the query [11]. If this value is found, it will also be appended to the global at [12]. If not, the shorter address from the REMOTE_ADDR request will be appended. Either one of these values will write after the 0x400 bytes leading to a buffer overflow.
.text:00009910 14 10 97 E5 LDR R1, [R7,#(dword_18B88 - 0x18B74)]
.text:00009914 F4 00 9F E5 LDR R0, =str.REMOTE_ADDR
.text:00009918 90 FD FF EB BL FCGX_GetParam ; [9]
.text:0000991C 00 70 50 E2 SUBS R7, R0, #0
.text:00009920 02 00 00 0A BEQ invalidAddress_1930
.text:00009924
.text:00009924 00 30 D7 E5 LDRB R3, [R7]
.text:00009928 00 00 53 E3 CMP R3, #0
.text:0000992C 0C 00 00 1A BNE appendRemoteIp_1964
...
.text:00009964 appendRemoteIp_1964
.text:00009964 02 6B 84 E2 ADD R6, R4, #globalContent.v_buffer?_800
.text:00009968 A8 10 9F E5 LDR R1, =str.remoteIp
.text:0000996C 04 60 86 E2 ADD R6, R6, #globalContent.v_buffer?_800+4-0x800
.text:00009970 05 00 A0 E1 MOV R0, R5 ; destination
.text:00009974 3D FD FF EB BL strcat ; [10] writes outside of bounds
.text:00009978
.text:00009978 05 10 A0 E1 MOV R1, R5
.text:0000997C 04 00 A0 E1 MOV R0, R4
.text:00009980 94 20 9F E5 LDR R2, =str.remoteP2P
.text:00009984 06 30 A0 E1 MOV R3, R6
.text:00009988 FB FD FF EB BL sub_917C ; [11] search for "remoteP2P="
.text:00009988
.text:0000998C 00 00 50 E3 CMP R0, #0
.text:00009990 06 10 A0 01 MOVEQ R1, R6
.text:00009994 05 00 A0 E1 MOV R0, R5 ; dest
.text:00009998 07 10 A0 11 MOVNE R1, R7
.text:0000999C 33 FD FF EB BL strcat ; [12]
Immediately following this code, another string will be appended to the already overflow buffer. This code will grab the current process id [13], convert it to a string [14], and then write it into a temporary buffer on the stack. After this is done, the service will then append the “&pid=” string to the global buffer [15], followed by the process id that was earlier converted to a string [16].
.text:000099A0 loc_99A0
.text:000099A0 44 38 94 E5 LDR R3, [R4,#globalContent.v_pid?_844]
.text:000099A4 74 20 9F E5 LDR R2, =0x7FFD
.text:000099A8 01 30 83 E2 ADD R3, R3, #1
.text:000099AC 02 00 53 E1 CMP R3, R2
.text:000099B0 44 38 84 E5 STR R3, [R4,#globalContent.v_pid?_844]
.text:000099B4 00 30 A0 C3 MOVGT R3, #0
.text:000099B8 44 38 84 C5 STRGT R3, [R4,#globalContent.v_pid?_844]
.text:000099BC 55 FD FF EB BL getpid ; [13] get the process id
.text:000099BC
.text:000099C0 44 28 94 E5 LDR R2, [R4,#globalContent.v_pid?_844]
.text:000099C4 58 30 9F E5 LDR R3, =gv_pid?_b278
.text:000099C8 58 10 9F E5 LDR R1, =str.u
.text:000099CC 0D 40 A0 E1 MOV R4, SP
.text:000099D0 00 28 82 E1 ORR R2, R2, R0,LSL#16
.text:000099D4 00 20 83 E5 STR R2, [R3]
.text:000099D8 0D 00 A0 E1 MOV R0, SP
.text:000099DC 7A FD FF EB BL sprintf ; [14] convert to a string
.text:000099DC
.text:000099E0 44 10 9F E5 LDR R1, =str.pid
.text:000099E4 05 00 A0 E1 MOV R0, R5
.text:000099E8 20 FD FF EB BL strcat ; [15] append "&pid=" to global
.text:000099E8
.text:000099EC 05 00 A0 E1 MOV R0, R5
.text:000099F0 0D 10 A0 E1 MOV R1, SP
.text:000099F4 1D FD FF EB BL strcat ; [16] append process id string
Exploit Proof-of-Concept
To trigger this request, this can be done with the combination of command line http client and Perl for generating the buffer. The buffer’s size is 0x400 bytes. However, due to a string concatenation it can be made to overwrite data after the buffer. The usage of the “remoteP2P” parameter will cause up to 0x400 more bytes to be overwritten. The following command should trigger the vulnerability.
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?"`perl -e '$_="remoteP2P=";print $_."A"x(0x400-length($_))'`
Timeline
2017-05-08 - Vendor Disclosure
2017-06-19 - Public Release
Discovered by Claudio Bozzato and another member of Cisco Talos.