SSRFmap - Introducing the AXFR module
After reading a great blog post about a CTF challenge where you had to chain several SSRF to get the flag, I took some time to improve SSRFmap, fix the bugs and merge the Pull Requests. Then I implemented a new module called axfr
to trigger a DNS zone transfer from the SSRF using the gopher protocol. This blog post is about my journey on implementing it.
Before going further, we need to define what is a DNS zone transfer. Let’s ask ChatGPT for a quick description of this interesting DNS feature.
A DNS zone transfer is a process used to replicate the DNS records from the master DNS server to secondary DNS servers. This ensures that all the DNS servers in a domain have the same information, which is crucial for redundancy and load balancing. Zone transfers are typically initiated by the secondary server, requesting a copy of the DNS zone file from the primary server. They can be done in two main ways: full zone transfer (AXFR) and incremental zone transfer (IXFR).
SSRFmap is a tool I started 6 years ago, I’m proud to release the v2 now, with new modules, improvements for the Docker container, and new SSRF scenarios in the tests cases. Also issues have been fixed and dependencies upgraded. It should be a breeze to use it for your SSRF exploitation scenario, either in CTF and real world targets.
Now let’s create a lab with a basic SSRF vulnerability and a DNS service. For the SSRF we will use the examples of SSRFmap: SSRFmap/examples/example.py, which is just a Flask app with the endpoint ssrf
that will spawn a curl
command with the provided URL.
This code is vulnerable to SSRF but also to Remote Command Execution. Be careful when you run it, and restrict your network interactions.
We also need the file SSRFmap/examples/ssrf_dns.py from the examples folder. Then we start both, either on your machine with python examples/example.py
and python examples/ssrf_dns.py
or with the docker commands to have every requirements correctly installed:
Now you should be able to interact with the ssrf service using a curl command: curl -i -X POST -d 'url=http://example.com' http://localhost:5000/ssrf
, and it will fetch the content of the example.com website.
The best way to debug when you are sending queries over the network is to run wireshark
in the background. This way you can inspect the request, save the payload and even replay it later.
Let’s start with a simple AXFR query directly to the DNS server: dig @127.0.0.1 -p 53 example.lab AXFR
In this example:
-
@127.0.0.1
: specify the DNS server to query -
53
is the port number to connect to on the DNS server -
example.lab
is the domain name for which the DNS records are being queried -
AXFR
, the type of DNS query to execute
Our wireshark capture contains the AXFR query.
Let’s inspect the query in details using the wireshark dissector:
This DNS query can be extracted as hex with Copy as hex stream
in Wireshark: 0034a9c100200001000000000001076578616d706c65036c61620000fc000100002904d000000000000c000a0008180f72afca818649
The AXFR request format is as follow.
Header
Data | Description |
---|---|
XX | Length |
XXXX | Two-byte query ID selected by the client |
0020 | Standard query |
00 | Recursion not available, no Z bits, RCODE 0 |
0001 | Exactly one question |
0000 | Answer records |
0000 | Authority records |
0001 | Additional records |
Query
Data | Description |
---|---|
07 | Length domain part 1: 7 |
6578616d706c65 | Domain part 1: example
|
03 | Length domain part 2: 3 |
6c616 | Domain part 2: lab
|
00fc | AXFR query: 252 |
0001 | Internet query class |
Additional Record
This part is not essential for our exploit, but I included it in this blog post for the sake of completeness.
Data | Description |
---|---|
00 | Name |
0029 | Type OPT |
04d00 | UDP payload |
00 | Something? |
00 | EDNSO version |
0000 | Z |
00c0 | Data length |
00a0008180f72afca818649 | Option COOKIE |
We will replay this query using netcat
on port 53 (TCP). This works because the DNS server from ssrf_dns.py
is listening on both UDP and TCP.
But we can shorten this payload by removing the Additional records
, change 01
to 00
in the correct part of the header.
Don’t forget to change the length in the header section.
Finally, the server answers with 4 subdomains on example.lab
.
Now the next step is to transform this request to be able to send it through an SSRF. For this, we will use the gopher protocol.
The Gopher protocol (/ˈɡoʊfər/) is a communication protocol designed for distributing, searching, and retrieving documents in Internet Protocol networks. - Wikipedia
It is very easy to use this protocol with the gopher scheme: gopher://IP:PORT/_DATA
.
In theory, the payload should be as follow: gopher://127.0.0.1:53/_%00%1d%a9%c1%00%20%00%01%00%00%00%00%00%00%07%65%78%61%6d%70%6c%65%03%6c%61%62%00%00%fc%00%01
. The final exploit requires an URL encoding.
However, in my first attempt it didn’t work because the gopher protocol is not accepting a NULL BYTE (%00
). I went into the issues of the curl project and I discovered this one : issues/9089
Yes, using libcurl/7.47.0. I can run this command curl ‘gopher://127.0.0.1:1234/_%0012’ without any error.
It picked my interest, here is a small table with known vulnerable version.
libcurl version | Vuln ? |
---|---|
7.47.0 | Vulnerable |
7.71.0 | Vulnerable |
7.79.1 | Not vulnerable |
8.X | Not vulnerable |
A patch seems to be implemented since version 7.71.1 Commit #31e53584db5879894809fbde5445aac7553ac3e2.
Anyway, if you encounter a vulnerable version in the wild, the final code for the module is released here: swisskyrepo/SSRFmap/modules/axfr.py and you can use the following command line to use it.
References
This blog post was inspired by these great write-ups of the FCSC 2024 challenges. Have a look at them ! :)