CVE-2019-5155
An exploitable command injection vulnerability exists in the cloud connectivity feature of WAGO PFC200. An attacker can inject operating system commands into any of the parameter values contained in the firmware update command.
WAGO PFC200 Firmware version 03.02.02(14) WAGO PFC200 Firmware version 03.01.07(13) WAGO PFC200 Firmware version 03.00.39(12)
https://www.wago.com/us/pfc200
7.2 - CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
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 Cloud Connectivity service of the WAGO PFC200 Controller allows for bi-directional communication with a variety of cloud providers including the Wago Cloud application, Microsoft Azure, IBM Cloud, AWS and SAP IoT Services. The Cloud Connectivity service enables the device to send telemetry data to the cloud and act on commands received from the cloud provider.
The Cloud Connectivity service supports Remote Firmware update through the Wago Cloud Azure IoT Hub application. When initiating a Firmware Update, the cloud sends a Firmware Update command which contains parameters that are used to configure URL’s and timeout values for the firmware update process. Each of the parameter values within the firmware update command is vulnerable to os command injection. The injected commands will run as the iot user. Web administrator privileges are required to configure the Cloud Connectivity service.
The code below shows each parameter value being extracted from the Firmware Update command stored in R10. The function sub_29900
is responsible for extracting the parameter data out of the json command.
.text:0002FD64 LDR R1, =(unk_CCE6C - 0x2FD74)
.text:0002FD68 MOV R0, R10
.text:0002FD6C ADD R1, PC, R1 ; points to string `FirmwareStorageAccount`
.text:0002FD70 BL sub_29900
.text:0002FD74 ADD R5, SP, #0xC68+var_3F8
.text:0002FD78 LDR R3, [SP,#0xC68+var_C54]
.text:0002FD7C ADD R5, R5, #8
.text:0002FD80 LDR R1, =(unk_CCE6C - 0x2FD90)
.text:0002FD84 MOV R6, R0
.text:0002FD88 ADD R1, PC, R1
.text:0002FD8C MOV R0, R5
.text:0002FD90 LDR R7, [R3]
.text:0002FD94 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:0002FD98 ADD R4, SP, #0xC68+var_BF0
.text:0002FD9C MOV R2, R5
.text:0002FDA0 MOV R1, R7
.text:0002FDA4 MOV R0, R4
.text:0002FDA8 BL sub_4ED64
.text:0002FDAC LDR R1, [SP,#0xC68+var_BF0]
.text:0002FDB0 MOV R0, R6
.text:0002FDB4 ADD R1, R1, #0x20 ; ' '
.text:0002FDB8 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_
.text:0002FDBC ADD R0, R4, #4
.text:0002FDC0 BL sub_284C4
.text:0002FDC4 MOV R0, R5
.text:0002FDC8 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0002FDCC LDR R1, =(unk_CCE84 - 0x2FDDC)
.text:0002FDD0 MOV R0, R10
.text:0002FDD4 ADD R1, PC, R1 ; points to string `FirmwareStoragePath`
.text:0002FDD8 BL sub_29900
.text:0002FDDC LDR R3, [SP,#0xC68+var_C54]
.text:0002FDE0 ADD R5, SP, #0xC68+var_408
.text:0002FDE4 LDR R1, =(unk_CCE84 - 0x2FDF8)
.text:0002FDE8 MOV R6, R0
.text:0002FDEC MOV R0, R5
.text:0002FDF0 ADD R1, PC, R1
.text:0002FDF4 LDR R7, [R3]
.text:0002FDF8 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:0002FDFC MOV R2, R5
.text:0002FE00 MOV R1, R7
.text:0002FE04 SUB R0, R4, #8
.text:0002FE08 BL sub_4ED64
.text:0002FE0C LDR R1, [SP,#0xC68+var_BF8]
.text:0002FE10 MOV R0, R6
.text:0002FE14 ADD R1, R1, #0x20 ; ' '
.text:0002FE18 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_
.text:0002FE1C SUB R0, R4, #4
.text:0002FE20 BL sub_284C4
.text:0002FE24 MOV R0, R5
.text:0002FE28 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0002FE2C LDR R1, =(unk_CCE9C - 0x2FE3C)
.text:0002FE30 MOV R0, R10
.text:0002FE34 ADD R1, PC, R1 ; points to string `FirmwareControlFile`
.text:0002FE38 BL sub_29900
.text:0002FE3C ADD R5, SP, #0xC68+var_428
.text:0002FE40 LDR R3, [SP,#0xC68+var_C54]
.text:0002FE44 ADD R5, R5, #8
.text:0002FE48 LDR R1, =(unk_CCE9C - 0x2FE58)
.text:0002FE4C MOV R6, R0
.text:0002FE50 ADD R1, PC, R1
.text:0002FE54 MOV R0, R5
.text:0002FE58 LDR R7, [R3]
.text:0002FE5C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:0002FE60 ADD R4, SP, #0xC68+var_C00
.text:0002FE64 MOV R2, R5
.text:0002FE68 MOV R1, R7
.text:0002FE6C MOV R0, R4
.text:0002FE70 BL sub_4ED64
.text:0002FE74 LDR R1, [SP,#0xC68+var_C00]
.text:0002FE78 MOV R0, R6
.text:0002FE7C ADD R1, R1, #0x20 ; ' '
.text:0002FE80 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_
.text:0002FE84 ADD R0, R4, #4
.text:0002FE88 BL sub_284C4
.text:0002FE8C MOV R0, R5
.text:0002FE90 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0002FE94 LDR R1, =(unk_CCEB4 - 0x2FEA4)
.text:0002FE98 MOV R0, R10
.text:0002FE9C ADD R1, PC, R1 ; points to string `FirmwareStorageSAS`
.text:0002FEA0 BL sub_29900
.text:0002FEA4 LDR R3, [SP,#0xC68+var_C54]
.text:0002FEA8 ADD R5, SP, #0xC68+var_438
.text:0002FEAC LDR R1, =(unk_CCEB4 - 0x2FEC0)
.text:0002FEB0 MOV R6, R0
.text:0002FEB4 MOV R0, R5
.text:0002FEB8 ADD R1, PC, R1
.text:0002FEBC LDR R7, [R3]
.text:0002FEC0 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:0002FEC4 MOV R2, R5
.text:0002FEC8 MOV R1, R7
.text:0002FECC SUB R0, R4, #8
.text:0002FED0 BL sub_4ED64
.text:0002FED4 LDR R1, [SP,#0xC68+var_C08]
.text:0002FED8 MOV R0, R6
.text:0002FEDC ADD R1, R1, #0x20 ; ' '
.text:0002FEE0 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_
.text:0002FEE4 SUB R0, R4, #4
.text:0002FEE8 BL sub_284C4
.text:0002FEEC MOV R0, R5
.text:0002FEF0 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0002FEF4 LDR R1, =(unk_CCECC - 0x2FF04)
.text:0002FEF8 MOV R0, R10
.text:0002FEFC ADD R1, PC, R1 ; points to string `TimeoutPrepared`
.text:0002FF00 BL sub_29900
.text:0002FF04 ADD R5, SP, #0xC68+var_458
.text:0002FF08 LDR R3, [SP,#0xC68+var_C54]
.text:0002FF0C ADD R5, R5, #8
.text:0002FF10 LDR R1, =(unk_CCECC - 0x2FF20)
.text:0002FF14 MOV R6, R0
.text:0002FF18 ADD R1, PC, R1
.text:0002FF1C MOV R0, R5
.text:0002FF20 LDR R7, [R3]
.text:0002FF24 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:0002FF28 ADD R4, SP, #0xC68+var_C10
.text:0002FF2C MOV R2, R5
.text:0002FF30 MOV R1, R7
.text:0002FF34 MOV R0, R4
.text:0002FF38 BL sub_4ED64
.text:0002FF3C LDR R1, [SP,#0xC68+var_C10]
.text:0002FF40 MOV R0, R6
.text:0002FF44 ADD R1, R1, #0x20 ; ' '
.text:0002FF48 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_
.text:0002FF4C ADD R0, R4, #4
.text:0002FF50 BL sub_284C4
.text:0002FF54 MOV R0, R5
.text:0002FF58 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0002FF5C LDR R1, =(unk_CCEE4 - 0x2FF6C)
.text:0002FF60 MOV R0, R10
.text:0002FF64 ADD R1, PC, R1 ; points to string `TimeoutUnconfirmed`
.text:0002FF68 BL sub_29900
The following code is looping through each of the parameter values that were previously extracted and building a string in the form -i "<keyname>=<user-supplied-value>"
. The result is stored in a basic_string var_C50
[1]
.text:0002FFBC ADD R3, SP, #0xC68+var_358
.text:0002FFC0 STR R3, [SP,#0xC68+var_360]
.text:0002FFC4 MOV R3, #0
.text:0002FFC8 STR R3, [SP,#0xC68+var_35C]
.text:0002FFCC STRB R3, [SP,#0xC68+var_358]
.text:0002FFD0 LDR R3, =(aI - 0x2FFE0) ; " -i \""
.text:0002FFD4 LDR R6, [SP,#0xC68+var_1D4]
.text:0002FFD8 ADD R3, PC, R3 ; " -i \""
.text:0002FFDC STR R3, [SP,#0xC68+var_C48]
.text:0002FFE0 LDR R3, =(aId+4 - 0x2FFEC) ; "="
.text:0002FFE4 ADD R3, PC, R3 ; "="
.text:0002FFE8 STR R3, [SP,#0xC68+var_C44]
.text:0002FFEC LDR R3, =(aI+4 - 0x2FFF8) ; "\""
.text:0002FFF0 ADD R3, PC, R3 ; "\""
.text:0002FFF4 STR R3, [SP,#0xC68+var_C40]
.text:0002FFF8
.text:0002FFF8 loc_2FFF8
.text:0002FFF8 ADD R3, SP, #0xC68+var_1E8
.text:0002FFFC ADD R4, SP, #0xC68+var_188
.text:00030000 ADD R3, R3, #0xC
.text:00030004 ADD R4, R4, #8
.text:00030008 CMP R6, R3
.text:0003000C ADD R3, SP, #0xC68+var_368
.text:00030010 ADD R3, R3, #8
.text:00030014 STR R3, [SP,#0xC68+var_C50]
.text:00030018 BEQ loc_300F8
.text:0003001C ADD R5, SP, #0xC68+var_338
.text:00030020 MOV R1, R3
.text:00030024 ADD R5, R5, #8
.text:00030028 MOV R0, R5
.text:0003002C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:00030030 LDR R1, [SP,#0xC68+var_C48]
.text:00030034 MOV R0, R5
.text:00030038 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc
.text:0003003C B loc_30044
.text:00030040 ; ---------------------------------------------------------------------------
.text:00030040 B loc_30284
.text:00030044 ; ---------------------------------------------------------------------------
.text:00030044
.text:00030044 loc_30044
.text:00030044 ADD R1, R6, #0x10
.text:00030048 MOV R0, R5
.text:0003004C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendERKS4_
.text:00030050 MOV R1, R0
.text:00030054 MOV R0, R4
.text:00030058 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:0003005C LDR R1, [SP,#0xC68+var_C44]
.text:00030060 MOV R0, R4
.text:00030064 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc
.text:00030068 ADD R7, SP, #0xC68+var_198
.text:0003006C MOV R1, R0
.text:00030070 MOV R0, R7
.text:00030074 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:00030078 ADD R1, R6, #0x28 ; '('
.text:0003007C MOV R0, R7
.text:00030080 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendERKS4_
.text:00030084 ADD R8, SP, #0xC68+var_1B8
.text:00030088 MOV R1, R0
.text:0003008C ADD R8, R8, #8
.text:00030090 MOV R0, R8
.text:00030094 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:00030098 LDR R1, [SP,#0xC68+var_C40]
.text:0003009C MOV R0, R8
.text:000300A0 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc
.text:000300A4 ADD R9, SP, #0xC68+var_1C8
.text:000300A8 MOV R1, R0
.text:000300AC MOV R0, R9
.text:000300B0 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:000300B4 MOV R1, R9
.text:000300B8 LDR R0, [SP,#0xC68+var_C50] ; [1]
.text:000300BC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEaSEOS4_
.text:000300C0 MOV R0, R9
.text:000300C4 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000300C8 MOV R0, R8
.text:000300CC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000300D0 MOV R0, R7
.text:000300D4 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000300D8 MOV R0, R4
.text:000300DC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000300E0 MOV R0, R5
.text:000300E4 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000300E8 MOV R0, R6
.text:000300EC BL _ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base ; std::_Rb_tree_increment(std::_Rb_tree_node_base *)
.text:000300F0 MOV R6, R0
.text:000300F4 B loc_2FFF8
Finally, the full command is built up resulting in the following command being executed by system()
[2] sudo fwupdate activate --keep-application -i "Command=506" -i "FirmwareControlFile=<user-supplied-value>" -i "FirmwareStorageAccount=<user-supplied-value>" -i "FirmwareStoragePath=<user-supplied-value>" -i "FirmwareStorageSAS=<user-supplied-value>" -i "SchemaVersion=1" -i "TimeoutPrepared=<user-supplied-value>" -i "TimeoutUnconfirmed=<user-supplied-value>"
Using var_C50
that was built above [1]. Each of the <user-supplied-value>
parameters is vulnerable to OS command injection via this command being run by system()
.text:00030128 ADD R1, PC, R1 ; "sudo "
.text:0003012C BL sub_28044
.text:00030130 LDR R1, =(aActivateKeepAp - 0x30140) ; " activate --keep-application "
.text:00030134 MOV R0, R6
.text:00030138 ADD R1, PC, R1 ; " activate --keep-application "
.text:0003013C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc
.text:00030140 MOV R1, R0
.text:00030144 MOV R0, R4
.text:00030148 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:0003014C LDR R1, [SP,#0xC68+var_C50] [1]
.text:00030150 MOV R0, R4
.text:00030154 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendERKS4_
.text:00030158 ADD R5, SP, #0xC68+var_348
.text:0003015C MOV R1, R0
.text:00030160 MOV R0, R5
.text:00030164 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:00030168 MOV R0, R4
.text:0003016C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:00030170 MOV R0, R6
.text:00030174 ADD R6, SP, #0xC68+var_308
.text:00030178 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:0003017C ADD R6, R6, #8
.text:00030180 LDR R1, =(aAgentcontrolFw - 0x30194) ; "AgentControl/FwUpdateConfigTool.cpp"
.text:00030184 MOV R2, R4
.text:00030188 MOV R3, #0x4A ; 'J'
.text:0003018C ADD R1, PC, R1 ; "AgentControl/FwUpdateConfigTool.cpp"
.text:00030190 MOV R0, R6
.text:00030194 STR R3, [SP,#0xC68+var_180]
.text:00030198 BL sub_615EC
.text:0003019C LDR R3, =(aExec - 0x301B4) ; "Exec: "
.text:000301A0 MOV R2, #0x20 ; ' '
.text:000301A4 STR R5, [SP,#0xC68+var_C68]
.text:000301A8 MOV R1, R6
.text:000301AC ADD R3, PC, R3 ; "Exec: "
.text:000301B0 MOV R0, #0x40 ; '@'
.text:000301B4 BL sub_3C678
.text:000301B8 MOV R0, R6
.text:000301BC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000301C0 LDR R0, [SP,#0xC68+var_348] ; command
.text:000301C4 BL system [2]
This vulnerability could be mitigated by disabling the Cloud Connectivity feature via the Web-based management web application.
2019-10-31 - Vendor Disclosure
2019-10-31 - Vendor acknowledged and passed to CERT@VDE for coordination/handling
2020-01-28 - Talos discussion with vendor; disclosure deadline extended
2020-03-09 - Public Release
Discovered by Kelly Leuschner of Cisco Talos.