Talos Vulnerability Report

TALOS-2019-0923

WAGO PFC100/200 Web-Based Management (WBM) Authentication Regex Information Disclosure Vulnerability

March 9, 2020
CVE Number

CVE-2019-5134

Summary

An exploitable regular expression without anchors vulnerability exists in the Web-Based Management (WBM) authentication functionality of WAGO PFC100/200 controllers. A specially crafted authentication request can bypass regular expression filters, resulting in sensitive information disclosure.

Tested Versions

WAGO PFC200 Firmware version 03.00.39(12) WAGO PFC200 Firmware version 03.01.07(13) WAGO PFC100 Firmware version 03.00.39(12)

Based on inspection of various firmware versions, this vulnerability appears to impact all versions from the current and going back to at least 10 and likely earlier.

Product URLs

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

CVSSv3 Score

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

CWE

CWE-777: Regular Expression without Anchors

Details

The WAGO PFC100 and PFC200 devices are programmable automation controllers that boast 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 application takes user input and, using regular expressions, attempts to locate and retrieve the relevant line in the /etc/lighttpd/lighttpd-htpasswd.user file (or /etc/shadow if configured to use system authentication) for use in password validation.

The PasswordCorrect() function from login.php:

function PasswordCorrect($passwordFilename, $username = '', $password = '')
{
$pwCorrect  = false;
//var_dump($username); var_dump($password);

// get password file and iterate over every line
$pwFileArray = file($passwordFilename);

foreach($pwFileArray as $lineNo => $pwFileLine)
{
    //var_dump($pwFileLine);
    // if username was found, filter password key
    if(preg_match("/^".$username.":/", $pwFileLine))
    {
    $pwFileKey  = str_replace($username.":", "", $pwFileLine);
    $pwFileKey  = str_replace("\n", "", $pwFileKey);

    if (preg_match('/\$(?P<algo>\d)\$(?P<salt>[a-zA-Z0-9\.\/]{2,16})\$(?<hash>.*)/', $pwFileKey, $userInfo))
    {
        $algo = $userInfo["algo"];
        $salt = $userInfo["salt"];
        //$hash = $userInfo["hash"];

        // evaluate key of password given by user and compare both
        $userKey    = crypt($password, "$".$algo."$".$salt);

        //var_dump($pwFileKey); printf("\n"); var_dump($userKey);   printf("\n");
        if($pwFileKey == $userKey)
        {
        $pwCorrect = true;
        }
    }
    }
}

return $pwCorrect;
}

username

A regex in an if statement checks for presence of the user input username:

preg_match("/^".$username.":/", $pwFileLine)
  • Check from beginning of line (^) looking for username ending with a colon
    • this pcre is easy to get around, for example a username of "....." or "adm*in" or numerous other varieties of input will match "admin"
  • if a match is found for the username, str_replace() to:
    • remove the username and the colon from the line
    • get rid of newline char at the end of the line
  • the result will be $pwFileKey set to something like: $6$nJ68diwYtzvoGdmO$riWD40K1ygPswo/bW0EhwGCNwpWw1mVaHKGXoiLU4vydECe2pXqpZAVkl0JM9GoibmyCGOTCp5Fk8hL8jGoFA0

password hash

preg_match('/\$(?P<algo>\d)\$(?P<salt>[a-zA-Z0-9\.\/]{2,16})\$(?<hash>.*)/', $pwFileKey, $userInfo)
  • parse the $pwFilekey var set above, save result in $userInfo and use named subpattern (named capture groups) to set vars $algo and $salt
    • Expression looks for $
    • then a named subpattern "algo" made up of a single digit (the algorithm number, 6 in this case)
    • then a $
    • then a named subpattern "salt" made up values in position 2-16, lower case, upper case, digit, ., and /
      • the salt will only contain the set of characters used in base64 encoding due to the process by which it is generated:
        • dd if=/dev/urandom bs=1 count=12 2> /dev/null | base64 | tr + . | tr -d "\n"
    • then a $
    • then a named subpattern "hash" which is commented out in the code (//$hash = $userInfo["hash"]) and therefore irrelevant
    • then a wildcard (because it doesn't care what the hash is at this point)

If both user input passes both regexes the results are sent to the php crypt() function.

Exploit Proof of Concept

The string (?x)admin.... will match on admin:$6$, allowing an attacker to test on each subsequent character by appending the tested character followed by a # and then use the PHP crypt() timing attack described in TALOS-2019-0924 to determine if the character is a match, thereby disclosing the full salt and password hash.

Timeline

2019-10-28 - Vendor Disclosure
2019-10-31 - Vendor passed to CERT@VDE for coordination/handling
2019-12-16 - Disclosure deadline extended
2020-01-28 - Talos discussion about vulnerabilities with Vendor
2020-03-09 - Public Release

Credit

Discovered by Patrick DeSantis and Lilith [-_-]; of Cisco Talos.