Information#
Version#
By | Version | Comment |
---|---|---|
noraj | 1.0 | Creation |
CTF#
- Name : angstromCTF 2018
- Website : www.angstromctf.com
- Type : Online
- Format : Jeopardy
- CTF Time : link
230 - The Best Website - Web#
I have created what I believe to be the best website ever. Or maybe it's just really boring. I don't know.
Hint: My database is humongous!
The website is very boring but we can see that two requests are made:
The additional request is: http://web.angstromctf.com:7667/boxes?ids=5aac9e638818c1001cc7391f,5aac9e638818c1001cc73920,5aac9e638818c1001cc73921
Let's see where does it come from.
The website template is Ion by TEMPLATED. Web can download the template to compare it with the actual website.
This script (/js/init.js
) has been modified from the original one of the template.
There is a new functionality:
1 | [...] |
So this is where the second request comes from. Let's request it manually:
1 | $ curl http://web.angstromctf.com:7667/boxes?ids=5aac9e638818c1001cc7391f,5aac9e638818c1001cc73920,5aac9e638818c1001cc73921 |
Here is the beautified output:
1 | { |
We can recognize the MongoDB JSON structure with _id
. So the arguments passed to boxes
endpoint are some ObjectId.
From the MongoDB documentation we can see that ObjectId are 12 bytes long and structured like that:
- a 4-byte value representing the seconds since the Unix epoch,
- a 3-byte machine identifier,
- a 2-byte process id, and
- a 3-byte counter, starting with a random value.
Let's reverse the logic with the first arg: 5aac9e638818c1001cc7391f
- epoch :
5aac9e63
(hex) =>1521262179
(dec) =>2018-03-17T04:49:39+00:00
(timestamp to date) - machine id :
8818c1
(hex) - process id :
001c
(hex) =>28
(dec) - counter :
c7391f
(hex) =>13056287
(dec)
Now let's introduce a new information: in the source code we can see this html comment:
1 | <!--developers: make sure to record your actions in log.txt--> |
log.txt
is containing those 4 lines:
1 | Sat Aug 10 2017 10:23:17 GMT-0400 (EDT) - Initial website |
We can see in the 3 arg passed to boxes
that they have the same Unix epoch 2018-03-17T04:49:39+00:00
but the log.txt
tells us that the flag was added at 2018-03-17T04:49:45+00:00
.
We just have to reverse the process, converting the date to timestamp and the decimal timestamp to hexadecimal: 2018-03-17T04:49:45+00:00
(ISO 8601 date) => 1521262185
(decimal) => 5aac9e69
(hexadecimal).
The machine id and process id don't change and we can see that the 2 first bytes of the counter are fixed: c739
. So we just have to bruteforce one byte from 0 to 255 (00-ff in hex).
Finally we have 5aac9e69
+ 8818c1
+ 001c
+ c739
+ 00
-ff
= from 5aac9e698818c1001cc73900
to 5aac9e698818c1001cc739ff
.
Requesting for non existing values will return null
:
1 | $ curl http://web.angstromctf.com:7667/boxes?ids=a,a,a |
So I made a ruby script to bruteforce those values:
1 |
|
And finally my script is outputting the following object:
1 | {"_id":"5aac9e698818c1001cc73922","data":"actf{0bj3ct_ids_ar3nt_s3cr3ts}","__v":0} |
140 - md5 - Web#
defund's a true MD5 fan, and he has a site to prove it.
Here is the source code:
1 |
|
We can't abuse md5 cryptography or PHP loose comparison this time.
But instead of providing strings to str1
and str2
we can call them as array by doing ?str1[]=a
instead of ?str1=a
.
If we call ?str1[]=a&str2[]=b
we will have two different values for $_GET["str1"] !== $_GET["str2"]
but we will fool PHP because it is doing a concatenation with $salt . $_GET["str1"]
so the array will be casted to a string.
And you know what? When an array is casted to a string in PHP the resulting string won't be about the content of the flattened array but the Array
word. Any array casted to string will be equal then. See by yourself:
1 | $ php -a |
Doing so we get the flag: actf{but_md5_has_charm}
.
120 - MadLibs - Web#
When Ian was a kid, he loved to play goofy Madlibs all day long. Now, he's decided to write his own website to generate them!
Source code:
1 | from flask import Flask, render_template, render_template_string, send_from_directory, request |
This a Flask web application, the parameter authorName
is vulnerable to SSTI (Server-Side Template Injection).
As I recommended you in ASIS 2017 Final write-ups, you can take a look at Exploring SSTI in Flask/Jinja2.
To check if the field is SSTI vulnerable we can use the following payload {{ 7*7 }}
. If the app displays 42
it is vulnerable.
To dump all the app config I usually inject {{ config.items() }}
, this will also include SECRET_KEY
.
But here it seems we can't use an input longer than 12 chars. So I used only {{config)}}
instead.
The flag is actf{wow_ur_a_jinja_ninja}
.
160 - File Storer - Web#
My friend made a file storage website that he says is super secure. Can you prove him wrong and get the admin password?
Just begin by signing up and logging in.
Then you will be able to upload remote files:
So provide a remote image like the french flag.
The URL of the uploaded image is: http://web2.angstromctf.com:8899/files/Flag_of_France.svg
So let's try a LFRU (Local File Remote Upload, it's a joke).
http://web2.angstromctf.com:8899/files/../../../../etc/passwd
won't work and will tell you file already exists
. So try to URL encode the slashes http://web2.angstromctf.com:8899/files/..%2F..%2F..%2F..%2Fetc%2Fpasswd
like for LFI.
Now you are able to leak local files:
/etc/passwd
is useless here, let's try /proc/self/environ
to see if there are some interesting environment variables http://web2.angstromctf.com:8899/files/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron
.
Flag is actf{2_und3rsc0res_h1des_n0th1ng}
.
Note: if you get file already exists
even with URL encoding, just add some ..%2F
because if another user already uploaded the file with the exact same path you can't override it.
50 - Intro to RSA - Crypto#
One common method of public key encryption is the RSA algorithm. Given p, q, e, and c, see if you can recover the message and find the flag!
RSA params are:
1 | p = 169524110085046954319747170465105648233168702937955683889447853815898670069828343980818367807171215202643149176857117014826791242142210124521380573480143683660195568906553119683192470329413953411905742074448392816913467035316596822218317488903257069007949137629543010054246885909276872349326142152285347048927 |
There is nothing to break, we only need to decipher it.
Here is my ruby script:
1 |
|
Just run it:
1 | $ ruby rsa.rb |