-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fetch assets within iris alert (#162)
* Add endpoint to fetch assets by alert ID * Update CaseModel state_id and state_name fields * Add stack provisioning router * Update rotate_empty_index_set field to be optional * send sap siem to shuffle endpoint * Add command to retrieve admin password * some precommit fixes * Refactor provision_wazuh_content_pack route in Graylog * Add function to load content pack JSON file * Add insert_content_pack function to Graylog content_packs service * wazuh content pack change * Fix bug in login functionality * Update Wazuh Content Pack name * Add logging for POST request response and implement content pack installation and graylog system checking to ensure graylog version supports the content pack * add active-response readme * active response testing * Add windows firewall active response * Refactor firewall script and add block_ip function * precommit fixes * Update Windows Firewall script to include IP unblocking * Add wait_for_complete parameter to provision_wazuh_content_pack_route() * Add firewall.spec to .gitignore * Update active response command and add IP blocking action * full path for netsh * Fix Windows firewall script and update README.md * Update Wazuh configuration in readme * Update action to unblock IP and add agents list parameter * add active response markdowns * Add InvokeActiveResponseRequest model and update provision_wazuh_content_pack_route * precommit fixes * Refactor active response validation and alert creation * precommit fixes * precommit fixes * README updates * updated dependencies * updated vite config * Handle exception when creating customer in provision_wazuh_customer * readme install python * add to windows_firewall ar logging * updated npm scripts * updated assets types * updated soc assets apis * added soc alert assets component * updated soc asset description * ready for prod build * precommit fixes --------- Co-authored-by: Davide Di Modica <[email protected]>
- Loading branch information
1 parent
20eee2c
commit dc5fda1
Showing
41 changed files
with
5,996 additions
and
229 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,3 +52,4 @@ backend/file-store/api.config.yaml | |
unplugin.components.d.ts | ||
package-lock.json | ||
*.checkpoint | ||
firewall.spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,22 @@ | ||
{ | ||
"cSpell.words": [ | ||
"ajoelp", | ||
"apexchart", | ||
"colord", | ||
"datejs", | ||
"datetimesec", | ||
"echarts", | ||
"Healthcheck", | ||
"majesticons", | ||
"mimecast", | ||
"picocolors", | ||
"redoc", | ||
"rushstack", | ||
"sparkline", | ||
"taze", | ||
"uvicorn", | ||
"venv", | ||
"Wazuh" | ||
"Wazuh", | ||
"xaxis" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
Windows custom active response configuration | ||
|
||
You can implement the custom Python script on Windows endpoints using two methods. The first method converts Python scripts to executable applications, while the second method uses a Windows Batch launcher to run the Python script. | ||
|
||
Both methods require Python installed on the Windows endpoint. Use the following steps below to install Python on the Windows endpoint. | ||
|
||
#. Download Python executable installer from the `official Python website <https://www.python.org/downloads/windows/>`\_\_. | ||
#. Run the Python installer once downloaded. Check the following boxes when prompted and start the installation: | ||
|
||
- **Use admin privileges when installing py.exe**. | ||
- **Add python.exe to PATH**. This places the interpreter in the execution path. | ||
|
||
Or you can use the following PowerShell command to install Python: | ||
|
||
``` | ||
Invoke-WebRequest -Uri "https://www.python.org/ftp/python/3.11.0/python-3.11.0-amd64.exe" -OutFile "$env:TEMP\python-3.11.0-amd64.exe"; Start-Process -FilePath "$env:TEMP\python-3.11.0-amd64.exe" -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1" -Wait -NoNewWindow | ||
``` | ||
|
||
Method 1: Convert the Python script to an executable application | ||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ||
|
||
#. Open an administrator PowerShell terminal and use `pip` to install `pyinstaller`: | ||
|
||
> pip install pyinstaller | ||
> pyinstaller --version | ||
|
||
#. Run the following command using PowerShell with administrator privileges to create the executable file: | ||
|
||
❗ - Make sure to point to the Wazuh DLLs | ||
|
||
```powershell | ||
pyinstaller --log-level DEBUG --add-data "C:\Program Files (x86)\ossec-agent\libwazuhext.dll;." --add-data "C:\Program Files (x86)\ossec-agent\libwinpthread-1.dll;." --add-data "C:\Program Files (x86)\ossec-agent\libwazuhshared.dll;." -F <PATH_TO_CUSTOM-AR.PY> | ||
``` | ||
|
||
You can find the created `custom-ar.exe` executable in the `C:\Users\<USER>\dist\` directory. | ||
|
||
#. Copy the `custom-ar.exe` executable file to `C:\Program Files (x86)\ossec-agent\active-response\bin\` directory on the monitored endpoint. | ||
#. Restart the Wazuh agent using PowerShell with administrator privileges to apply the changes: | ||
|
||
.. code-block:: console | ||
|
||
> Restart-Service -Name wazuh | ||
|
||
#. On the Wazuh server, add the `<command>` and `<active-response>` blocks below to the `/var/ossec/etc/ossec.conf` configuration file. This uses the `custom-ar.exe` executable for Windows endpoints. | ||
|
||
```xml | ||
<command> | ||
<name>windows_firewall</name> | ||
<executable>windows_firewall.exe</executable> | ||
<timeout_allowed>no</timeout_allowed> | ||
</command> | ||
|
||
<active-response> | ||
<disabled>no</disabled> | ||
<command>windows_firewall</command> | ||
<location>local</location> | ||
<timeout>60</timeout> | ||
</active-response> | ||
``` | ||
|
||
#. Restart the Wazuh manager to apply the changes: | ||
|
||
```console | ||
systemctl restart wazuh-manager | ||
``` | ||
|
||
With this configuration, Wazuh runs an executable instead of a Python script when triggering an active response on a Windows endpoint. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import json | ||
from pathlib import Path | ||
|
||
import aiofiles | ||
from fastapi import APIRouter | ||
from fastapi import HTTPException | ||
from fastapi import Security | ||
from fastapi.responses import JSONResponse | ||
from loguru import logger | ||
|
||
from app.active_response.schema.active_response import ActiveResponse | ||
from app.active_response.schema.active_response import ActiveResponseDetails | ||
from app.active_response.schema.active_response import ActiveResponsesSupported | ||
from app.active_response.schema.active_response import ActiveResponsesSupportedResponse | ||
from app.active_response.schema.active_response import InvokeActiveResponseRequest | ||
from app.active_response.schema.active_response import InvokeActiveResponseResponse | ||
from app.auth.utils import AuthHandler | ||
from app.connectors.wazuh_manager.utils.universal import send_put_request | ||
|
||
active_response_router = APIRouter() | ||
|
||
|
||
async def verify_active_response_name(active_response_name: str) -> None: | ||
""" | ||
Verify the active response name | ||
""" | ||
active_response_name = active_response_name.upper() | ||
if active_response_name not in ActiveResponsesSupported.__members__: | ||
raise HTTPException(status_code=404, detail="Active Response not found") | ||
|
||
|
||
def get_markdown_content_path(directory: str, filename: str) -> str: | ||
""" | ||
Get the path to the markdown content | ||
""" | ||
current_directory = Path(__file__).parent.parent | ||
return str(current_directory / f"scripts/{directory}/{filename}") | ||
|
||
|
||
async def read_markdown_file(file_path: str) -> str: | ||
""" | ||
Read the content of a markdown file | ||
""" | ||
async with aiofiles.open(file_path, "r") as file: | ||
return await file.read() | ||
|
||
|
||
@active_response_router.get( | ||
"/describe/{active_response_name}", | ||
response_model=ActiveResponse, | ||
description="Get the details of a specific active response", | ||
dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], | ||
) | ||
async def get_active_response_details_route(active_response_name: str) -> ActiveResponse: | ||
""" | ||
Get the details of a specific active response | ||
""" | ||
await verify_active_response_name(active_response_name) | ||
directory = active_response_name.split("_")[0].lower() | ||
file_path = get_markdown_content_path(directory, f"{active_response_name}.md") | ||
logger.info(f"Reading markdown file: {file_path}") | ||
response = ActiveResponseDetails( | ||
name=active_response_name, | ||
description=ActiveResponsesSupported[active_response_name.upper()].value, | ||
markdown_content=await read_markdown_file(file_path), | ||
) | ||
return JSONResponse(content=response.dict()) | ||
|
||
|
||
@active_response_router.get( | ||
"/supported", | ||
response_model=ActiveResponsesSupportedResponse, | ||
description="Get the list of supported active responses", | ||
dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], | ||
) | ||
async def get_supported_active_responses_route() -> ActiveResponsesSupportedResponse: | ||
""" | ||
Get the list of supported active responses | ||
""" | ||
return ActiveResponsesSupportedResponse( | ||
supported_active_responses=[ | ||
ActiveResponse(name=active_response.name, description=active_response.value) for active_response in ActiveResponsesSupported | ||
], | ||
success=True, | ||
message="Supported Active Responses retrieved successfully", | ||
) | ||
|
||
|
||
@active_response_router.post( | ||
"/invoke", | ||
response_model=InvokeActiveResponseResponse, | ||
description="Invoke an active response", | ||
dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], | ||
) | ||
async def invoke_active_response_route( | ||
request: InvokeActiveResponseRequest, | ||
) -> InvokeActiveResponseResponse: | ||
""" | ||
Invoke an active response. | ||
Args: | ||
request (InvokeActiveResponseRequest): The request object containing the command, custom, arguments, and alert. | ||
Returns: | ||
InvokeActiveResponseResponse: The response object indicating the success or failure of the active response invocation. | ||
""" | ||
logger.info("Invoking Wazuh Active Response...") | ||
# Append '0' to the command - This is required for Wazuh Active Response | ||
request.command = f"{request.command.value}0" | ||
# Create a dictionary with the request data | ||
data_dict = {"command": request.command, "custom": request.custom, "arguments": request.arguments, "alert": request.alert} | ||
await send_put_request( | ||
endpoint=request.endpoint, | ||
data=json.dumps(data_dict), | ||
params=request.params, | ||
) | ||
|
||
return InvokeActiveResponseResponse(success=True, message="Wazuh Active Response invoked successfully") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from enum import Enum | ||
from typing import Any | ||
from typing import Dict | ||
from typing import List | ||
|
||
from fastapi import HTTPException | ||
from pydantic import BaseModel | ||
from pydantic import Field | ||
from pydantic import root_validator | ||
|
||
|
||
class ActiveResponsesSupported(Enum): | ||
WINDOWS_FIREWALL = "Block or unblock any outbound traffic to the defined IP address via the Windows Firewall" | ||
# Add more active responses here as needed | ||
|
||
|
||
class ActiveResponse(BaseModel): | ||
name: str | ||
description: str | ||
|
||
|
||
class ActiveResponsesSupportedResponse(BaseModel): | ||
supported_active_responses: List[ActiveResponse] | ||
success: bool | ||
message: str | ||
|
||
|
||
class ActiveResponseDetails(BaseModel): | ||
name: str | ||
description: str | ||
markdown_content: str | ||
|
||
class Config: | ||
json_encoders = {str: lambda v: v.encode("utf-8", "ignore").decode("utf-8")} | ||
|
||
|
||
# ! Invoke Active Response ! # | ||
class AlertAction(str, Enum): | ||
unblock = "unblock" | ||
block = "block" | ||
|
||
|
||
class BaseModelWithEnum(BaseModel): | ||
class Config: | ||
use_enum_values = True | ||
|
||
|
||
class WindowsFirewallAlert(BaseModelWithEnum): | ||
action: AlertAction | ||
ip: str | ||
|
||
|
||
class LinuxFirewallAlert(BaseModelWithEnum): | ||
action: AlertAction | ||
ip: str | ||
|
||
|
||
class ActiveResponseCommand(str, Enum): | ||
windows_firewall = "windows_firewall" | ||
linux_firewall = "linux_firewall" | ||
|
||
@classmethod | ||
def _missing_(cls, value): | ||
for member in cls: | ||
if member.name == value: | ||
return member | ||
|
||
for active_response in ActiveResponsesSupported: | ||
if active_response.name.lower() == value.lower(): | ||
return cls[f"{value}0"] | ||
|
||
raise HTTPException( | ||
status_code=400, | ||
detail=f"Invalid command: {value}, must be one of {', '.join([member.name for member in cls])}", | ||
) | ||
|
||
|
||
class ParamsModel(BaseModel): | ||
wait_for_complete: bool | ||
agents_list: List[str] | ||
|
||
|
||
class InvokeActiveResponseRequest(BaseModel): | ||
endpoint: str = Field("active-response", const=True) | ||
arguments: list[str] = Field(default_factory=list) | ||
command: ActiveResponseCommand | ||
custom: bool = Field(True, const=True) | ||
alert: Dict[str, Any] | ||
params: ParamsModel | ||
|
||
@root_validator(pre=True) | ||
def create_alert(cls, values): | ||
command = values.get("command") | ||
alert = values.get("alert") | ||
if command == ActiveResponseCommand.windows_firewall: | ||
values["alert"] = WindowsFirewallAlert(**alert) | ||
elif command == ActiveResponseCommand.linux_firewall: | ||
values["alert"] = LinuxFirewallAlert(**alert) | ||
else: | ||
raise HTTPException(status_code=400, detail="Invalid command for alert") | ||
|
||
return values | ||
|
||
|
||
class InvokeActiveResponseResponse(BaseModel): | ||
success: bool | ||
message: str |
Oops, something went wrong.