Buckeye CTF - assouline.sh

Buckeye CTF

Web

EBG13

“V znqr na ncc gung yrgf lbh ivrj gur ebg13 irefvba bs nal jrofvgr!”

The website for this challenge lets you input a url and returns the contents of the given website encoded in ROT13. We are given the source code in server.js which specifies an endpoint /admin that will return the flag if the request originates from localhost.

fastify.get('/admin', async (req, reply) => {
    if (req.ip === "127.0.0.1" || req.ip === "::1" || req.ip === "::ffff:127.0.0.1") {
      return reply.type('text/html').send(`Hello self! The flag is ${FLAG}.`)
    }

    return reply.type('text/html').send(`Hello ${req.ip}, I won't give you the flag!`)
})

Since we also know from the source code that the web app is running on port 3000, we can navigate to https://ebg13.challs.pwnoh.io/ebj13?url=http://127.0.0.1:3000/admin to pass the internal url to the web app, thus bypassing the IP restriction and successfully returning the flag!

🚩bctf{what_happens_if_i_use_this_website_on_itself}

Ramesses

“Do you dare enter the tomb of Pharaoh Dave Ramesses?”

The website has two input boxes, one for a name and one for a secret password to “enter the tomb”. After submitting test values, I noticed that the cookie is a base64 encoded value, and decoding it shows there is a flag is_pharaoh set to false.

ramesses

I set the value to true, base64 encoded it, set it as the cookie value for my request, and got the flag.

ramesses

🚩bctf{s0_17_w45_wr177en_50_1t_w45_d0n3}

Big Chungus

“There’s wabbit twouble afoot”

The website for this challenge is nonsensical, but there’s a box for inputting a username. Inspecting the index.js source code, we see an interesting line:

if (req.query.username.length > 0xB16_C4A6A5) {
    res.send(`
...
<p style="font-size: 30px; color: lime;">FLAG: ${
    process.env.FLAG || "FLAG_NOT_SET"
  }</p>

Only if we input a username that is longer than 47,626,626,725 characters will we get the page that renders the flag. Unless…! We could specify ourselves the length of the username ourselves.

With the following payload:

curl "https://big-chungus.challs.pwnoh.io/?username%5Blength%5D=99999999999999999999999"

the web app will evaluate username.length and since the username is an object with a length property set to a number > 47 billion, it will pass the check!

🚩bctf{b16_chun6u5_w45_n3v3r_7h15_b16}

Awklet

“Well this is awkward…”

The website for this challenge takes text input and returns it in different styles of ascii art. We get the source code awklet.awk which has a notable vulnerability when loading the font:

function load_font(font_name, font, filename, line, char, row, c) {
    filename = font_name ".txt"

The given font_name is concatenated with .txt without any checks. We can also see in the given Dockerfile that the flag is passed to the process’s environment and could thus be read from /proc/self/environ, while the web app is running from /usr/lib/cgi-bin/

RUN echo "PassEnv FLAG" >> /etc/apache2/conf-available/flag.conf && \
    a2enconf flag
...

RUN chmod +x /usr/lib/cgi-bin/awklet.awk

This all points to a directory traversal exploit. We can set the font to be font=../../../../proc/self/environ. It took me a while to realize a null terminator %00 is needed to prevent the .txt from being appended. The final payload gives us the flag:

https://awklet.challs.pwnoh.io/cgi-bin/awklet.awk?name=x&font=../../../../proc/self/environ%00

🚩bctf{n3xt_t1m3_1m_wr171ng_1t_1n_53d}

Pwn

Waddler

“Poke it the right way and it hums back something useful.”

Open the binary in ghidra and see there is a function duck() that reads flag.txt and prints it. However, this function is not called anywhere. This seems like a basic buffer overflow where we need to overwrite the return address with the address of duck(). We calculate the offset by taking the sum of the buffer (64) + saved RBP (8) and find the function address to be at 0x40128C. The following exploit gets us our flag:

waddler

🚩v1t{w4ddl3r_3x1t5_4e4d6c332b6fe62a63afe56171fd3725}

Viewer

“Viewer to view many things”

We are given the source code and can see there are four options of things to view, one of which is the flag. We just need to set is_admin to true, which we can do by overflowing input[10] because gets(input) does no bounds checking.

int main() {
    viewee_t viewee = INVALID;
    char input[10];
    bool is_admin = false;
    ...
    gets(input);
    ...
    } else if (strcmp(input, "flag") == 0 && is_admin) {
        viewee = FLAG;
    ...
}

The payload starts with flag and we add a null terminator so strcmp knows to stop here. Then we fill the rest of input[10] with 5 more bytes, and then end the payload with a non-zero byte value so we overwrite is_admin to true.

viewer

🚩bctf{I_C4nt_Enum3rAte_7hE_vuLn3r4biliTI3s}

Forensic

The Professor’s Files

“A professor uploaded their ethics report for review, but something about it seems… off. The document’s formatting feels inconsistent, almost like it was edited by hand rather than exported normally. The metadata looks strange too, and there’s a rumor that the professor hides sensitive data in plain sight”

The description says to look at the metadata of the given file, and when I did I saw hints at there being a zip file within it.

professor

Running binwalk extracted a bunch of files, and the flag was in a file theme1.xml

🚩bctf{docx_is_zip}

Misc

Mind Boggle

“My friend gave me this file, but I have no idea what it means!!!!! HELP”

We are given a file that has what I recognize as Brainfuck, a trippy programming language often used in CTF challs. I went to dcode and pasted in the contents of the file. Cyberchef then helped me decode the result from hex and then from base64 to get the flag.

boggle

🚩bctf{tr1pl3_7H3_l4yeRs_Tr1pl3_thE_EncryPt10N}

Talking Duck

“Bro this duck is talking to me or something?”

Open the audio file in Audacity and convert the small and big waves to morse code. The short waves are dots and the longer waves are dashes.

talkingduck

This is the result: ...- .---- - -.. ..- -.-. -.- ... ----- ... ... ----- .... Decode this to get the flag!

🚩v1t{DUCK_S0S_S0S}

RotBrain

“Ts fr lwk pmo gng”

The attachment was named gnp.egami which is image.png backwards. PNG magic bytes were backwards at the end of the file, so I knew I had to reverse the bytes to get the original image. Had a lot of trouble with this and went down a rabbit hole of thinking maybe a ROT cipher had been applied somehow. But actually, I needed to decode it from UTF-8 and instead encode it to Latin1, since this maps byte values 0–255 directly to Unicode code points so you can safely convert from bytes to str.

python3 -c "data=open('gnp.egami','rb').read(); open('out.png','wb').write(data.decode('utf-8').encode('latin1')[::-1])"

Open the resulting image for the flag.

🚩 v1t{r3v_1mg_4ge}

What I Didn’t Solve

(Forensic) Cosmonaut

  • Needed to run the program on varios OSs, or patch the program
  • Solve

(PWN) Character Assassination

  • The flag is stored in memory directly before an array we index (with our input). Loop providing a negative index to read the flag
  • Solve