CVE-2019-5073
An exploitable information exposure vulnerability exists in the iocheckd service “I/O-Check” functionality of WAGO PFC 200. A specially crafted set of packets can cause an external tool to fail, resulting in uninitialized stack data to be copied to the response packet buffer. An attacker can send unauthenticated packets to trigger this vulnerability.
WAGO PFC200 Firmware version 03.01.07(13) WAGO PFC200 Firmware version 03.00.39(12) WAGO PFC100 Firmware version 03.00.39(12)
https://www.wago.com/us/pfc200 https://www.wago.com/us/pfc100
5.3 CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CWE-201: Information Exposure Through Sent Data
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 function to get stdout output from tools that run as subprocesses on the system. When the subprocess fails, the stack buffer remains uninitialized. When responding to the messages outlined below, uninitialized stack data is copied to the response buffer and sent over the network. When used in conjunction with other vulnerabilities this information can be used to bypass security protections such as ASLR.
There are three different messages that call the vulnerable code that triggers this vulnerability as outlined in the PoC code below.
The type and amount of data exposed by this vulnerability varies greatly between firmware revisions. This is due to stack data being copied until the first NULL is encountered since sprintf is used.
[Annotated Disassembly / Decompilation output]
.text:000149E4 PUSH {R4-R11,LR}
.text:000149E8 SUB SP, SP, #0x2FC
.text:000149EC ADD R3, SP, #0x320+var_228
.text:000149F0 ADD R5, SP, #0x320+var_268
.text:000149F4 MOV R1, #0x40
.text:000149F8 MOV R11, R0 ; the first argument to this function is the response packet buffer
.text:000149FC MOV R2, #0x800
.text:00014A00 MOV R0, R3
.text:00014A04 MOVT R2, #2
.text:00014A08 STR R3, [SP,#0x320+var_2FC]
.text:00014A0C BL sub_12EA0
.text:00014A10 MOV R0, R5
.text:00014A14 BL sub_1340C
.text:00014A18 ADD R9, SP, #0x1B8
.text:00014A1C ADD R0, SP, #0x38
.text:00014A20 ADD R4, SP, #0x278 ; stack buffer stored in R4
.text:00014A24 BL sub_13308
.text:00014A28 ADD R0, SP, #0x78
.text:00014A2C BL sub_13378
.text:00014A30 ADD R6, SP, #0x138
.text:00014A34 MOV R0, R9
.text:00014A38 ADD R8, SP, #0x178
.text:00014A3C BL sub_13308
.text:00014A40 MOV R1, #0x40 ; '@'
.text:00014A44 MOV R0, R4 ; stack buffer used to get output from an external tool
.text:00014A48 MOV R2, #0x207CC ; string in .rodata containing command to execute
.text:00014A50 ADD R10, SP, #0x1F8
.text:00014A54 BL sub_12EA0 ; runs external tool and on success copies output to the buffer in the first argument
sub_12EA0 executes the command and if it succeeds, reads stdout into the buffer in the first argument
.text:00012EA0 sub_12EA0
.text:00012EA0
.text:00012EA0 PUSH {R4-R8,LR}
.text:00012EA4 MOV R6, R0
.text:00012EA8 MOV R0, R2
.text:00012EAC MOV R4, R2
.text:00012EB0 MOV R7, R1
.text:00012EB4 BL strlen
.text:00012EB8 ADD R0, R0, #0xE
.text:00012EBC BL malloc
.text:00012EC0 MOV R2, R4
.text:00012EC4 MOV R3, #0x628
.text:00012EC8 MOV R1, #0x20E8
.text:00012ECC MOVT R3, #2
.text:00012ED0 MOVT R1, #2
.text:00012ED4 MOV R5, R0
.text:00012ED8 BL sprintf
.text:00012EDC MOV R0, R5
.text:00012EE0 MOV R1, #0x2128C
.text:00012EE8 BL popen
.text:00012EEC SUBS R4, R0, #0 ; if popen failed:
.text:00012EF0 MOVEQ R4, #7 ; return value is set to 7 and the input buffer is unchanged
.text:00012EF4 BEQ loc_12F24 ; return 7
.text:00012EF8 MOV R2, R4
.text:00012EFC MOV R1, R7
.text:00012F00 MOV R0, R6
.text:00012F04 BL fgets
.text:00012F08 CMP R0, #0
.text:00012F0C MOV R0, R4
.text:00012F10 MOVEQ R4, #8
.text:00012F14 MOVNE R4, #0
.text:00012F18 BL pclose
.text:00012F1C CMN R0, #1
.text:00012F20 MOVEQ R4, #9
.text:00012F24
.text:00012F24 loc_12F24 ; CODE XREF: sub_12EA0+54↑j
.text:00012F24 MOV R0, R5
.text:00012F28 BL free
.text:00012F2C MOV R0, R4
.text:00012F30 POP {R4-R8,PC}
Back in the caller - the return value of sub_12EA0 is ignored and it is assumed that the stack buffer contains valid data
.text:00014A58 MOV R0, R6 ; return value is ignored
Later on in the function, the stack data is copied into the response buffer
.text:00014A88 LDR R3, [SP,#0x24]
.text:00014A8C ADD R2, SP, #0x38
.text:00014A90 STR R10, [SP,#0x1C]
.text:00014A94 STR R9, [SP,#0x18]
.text:00014A98 MOV R0, R11 ; this is our response packet buffer
.text:00014A9C STR R3, [SP,#4]
.text:00014AA0 MOV R1, #0xE98
.text:00014AA4 ADD R3, SP, #0x78
.text:00014AA8 MOVT R1, #2 ; format
.text:00014AAC STR R8, [SP,#0x14]
.text:00014AB0 STR R7, [SP,#0x10]
.text:00014AB4 STR R6, [SP,#0xC]
.text:00014AB8 STR R5, [SP,#8]
.text:00014ABC STR R4, [SP] ; this is the uninitialized stack buffer being copied into the response packet
.text:00014AC0 BL sprintf
This vulnerability could be mitigated by disabling the iocheckd service “I/O-Check” via the Web-based management web application.
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
Discovered by Kelly Leuschner of Cisco Talos