Skip to content

Commit ed99db3

Browse files
committed
[docs] feat(executors): add documentation on Palo Alto Cortex executor configuration
1 parent a4404ef commit ed99db3

8 files changed

Lines changed: 173 additions & 10 deletions

docs/administration/enterprise.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ according to the [OpenAEV architecture](../deployment/platform/overview.md#archi
5858
### Palo Alto Cortex Agent
5959

6060
The Palo Alto Cortex Agent can be leveraged to execute implants as detached processes that will then execute payloads
61-
according to the [OpenAEV architecture](../deployment/platform/overview.md#architecture)
61+
according to the [OpenAEV architecture](../deployment/platform/overview.md#architecture).
62+
63+
On Windows, because Palo Alto Cortex whitelists its own process tree, OpenAEV creates a scheduled task to detach the process that will execute the payloads.
6264

6365
## Remediations in CVES
6466

39.1 KB
Loading
83.5 KB
Loading
81.8 KB
Loading
75.7 KB
Loading
84.2 KB
Loading
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import os
2+
import subprocess
3+
import sys
4+
import traceback
5+
import ctypes
6+
import re
7+
8+
PtyProcess = None
9+
try:
10+
from winpty import PtyProcess
11+
except:
12+
PtyProcess = None
13+
14+
15+
def ensure_console():
16+
"""Ensure the current process has a valid console.
17+
18+
When launched from Palo Alto's agent, the process may not have
19+
a console attached, which causes child processes (especially cmd.exe)
20+
to fail when they try to inherit console handles.
21+
"""
22+
kernel32 = ctypes.windll.kernel32
23+
if not kernel32.AttachConsole(-1):
24+
kernel32.AllocConsole()
25+
hwnd = kernel32.GetConsoleWindow()
26+
if hwnd:
27+
ctypes.windll.user32.ShowWindow(hwnd, 0)
28+
29+
30+
def remove_control_characters(text):
31+
"""Remove all the control characters from a string"""
32+
text = re.sub(r"\x1b\[\??(\d;?)*\w", '', text)
33+
text = re.sub(r"\x1b\]0;.*\x07", '\r\n', text)
34+
text = re.sub("\r\n>> ", '', text)
35+
return text
36+
37+
38+
def remove_leading_cmd(text):
39+
"""Removes leading "cmd" or "cmd.exe" from the text if present.
40+
41+
:param text: a string
42+
:type text: str
43+
:rtype: str
44+
"""
45+
if text.startswith("cmd.exe"):
46+
return text[7:]
47+
elif text.startswith("cmd"):
48+
return text[3:]
49+
return text
50+
51+
52+
def is_direct_executable(command):
53+
"""Check if the command starts with a known executable."""
54+
first_token = command.strip().split()[0].lower()
55+
return first_token in [
56+
"powershell.exe", "powershell",
57+
"cmd.exe", "cmd",
58+
"pwsh.exe", "pwsh"
59+
]
60+
61+
62+
def run(commands_list):
63+
"""Runs a list of commands via winpty, falling back to subprocess.
64+
65+
:param commands_list: list of commands
66+
:type commands_list: list
67+
:return: commands output dict - {<command>: <output>}
68+
:rtype: dict
69+
"""
70+
ensure_console()
71+
72+
result = {}
73+
for command in commands_list:
74+
sys.stdout.write(f"Running command <{command[:100]}>...\n")
75+
args = command
76+
try:
77+
env = os.environ.copy()
78+
79+
if 'COMSPEC' not in env:
80+
env['COMSPEC'] = os.path.expandvars("%SYSTEMROOT%\\System32\\cmd.exe")
81+
if 'SystemRoot' not in env:
82+
env['SystemRoot'] = os.environ.get('SystemRoot', 'C:\\Windows')
83+
84+
if is_direct_executable(command):
85+
# Direct executable call (e.g., powershell.exe -encodedCommand ...)
86+
# Run without shell wrapping or winpty
87+
with subprocess.Popen(
88+
args=args,
89+
shell=False,
90+
stdout=subprocess.PIPE,
91+
stderr=subprocess.PIPE,
92+
env=env,
93+
) as process:
94+
stdout, stderr = process.communicate(timeout=300)
95+
if stderr:
96+
sys.stderr.write(f"stderr: \n{stderr.decode('utf-8', errors='replace')}\n")
97+
if stdout:
98+
sys.stdout.write(f"stdout: \n{stdout.decode('utf-8', errors='replace')}\n")
99+
result[command] = stdout.decode('utf-8', errors='replace').splitlines()
100+
else:
101+
result[command] = None
102+
elif PtyProcess:
103+
command = remove_leading_cmd(command)
104+
command_prompt_path = os.path.expandvars("%SYSTEMROOT%\\System32\\cmd.exe")
105+
proc = PtyProcess.spawn(command_prompt_path, cwd="c:\\")
106+
proc.write("cls\r\n")
107+
proc.read()
108+
proc.write(command + '\r\n')
109+
proc.write('exit\r\n')
110+
stdout = ""
111+
while proc.isalive():
112+
buff = proc.read(32768)
113+
stdout += buff
114+
if proc.eof():
115+
break
116+
sys.stdout.write(f"stdout: {stdout}")
117+
stdout = remove_control_characters(stdout)
118+
stdout = stdout.split(command, 1)[1]
119+
stdout = stdout.split(":\\>exit", 1)[0][:-1]
120+
lines = stdout.split("\r\n")
121+
lines = [line.strip() for line in lines if line.strip()]
122+
result[command] = lines
123+
proc.terminate()
124+
else:
125+
sys.stderr.write(
126+
"WARNING: winpty not available, falling back to subprocess\n"
127+
)
128+
with subprocess.Popen(
129+
args=args,
130+
shell=True,
131+
stdin=subprocess.PIPE,
132+
stdout=subprocess.PIPE,
133+
stderr=subprocess.PIPE,
134+
env=env,
135+
creationflags=subprocess.CREATE_NEW_CONSOLE,
136+
) as process:
137+
stdout, stderr = process.communicate(timeout=300)
138+
if stderr:
139+
sys.stderr.write(f"stderr: \n{stderr.decode('utf-8', errors='replace')}\n")
140+
if stdout:
141+
sys.stdout.write(f"stdout: \n{stdout.decode('utf-8', errors='replace')}\n")
142+
result[command] = stdout.decode('utf-8', errors='replace').splitlines()
143+
else:
144+
result[command] = None
145+
except subprocess.TimeoutExpired:
146+
sys.stderr.write(f"Command timed out: <{command[:100]}>...\n")
147+
result[command] = None
148+
except Exception:
149+
sys.stderr.write(f"Failed open command: <{command[:100]}>, error: {traceback.format_exc()}")
150+
151+
return result
152+
153+
154+
if __name__ == '__main__':
155+
results = run(commands_list)

docs/deployment/ecosystem/executors.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ Endpoint on the OpenAEV endpoint page.
299299
The Palo Alto Cortex agent can be leveraged to execute implants as detached processes that will then execute payloads
300300
according to the [OpenAEV architecture](https://docs.openaev.io/latest/deployment/overview).
301301

302+
On Windows, because Palo Alto Cortex whitelists its own process tree, OpenAEV creates a scheduled task to detach the process that will execute the payloads.
303+
302304
The implants will be downloaded to these folders on the different assets:
303305

304306
* On Windows assets: `C:\Program Files (x86)\Filigran\OAEV Agent\runtimes\implant-XXXXX`
@@ -311,11 +313,11 @@ This ensures that the implants are unique and will be deleted on assets' restart
311313

312314
#### Upload OpenAEV scripts
313315

314-
First of all, you need to create one custom script for Unix, covering both Linux and MacOS systems.
315-
For Windows, we use the existing Palo Alto script named `execute_commands`.
316+
First of all, you need to create one custom script for Unix, covering both Linux and MacOS systems and another one for Windows.
316317

317-
To create it, go to `Incident Response` > `Action Center` > `Agent Script Library` > `+ New Script`. The names
318+
To create these scripts, go to `Investigation & responses` > `Action Center` > `Agent Script Library` > `+ New Script`. The names
318319
of the scripts can be changed if necessary, the ids will be put in the OpenAEV configuration.
320+
To get the scripts IDs, it may be necessary to add the Script UID column to the scripts list view.
319321

320322
*Unix Script*
321323

@@ -325,25 +327,29 @@ Upload the following Python script:
325327

326328
Put the following Input schema:
327329

328-
![Palo Alto Cortex unix script1](../assets/paloaltocortex-unix-script.png)
330+
![Palo Alto Cortex unix script1](../assets/paloaltocortex-unix-script-general.png)
331+
![Palo Alto Cortex unix script2](../assets/paloaltocortex-unix-script-inputs-outputs.png)
329332

330333
*Windows script*
331334

332-
Existing Palo Alto script named `execute_commands`.
335+
Upload the following Python script:
333336

334-
Once created, your Remote Ops scripts should have something like this:
337+
[Download](../assets/paloaltocortex_subprocessor_windows.py)
338+
339+
Put the following Input schema:
335340

336-
![Palo Alto Cortex RTR script](../assets/paloaltocortex-scripts.png)
341+
![Palo Alto Cortex windows script1](../assets/paloaltocortex-windows-script-general.png)
342+
![Palo Alto Cortex windows script2](../assets/paloaltocortex-windows-script-inputs-outputs.png)
337343

338344
#### Create a group with your targeted assets
339345

340-
To create a group, go to `Endpoints` > `Endpoint Groups`.
346+
To create a group, go to `Inventory` > `Endpoints` > `Groups`.
341347

342348
### Configure the OpenAEV platform
343349

344350
!!! warning "Palo Alto Cortex API Key"
345351

346-
Please note that the Palo Alto Cortex API key created in "Settings/API Keys" should have the following minimum role: “Instance Administrator” and security level: "Standard".
352+
Please note that the Palo Alto Cortex API key created in "Settings/Configurations/API Keys" should have the following minimum role: “Instance Administrator” and security level: "Standard".
347353

348354
To use the Palo Alto Cortex executor, just fill the following configuration in the Integrations (Executors) tab from OpenAEV menu.
349355

0 commit comments

Comments
 (0)