CVE-2019-5157
An exploitable command injection vulnerability exists in the Cloud Connectivity functionality of WAGO PFC200. An attacker can inject OS commands into the TimeoutUnconfirmed 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 TimeoutUnconfirmed
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.
[Annotated Disassembly / Decompilation output]
The call to sub_29900
extracts the specified value from the provided json data. This snippet extracts the TimeoutUnconfirmed
value.
.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
Later, the value extracted above is passed as an argument to /etc/config-tools/fwupdate activate --keep-application -i "TimeoutUnconfirmed=<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 "TimeoutUnconfirmed=<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 TimeoutUnconfirmed
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:000308E4 LDR R1, =(unk_CCEE4 - 0x308FC)
.text:000308E8 ADD R3, SP, #0xC68+var_178
.text:000308EC MOV R0, R7
.text:000308F0 STR R3, [SP,#0xC68+var_180]
.text:000308F4 ADD R1, PC, R1 ; points to string `TimeoutUnconfirmed`
.text:000308F8 MOV R3, #0
.text:000308FC STR R3, [SP,#0xC68+var_17C]
.text:00030900 STRB R3, [SP,#0xC68+var_178]
.text:00030904 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:00030908 MOV R2, R4 ; [2]
.text:0003090C MOV R1, R7
.text:00030910 MOV R0, R9
.text:00030914 BL sub_5079C ; [3]
.text:00030918 MOV R8, R0
.text:0003091C STRB R0, [SP,#0xC68+var_C29]
.text:00030920 MOV R0, R7
.text:00030924 BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv
.text:00030928 CMP R8, #0
.text:0003092C BNE loc_309C8
.text:00030930 ADD R5, SP, #0xC68+var_8D0
.text:00030934 MOV R1, R4
.text:00030938 MOV R0, R5
.text:0003093C BL _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1ERKS4_
.text:00030940 MOV R1, R5
.text:00030944 MOV R0, R9
.text:00030948 BL sub_5061C; [4]
Here, the second parameter R1 is the value provided by Firmware update command TimeoutUnconfirmed
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.