Talos Vulnerability Report

TALOS-2019-0949

WAGO PFC200 Cloud Connectivity TimeoutPrepared Command Injection Vulnerability

March 9, 2020
CVE Number

CVE-2019-5156

Summary

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.

Tested Versions

WAGO PFC200 Firmware version 03.02.02(14) WAGO PFC200 Firmware version 03.01.07(13) WAGO PFC200 Firmware version 03.00.39(12)

Product URLs

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

CVSSv3 Score

7.2 - CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

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 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]

Mitigation

This vulnerability could be mitigated by disabling the Cloud Connectivity feature via the Web-based management web application.

Timeline

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

Credit

Discovered by Kelly Leuschner of Cisco Talos.