Beyond
This is the beyond page that an additional post enumeration and assessment are conducted as SYSTEM
after compromising the target system.
Scheduled Tasks
DevTasks
SYSTEM
cmd.exe C:\windows\system32\DevTasks.exe --deploy C:\work\dev.yaml --user ariah -p "Tm93aXNlU2xvb3BUaGVvcnkxMzkK" --server nickel-dev --protocol ssh
C:\windows\system32\DevTasks.exe
ws33333
SYSTEM
powershell.exe -nop -ep bypass C:\windows\system32\ws33333.ps1
C:\windows\system32\ws33333.ps1
PS C:\Windows\System32> cat C:\windows\system32\ws33333.ps1
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://+:33333/")
$listener.Start()
while ($listener.IsListening) {
$context = $listener.GetContext()
if ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/') {
[string]$html = 'Invalid Token'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/list-running-procs') {
[string]$html = '<p>Cannot "GET" /list-running-procs</p>'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/list-running-procs') {
$html = (Get-WmiObject win32_process | select name,commandline | Where-Object { $_ -NotMatch "svchost.exe"} | Format-List | Out-String)
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/list-current-deployments') {
[string]$html = '<p>Cannot "GET" /list-current-deployments</p>'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/list-active-nodes') {
[string]$html = '<p>Cannot "GET" /list-active-nodes</p>'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'POST') {
[string]$html = '<p>Not Implemented</p>'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'GET') {
[string]$html = '<p>Not Found</p>'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
}
ws80
SYSTEM
powershell.exe -nop -ep bypass C:\windows\system32\ws80.ps1
C:\windows\system32\ws80.ps1
PS C:\Windows\System32> cat C:\windows\system32\ws80.ps1 Param([STRING]$BINDING = 'http://+:80/', [STRING]$BASEDIR = "C:\temp\")
if ($BASEDIR -eq "")
{ # current filesystem path as base path for static content
$BASEDIR = (Get-Location -PSProvider "FileSystem").ToString()
}
# convert to absolute path
$BASEDIR = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BASEDIR)
# HTML answer templates for specific calls, placeholders !RESULT, !FORMFIELD, !PROMPT are allowed
$HTMLRESPONSECONTENTS = @{
'GET /' = @"
<!doctype html><html><body>dev-api started at $(Get-Date -Format s)
<pre>!RESULT</pre>
</body></html>
"@
}
# Set navigation header line for all web pages
# $HEADERLINE = "<p><a href='/'>Command execution</a> <a href='/script'>Execute script</a> <a href='/download'>Download file</a> <a href='/upload'>Upload file</a> <a href='/log'>Web logs</a> <a href='/starttime'>Webserver start time</a> <a href='/time'>Current time</a> <a href='/beep'>Beep</a> <a href='/quit'>Stop webserver</a></p>"
# Starting the powershell webserver
"$(Get-Date -Format s) Starting powershell webserver..."
$LISTENER = New-Object System.Net.HttpListener
$LISTENER.Prefixes.Add($BINDING)
$LISTENER.Start()
$Error.Clear()
try
{
"$(Get-Date -Format s) Powershell webserver started."
$WEBLOG = "$(Get-Date -Format s) Powershell webserver started.`n"
while ($LISTENER.IsListening)
{
# analyze incoming request
$CONTEXT = $LISTENER.GetContext()
$REQUEST = $CONTEXT.Request
$RESPONSE = $CONTEXT.Response
$RESPONSEWRITTEN = $FALSE
# log to console
"$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)"
# and in log variable
$WEBLOG += "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)`n"
# is there a fixed coding for the request?
$RECEIVED = '{0} {1}' -f $REQUEST.httpMethod, $REQUEST.Url.LocalPath
$HTMLRESPONSE = $HTMLRESPONSECONTENTS[$RECEIVED]
$RESULT = ''
# check for known commands
switch ($RECEIVED)
{
"GET /"
{
# $FORMFIELD = ''
$FORMFIELD = [URI]::UnescapeDataString(($REQUEST.Url.Query -replace "\?",""))
if (![STRING]::IsNullOrEmpty($FORMFIELD))
{
try {
$RESULT = Invoke-Expression -EA SilentlyContinue $FORMFIELD 2> $NULL | Out-String
}
catch
{
# just ignore. Error handling comes afterwards since not every error throws an exception
}
if ($Error.Count -gt 0)
{ # retrieve error message on error
$RESULT += "`nError while executing '$FORMFIELD'`n`n"
$RESULT += $Error[0]
$Error.Clear()
}
}
}
{ $_ -like "* /download" } # GET or POST method are allowed for download page
{ # download file
# is POST data in the request?
if ($REQUEST.HasEntityBody)
{ # POST request
# read complete header into string
$READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
$DATA = $READER.ReadToEnd()
$READER.Close()
$REQUEST.InputStream.Close()
# get headers into hash table
$HEADER = @{}
$DATA.Split('&') | ForEach-Object { $HEADER.Add([URI]::UnescapeDataString(($_.Split('=')[0] -replace "\+"," ")), [URI]::UnescapeDataString(($_.Split('=')[1] -replace "\+"," "))) }
# read header 'filepath'
$FORMFIELD = $HEADER.Item('filepath')
# remove leading and trailing double quotes since Test-Path does not like them
$FORMFIELD = $FORMFIELD -replace "^`"","" -replace "`"$",""
}
else {
}
# when path is given...
if (![STRING]::IsNullOrEmpty($FORMFIELD))
{ # check if file exists
if (Test-Path $FORMFIELD -PathType Leaf)
{
try {
# ... download file
$BUFFER = [System.IO.File]::ReadAllBytes($FORMFIELD)
$RESPONSE.ContentLength64 = $BUFFER.Length
$RESPONSE.SendChunked = $FALSE
$RESPONSE.ContentType = "application/octet-stream"
$FILENAME = Split-Path -Leaf $FORMFIELD
$RESPONSE.AddHeader("Content-Disposition", "attachment; filename=$FILENAME")
$RESPONSE.AddHeader("Last-Modified", [IO.File]::GetLastWriteTime($FORMFIELD).ToString('r'))
$RESPONSE.AddHeader("Server", "Powershell Webserver/1.2 on ")
$RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
# mark response as already given
$RESPONSEWRITTEN = $TRUE
}
catch
{
# just ignore. Error handling comes afterwards since not every error throws an exception
}
if ($Error.Count -gt 0)
{ # retrieve error message on error
$RESULT += "`nError while downloading '$FORMFIELD'`n`n"
$RESULT += $Error[0]
$Error.Clear()
}
}
else
{
# ... file not found
$RESULT = "File $FORMFIELD not found"
}
}
# preset form value with file path for the caller's convenience
$HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD
break
}
"GET /upload"
{ # present upload form, nothing to do here
break
}
"POST /upload"
{ # upload file
# only if there is body data in the request
if ($REQUEST.HasEntityBody)
{
# set default message to error message (since we just stop processing on error)
$RESULT = "Received corrupt or incomplete form data"
# check content type
if ($REQUEST.ContentType)
{
# retrieve boundary marker for header separation
$BOUNDARY = $NULL
if ($REQUEST.ContentType -match "boundary=(.*);")
{ $BOUNDARY = "--" + $MATCHES[1] }
else
{ # marker might be at the end of the line
if ($REQUEST.ContentType -match "boundary=(.*)$")
{ $BOUNDARY = "--" + $MATCHES[1] }
}
if ($BOUNDARY)
{ # only if header separator was found
# read complete header (inkl. file data) into string
$READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
$DATA = $READER.ReadToEnd()
$READER.Close()
$REQUEST.InputStream.Close()
# variables for filenames
$FILENAME = ""
$SOURCENAME = ""
# separate headers by boundary string
$DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | ForEach-Object {
# omit leading empty header and end marker header
if (($_ -ne "") -and ($_ -ne "--"))
{
# only if well defined header (seperation between meta data and data)
if ($_.IndexOf("`r`n`r`n") -gt 0)
{
# header data before two CRs is meta data
# first look for the file in header "filedata"
if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*);")
{
$HEADERNAME = $MATCHES[1] -replace '\"'
# headername "filedata"?
if ($HEADERNAME -eq "filedata")
{ # yes, look for source filename
if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "filename=(.*)")
{ # source filename found
$SOURCENAME = $MATCHES[1] -replace "`r`n$" -replace "`r$" -replace '\"'
# store content of file in variable
$FILEDATA = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$"
}
}
}
else
{ # look for other headers (we need "filepath" to know where to store the file)
if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*)")
{ # header found
$HEADERNAME = $MATCHES[1] -replace '\"'
# headername "filepath"?
if ($HEADERNAME -eq "filepath")
{ # yes, look for target filename
$FILENAME = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" -replace "`r$" -replace '\"'
}
}
}
}
}
}
if ($FILENAME -ne "")
{ # upload only if a targetname is given
if ($SOURCENAME -ne "")
{ # only upload if source file exists
# check or construct a valid filename to store
$TARGETNAME = ""
# if filename is a container name, add source filename to it
if (Test-Path $FILENAME -PathType Container)
{
$TARGETNAME = Join-Path $FILENAME -ChildPath $(Split-Path $SOURCENAME -Leaf)
} else {
# try name in the header
$TARGETNAME = $FILENAME
}
try {
# ... save file with the same encoding as received
[IO.File]::WriteAllText($TARGETNAME, $FILEDATA, $REQUEST.ContentEncoding)
}
catch
{
# just ignore. Error handling comes afterwards since not every error throws an exception
}
if ($Error.Count -gt 0)
{ # retrieve error message on error
$RESULT += "`nError saving '$TARGETNAME'`n`n"
$RESULT += $Error[0]
$Error.Clear()
}
else
{ # success
$RESULT = "File $SOURCENAME successfully uploaded as $TARGETNAME"
}
}
else
{
$RESULT = "No file data received"
}
}
else
{
$RESULT = "Missing target file name"
}
}
}
}
else
{
$RESULT = "No client data received"
}
break
}
"GET /log"
{ # return the webserver log (stored in log variable)
$RESULT = $WEBLOG
break
}
"GET /time"
{ # return current time
$RESULT = Get-Date -Format s
break
}
"GET /starttime"
{ # return start time of the powershell webserver (already contained in $HTMLRESPONSE, nothing to do here)
break
}
"GET /beep"
{ # Beep
[CONSOLE]::beep(800, 300) # or "`a" or [char]7
break
}
"GET /quit"
{ # stop powershell webserver, nothing to do here
break
}
"GET /exit"
{ # stop powershell webserver, nothing to do here
break
}
default
{ # unknown command, check if path to file
# create physical path based upon the base dir and url
$CHECKDIR = $BASEDIR.TrimEnd("/\") + $REQUEST.Url.LocalPath
$CHECKFILE = ""
if (Test-Path $CHECKDIR -PathType Container)
{ # physical path is a directory
$IDXLIST = "/index.htm", "/index.html", "/default.htm", "/default.html"
foreach ($IDXNAME in $IDXLIST)
{ # check if an index file is present
$CHECKFILE = $CHECKDIR.TrimEnd("/\") + $IDXNAME
if (Test-Path $CHECKFILE -PathType Leaf)
{ # index file found, path now in $CHECKFILE
break
}
$CHECKFILE = ""
}
if ($CHECKFILE -eq "")
{ # generate directory listing
$HTMLRESPONSE = "<!doctype html><html><head><title>$($REQUEST.Url.LocalPath)</title><meta charset=""utf-8""></head><body><H1>$($REQUEST.Url.LocalPath)</H1><hr><pre>"
if ($REQUEST.Url.LocalPath -ne "" -And $REQUEST.Url.LocalPath -ne "/" -And $REQUEST.Url.LocalPath -ne "`\" -And $REQUEST.Url.LocalPath -ne ".")
{ # link to parent directory
$PARENTDIR = (Split-Path $REQUEST.Url.LocalPath -Parent) -replace '\\','/'
if ($PARENTDIR.IndexOf("/") -ne 0) { $PARENTDIR = "/" + $PARENTDIR }
$HTMLRESPONSE += "<pre><a href=""$PARENTDIR"">[To Parent Directory]</a><br><br>"
}
# read in directory listing
$ENTRIES = Get-ChildItem -EA SilentlyContinue -Path $CHECKDIR
# process directories
$ENTRIES | Where-Object { $_.PSIsContainer } | ForEach-Object { $HTMLRESPONSE += "$($_.LastWriteTime.ToString()) <dir> <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)"">$($_.Name)</a><br>" }
# process files
$ENTRIES | Where-Object { !$_.PSIsContainer } | ForEach-Object { $HTMLRESPONSE += "$($_.LastWriteTime.ToString()) $("{0,10}" -f $_.Length) <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)"">$($_.Name)</a><br>" }
# end of directory listing
$HTMLRESPONSE += "</pre><hr></body></html>"
}
}
else
{ # no directory, check for file
if (Test-Path $CHECKDIR -PathType Leaf)
{ # file found, path now in $CHECKFILE
$CHECKFILE = $CHECKDIR
}
}
if ($CHECKFILE -ne "")
{ # static content available
try {
# ... serve static content
$BUFFER = [System.IO.File]::ReadAllBytes($CHECKFILE)
$RESPONSE.ContentLength64 = $BUFFER.Length
$RESPONSE.SendChunked = $FALSE
$EXTENSION = [IO.Path]::GetExtension($CHECKFILE)
if ($MIMEHASH.ContainsKey($EXTENSION))
{ # known mime type for this file's extension available
$RESPONSE.ContentType = $MIMEHASH.Item($EXTENSION)
}
else
{ # no, serve as binary download
$RESPONSE.ContentType = "application/octet-stream"
$FILENAME = Split-Path -Leaf $CHECKFILE
$RESPONSE.AddHeader("Content-Disposition", "attachment; filename=$FILENAME")
}
$RESPONSE.AddHeader("Last-Modified", [IO.File]::GetLastWriteTime($CHECKFILE).ToString('r'))
$RESPONSE.AddHeader("Server", "Powershell Webserver/1.2 on ")
$RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
# mark response as already given
$RESPONSEWRITTEN = $TRUE
}
catch
{
# just ignore. Error handling comes afterwards since not every error throws an exception
}
}
else
{ # no file to serve found, return error
if (!(Test-Path $CHECKDIR -PathType Container))
{
$RESPONSE.StatusCode = 404
$HTMLRESPONSE = '<!doctype html><html><body>Incorrect Parameter</body></html>'
}
}
}
}
# only send response if not already done
if (!$RESPONSEWRITTEN)
{
# insert header line string into HTML template
$HTMLRESPONSE = $HTMLRESPONSE -replace '!HEADERLINE', $HEADERLINE
# insert result string into HTML template
$HTMLRESPONSE = $HTMLRESPONSE -replace '!RESULT', $RESULT
# return HTML answer to caller
$BUFFER = [Text.Encoding]::UTF8.GetBytes($HTMLRESPONSE)
$RESPONSE.ContentLength64 = $BUFFER.Length
$RESPONSE.AddHeader("Last-Modified", [DATETIME]::Now.ToString('r'))
$RESPONSE.AddHeader("Server", "Powershell Webserver/1.2 on ")
$RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
}
# and finish answer to client
$RESPONSE.Close()
# received command to stop webserver?
if ($RECEIVED -eq 'GET /exit' -or $RECEIVED -eq 'GET /quit')
{ # then break out of while loop
"$(Get-Date -Format s) Stopping powershell webserver..."
break;
}
}
}
finally
{
# Stop powershell webserver
$LISTENER.Stop()
$LISTENER.Close()
"$(Get-Date -Format s) Powershell webserver stopped."
}
ws8089
SYSTEM
powershell.exe -nop -ep bypass C:\windows\system32\ws8089.ps1
C:\windows\system32\ws8089.ps1
PS C:\Windows\System32> cat C:\windows\system32\ws8089.ps1 Start-Sleep -Seconds 30
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://+:8089/")
$listener.Start()
$ipv4 = Test-Connection -ComputerName (hostname) -Count 1 | Select -ExpandProperty IPV4Address
$ipv4address = $ipv4.IPAddressToString
while ($listener.IsListening) {
$context = $listener.GetContext()
if ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/') {
[string]$html = "<h1>DevOps Dashboard</h1>
<hr>
<form action='http://$($ipv4address):33333/list-current-deployments' method='GET'>
<input type='submit' value='List Current Deployments'>
</form>
<br>
<form action='http://$($ipv4address):33333/list-running-procs' method='GET'>
<input type='submit' value='List Running Processes'>
</form>
<br>
<form action='http://$($ipv4address):33333/list-active-nodes' method='GET'>
<input type='submit' value='List Active Nodes'>
</form>
<hr>"
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
elseif ($context.Request.HttpMethod -eq 'GET') {
[string]$html = 'Not Found'
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
}
FTP
ariah
is the sole user
C:\ftp
directory