From 8cf3244551714fffa72ca504540fa1a304612bac Mon Sep 17 00:00:00 2001 From: Alexander Ponticello Date: Sun, 19 Jan 2020 18:56:47 +0100 Subject: [PATCH] Fix paths to images --- writeups/aliponti/asis19.md | 2 +- writeups/aliponti/asis192.md | 253 ---------------------- writeups/aliponti/otw_advent_bonanza19.md | 2 +- writeups/aliponti/ructfe19.md | 12 +- 4 files changed, 8 insertions(+), 261 deletions(-) delete mode 100644 writeups/aliponti/asis192.md diff --git a/writeups/aliponti/asis19.md b/writeups/aliponti/asis19.md index 5427166..1b65cde 100644 --- a/writeups/aliponti/asis19.md +++ b/writeups/aliponti/asis19.md @@ -120,7 +120,7 @@ My next thought was that some kind of steganography was used in order to hide da Next I tried one of the useful online tools. [https://aperisolve.fr/](https://aperisolve.fr/) let's you upload images for analysis. So I did that for all the images and bingo, 3 of them (exactly the ones with no text comment) had some suspicious looking least significant bit (LSB) values. When split into plains for every bit of every color channel, one could see that the least-significant bits where almost all black, except for a single line at the beginning. This is a very common technique to hide data in images, since the change introduced is almost invisible to the naked eye. I used the website and also some scripts to extract the data but it did not make any sense. I tried several permutations and different was of extracting it, without any luck. I also checked different CTF challenges from previous contests ([1](https://github.com/krx/CTF-Writeups/blob/master/CSAW%2016%20Quals/for250%20-%20Watchword/jk_actual_writeup.md), [2](https://hexpresso.wordpress.com/2014/05/10/asis-ctf-quals-2014-stego-100-blocks-write-up/), [3](https://honeysec.blogspot.com/2019/05/writeup-csactf19-challenges.html?m=1), [4](https://veteransec.com/2018/10/18/vetsec-takes-first-in-the-hacktober-ctf-summary-steganography-write-up/), [5](https://shankaraman.wordpress.com/category/ctf/stegano/) to find any other method I could use but did not find anything so after some time I gave up. -[Image split into planes](plain_analysis.png) +[Image split into planes](asis19/plain_analysis.png) After the CTF was over, I checked the write ups and found a [solution to this challenge by p4](https://github.com/p4-team/ctf/tree/master/2019-11-16-asis-finals/secrets). Apparently the authors used [this tool](https://github.com/mgeitz/albumfs) from github to create the challenge. It's a steganography tool that hides a file system in a bunch of images, using the LSB method as well as XOR-encryption with a key. I never saw this tool before as I do not think it is very popular, judging by the relatively few stars and forks it got on github. However, now it became clear that the challenge name was a hint to this tool, as well as the comment I found. With more profound research from my side I should have been able to find this tool and make more sense of this challenge. I tried using the tool to solve the challenge at this point but apparently the code does not compile without editing and since my C skills are scary low, I did not proceed with it. diff --git a/writeups/aliponti/asis192.md b/writeups/aliponti/asis192.md deleted file mode 100644 index 764e2cc..0000000 --- a/writeups/aliponti/asis192.md +++ /dev/null @@ -1,253 +0,0 @@ -# ASIS CTF Finals 2019 - -While playing ASIS CTF Finals 2019, I worked on the following challenges. - -## Protected Area - -| Points | Solves | Category | -|--------|--------|----------| -| 119 | 41 | web | - -Description: - -> We have built an area protected by a hard password -> -> Note: DO NOT Brute force the server (the rate limit will ban you), the question may need an OFFLINE brute-force! - -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`: - -``` -var file_check = function(file){ - - $.ajax({ - url: '/check_perm/readable/', - data: {'file': file} - }).done(function(data){ - if (data == "True") { - file_read(file) - }else{ - console.log('fail') - } - }) -} - -var file_read = function(file){ - - $.ajax({ - url: '/read_file/', - data: {'file': file} - }).done(function(data){ - update_page(data) - }) - - return -} - -var update_page = function(text){ - $("#t").append(text) -} - -$(document).ready(function() { - console.log("ready!"); - - file_check('public.txt'); -}); -``` - -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. - -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. - -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. - -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. - -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: - -``` -from .functions import * -... -@app.route('/protected_area_0098', methods=['GET']) -@check_login -def app_protected_area() -> str: - return Config.FLAG -``` - -and looking at `functions.py`, we see: - -``` -def check_login(f): - """ - Wraps routing functions that require a user to be logged in - """ - @wraps(f) - def wrapper(*args, **kwds): - try: - ah = request.headers.get('ah') - - if ah == hashlib.md5((Config.ADMIN_PASS + Config.SECRET).encode("utf-8")).hexdigest(): - return f(*args, **kwds) - else: - return abort(403) - except: - return abort(403) - return wrapper -``` - -So we finaly check `http://66.172.33.148:8008/read_file/?file=....//....//....///app/config.py&test=ha.txt`: - -``` - FLAG = os.environ.get('FLAG') - SECRET = "s3cr3t" - ADMIN_PASS = "b5ec168843f71c6f6c30808c78b9f55d" -``` - -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}` - -## Secrets - -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 - -https://github.com/apsdehal/awesome-ctf#forensics-1 -https://aperisolve.fr/ -https://github.com/DominicBreuker/stego-toolkit -https://georgeom.net/StegOnline/extract -https://github.com/krx/CTF-Writeups/blob/master/CSAW%2016%20Quals/for250%20-%20Watchword/jk_actual_writeup.md -https://hexpresso.wordpress.com/2014/05/10/asis-ctf-quals-2014-stego-100-blocks-write-up/ -https://honeysec.blogspot.com/2019/05/writeup-csactf19-challenges.html?m=1 -https://shankaraman.wordpress.com/category/ctf/stegano/ -https://veteransec.com/2018/10/18/vetsec-takes-first-in-the-hacktober-ctf-summary-steganography-write-up/ - - -## Serifin - -This crypto challenge presents us with some code used to encrypt a flag, we also get the output of said encryption operation: - -``` -#!/usr/bin/env python - -from Crypto.Util.number import * -from flag import flag -import gmpy2 - -def serifin(a, l): - S, s = a, a - while True: - S += float(a)/float(l) - if S - s < .0001: - return int(S) + 1 - else: - s, a = S, float(a)/float(l) - -def genPrime(nbit): - while True: - p = getPrime(512) - if p % 9 == 1 and p % 27 >= 2: - q = gmpy2.next_prime(serifin(p, 3) + serifin(p, 9) + serifin(p, 27)) - if q % 9 == 1 and q % 27 >= 2: - return int(p), int(q) - -def encrypt(m, n): - m = bytes_to_long(m) - assert m < n - return pow(m, 3, n) - -nbit = 512 -p, q = genPrime(nbit) -n = p * q -c = encrypt(flag, n) - -print 'c =', c -print 'n =', n -``` -``` -c = 78643169701772559588799235367819734778096402374604527417084323620408059019575192358078539818358733737255857476385895538384775148891045101302925145675409962992412316886938945993724412615232830803246511441681246452297825709122570818987869680882524715843237380910432586361889181947636507663665579725822511143923 -n = 420908150499931060459278096327098138187098413066337803068086719915371572799398579907099206882673150969295710355168269114763450250269978036896492091647087033643409285987088104286084134380067603342891743645230429893458468679597440933612118398950431574177624142313058494887351382310900902645184808573011083971351 -``` - -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: - -``` ->>> p -10718841513477905819120058147135847238291365362593720675636253771082993743788743083816117407101738585993985506647694375447285228211151896705183442597773779L ->>> q -39268063621491165558293370755773815300739539018598511154583095766289789691669875090357609442626770422364550257841199885538175542665396553281747272813511469L -``` - -So this should be easy from there, just do regular RSA decryption and we are done here. - -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 - -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: - -``` -#assumes p prime, it returns all cube roots of a mod p -def cuberoots(a, p): - - #Non-trivial solution of x^r=1 - def onemod(p,r): - t=p-2 - while pow(t,(p-1)/r,p)==1: t-=1 - return pow(t,(p-1)/r,p) - - def solution(p,root): - g=onemod(p,3) - return [root%p,(root*g)%p,(root*g^2)%p] - - #---MAIN--- - a=a%p - - if p in [2,3] or a==0: return [a] - if p%3 == 2: return [pow(a,(2*p - 1)/3, p)] - - #There are 3 or no solutions - - #No solution - if pow(a,(p-1)/3,p)>1: return [] - - - if p%9 == 4: #[13, 31, 67] - root = pow(a,(2*p + 1)/9, p) - if pow(root,3,p) == a: return solution(p,root) - else: return [] - - if p%9 == 7: #[7, 43, 61, 79, 97 - root = pow(a,(p + 2)/9, p) - if pow(root,3,p) == a: return solution(p,root) - else: return [] - - if p%27 == 10: #[37, 199] - root = pow(a,(2*p +7)/27, p) - h=onemod(p,9) - for i in xrange(9): - if pow(root,3,p) == a: return solution(p,root) - root*=h - return [] - - if p%27 == 19: #[19, 73, 127, 181] - root = pow(a,(p + 8)/27, p) - h=onemod(p,9) - for i in xrange(9): - if pow(root,3,p) == a: return solution(p,root) - root*=h - return [] - #We need an algorithm for the remaining cases - return tonelli3(a,p,True) -``` - -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. - -Afterwards, this write-up got published https://github.com/p4-team/ctf/tree/master/2019-11-16-asis-finals/serifin - -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. - -## Ema's secret - -look at code, discuss with collegues, propose decryption, - -## Close primes - -same pow as before, check if there are scripts for next prime, note mistake in condition, naive gmpy next_prime implementation - -https://math.stackexchange.com/questions/35300/what-is-the-most-efficient-algorithm-to-find-the-closest-prime-less-than-a-given -https://stackoverflow.com/questions/10143431/finding-the-nth-twin-prime diff --git a/writeups/aliponti/otw_advent_bonanza19.md b/writeups/aliponti/otw_advent_bonanza19.md index 915dd5c..4b06bdc 100644 --- a/writeups/aliponti/otw_advent_bonanza19.md +++ b/writeups/aliponti/otw_advent_bonanza19.md @@ -14,7 +14,7 @@ The second Easter egg had no description at all just the link to the CTFs websit For Easter egg number 3 we got a link to a tweet by the organizers showing a picture of a matrix barcode that had the characteristic corner elements of a QR-code. So i scanned it, obtaining the resulting string `137:64:137:154:171:146:63:175`. This fits the format of an IPv6 address. So I checked, but no server was responding to this IP. I also tried converting the numbers to ASCII symbols, since the numbers would be in the range of octal numbers for ASCII characters. but the result did not look like anything meaningful to me `_4_lyf3}`. What I missed, but luckily not one of my colleagues who solved the challenge in the end, is that the middle part of the matrix code was in fact not part of the QR-encoding, but is part is the position marker for another matrix encoding known as [Aztec Code](https://en.wikipedia.org/wiki/Aztec_Code). So what we have after all is a matrix barcode polyglot, since the code is both a valid QR-Code as well as Aztec-Code. This is possible because such codes have error correcting properties that allow for extra stuff to be included while keeping the code readable. [1](https://hackaday.com/2011/08/11/how-to-put-your-logo-in-a-qr-code/) -[Easter Egg 3](easter_egg3.jpeg) +[Easter Egg 3](otw_advent19/easter_egg3.jpeg) ## Time report diff --git a/writeups/aliponti/ructfe19.md b/writeups/aliponti/ructfe19.md index 2603966..7c51bef 100644 --- a/writeups/aliponti/ructfe19.md +++ b/writeups/aliponti/ructfe19.md @@ -10,11 +10,11 @@ I work on the locator service together with Markus Bauer. The service was, simil This already made it clear what we want to achieve, namely access other users (the GS) info data. In order to find a vulnerability we looked through the code and fund a hardcoded `secretHashKey`, which was used in the context of session generation (see Figure 1). -[Fixed hash key](fixed_secret.png) +[Fixed hash key](ructfe19/fixed_secret.png) So our first thought was that maybe we are able to generate a valid session for another user. We generated some sessions for ourself, trying to find some pattern in it but without any luck. So we looked further into the code in order to understand, how sessions generation was done and where the hardcoded secret was used. But by doing so, we stumbled over something even more interesting. The service included a class with the sounding name `RuntimeClassLoader`, which did exactly as its name suggests, loading classes at runtime. -[RuntimeClassLoader class](class_loader.png) +[RuntimeClassLoader class](ructfe19/fclass_loader.png) This is known to cause problems [1](https://stackoverflow.com/questions/42720149/what-is-a-purpose-of-the-secureclassloader), also the IDE I used highlighted already that the `defineClass` method used had been deprecated. If we are able to inject arbitrary bytecode into this method, we have a potential RCE. We found one usage of the method in question in the `Decoder` class `isValidKey` method, which gets its input from the `isValid` method of the `RegisterData` class. This class is annotated as serializable. We figured out that the registers frontend sends the data as a json-encoded serialized Java object. So in order in get our custom class loaded we have to compile it, encode it and send it to the `/register` endpoint as a custom filed. Another important puzzle piece was found in the `decodeMessage` method of the `Decoder` class. Here, the previously input class gets instanciated, resulting the execution of its constructor method. We can use this to execute arbitrary code and retrieve the output via a call to the `/info` endpoint. @@ -22,7 +22,7 @@ This is known to cause problems [1](https://stackoverflow.com/questions/42720149 In order to exploit this, the first thing to do is write a Java class where to constructor contains the code one wants to execute. In this case, we retrieved a list of users and read their message data containing the flags. We made use of the service's OR mapping functionality. The so build Java class can the be compiled, also using the pre-build Java jar. By doing so we obtain the Java bytecode. This can then be send to the `/register` endpoint, along with the other necessary data. We did so using a python script and the requests library. The output of the code can then be accessed via the `/info` endpoint. -[Java class used in the exploit](exploit_class.png) +[Java class used in the exploit](ructfe19/fexploit_class.png) #### Considerations @@ -32,17 +32,17 @@ Since this was an attack-defense CTF, there are some additional steps one might Before exploiting the services of other teams, we tried to fix our own service, since we expected other teams to copy our exploit rather quickly. So we deployed a quick-and-dirty fix by filtering out every request that was larger than a certain threshold. We check typical GS requests to the `/register` endpoint and noticed that they where significantly smaller than requests produced by our exploit. This fix proved sufficient for the rest of the CTF since it constrained other teams well enough in the exploiting that we did not loose any flags. Of course, in a real-world scenario this is not an ideal solution. A better patch would be letting `RuntimeClassLoader` inherit from `SecureClassLoader` provided by Java/Kotlin and restrain the loaded class accordingly. This could prove efficient since a standard class used by this service does not run any code so restrictions could be quite strict. -[Patched RuntimeClassLoader class](patch_classloader.png) +[Patched RuntimeClassLoader class](ructfe19/fpatch_classloader.png) ### Further developing the exploit When the fix was deployed and the first version of the exploit was busy stealing flags, we had still plenty of time left in the CTF to become creative with this RCE. The most promising idea that came up was, using the RCE to patch other teams services in order for them to not be vulnerable to the exploit any more. This should prevent other teams from exploiting weaker teams who did not patch their service and where therefore an easy target. Of course, in order for us to be able to still exploit the service, we implemented a backdoor in our patch. So we patched the `RuntimeClassLoader` class to include a blacklist for the `loadClass` method, filtering out malicious functionality such as accessing user messages. The backdoor was a simple hard coded string that let every input get through. We then modified our exploit Java class to replace the `RuntimeClassLoader` in the original service jar with our adapted version. -[Placing the modified class in the jar](patch_class.png) +[Placing the modified class in the jar](ructfe19/fpatch_class.png) Lastly, we had to force the container to restart to use the patched version of the code. This was easy, since the Docker-compose configuration used had `restart: always` set, meaning the container restarts automatically once the executed program terminates. So we could just call `System.exit(0)` inside our malicious Java class. During the CTF, other teams adapted their exploits to bypass our implemented patch, which in order made us adapt our blacklist and repatch services through our backdoor and so on and so fourth. -[Final version of the patch blacklist](patch_advances.png) +[Final version of the patch blacklist](ructfe19/fpatch_advances.png) ### The take away -- 2.43.0