]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/ferdl/tasteless2019.md
phager
[pub/jan/ctf-seminar.git] / writeups / ferdl / tasteless2019.md
1 # Retrospective
2 Tasteless was a very cool CTF, with very challenging tasks. Although I couldn't contribute too much because
3 I don't have that much experience, the skill level was demanding enough to actually learn a lot of new stuff.
4
5 The timewarp challenge was also a very interesting (and funny!) one, which I think was the first ctf exploiting the fact of
6 the fact that there's a time change happening during the ctf. 
7
8 Overall it was a very pleasant experience and I was able to learn a lot of new stuff :)
9 # Attempted Challenges
10 ## web-gabbr
11
12 Gabbr is a website implementing a simple chat functionality with WebSockets.
13 Every room has a different UUID, e.g. https://gabbr.hitme.tasteless.eu/#8f332afe-8f1d-411f-80f3-44bb2302405d
14 If nothing is specified, a new one is generated.
15 There also is a report functionality where an admin joins into the room, stays for 15 seconds and then posts an image that there's nothing to see.
16
17 The idea is to post some xss (e.g. `<img src=x onerror="alert('lel')"/>`) which the admin then executes, however the CSP doesn't let us execute Javascript:
18 `Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'nonce-0f9635147014ab5895de26c6'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.`
19
20 So every Javascript code must have the correct <nonce> set in order to be executed.
21 We thought long about how to leak the nonce from the website and eventually found this link: https://sirdarckcat.blogspot.com/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html
22
23 The idea is to inject some css which sets background-url images to a server we control. When the admin then joins the room he will make calls to our server with the characters of the nonce.
24 This nonce can then be used to inject the JS to send the admin cookie to us. The comrades further tried to solve this challenge and I went on to some stego challenges.
25
26 Next day I came back to this challenge and we managed to solve it!
27 The full script which was running can be found here: https://w0y.at/writeup/2019/10/27/tasteless-2019-gabbr.html
28
29 ## stego-rgb
30 RGB was a stego challenge where a pcap network dump was given in which three flags were hidden: r, g and b.
31 By analysing the network dump with wireshark I found a GET request to `ctf.tasteless.eu/stegano` which responded with a PNG image file.
32 I exported the image out of the dump with wireshark and put it inside Stegsolver. It showed some hints at some color panes which apparently are references to RFC articles:
33 ```
34 Red plane 0:
35 2616
36 Category: Standards Track
37 == HTTP1.1
38 Green plane 0:
39 2083
40 Category: Informational
41 == PNG
42 Blue plane 0:
43 1951
44 Category: Informational
45 == DEFLATE
46 ```
47
48 I read all the RFC articles trying to find some hints but I couldn't find anything useful.
49 After some time a colleague mentioned that it's kind of weird that the response to the GET request where the image is transmitted is chunked.
50 The flag is hidden in there!
51 Each chunk starts with 1000; followed by a character. By following the TCP stream in wireshark and searching for "1000;" one can easily obtain the flag.
52 It was: `tctf{NoB0dy_3xPec7s_chUnK_ex7En5iOnz}`
53
54 The next flag was stored in the CRC fields of the IDAT chunks in the PNG image. It was solved by a colleague, however I couldn't gather more information on it from the mattermost channel.
55
56 ## web-timewarp
57 There are 2 endpoints on the website, /token and /timewarp.
58 At /token you can get a token which must be submitted at /timewarp, however it is only valid for 5 seconds. When submitting the token the server sends some lines and takes 1 second per line, only after several seconds the message "oh no! too slow! your token is not valid anymore :(" appears.
59
60 So we somehow have to make this token valid for a longer period. Let's look at the token:
61 ```Z2l2ZUZsYWcvRXVyb3BlL0Jlcmxpbg==.U2F0IE9jdCAyNiAxNzowOTo0NCAyMDE5.zCCIicp0CJdsvvoBiR34JHPeEufJej1MwyzfH3x7O_oyWpjftaMM0RFjd7yb4vVxSKG0CkIRaXrZfCgSIG3ADQ==```
62
63 which is
64 ```giveFlag/Europe/Berlin.Sat Oct 26 17:09:44 2019. t\bl\ 1\1d$s\12z=L,\1f|{:7h4DX&\R(m\ 2Z^``` when base64 decoded.
65
66 The structure is something like "giveFlag/Timezone.Timestamp.Gibberish"
67
68 Obviously we can try to change the time and submit it, however this results in: "oh no! what happened to your integrity?"
69 So there's probably a checksum in the gibberish part of the token which we also must manipulate...
70
71 So we need to figure out what the gibberish in the token means... but here I got stuck.
72
73 Another idea is based on the fact that in the night the daylight's saving time ends which means that the clock is gonna be set back one hour.
74 So maybe one can generate a token before the change and then you have a 1-hour window where the token is valid.
75 The challenge description "!!! challenge will shutdown at approx 02:30 UTC !!!" also hints at that...
76
77 A colleague wrote a script which tried getting a token and submitting it every 10 minutes:
78
79 ```
80 #!/usr/bin/python3
81
82 import requests
83 import re
84 from datetime import datetime
85 import time
86
87 server = 'http://hitme.tasteless.eu:10101'
88 get_token_path = '/token'
89
90 def get_token():
91
92     r = requests.get(server + get_token_path)
93     response = r.text
94
95     match = re.search(r'href=\"(.+)\".*', response)
96     if(match):
97         use_token_path = match.groups()[0]
98         return(server + use_token_path)
99     else:
100         return None
101
102 def use_token(use_token_url):
103     r = requests.get(use_token_url)
104     response = r.text
105     return response
106
107 tokens = {}
108 while(datetime.now() < datetime(2019, 10, 27, 4, 15)):
109     if(datetime.now() > datetime(2019,10,27, 1, 50)):
110         new_token = get_token()
111         if(new_token):
112             tokens[datetime.now()] = new_token
113
114         for timestamp in tokens.keys():
115             response = use_token(tokens[timestamp])
116             print("Token acquired at '{0}' lead to the following response when used at '{1}':\n\n{2}\n".format(timestamp, datetime.now(), response))
117     
118         print('------- Going to sleep ------\n\n')
119         time.sleep(60 * 10)
120 ```
121
122 It worked like a charm. However, I think someone else solved it manually first, at least according to the mattermost channel. Unfortunately I wasn't up at that time.