Talos Vulnerability Report

TALOS-2019-0873

WAGO PFC200 iocheckd service "I/O-Check" ReadPCBManuNum remote code execution vulnerability

December 16, 2019
CVE Number

CVE-2019-5081

Summary

An exploitable heap buffer overflow vulnerability exists in the iocheckd service “I/O-Check” functionality of WAGO PFC 200. A specially crafted set of packets can cause a heap buffer overflow, potentially resulting in code execution. An attacker can send unauthenticated packets to trigger this vulnerability.

Tested Versions

WAGO PFC200 Firmware version 03.01.07(13) WAGO PFC200 Firmware version 03.00.39(12) WAGO PFC100 Firmware version 03.00.39(12)

Product URLs

https://www.wago.com/us/pfc200 https://www.wago.com/us/pfc100

CVSSv3 Score

10.0 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H

CWE

CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)

Details

The WAGO PFC200 Controller is one of WAGO’s programmable automation controllers that boasts high cybersecurity standards by including VPN, SSL and firewall software. WAGO controllers are used in many industries including automotive, rail, power engineering, manufacturing, and building management. The WAGO PFC200 Controller communicates via both standard and custom protocols.

The service protocol (port 6626) provided by the iocheckd service supports reading and writing key,value pairs to the EEPROM of the device. The ReadPCBManuNum message processing code calls strcpy with a fixed sized heap destination buffer, where the source data is the EEPROM value for WAGONRSTR. An attacker can send an unauthenticated packet to overwrite the WAGONRSTR value in EEPROM enabling attacker controlled data to overflow this heap buffer potentially resulting in arbitrary code execution. 

[Annotated Disassembly / Decompilation output]

Here, the buffer is allocated on the heap, size 0x28:

.text:0001F520                 MOV             R1, #0x28
.text:0001F524                 LDR             R0, [R4]
.text:0001F528                 MOV             R5, R1
.text:0001F52C                 BL              realloc
.text:0001F530                 STR             R0, [R4]

Here, R5 contains the EEPROM value for WAGONRSTR, R4 contains our heap buffer allocated above.

.text:0001FCA4                 LDR             R5, [SP] ; value for WAGONRSTR read from EEPROM
.text:0001FCA8                 LDR             R0, [R4] ; this is our heap buffer size 0x28
.text:0001FCAC                 MOV             R1, R5
.text:0001FCB0                 ADD             R0, R0, #8
.text:0001FCB4                 BL              strcpy ; overflow heap buffer if length of WAGONRSTR value is > 0x28

Crash Information

These are the register values before the overflow. The heap pointer is actually -8 from r0: 0x424c8

r0             0x424d0  0x424d0 ; this is the destination buffer that will be overflowed
r1             0x42d00  0x42d00
r2             0x80000000       0x80000000
r3             0xc      0xc
r4             0xbefffbc4       0xbefffbc4
r5             0x42d00  0x42d00
r6             0x1fbf0  0x1fbf0
r7             0x0      0x0
r8             0x91c08  0x91c08
r9             0xb6ff44e0       0xb6ff44e0
r10            0x0      0x0
r11            0x30005  0x30005
r12            0x42d28  0x42d28
sp             0xbefffba8       0xbefffba8
lr             0xb6f83b30       0xb6f83b30
pc             0x1fcb4  0x1fcb4
cpsr           0x60010010       0x60010010
fpscr          0x0      0x0

Heap chunk adjacent to destination buffer before overflow:

0x424f0:        0x110005        0x39    0x7     0x630101 ; chunk: prev_size, size, data

Heap chunk adjacent to destination buffer after overflow:

0x424f0:        0x42424242      0x58585858      0x0     0x630101 ; chunk: prev_size, size, data

Backtrace isn’t helpful here so here are the registers in malloc_printerr just before abort(). The crash is when the destination buffer is freed.

$r0  : 0x2
$r1  : 0xb6be65fc  →  "*** Error in `%s': %s: 0x%s ***"
$r2  : 0xbefffea3  →  "iocheckd"
$r3  : 0xb6be6738  →  "free(): invalid next size (fast)"
$r4  : 0x2
$r5  : 0xbefffb74  →  "000424c8"
$r6  : 0xb6be6738  →  "free(): invalid next size (fast)"
$r7  : 0x000424f0  →  "BBBBXXXX"
$r8  : 0x91c08
$r9  : 0x0
$r10 : 0x0
$r11 : 0x30005
$r12 : 0x0
$sp  : 0xbefffb68  →  0xbefffb74  →  "000424c8"
$lr  : 0xb6b38a90  →   add sp,  sp,  #24
$pc  : 0xb6b32d30  →   push {r1,  r2,  r3}

Backtrace

Program received signal SIGABRT, Aborted.
#0  0xb6af67a0 in raise () from target:/lib/libc.so.6
#1  0xb6af7b08 in abort () from target:/lib/libc.so.6
#2  0xb6b32fbc in ?? () from target:/lib/libc.so.6
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

Mitigation

This vulnerability could be mitigated by disabling the iocheckd service “I/O-Check” via the Web-based management web application.

Timeline

2019-07-30 - Vendor disclosure
2019-09-06 - 30+ day follow up
2019-10-02 - 60+ day follow up; vendor acknowledged
2019-10-31 - Vendor passed to CERT@VDE for coordination; Talos extended public disclosure deadline
2019-12-16 - Public Release

Credit

Discovered by Kelly Leuschner of Cisco Talos