]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/aliponti/asis192.md
Add writeups Alexander Ponticello
[pub/jan/ctf-seminar.git] / writeups / aliponti / asis192.md
1 # ASIS CTF Finals 2019
2
3 While playing ASIS CTF Finals 2019, I worked on the following challenges.
4
5 ## Protected Area
6
7 | Points | Solves | Category |
8 |--------|--------|----------|
9 | 119    | 41     | web      |
10
11 Description:
12
13 > We have built an area protected by a hard password
14
15 > Note: DO NOT Brute force the server (the rate limit will ban you), the question may need an OFFLINE brute-force!
16
17 This challenge presents us with a webpage, that displays some text and after an instance, some more text apears. Therefore, we immediately look at the source code and find the resposible javascript in `app.js`:
18
19 ```
20 var file_check = function(file){
21
22     $.ajax({
23         url: '/check_perm/readable/',
24         data: {'file': file}
25     }).done(function(data){
26         if (data == "True") {
27             file_read(file)
28         }else{
29             console.log('fail')
30         }
31     })
32 }
33
34 var file_read = function(file){
35
36     $.ajax({
37         url: '/read_file/',
38         data: {'file': file}
39     }).done(function(data){
40         update_page(data)
41     })
42
43     return
44 }
45
46 var update_page = function(text){
47     $("#t").append(text)
48 }
49
50 $(document).ready(function() {
51     console.log("ready!");
52
53     file_check('public.txt');
54 });
55 ```
56
57 So there apear to exist at least two endpoint, `/check_perm/readable/` and `/read_file/`. We look at the first one and notice that it returns `True` supossedly if the requested file exists and we can read it, otherwise `0` is returned. The way of writing `True` with capital `T` strongly suggestets that we are dealing with a python backend, probably flask. 
58
59 The other endpoint returns the content of the requested file. Just out of curiosity we try `http://66.172.33.148:8008/read_file/?file=private.txt` but no flag there. Requests for files like `flag.txt` returns `500`, while `flag` without ending returns `Security`. Strange. Further investigation shows that all non-txt file requests return `500` (probably file does not exists) while requests for other file endings return `Security`. Therefore we supose, only txt files can be returned.
60
61 A common vulnerability of file access systems like this is path traversal, where we use a relative path to break out of the current directory and access files outside of the normal scope. We can try this by requesting `../../../../../../../../../../etc/passwd` for both endpoints. The first one does return `True`, so we know that path traversal is possible, however no luck on the second one, that returns `Security` as expected, since it's no txt file. Adding a `.txt` ending to the file turns this into a `500` response, since that file does not exist.
62
63 My guess now was that we need to exploit some weird behaviour of the file open funtion in python, maybe including wildcards like `*` or `?`, but no luck there. This would only work if the app uses the `glob` function. However we notice something interesting, namely the second endpoint does strip out `../`, while the first one does not. We can gain path traversal on the second endpoint with `..././`. I also tried to exploit this fact to bypass the txt-only check with requests for files like `/etc/passwd.tx../t` but this does not help in any way. This is where everything got stuck a bit and I continued with mapping out what files are on the system and trying to find out where our current working directory is but no jucy information was to be found.
64
65 We came to a solution when someone in the mattermost chat found the trick to include non-txt files, like so `http://66.172.33.148:8008/read_file/?file=....//....//....///app/main.py&test=ha.txt`. This means the app is checking the whole argument string for a txt ending and not only the relevant paramter. With this, we where able to download the app code, which, apart from the stuff we already figured out, had some intersting functionality included:
66
67 ```
68 from .functions import *
69 ...
70 @app.route('/protected_area_0098', methods=['GET'])
71 @check_login
72 def app_protected_area() -> str:
73     return Config.FLAG
74 ```
75
76 and looking at `functions.py`, we see:
77
78 ```
79 def check_login(f):
80     """
81     Wraps routing functions that require a user to be logged in
82     """
83     @wraps(f)
84     def wrapper(*args, **kwds):
85         try:
86             ah = request.headers.get('ah')
87
88             if ah == hashlib.md5((Config.ADMIN_PASS + Config.SECRET).encode("utf-8")).hexdigest():
89                 return f(*args, **kwds)
90             else:
91                 return abort(403)
92         except:
93             return abort(403)
94     return wrapper
95 ```
96
97 So we finaly check `http://66.172.33.148:8008/read_file/?file=....//....//....///app/config.py&test=ha.txt`:
98
99 ```
100     FLAG       = os.environ.get('FLAG')
101     SECRET     = "s3cr3t"
102     ADMIN_PASS = "b5ec168843f71c6f6c30808c78b9f55d"
103 ```
104
105 So we can craft the request as follows `curl -H "ah: cbd54a3499ba0f4b221218af1958e281" http://66.172.33.148:8008/protected_area_0098` and jackpot: `ASIS{f70a0203d638a0c90a490ad46a94e394}`
106
107 ## Secrets
108
109 Download pictures, exiftool, binwalk, foremost, strings, get comments, get base64 string, get password, extract palete, test for stegano, find DOS executale, try making it work, more stegano, even more, find out about lsb in certain images, extract bits, try figuring it out manually, notice commend makes kind of sense, use tools, different outputs, look into password algos 
110
111 https://github.com/apsdehal/awesome-ctf#forensics-1
112 https://aperisolve.fr/
113 https://github.com/DominicBreuker/stego-toolkit
114 https://georgeom.net/StegOnline/extract
115 https://github.com/krx/CTF-Writeups/blob/master/CSAW%2016%20Quals/for250%20-%20Watchword/jk_actual_writeup.md
116 https://hexpresso.wordpress.com/2014/05/10/asis-ctf-quals-2014-stego-100-blocks-write-up/
117 https://honeysec.blogspot.com/2019/05/writeup-csactf19-challenges.html?m=1
118 https://shankaraman.wordpress.com/category/ctf/stegano/
119 https://veteransec.com/2018/10/18/vetsec-takes-first-in-the-hacktober-ctf-summary-steganography-write-up/
120
121
122 ## Serifin
123
124 This crypto challenge presents us with some code used to encrypt a flag, we also get the output of said encryption operation:
125
126 ```
127 #!/usr/bin/env python
128
129 from Crypto.Util.number import *
130 from flag import flag
131 import gmpy2
132
133 def serifin(a, l):
134     S, s = a, a
135     while True:
136         S += float(a)/float(l)
137         if S - s < .0001:
138             return int(S) + 1
139         else:
140             s, a = S, float(a)/float(l)
141
142 def genPrime(nbit):
143     while True:
144         p = getPrime(512)
145         if p % 9 == 1 and p % 27 >= 2:
146             q = gmpy2.next_prime(serifin(p, 3) + serifin(p, 9) + serifin(p, 27))
147             if q % 9 == 1 and q % 27 >= 2:
148                 return int(p), int(q)
149
150 def encrypt(m, n):
151     m = bytes_to_long(m)
152     assert m < n
153     return pow(m, 3, n)
154
155 nbit = 512
156 p, q = genPrime(nbit)
157 n = p * q
158 c = encrypt(flag, n)
159
160 print 'c =', c
161 print 'n =', n
162 ```
163 ```
164 c = 78643169701772559588799235367819734778096402374604527417084323620408059019575192358078539818358733737255857476385895538384775148891045101302925145675409962992412316886938945993724412615232830803246511441681246452297825709122570818987869680882524715843237380910432586361889181947636507663665579725822511143923
165 n = 420908150499931060459278096327098138187098413066337803068086719915371572799398579907099206882673150969295710355168269114763450250269978036896492091647087033643409285987088104286084134380067603342891743645230429893458468679597440933612118398950431574177624142313058494887351382310900902645184808573011083971351
166 ```
167
168 So at a first glance, this looks like textbook-RSA with `e = 3`. What is even more suspicious is that the primes get derived with some weird, fishy looking algorithm. Without further analysing that I checked factordb if the `n` has known factors and indeed, we get lucky:
169
170 ```
171 >>> p
172 10718841513477905819120058147135847238291365362593720675636253771082993743788743083816117407101738585993985506647694375447285228211151896705183442597773779L
173 >>> q
174 39268063621491165558293370755773815300739539018598511154583095766289789691669875090357609442626770422364550257841199885538175542665396553281747272813511469L
175 ```
176
177 So this should be easy from there, just do regular RSA decryption and we are done here.
178
179 But wait, not so fast. As it turns out, `gcd(3, phi(n)) = 3`. Doh! So RSA decryption does no work since there is no multiplicative inverse `e^-1 mod n`. So I searched the web for this problem of RSA and found two relevant CTF challenges that look similar here https://github.com/p4-team/ctf/tree/master/2016-03-12-0ctf/rsa and here https://github.com/p4-team/ctf/tree/master/2015-10-18-hitcon/crypto_314_rsabin#eng-version
180
181 The first link looks especially promising so I tried to implement their code used to solve it. On of the main problems is finding the cube root `mod n` boils down to find the cube root `mod p` or `mod q` accordingly, thanks to the Chinese Remainder Theorem. Unfortunately I had less luck than the authors of the write up, since Wolfram Alpha was not able to calculate the roots for me. So I ended up using this code I found on Stackoverflow: 
182
183 ```
184 #assumes p prime, it returns all cube roots of a mod p
185 def cuberoots(a, p):
186
187     #Non-trivial solution of x^r=1
188     def onemod(p,r):
189         t=p-2
190         while pow(t,(p-1)/r,p)==1: t-=1
191         return pow(t,(p-1)/r,p)
192
193     def solution(p,root):
194         g=onemod(p,3)
195         return [root%p,(root*g)%p,(root*g^2)%p]
196
197     #---MAIN---
198     a=a%p
199
200     if p in [2,3] or a==0: return [a]
201     if p%3 == 2: return [pow(a,(2*p - 1)/3, p)]
202
203     #There are 3 or no solutions
204
205     #No solution
206     if pow(a,(p-1)/3,p)>1: return []
207
208
209     if p%9 == 4:                                #[13, 31, 67]
210         root = pow(a,(2*p + 1)/9, p)
211         if pow(root,3,p) == a: return solution(p,root)
212         else: return []
213
214     if p%9 == 7:                                #[7, 43, 61, 79, 97
215         root = pow(a,(p + 2)/9, p)
216         if pow(root,3,p) == a: return solution(p,root)
217         else: return []
218
219     if p%27 == 10:                              #[37, 199]
220         root = pow(a,(2*p +7)/27, p)
221         h=onemod(p,9)
222         for i in xrange(9):
223             if pow(root,3,p) == a: return solution(p,root)
224             root*=h
225         return []
226
227     if p%27 == 19:                              #[19, 73, 127, 181]
228         root = pow(a,(p + 8)/27, p)
229         h=onemod(p,9)
230         for i in xrange(9):
231             if pow(root,3,p) == a: return solution(p,root)
232             root*=h
233         return []
234     #We need an algorithm for the remaining cases
235     return tonelli3(a,p,True)
236 ```
237
238 Here I noticed some similarities with the original code, namely the line `if q % 9 == 1 and q % 27 >= 2`. This looks like we are on the right track and indeed, `p` and `q` are equal to `10`, `19` `mod 27` respectively. Si we know we will find the roots if the exist and indeed we find 3 roots for `p` but none for `q` so this leads is also a dead end. I looked into the other link, namely Rabin's Algorithm, where we can decrypt a similar text if `e` is a power of 2, but I could no come up with a way to get rid of the factor 3, which remains always regardles of the fator we mutliply it with. I also read more about CRT and found some more interesting links here http://www.math.clemson.edu/~sgao/crypto_mod/node3.html and here https://www.rootnetsec.com/picoctf-weird-rsa but nothing led anywhere so this is how far I came during the CTF.
239
240 Afterwards, this write-up got published https://github.com/p4-team/ctf/tree/master/2019-11-16-asis-finals/serifin
241
242 So aparently I was not that far off, I just made a mistack at calculating the roots, somehow the algorithm I used faild me. What we learn is, never blindly trust code. Also my understanding of the matter was not profound enough to really reflect on what is going on, I mostly just mimicked what other people where doing, still lot's to learn.
243
244 ## Ema's secret
245
246 look at code, discuss with collegues, propose decryption, 
247
248 ## Close primes
249
250 same pow as before, check if there are scripts for next prime, note mistake in condition, naive gmpy next_prime implementation
251
252 https://math.stackexchange.com/questions/35300/what-is-the-most-efficient-algorithm-to-find-the-closest-prime-less-than-a-given
253 https://stackoverflow.com/questions/10143431/finding-the-nth-twin-prime