CVE-2019-5156
An exploitable command injection vulnerability exists in the cloud connectivity functionality of WAGO PFC200. An attacker can inject operating system commands into the TimeoutPrepared parameter value 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. The value passed as the TimeoutPrepared
parameter can be used to inject OS commands, which are run as the iot user. Web administrator privileges are required to configure the Cloud Connectivity service.
The call to sub_29900
extracts the specified value from the provided json data. This snippet extracts the TimeoutPrepared
value.
.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
Later, the value extracted above is passed as an argument to /etc/config-tools/fwupdate activate --keep-application -i "TimeoutPrepared=<user supplied value>"
at [1]
.text:00030118 LDR R2, =(unk_CCD34 - 0x30128)
.text:0003011C LDR R1, =(aSudo - 0x30130)
.text:00030120 ADD R2, PC, R2 ; "/etc/config-tools/fwupdate"
.text:00030124 MOV R0, R6
.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]
.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 ; [1]
The script /etc/config-tools/fwupdate activate --keep-application -i "TimeoutPrepared=<user supplied value>"
executed above eventually calls a script called fwupdate_mode
which writes the key/value pair to disk.
# Function to store initial key/value data from optional parameters
# -i/--init-storage.
#
# Return: 0 on success, aborts/cancels FW-Update otherwise
#-----------------------------------------------------------------------------#
store_init_data()
{
local RESULT=0
local i=0
while (( i < ${#STORAGEVALUES[@]} )); do
local INIT_PAIR=${STORAGEVALUES[$i]}
local INIT_KEY="$(echo -n "$INIT_PAIR" | cut -d"=" -f1)"
local INIT_VALUE="$(echo -n "$INIT_PAIR" | cut -d"=" -f2-$(( WAGO_FW_UPDATE_STORAGE_MIN_SPACE_KB * 1024 )))"
if [ -z "$INIT_KEY" ]; then
fwupdate_report_error "Failed to extract key name from init pair \"$INIT_PAIR\""
RESULT=$INVALID_PARAMETER
else
"$WAGO_ST_DIR/fwupdate_storage" "--set" "$INIT_KEY" "$INIT_VALUE"
RESULT=$?
fi
i=$(( i + 1 ))
done
# Revert activation on error
if [ "$RESULT" -ne "0" ]; then
fwupdate_abort 102 "Failed to initialize key/value store with given init data \"$STORAGEVALUES\"" $RESULT
fi
return 0
}
The call to sub_5079C
[3] calls the script fwupdate storage --get
which reads the value of TimeoutPrepared
from the on-disk storage and stores it in R4 [2]. That value is then passed to sub_5061C
as the second parameter R1 [4].
.text:0003062C LDR R1, =(unk_CCECC - 0x30640)
.text:00030630 MOV R0, R6
.text:00030634 ADD R3, SP, #0xC68+var_178
.text:00030638 ADD R1, PC, R1 ; points to string `TimeoutPrepared`
.text:0003063C STR R3, [SP,#0xC68+var_180]
.text:00030640 MOV R3, #0
.text:00030644 STR R3, [SP,#0xC68+var_17C]
.text:00030648 STRB R3, [SP,#0xC68+var_178]
.text:0003064C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:00030650 MOV R2, R4 ; [2]
.text:00030654 MOV R1, R6
.text:00030658 MOV R0, R8
.text:0003065C BL sub_5079C ; [3]
.text:00030660 MOV R7, R0
.text:00030664 STRB R0, [SP,#0xC68+var_C29]
.text:00030668 MOV R0, R6
.text:0003066C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:00030670 CMP R7, #0
.text:00030674 BEQ loc_306DC
.text:00030678 LDR R1, =(aFwupdateStorag - 0x30694) ; "fwupdate storage --get failed. Exit cod"...
.text:0003067C MOV R3, #0x2719
.text:00030680 STR R3, [SP,#0xC68+var_114]
.text:00030684 ADD R0, SP, #0xC68+var_118
.text:00030688 LDRB R3, [SP,#0xC68+var_C29]
.text:0003068C ADD R1, PC, R1 ; "fwupdate storage --get failed. Exit cod"...
.text:00030690 ADD R0, R0, #8
.text:00030694 ADD R1, R1, R3
.text:00030698 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEaSEPKc
.text:0003069C ADD R6, SP, #0xC68+var_5B8
.text:000306A0 LDR R1, =(aAgentcontrolFi - 0x306B4) ; "AgentControl/FirmwareUpdateManager.cpp"
.text:000306A4 MOV R2, R9
.text:000306A8 MOV R3, #0x1A5
.text:000306AC ADD R1, PC, R1 ; "AgentControl/FirmwareUpdateManager.cpp"
.text:000306B0 MOV R0, R6
.text:000306B4 STR R3, [SP,#0xC68+var_198]
.text:000306B8 BL sub_615EC
.text:000306BC LDR R1, =(unk_CCECC - 0x306D0)
.text:000306C0 SUB R2, R5, #9
.text:000306C4 MOV R0, R6
.text:000306C8 ADD R1, PC, R1 ; unk_CCECC
.text:000306CC BL sub_4E378
.text:000306D0 MOV R0, R6
.text:000306D4 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000306D8 B loc_30A28
.text:000306DC ; ---------------------------------------------------------------------------
.text:000306DC
.text:000306DC loc_306DC ; CODE XREF: sub_2D488+31EC↑j
.text:000306DC ADD R5, SP, #0xC68+var_5D8
.text:000306E0 MOV R1, R4
.text:000306E4 ADD R5, R5, #8
.text:000306E8 MOV R0, R5
.text:000306EC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:000306F0 MOV R1, R5
.text:000306F4 MOV R0, R8
.text:000306F8 BL sub_5061C ; [4]
Here, the second parameter R1 is the value provided by Firmware update command TimeoutPrepared
value. The value is appended to the string “ settimeout -c “ at [5] without properly being escaped. It is then passed to a call to system
at [6].
.text:0005061C sub_5061C ; CODE XREF: sub_2D488+3270↑p
.text:0005061C ; sub_2D488+34C0↑p
.text:0005061C
.text:0005061C var_60 = -0x60
.text:0005061C command = -0x58
.text:0005061C var_40 = -0x40
.text:0005061C var_28 = -0x28
.text:0005061C
.text:0005061C ; __unwind { // __gcc_personality_v0
.text:0005061C PUSH {R4-R6,LR}
.text:00050620 SUB SP, SP, #0x50
.text:00050624 MOV R6, R1
.text:00050628 BL sub_3DDE0
.text:0005062C CMP R0, #0
.text:00050630 MOVEQ R5, #0x64 ; 'd'
.text:00050634 BEQ loc_5077C
.text:00050638 ADD R4, SP, #0x60+var_40
.text:0005063C LDR R2, =(unk_CCD34 - 0x50650)
.text:00050640 LDR R1, =(aSudo - 0x50654) ; "sudo "
.text:00050644 MOV R0, R4
.text:00050648 ADD R2, PC, R2 ; unk_CCD34
.text:0005064C ADD R1, PC, R1 ; "sudo "
.text:00050650 BL sub_28044
.text:00050654 LDR R1, =(aSettimeoutC - 0x50664) ; " settimeout -c "
.text:00050658 MOV R0, R4
.text:0005065C ADD R1, PC, R1 ; " settimeout -c "
.text:00050660 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKc
.text:00050664 ADD R5, SP, #0x60+var_28
.text:00050668 MOV R1, R0
.text:0005066C MOV R0, R5
.text:00050670 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:00050674 MOV R1, R6
.text:00050678 MOV R0, R5
.text:0005067C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendERKS4_ ; [5]
.text:00050680 ADD R6, SP, #0x60+command
.text:00050684 MOV R1, R0
.text:00050688 MOV R0, R6
.text:0005068C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EOS4_
.text:00050690 MOV R0, R5
.text:00050694 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:00050698 MOV R0, R4
.text:0005069C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000506A0 LDR R1, =(aAgentcontrolFw - 0x506B4) ; "AgentControl/FwUpdateConfigTool.cpp"
.text:000506A4 MOV R2, R4
.text:000506A8 MOV R0, R5
.text:000506AC ADD R1, PC, R1 ; "AgentControl/FwUpdateConfigTool.cpp"
.text:000506B0 MOV R3, #0xDA
.text:000506B4 STR R3, [SP,#0x60+var_40]
.text:000506B8 BL sub_615EC
.text:000506BC LDR R3, =(aExec - 0x506D4) ; "Exec: "
.text:000506C0 MOV R2, #0x20 ; ' '
.text:000506C4 STR R6, [SP,#0x60+var_60]
.text:000506C8 MOV R1, R5
.text:000506CC ADD R3, PC, R3 ; "Exec: "
.text:000506D0 MOV R0, #0x40 ; '@'
.text:000506D4 BL sub_3C678
.text:000506D8 MOV R0, R5
.text:000506DC BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:000506E0 LDR R0, [SP,#0x60+command] ; command
.text:000506E4 BL system ; [6]
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.