Skip to content

Commit

Permalink
[PLGN-406] Sentinelone Path Traversal (#2140)
Browse files Browse the repository at this point in the history
* Update to remove ../ and ..\ from file paths in the Threats Fetch File action

* Reformat

* Update dockerfile
  • Loading branch information
ablakley-r7 authored Nov 27, 2023
1 parent 72ec9ad commit ee0ee11
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 16 deletions.
8 changes: 4 additions & 4 deletions plugins/sentinelone/.CHECKSUM
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"spec": "dea9bcebb2466c647f32cff50ff770a2",
"manifest": "d22df2cb908420ee272ffd40aa540d14",
"setup": "3d39e9768c2fda42e7f413e0d7681642",
"spec": "6dcb8d0a5d1bac240f2a54503842098d",
"manifest": "fe468b544dd9d9c0578e754075945ea0",
"setup": "e88d857ff461a794516f4019571d3f6c",
"schemas": [
{
"identifier": "activities_list/schema.py",
Expand Down Expand Up @@ -93,7 +93,7 @@
},
{
"identifier": "move_between_sites/schema.py",
"hash": "da998f50504a8bd40a9696bd6612ebe2"
"hash": "c60390ee96acd2451273a7e9bee1f862"
},
{
"identifier": "name_available/schema.py",
Expand Down
2 changes: 1 addition & 1 deletion plugins/sentinelone/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rapid7/insightconnect-python-3-38-slim-plugin:5
FROM rapid7/insightconnect-python-3-plugin:5

LABEL organization=rapid7
LABEL sdk=python
Expand Down
2 changes: 1 addition & 1 deletion plugins/sentinelone/bin/komand_sentinelone
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from sys import argv

Name = "SentinelOne"
Vendor = "rapid7"
Version = "9.1.0"
Version = "9.1.1"
Description = "The SentinelOne plugin allows you to manage and mitigate all your security operations through SentinelOne"


Expand Down
1 change: 1 addition & 0 deletions plugins/sentinelone/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,7 @@ Example output:

# Version History

* 9.1.1 - `Threats Fetch File`: Updated action to prevent possible movement through file system
* 9.1.0 - `Move Agent to Another Site`: Action added
* 9.0.0 - Update plugin to allow cloud connections to be configured | Rename URL input to Instance in connection | Code refactor
* 8.1.0 - Added New actions: Fetch file for agent ID and Run remote script. Updated description for Trigger resolved field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


class Component:
DESCRIPTION = "Move an agent to another site. This action requires Account or Global level access for your user role"
DESCRIPTION = "Move an agent to another site, This action requires Account or Global level access for your user role"


class Input:
Expand Down
12 changes: 6 additions & 6 deletions plugins/sentinelone/komand_sentinelone/util/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import List, Any, Dict, Tuple
from urllib.parse import urlsplit, unquote
from logging import Logger
from komand_sentinelone.util.helper import Helper, clean
from komand_sentinelone.util.helper import Helper, clean, sanitise_url
from komand_sentinelone.util.constants import (
DATA_FIELD,
API_TOKEN_FIELD,
Expand Down Expand Up @@ -251,20 +251,20 @@ def download_file(self, agent_filter: dict, fetch_date: str, password: str) -> d
break
self.logger.info("Waiting 5 seconds for successful threat file upload...")
time.sleep(5)

threat_details = activities.get("data", [{}])[0].get("data", {})
file_url = threat_details.get("filePath", "")[1:]
file_name = threat_details.get("fileDisplayName")
response = self._call_api("GET", file_url, full_response=True)
sanitised_file_url = sanitise_url(file_url)
response = self._call_api("GET", sanitised_file_url, full_response=True)
try:
with zipfile.ZipFile(io.BytesIO(response.content)) as downloaded_zipfile:
downloaded_zipfile.setpassword(password.encode("UTF-8"))
file_info = downloaded_zipfile.infolist()[-1]
file_content = downloaded_zipfile.read(file_info.filename)

return {
"filename": file_name,
"content": base64.b64encode(downloaded_zipfile.read(downloaded_zipfile.infolist()[-1])).decode(
"utf-8"
),
"content": base64.b64encode(file_content).decode("utf-8"),
}
except Exception as error:
raise PluginException(
Expand Down
12 changes: 12 additions & 0 deletions plugins/sentinelone/komand_sentinelone/util/helper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from insightconnect_plugin_runtime.exceptions import PluginException
from insightconnect_plugin_runtime.helper import return_non_empty
from typing import Union
Expand All @@ -12,6 +14,16 @@ def clean(item_to_clean: Union[dict, list]) -> Union[dict, list]:
return return_non_empty(item_to_clean.copy())


def sanitise_url(file_url: str) -> str:
# Sanitise URLs to help guard against path traversal
sanitised_url = file_url.replace("%2e%2e%2f", "../")
sanitised_url = sanitised_url.replace("../", "")
sanitised_url = sanitised_url.replace("%2e%2e%5C", "..\\")
sanitised_url = sanitised_url.replace("..\\", "")
sanitised_url = os.path.normpath(sanitised_url)
return sanitised_url


class Helper:
@staticmethod
def join_or_empty(joined_array: list) -> str:
Expand Down
2 changes: 1 addition & 1 deletion plugins/sentinelone/plugin.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extension: plugin
products: [insightconnect]
name: sentinelone
title: SentinelOne
version: 9.1.0
version: 9.1.1
connection_version: 9
cloud_ready: true
sdk:
Expand Down
2 changes: 1 addition & 1 deletion plugins/sentinelone/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


setup(name="sentinelone-rapid7-plugin",
version="9.1.0",
version="9.1.1",
description="The SentinelOne plugin allows you to manage and mitigate all your security operations through SentinelOne",
author="rapid7",
author_email="",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "1000000000000000001",
"password": "Str0ngP455word"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"data": [
{
"accountId": "1234567898765432112",
"accountName": "Example",
"activityType": 19,
"agentId": "1234567898765432112",
"agentUpdatedVersion": null,
"comments": null,
"createdAt": "2020-12-17T22:39:24.305435Z",
"data": {
"accountName": "Example",
"computerName": "so-agent-win12",
"confidenceLevel": "malicious",
"fileContentHash": "02699626f388ed830012e5b787640e71c56d42d8",
"fileDisplayName": "test.txt",
"filePath": "\\..\\..\\Fake\\HarddiskVolume2\\Users\\Administrator\\Desktop\\test.txt",
"groupName": "Default Group",
"siteName": "Example",
"threatClassification": "Trojan",
"threatClassificationSource": "Cloud",
"username": null
},
"description": null,
"groupId": "1234567898765432112",
"groupName": "Default Group",
"hash": null,
"id": "1234567898765432112",
"osFamily": null,
"primaryDescription": "Threat with confidence level malicious detected: test.txt",
"secondaryDescription": "02699626f388ed830012e5b787640e71c56d42d8",
"siteId": "1234567898765432112",
"siteName": "Example",
"threatId": "1234567898765432112",
"updatedAt": "2020-12-17T22:39:24.299235Z",
"userId": null
},
{
"accountId": "1234567898765432112",
"accountName": "Example",
"activityType": 2001,
"agentId": "1234567898765432112",
"agentUpdatedVersion": null,
"comments": null,
"createdAt": "2020-12-17T22:39:24.423814Z",
"data": {
"accountName": "Example",
"computerName": "so-agent-win12",
"fileContentHash": "02699626f388ed830012e5b787640e71c56d42d8",
"fileDisplayName": "test.txt",
"filePath": "\\..\\..\\Fake\\HarddiskVolume2\\Users\\Administrator\\Desktop\\test.txt",
"fullScopeDetails": "Group Default Group in Site Example of Account Example",
"globalStatus": "success",
"groupName": "Default Group",
"scopeLevel": "Group",
"scopeName": "Default Group",
"siteName": "Example",
"threatClassification": "Trojan",
"threatClassificationSource": "Cloud"
},
"description": null,
"groupId": "1234567898765432112",
"groupName": "Default Group",
"hash": null,
"id": "1234567898765432112",
"osFamily": null,
"primaryDescription": "The agent so-agent-win12 successfully killed the threat: test.txt.",
"secondaryDescription": "\\..\\..\\Fake\\HarddiskVolume2\\Users\\Administrator\\Desktop\\test.txt",
"siteId": "1234567898765432112",
"siteName": "Example",
"threatId": "1234567898765432112",
"updatedAt": "2020-12-17T22:39:24.419607Z",
"userId": null
}
],
"pagination": {
"nextCursor": null,
"totalItems": 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"data": [
{
"accountId": "1000000000000000001",
"accountName": "Account",
"activityType": 86,
"activityUuid": "aaaaa-aa-adsadsad-asadasd",
"agentId": "1000000000000000001",
"agentUpdatedVersion": null,
"comments": null,
"createdAt": "2023-09-27T12:16:43.525758Z",
"data": {
"accountName": "Rapid7",
"commandBatchUuid": "aaaaa-aa-adsadsad-asadasd",
"commandId": 1000000000000000001,
"computerName": "WindowsX64",
"downloadUrl": "../../fake/1000000000000000001/uploads/1000000000000000001",
"escapedMaliciousProcessArguments": null,
"externalIp": "0.0.0.0",
"fileContentHash": "1000000000000000000",
"fileDisplayName": "file.txt",
"filePath": "../../fake/1000000000000000001/uploads/1000000000000000001",
"fileSize": 68,
"filename": "WindowsX64_2023-09-27_12_16_43.503.zip",
"fullScopeDetails": "Details",
"fullScopeDetailsPath": "Details Path",
"groupName": "Default Group",
"ipAddress": null,
"realUser": null,
"siteName": "Example Site",
"sourceType": "API",
"storyline": "1000000000000000001",
"threatClassification": "Malware",
"threatClassificationSource": "Static",
"uploadedFilename": "WindowsX64_2023-09-27_07:16:43.zip"
},
"description": null,
"groupId": "1000000000000000001",
"groupName": "Default Group",
"hash": null,
"id": "1000000000000000001",
"osFamily": null,
"primaryDescription": "Agent WindowsX64 (0.0.0.0) successfully uploaded a threat file.",
"secondaryDescription": "WindowsX64_2023-09-27_07:16:43.zip (Group ID: 1000000000000000001).",
"siteId": "1000000000000000001",
"siteName": "Example Site",
"threatId": "1000000000000000001",
"updatedAt": "2023-09-27T12:16:43.525095Z",
"userId": null
}
],
"pagination": {
"nextCursor": null,
"totalItems": 1
}
}
5 changes: 5 additions & 0 deletions plugins/sentinelone/unit_test/test_threats_fetch_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ def setUpClass(cls, mock_request) -> None:
Util.read_file_to_dict("inputs/threats_fetch_file.json.inp"),
Util.read_file_to_dict("expected/threats_fetch_file.json.exp"),
],
[
"threats_fetch_file_traversal_file_path",
Util.read_file_to_dict("inputs/threats_fetch_file_invalid_path.json.inp"),
Util.read_file_to_dict("expected/threats_fetch_file.json.exp"),
],
]
)
def test_threats_fetch_file(self, mock_request, test_name, input_params, expected):
Expand Down
16 changes: 15 additions & 1 deletion plugins/sentinelone/unit_test/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def raise_for_status(self):
return MockResponse(200, "affected_2")
return MockResponse(200, "affected_1")
elif args[1] == "https://rapid7.sentinelone.net/web/api/v2.1/activities":
if params.get("threatIds") == "1000000000000000001":
return MockResponse(200, "activities_malicious_file_path")
if params.get("countOnly"):
return MockResponse(200, "activities_count_only")
if params == {
Expand Down Expand Up @@ -294,7 +296,12 @@ def raise_for_status(self):
return MockResponse(200, "name_not_available")
return MockResponse(200, "name_available")
elif args[1] == "https://rapid7.sentinelone.net/web/api/v2.1/threats":
if params.get("ids") in [["valid_threat_id_1"], ["valid_threat_id_2"], ["1000000000000000000"]]:
if params.get("ids") in [
["valid_threat_id_1"],
["valid_threat_id_2"],
["1000000000000000000"],
["1000000000000000001"],
]:
return MockResponse(200, "threats")
if params.get("ids") == ["same_status_threat_id_1"]:
return MockResponse(200, "threats_same_status")
Expand Down Expand Up @@ -342,6 +349,13 @@ def raise_for_status(self):
mock_response = MockResponse(200)
mock_response.content = Util.read_file_to_bytes(f"responses/threats_fetch_file_download.zip.resp")
return mock_response
elif (
args[1]
== r"https://rapid7.sentinelone.net/web/api/v2.1/Fake\HarddiskVolume2\Users\Administrator\Desktop\test.txt"
):
mock_response = MockResponse(200)
mock_response.content = Util.read_file_to_bytes(f"responses/threats_fetch_file_download.zip.resp")
return mock_response
elif args[1] == "https://rapid7.sentinelone.net/web/api/v2.1/dv/events":
if params.get("queryId") == "q9679169d5a4607cc41a9101234567891":
return MockResponse(404)
Expand Down

0 comments on commit ee0ee11

Please sign in to comment.