Talos Vulnerability Report

TALOS-2019-0954

WAGO PFC200 Cloud Connectivity Remote Code Execution Vulnerability

March 9, 2020
CVE Number

CVE-2019-5161

Summary

An exploitable remote code execution vulnerability exists in the Cloud Connectivity functionality of WAGO PFC200. A specially crafted XML file will direct the Cloud Connectivity service to download and execute a shell script with root privileges.

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

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

CWE

CWE-646 Reliance on File Name or Extension of Externally-Supplied File

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. One step in the remote firmware update process is to parse an XML file containing remote URL's and local paths for files to be downloaded. The Cloud Connectivity service does not validate the path, file name or file type of the downloaded files. After downloading the files specified in the Firmware Control XML file, the next step in the firmware update process is to look for a shell script with the name /tmp/fwupdate/fwupdate_hook.sh. If that file exists, it is marked as executable and then executed. This script is executed before any signature verification is performed on the firmware update file.

When the Cloud Connectivity service is configured to connect to an attacker controlled Azure IoT Hub node, the attacker has the ability to write a shell script to the specific location /tmp/fwupdate/fwupdate_hook.sh and it will be executed as root. Web administrator privileges are required to configure the Cloud Connectivity service.

Here, if the XML File Type property is equal to "rauc" it will continue on to download it below

.text:0002DEC8                 LDR             R3, =(aFile_0 - 0x2DED8) ; "File"
.text:0002DECC                 LDR             R7, [R9,#0xC]
.text:0002DED0                 ADD             R3, PC, R3 ; "File"
.text:0002DED4                 STR             R3, [SP,#0xC68+var_C38]
.text:0002DED8                 LDR             R3, =(aCloudtype+5 - 0x2DEE4) ; "Type"
.text:0002DEDC                 ADD             R3, PC, R3 ; "Type"
.text:0002DEE0                 STR             R3, [SP,#0xC68+var_C3C]
.text:0002DEE4
.text:0002DEE4 loc_2DEE4                               ; CODE XREF: sub_2D488+D98↓j
.text:0002DEE4                 CMP             R7, #0
.text:0002DEE8                 BEQ             loc_2E224
.text:0002DEEC                 LDR             R1, [SP,#0xC68+var_C38]
.text:0002DEF0                 LDR             R0, [R7,#8]
.text:0002DEF4                 BL              xmlStrcmp
.text:0002DEF8                 CMP             R0, #0
.text:0002DEFC                 BNE             loc_2E21C
.text:0002DF00                 LDR             R3, [SP,#0xC68+var_C3C]
.text:0002DF04                 ADD             R1, SP, #0xC68+var_1F8
.text:0002DF08                 LDR             R0, [R3]
.text:0002DF0C                 LDRB            R3, [R3,#4]
.text:0002DF10                 STR             R0, [SP,#0xC68+var_1F8]
.text:0002DF14                 MOV             R0, R7
.text:0002DF18                 STRB            R3, [R1,#4]
.text:0002DF1C                 BL              xmlGetProp
.text:0002DF20                 ADD             R4, SP, #0xC68+var_1B8
.text:0002DF24                 STR             R0, [SP,#0xC68+var_C48]
.text:0002DF28                 ADD             R4, R4, #8
.text:0002DF2C                 MOV             R1, R0
.text:0002DF30                 MOV             R0, R4
.text:0002DF34                 BL              sub_516D4
.text:0002DF38                 LDR             R1, =(aRauc - 0x2DF48); "rauc"
.text:0002DF3C                 MOV             R0, R4
.text:0002DF40                 ADD             R1, PC, R1 ; "rauc"
.text:0002DF44                 BL              sub_280BC

Here, the TargetPath property is extracted, but not validated

.text:0002DF9C                 LDR             R2, =(aTargetpath - 0x2DFAC) ; "TargetPath"
.text:0002DFA0                 ADD             R3, SP, #0xC68+var_1C8
.text:0002DFA4                 ADD             R2, PC, R2 ; "TargetPath"
.text:0002DFA8                 LDR             R0, [R2] ; "TargetPath"
.text:0002DFAC                 LDR             R1, [R2,#(aTargetpath+4 - 0x9D0A1)]
.text:0002DFB0                 STM             R3!, {R0,R1}
.text:0002DFB4                 MOV             R0, R7
.text:0002DFB8                 LDRH            R1, [R2,#(aTargetpath+8 - 0x9D0A1)]
.text:0002DFBC                 LDRB            R2, [R2,#(aTargetpath+0xA - 0x9D0A1)]
.text:0002DFC0                 STRH            R1, [R3]
.text:0002DFC4                 ADD             R1, SP, #0xC68+var_1C8
.text:0002DFC8                 STRB            R2, [R3,#2]
.text:0002DFCC                 BL              xmlGetProp

Next, sub_62E38 is called with the first parameter being the value of TargetPath from the XML file, and the second is the url of the file to be downloaded

.text:00062E38 sub_62E38                               ; CODE XREF: sub_2D488+768↑p
.text:00062E38                                         ; sub_2D488+1024↑p
.text:00062E38
.text:00062E38 var_150         = -0x150
.text:00062E38 var_14C         = -0x14C
.text:00062E38 var_148         = -0x148
.text:00062E38 var_144         = -0x144
.text:00062E38 var_140         = -0x140
.text:00062E38 var_13C         = -0x13C
.text:00062E38 var_138         = -0x138
.text:00062E38 var_120         = -0x120
.text:00062E38 var_108         = -0x108
.text:00062E38 var_F0          = -0xF0
.text:00062E38 var_EC          = -0xEC
.text:00062E38 var_28          = -0x28
.text:00062E38
.text:00062E38                 PUSH            {R4-R11,LR}
.text:00062E3C                 MOV             R6, R1
.text:00062E40                 LDR             R1, =(aStateBlinkingC_1+0x1A - 0x62E58) ; "w"
.text:00062E44                 SUB             SP, SP, #0x12C
.text:00062E48                 MOV             R5, R0
.text:00062E4C                 LDR             R0, [R0] ; filename - TargetPath from XML file
.text:00062E50                 ADD             R1, PC, R1 ; "w" ; modes
.text:00062E54                 BL              fopen
.text:00062E58                 SUBS            R7, R0, #0
.text:00062E5C                 BNE             loc_62EB8

sub_62E38 continued - the second parameter is set as the URL and downloaded

.text:00062EB8 loc_62EB8                               ; CODE XREF: sub_62E38+24↑j
.text:00062EB8                 BL              curl_easy_init
.text:00062EBC                 SUBS            R4, R0, #0
.text:00062EC0                 BEQ             loc_6310C
.text:00062EC4                 LDR             R2, [R6] ; setting curl URL to second argument to function
.text:00062EC8                 MOV             R1, #0x2712 ; CURLOPT_URL
.text:00062ECC                 BL              curl_easy_setopt
.text:00062ED0                 LDR             R2, =(aEtcSslCertsCaC - 0x62EE4) ; "/etc/ssl/certs/ca-certificates.crt"
.text:00062ED4                 MOV             R1, #0x2751
.text:00062ED8                 MOV             R0, R4
.text:00062EDC                 ADD             R2, PC, R2 ; "/etc/ssl/certs/ca-certificates.crt"
.text:00062EE0                 BL              curl_easy_setopt
.text:00062EE4                 MOV             R2, #1
.text:00062EE8                 MOV             R1, #0x40 ; '@'
.text:00062EEC                 MOV             R0, R4
.text:00062EF0                 BL              curl_easy_setopt
.text:00062EF4                 MOV             R2, #2
.text:00062EF8                 MOV             R1, #0x51 ; 'Q'
.text:00062EFC                 MOV             R0, R4
.text:00062F00                 BL              curl_easy_setopt
.text:00062F04                 LDR             R2, =(sub_3C3F8 - 0x62F18)
.text:00062F08                 MOV             R1, #0x4E2B
.text:00062F0C                 MOV             R0, R4
.text:00062F10                 ADD             R2, PC, R2 ; sub_3C3F8
.text:00062F14                 BL              curl_easy_setopt
.text:00062F18                 MOV             R2, R7
.text:00062F1C                 MOV             R1, #0x2711
.text:00062F20                 MOV             R0, R4
.text:00062F24                 BL              curl_easy_setopt
.text:00062F28                 MOV             R2, #1
.text:00062F2C                 MOV             R1, #0x34 ; '4'
.text:00062F30                 MOV             R0, R4
.text:00062F34                 BL              curl_easy_setopt
.text:00062F38                 MOV             R2, #0x258
.text:00062F3C                 MOV             R1, #0xD
.text:00062F40                 MOV             R0, R4
.text:00062F44                 BL              curl_easy_setopt
.text:00062F48                 MOV             R0, R4
.text:00062F4C                 BL              curl_easy_perform

After the file is downloaded, the next step is for the script /usr/sbin/fwupdate_background_service to execute the shell script located at /tmp/fwupdate/fwupdate_hook.sh

# Call pre install update hook if present
if [[ -e "$WAGO_FW_UPDATE_PREINSTALL_HOOK" ]]; then
    fwupdate_report_info "Found pre install update hook to call: \"$WAGO_FW_UPDATE_PREINSTALL_HOOK\""
    chmod a+x "$WAGO_FW_UPDATE_PREINSTALL_HOOK"
    "$WAGO_FW_UPDATE_PREINSTALL_HOOK"
    local HOOK_RESULT=$?
    if [[ $HOOK_RESULT -ne 0 ]]; then
        fwupdate_report_warning "Pre install update hook failed on call: \"$WAGO_FW_UPDATE_PREINSTALL_HOOK\" (exit code $HOOK_RESULT)"
    fi
fi

Environment variable definitions from /usr/sbin/fwupdate_basic_defines

readonly WAGO_FW_UPDATE_DEFAULT_TMP_DIR="/tmp/fwupdate"
readonly WAGO_FW_UPDATE_DEFAULT_WORK_DIR="$WAGO_FW_UPDATE_DEFAULT_TMP_DIR"
readonly WAGO_FW_UPDATE_PREINSTALL_HOOK="$WAGO_FW_UPDATE_DEFAULT_WORK_DIR/fwupdate_hook_preinstall.sh"

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.