CVE-2019-5185, CVE-2019-5186
An exploitable stack buffer overflow vulnerability vulnerability exists in the iocheckd service “I/O-Check” functionality of WAGO PFC 200. A specially crafted xml cache file written to a specific location on the device can cause a stack buffer overflow, resulting in a denial of service and potentially code execution. An attacker can send a specially crafted packet to trigger the parsing of this cache file.
WAGO PFC200 Firmware version 03.02.02(14)
https://www.wago.com/us/pfc200
7.0 - CVSS:3.0/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)
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 iocheckd service “I/O-Check” implements a custom configuration protocol used by WAGO controllers. The iocheckd service “I/O-Check” functionality of WAGO PFC 200 uses a file-backed cache to perform some network configuration functionality. The file used for the cache is stored at /tmp/iocheckCache.xml
which is globally writeable. During parsing of the iocheckCache.xml
file, several parameters can be used to cause buffer overflows. Due to the stack layout of the vulnerable function, the result of these overflows is invalid memory access. This causes the iocheckd program to crash, without setting a status variable within the shared memory used by the process. Depending on the compiler and optimization level used used, this vulnerability could potentially result in code execution. Exploiting this vulnerability in its basic form results in a denial of service for the following iocheckd messages. These messages will respond with an error rather than carrying out the intended functionality after exploitation:
To restore normal functionality of iocheckd, the user must reboot the device or log in as the root user and delete /dev/shm/wago_IO_Check
.
In order to exercise this vulnerability, the attacker must place the malicious xml file at /tmp/iocheckCache.xml
. All users have write access for /tmp
and can write this file. The vulnerability can be triggered by sending the BC_SaveParameter message which will cause the iocheckCache.xml
file to be parsed.
The vulnerable code exists for each node extracted from the iocheckCache.xml
file. The following example shows the vulnerable code path for the state
parameter. The code paths for other vulnerable nodes are similar to this one:
.text:0001E478 MOV R0, cur_node
.text:0001E47C BL xmlNodeGetContent
.text:0001E480 MOV R1, R4
.text:0001E484 MOV R7, R0 ; R7 contains xml node contents
...
.text:0001E5D4 MOV R1, #0x1D54
.text:0001E5D8 LDR R0, [R10,#8]
.text:0001E5DC MOVT R1, #2 ; Comparing to string `state`
.text:0001E5E0 BL xmlStrcmp
.text:0001E5E4 LDR R3, [SP,#0x868+var_840]
.text:0001E5E8 CMP R0, #0
.text:0001E5EC MOVEQ R3, R7
.text:0001E5F0 STR R3, [SP,#0x868+var_840] ; store xml contents in `var_840`
.text:0001E5F4 B loc_1E568
...
.text:0001E9E0 LDR R3, [SP,#0x868+var_840] ; `var_84c` contains xml contents of `state` node
.text:0001E9E4 CMP R3, #0
.text:0001E9E8 BEQ loc_1EBA8
.text:0001E9EC MOV R1, #0x1668
.text:0001E9F0 MOV R2, R5 ; R5 points to stack buffer sp+0x40 (also our dest). At this point the buffer contains "/etc/config-tools/config_interfaces interface=<contents of interface node>"
.text:0001E9F4 MOVT R1, #2 ; format "%s state=%s"
.text:0001E9F8 MOV R0, R5 ; dest - R5 points to stack buffer sp+0x40 which is 512 bytes in length
.text:0001E9FC BL sprintf ; overflow stack buffer sp+0x40
...
.text:0001EA00 MOV R1, R5 ; src - stack buffer sp+0x40 that was previously overflowed
.text:0001EA04 MOV R0, R4 ; dest - stack buffer sp+0x440 which is immediately adjacent to sp+0x40 on the stack
.text:0001EA08 BL strcpy ; this strcpy results in invalid memory access crashing the process because there is no NULL termination due to the overflow of the stack buffer sp+0x40
The following example shows the crash for the state
parameter. The crashes for other vulnerable nodes are similar to this one:
Thread 2 "iocheckd" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 12836.12966]
0xb6ad9698 in strcpy () from target:/lib/libc.so.6
(gdb) i r
r0 0xb652d000 3058880512
r1 0xb652cc08 3058879496
r2 0x41414141 1094795585
r3 0x41414141 1094795585
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x1010101 16843009
r8 0x2108c 135308
r9 0xb5c0b918 3049306392
r10 0x0 0
r11 0x0 0
r12 0xb652b9f0 3058874864
sp 0xb652b5a0 0xb652b5a0
lr 0x1ea0c 125452
pc 0xb6ad9698 0xb6ad9698 <strcpy+104>
cpsr 0x60070010 1611071504
fpscr 0x0 0
(gdb) bt
#0 0xb6ad9698 in strcpy () from target:/lib/libc.so.6
#1 0x0001ea0c in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
At 0x1ea28 the extracted state value from the xml file is used as an argument to /etc/config-tools/config_interfaces interface=X1 state=<contents of state node>
using sprintf()
. The destination buffer sp+0x40
is overflowed with the call to sprintf()
for any state
values that are greater than 512-len("/etc/config-tools/config_interfaces interface=X1 state=")
in length. Later, at 0x1ea08 strcpy()
is used to copy the contents of the stack buffer that was overflowed sp+0x40
into sp+0x440
. The buffer sp+0x440
is immediately adjacent to sp+0x40
on the stack. Therefore, there is no NULL termination on the buffer sp+0x40
since it overflowed into sp+0x440
. The strcpy()
will result in invalid memory access. An state
value of length 0x3c9
will cause the service to crash.
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<network>
<interfaces>
<X1>
<state>OVERFLOW</state>
</X1>
</interfaces>
</network>
</settings>
At 0x1eb9c the extracted interface element name from the xml file is used as an argument to /etc/config-tools/config_interfaces interface=<contents of interface element>
using sprintf()
. The destination buffer sp+0x40
is overflowed with the call to sprintf()
for any interface
values that are greater than 512-len("/etc/config-tools/config_interfaces interface=")
in length. Later, at 0x1ea08 strcpy()
is used to copy the contents of the stack buffer that was overflowed sp+0x40
into sp+0x440
. The buffer sp+0x440
is immediately adjacent to sp+0x40
on the stack. Therefore, there is no NULL termination on the buffer sp+0x40
since it overflowed into sp+0x440
. The strcpy()
will result in invalid memory access. An interface
value of length 0x3c4
will cause the service to crash.
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<network>
<interfaces>
<OVERFLOW>
<ip>192.168.1.30</ip>
</OVERFLOW>
</interfaces>
</network>
</settings>
This vulnerability could be mitigated by disabling iocheckd caching
#Author : Kelly Leuschner, Cisco Talos import argparse, socket
if name==”main”:
parser = argparse.ArgumentParser(description="Disable iocheckd Caching on WAGO PFC200 via iocheckd:RC_WriteRegister")
parser.add_argument('ipAddr', help='ip address of PLC')
parser.add_argument('port', type = int, help='Service protocol port number (6626)')
args = parser.parse_args()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.ipAddr, args.port))
print("Sending RC_WriteRegister message to disable iocheckd caching")
s.send(b'\x88\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x04\x00\x00\x00\x00\n\x00\x0b\x00\x00\x00')
s.recv(1024)
s.close()
2019-12-06 - Vendor Disclosure
2020-01-28 - Talos discussion about vulnerabilities with Vendor; disclosure deadline extended
2020-03-09 - Public Release
Discovered by Kelly Leuschner of Cisco Talos