Talos Vulnerability Report

TALOS-2019-0950

WAGO PFC200 Cloud Connectivity TimeoutUnconfirmed Command Injection Vulnerability

March 9, 2020
CVE Number

CVE-2019-5157

Summary

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.

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

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.