add option for specific OS's in scripts.

change view in script editor to match the block view (looks much nicer and much more organized).
This commit is contained in:
KingKDot
2026-03-17 13:27:44 -05:00
parent 0564bbd1ea
commit 7ee22bb004
6 changed files with 217 additions and 279 deletions
+142 -12
View File
@@ -14,6 +14,7 @@ const saveScriptBtn = document.getElementById("save-script-btn");
const savedScriptsList = document.getElementById("saved-scripts-list"); const savedScriptsList = document.getElementById("saved-scripts-list");
const autoTaskName = document.getElementById("auto-task-name"); const autoTaskName = document.getElementById("auto-task-name");
const autoTaskTrigger = document.getElementById("auto-task-trigger"); const autoTaskTrigger = document.getElementById("auto-task-trigger");
const autoTaskOsCheckboxes = document.querySelectorAll("input[name='auto-task-os']");
const autoTaskSaveBtn = document.getElementById("auto-task-save-btn"); const autoTaskSaveBtn = document.getElementById("auto-task-save-btn");
const autoTaskCancelBtn = document.getElementById("auto-task-cancel-btn"); const autoTaskCancelBtn = document.getElementById("auto-task-cancel-btn");
const autoTaskList = document.getElementById("auto-task-list"); const autoTaskList = document.getElementById("auto-task-list");
@@ -351,6 +352,7 @@ function resetAutoTaskForm() {
autoTaskEditingId = null; autoTaskEditingId = null;
autoTaskName.value = ""; autoTaskName.value = "";
autoTaskTrigger.value = "on_connect"; autoTaskTrigger.value = "on_connect";
autoTaskOsCheckboxes.forEach((cb) => (cb.checked = false));
autoTaskCancelBtn.classList.add("hidden"); autoTaskCancelBtn.classList.add("hidden");
autoTaskSaveBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> Save Auto Task'; autoTaskSaveBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> Save Auto Task';
} }
@@ -362,6 +364,10 @@ function setAutoTaskForm(task) {
scriptType.value = task.scriptType || "powershell"; scriptType.value = task.scriptType || "powershell";
setEditorMode(scriptType.value); setEditorMode(scriptType.value);
setEditorValue(task.script || ""); setEditorValue(task.script || "");
const osFilter = Array.isArray(task.osFilter) ? task.osFilter : [];
autoTaskOsCheckboxes.forEach((cb) => {
cb.checked = osFilter.includes(cb.value);
});
autoTaskCancelBtn.classList.remove("hidden"); autoTaskCancelBtn.classList.remove("hidden");
autoTaskSaveBtn.innerHTML = '<i class="fa-solid fa-pen"></i> Update Auto Task'; autoTaskSaveBtn.innerHTML = '<i class="fa-solid fa-pen"></i> Update Auto Task';
} }
@@ -393,7 +399,7 @@ function renderAutoTasks() {
<div class="flex items-start justify-between gap-2 px-3 py-2 rounded-lg border border-slate-700 bg-slate-800/50"> <div class="flex items-start justify-between gap-2 px-3 py-2 rounded-lg border border-slate-700 bg-slate-800/50">
<div class="min-w-0"> <div class="min-w-0">
<div class="font-semibold text-slate-100 truncate">${escapeHtml(task.name)}</div> <div class="font-semibold text-slate-100 truncate">${escapeHtml(task.name)}</div>
<div class="text-xs text-slate-400">${escapeHtml(triggerLabel(task.trigger))} • ${escapeHtml(task.scriptType)}</div> <div class="text-xs text-slate-400">${escapeHtml(triggerLabel(task.trigger))} • ${escapeHtml(task.scriptType)}${Array.isArray(task.osFilter) && task.osFilter.length > 0 ? ` • ${task.osFilter.map(escapeHtml).join(", ")}` : ""}</div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<label class="flex items-center gap-1 text-xs text-slate-400"> <label class="flex items-center gap-1 text-xs text-slate-400">
@@ -469,6 +475,9 @@ async function saveAutoTask() {
const trigger = autoTaskTrigger.value; const trigger = autoTaskTrigger.value;
const scriptTypeValue = scriptType.value; const scriptTypeValue = scriptType.value;
const script = getEditorValue(); const script = getEditorValue();
const osFilter = Array.from(autoTaskOsCheckboxes)
.filter((cb) => cb.checked)
.map((cb) => cb.value);
if (!name) { if (!name) {
showToast("Please provide a task name", "warning", 3000); showToast("Please provide a task name", "warning", 3000);
@@ -481,7 +490,7 @@ async function saveAutoTask() {
autoTaskSaveBtn.disabled = true; autoTaskSaveBtn.disabled = true;
try { try {
const payload = { name, trigger, script, scriptType: scriptTypeValue }; const payload = { name, trigger, script, scriptType: scriptTypeValue, osFilter };
const res = await fetch(autoTaskEditingId ? `/api/auto-scripts/${autoTaskEditingId}` : "/api/auto-scripts", { const res = await fetch(autoTaskEditingId ? `/api/auto-scripts/${autoTaskEditingId}` : "/api/auto-scripts", {
method: autoTaskEditingId ? "PUT" : "POST", method: autoTaskEditingId ? "PUT" : "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -512,16 +521,6 @@ clearSelectionBtn.addEventListener("click", () => {
renderClients(); renderClients();
}); });
document.querySelectorAll(".template-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const script = btn.dataset.script;
const type = btn.dataset.type;
setEditorValue(script);
scriptType.value = type;
setEditorMode(type);
});
});
executeBtn.addEventListener("click", async () => { executeBtn.addEventListener("click", async () => {
if (selectedClients.size === 0) { if (selectedClients.size === 0) {
alert("Please select at least one client"); alert("Please select at least one client");
@@ -607,10 +606,141 @@ scriptType?.addEventListener("change", () => {
setEditorMode(scriptType.value); setEditorMode(scriptType.value);
}); });
const TEMPLATE_CATEGORIES = [
{
label: "Windows",
icon: "fa-brands fa-windows",
color: { bg: "bg-blue-900/40", border: "border-blue-700/60", text: "text-blue-300", hover: "hover:bg-blue-800/40", item: "bg-blue-900/20 border-blue-700/40 hover:bg-blue-800/30" },
templates: [
{ label: "System Info", desc: "Get computer information", type: "powershell", script: "Get-ComputerInfo | Select-Object CsName, WindowsVersion, OsArchitecture, CsProcessors" },
{ label: "Top Processes", desc: "Show top 10 processes by CPU", type: "powershell", script: "Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU, WorkingSet" },
{ label: "Network Info", desc: "List network adapters and IPs", type: "powershell", script: "Get-NetIPAddress | Where-Object {$_.AddressFamily -eq 'IPv4'} | Select-Object IPAddress, InterfaceAlias" },
{ label: "AV Status Check", desc: "Query registered antivirus products", type: "powershell", script: "$av = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct -ErrorAction SilentlyContinue; if ($av) { $av | Select-Object displayName, pathToSignedProductExe, timestamp } else { Write-Output 'No AV product data returned from SecurityCenter2' }" },
{ label: "Defender Health", desc: "Show Microsoft Defender status", type: "powershell", script: "Get-MpComputerStatus | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated" },
{ label: "Running Services", desc: "List active Windows services", type: "powershell", script: "Get-Service | Where-Object {$_.Status -eq 'Running'} | Sort-Object DisplayName | Select-Object -First 30 Name, DisplayName, Status" },
],
},
{
label: "Linux",
icon: "fa-brands fa-linux",
color: { bg: "bg-amber-900/40", border: "border-amber-700/60", text: "text-amber-300", hover: "hover:bg-amber-800/40", item: "bg-amber-900/20 border-amber-700/40 hover:bg-amber-800/30" },
templates: [
{ label: "Disk Usage", desc: "Show mounted filesystem usage", type: "bash", script: "df -h" },
{ label: "System Status", desc: "Show system status (Linux)", type: "bash", script: "top -bn1 | head -20" },
{ label: "OS + Kernel Info", desc: "Show distro and kernel details", type: "bash", script: "uname -a; echo; lsb_release -a 2>/dev/null || cat /etc/os-release" },
{ label: "Top Processes", desc: "Show top CPU-consuming processes", type: "bash", script: "ps aux --sort=-%cpu | head -15" },
{ label: "Network Interfaces", desc: "List interfaces and assigned IPs", type: "bash", script: "ip addr 2>/dev/null || ifconfig" },
{ label: "Failed Services", desc: "Show failing services if available", type: "bash", script: "systemctl --failed 2>/dev/null || service --status-all 2>/dev/null" },
],
},
{
label: "macOS",
icon: "fa-brands fa-apple",
color: { bg: "bg-purple-900/40", border: "border-purple-700/60", text: "text-purple-300", hover: "hover:bg-purple-800/40", item: "bg-purple-900/20 border-purple-700/40 hover:bg-purple-800/30" },
templates: [
{ label: "System Info", desc: "Show macOS version and software details", type: "bash", script: "sw_vers; echo; system_profiler SPSoftwareDataType 2>/dev/null | head -40" },
{ label: "Top Processes", desc: "Show top CPU-consuming processes", type: "bash", script: "ps aux --sort=-%cpu | head -15" },
{ label: "Network Interfaces", desc: "List interfaces and assigned IPs", type: "bash", script: "ip addr 2>/dev/null || ifconfig" },
{ label: "Sudo Rules", desc: "Show sudo permissions for the current user", type: "bash", script: "sudo -l 2>/dev/null || echo 'No sudo access or sudo not available'" },
{ label: "SSH Keys Hunt", desc: "Locate SSH private keys and authorized_keys files", type: "bash", script: "find /home /root /Users -maxdepth 4 \\( -name 'id_rsa' -o -name 'id_ed25519' -o -name 'id_ecdsa' -o -name 'authorized_keys' \\) 2>/dev/null" },
],
},
{
label: "Red Team — Windows",
icon: "fa-solid fa-skull",
color: { bg: "bg-red-900/40", border: "border-red-700/60", text: "text-red-300", hover: "hover:bg-red-800/40", item: "bg-red-900/20 border-red-700/40 hover:bg-red-800/30" },
templates: [
{ label: "Whoami + Privileges", desc: "Current user identity, groups and token privileges", type: "powershell", script: "whoami /all" },
{ label: "Local Users & Admins", desc: "Enumerate local accounts and administrator group members", type: "powershell", script: "Get-LocalUser | Select-Object Name,Enabled,LastLogon | Format-Table -AutoSize; Write-Host '--- Local Admins ---'; Get-LocalGroupMember -Group Administrators | Select-Object Name,ObjectClass,PrincipalSource" },
{ label: "Defender Exclusions", desc: "Show Defender exclusion paths, processes and extensions", type: "powershell", script: "$p = Get-MpPreference; 'Exclusion Paths:'; $p.ExclusionPath; ''; 'Exclusion Processes:'; $p.ExclusionProcess; ''; 'Exclusion Extensions:'; $p.ExclusionExtension" },
{ label: "PS Language Mode", desc: "Check PowerShell CLM and script block logging policy", type: "powershell", script: "Write-Host 'Language Mode:' $ExecutionContext.SessionState.LanguageMode; Write-Host 'Script Block Logging:' (Get-ItemProperty 'HKLM:\\Software\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging' -ErrorAction SilentlyContinue).EnableScriptBlockLogging" },
{ label: "Credential Manager", desc: "List stored Windows credential manager entries", type: "powershell", script: "cmdkey /list" },
{ label: "PS Command History", desc: "Show last 50 PowerShell history entries from PSReadLine", type: "powershell", script: "Get-Content (Get-PSReadLineOption).HistorySavePath -ErrorAction SilentlyContinue | Select-Object -Last 50" },
{ label: "Scheduled Tasks (Non-MS)", desc: "List non-Microsoft scheduled tasks (persistence / privesc)", type: "powershell", script: "Get-ScheduledTask | Where-Object {$_.TaskPath -notlike '\\Microsoft*'} | Select-Object TaskName,TaskPath,State | Format-Table -AutoSize" },
{ label: "Unquoted Service Paths", desc: "Find services with unquoted paths (privilege escalation vector)", type: "powershell", script: "Get-WmiObject Win32_Service | Where-Object {$_.PathName -like '* *' -and -not $_.PathName.StartsWith([char]34)} | Select-Object Name,PathName,State | Format-List" },
],
},
{
label: "Red Team — Linux",
icon: "fa-solid fa-bug",
color: { bg: "bg-rose-900/40", border: "border-rose-700/60", text: "text-rose-300", hover: "hover:bg-rose-800/40", item: "bg-rose-900/20 border-rose-700/40 hover:bg-rose-800/30" },
templates: [
{ label: "SUID Binaries", desc: "Find SUID binaries (potential privilege escalation)", type: "bash", script: "find / -perm -4000 -type f 2>/dev/null | sort" },
{ label: "Sudo Rules", desc: "Show sudo permissions for the current user", type: "bash", script: "sudo -l 2>/dev/null || echo 'No sudo access or sudo not available'" },
{ label: "Capabilities", desc: "Find binaries with elevated Linux capabilities", type: "bash", script: "getcap -r / 2>/dev/null || echo 'getcap not available'" },
{ label: "SSH Keys Hunt", desc: "Locate SSH private keys and authorized_keys files", type: "bash", script: "find /home /root /Users -maxdepth 4 \\( -name 'id_rsa' -o -name 'id_ed25519' -o -name 'id_ecdsa' -o -name 'authorized_keys' \\) 2>/dev/null" },
{ label: "Cron Jobs", desc: "Enumerate system-wide and user cron jobs", type: "bash", script: "echo '=== /etc/crontab ==='; cat /etc/crontab 2>/dev/null; echo '=== /etc/cron.d ==='; ls /etc/cron.d/ 2>/dev/null && cat /etc/cron.d/* 2>/dev/null; echo '=== User Crontabs ==='; ls /var/spool/cron/crontabs/ 2>/dev/null" },
],
},
];
function renderTemplatePalette() {
const palette = document.getElementById("template-palette");
const searchInput = document.getElementById("template-search");
if (!palette) return;
function render(term) {
palette.innerHTML = "";
for (const cat of TEMPLATE_CATEGORIES) {
const c = cat.color;
const filtered = term
? cat.templates.filter(
(t) =>
t.label.toLowerCase().includes(term) ||
t.desc.toLowerCase().includes(term),
)
: cat.templates;
if (term && filtered.length === 0) continue;
const section = document.createElement("div");
section.className = "mb-1";
const header = document.createElement("button");
header.className = `w-full flex items-center gap-2 px-3 py-2 rounded-lg ${c.bg} ${c.border} border text-sm font-semibold ${c.text} ${c.hover} transition-colors`;
header.innerHTML = `<i class="${cat.icon}"></i> ${escapeHtml(cat.label)} <span class="ml-auto text-xs opacity-60">${filtered.length}</span>`;
header.addEventListener("click", () => {
list.classList.toggle("hidden");
});
section.appendChild(header);
const list = document.createElement("div");
list.className = `flex flex-col gap-1 mt-1 pl-2${term ? "" : " hidden"}`;
for (const tmpl of filtered) {
const item = document.createElement("button");
item.className = `w-full text-left flex items-start gap-2 px-3 py-2 rounded-lg border ${c.item} transition-colors`;
item.innerHTML = `<div class="min-w-0"><div class="text-sm font-medium ${c.text} truncate">${escapeHtml(tmpl.label)}</div><div class="text-xs text-slate-500 truncate">${escapeHtml(tmpl.desc)}</div></div>`;
item.addEventListener("click", () => {
setEditorValue(tmpl.script);
scriptType.value = tmpl.type;
setEditorMode(tmpl.type);
});
list.appendChild(item);
}
section.appendChild(list);
palette.appendChild(section);
}
}
render("");
if (searchInput) {
searchInput.addEventListener("input", () => {
const term = searchInput.value.toLowerCase().trim();
render(term);
if (term) {
palette.querySelectorAll(".flex.flex-col.gap-1").forEach((l) => l.classList.remove("hidden"));
}
});
}
}
checkAuth(); checkAuth();
loadClients(); loadClients();
renderSavedScripts(); renderSavedScripts();
loadAutoTasks(); loadAutoTasks();
renderTemplatePalette();
if (window.CodeMirror && scriptEditor) { if (window.CodeMirror && scriptEditor) {
editorInstance = window.CodeMirror.fromTextArea(scriptEditor, { editorInstance = window.CodeMirror.fromTextArea(scriptEditor, {
+32 -263
View File
@@ -176,6 +176,29 @@
<option value="on_first_connect">On first connect (new clients only)</option> <option value="on_first_connect">On first connect (new clients only)</option>
</select> </select>
<div>
<div class="text-sm text-slate-400 mb-1.5">
Run on OS <span class="text-slate-500">(unchecked = any OS)</span>
</div>
<div class="flex flex-wrap gap-x-4 gap-y-1.5">
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
<input type="checkbox" id="auto-task-os-windows" name="auto-task-os" value="windows" class="w-4 h-4 rounded accent-emerald-500"> Windows
</label>
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
<input type="checkbox" id="auto-task-os-linux" name="auto-task-os" value="linux" class="w-4 h-4 rounded accent-emerald-500"> Linux
</label>
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
<input type="checkbox" id="auto-task-os-darwin" name="auto-task-os" value="darwin" class="w-4 h-4 rounded accent-emerald-500"> macOS
</label>
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
<input type="checkbox" id="auto-task-os-android" name="auto-task-os" value="android" class="w-4 h-4 rounded accent-emerald-500"> Android
</label>
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
<input type="checkbox" id="auto-task-os-freebsd" name="auto-task-os" value="freebsd" class="w-4 h-4 rounded accent-emerald-500"> FreeBSD
</label>
</div>
</div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
id="auto-task-save-btn" id="auto-task-save-btn"
@@ -201,270 +224,16 @@
</div> </div>
<div class="bg-slate-900/70 rounded-xl border border-slate-800 p-6"> <div class="bg-slate-900/70 rounded-xl border border-slate-800 p-6">
<h2 class="text-xl font-semibold mb-4">Script Templates</h2> <div class="flex items-center gap-2 mb-3">
<div class="space-y-2"> <h2 class="text-xl font-semibold flex-1">Script Templates</h2>
<button <input
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors" id="template-search"
data-type="powershell" type="text"
data-script="Get-ComputerInfo | Select-Object CsName, WindowsVersion, OsArchitecture, CsProcessors" placeholder="Search..."
> class="px-2 py-1.5 text-xs rounded-lg border border-slate-700 bg-slate-800 text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500 w-32"
<div class="font-semibold">[Windows] System Info</div> />
<div class="text-sm text-slate-400">
Get computer information
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU, WorkingSet"
>
<div class="font-semibold">[Windows] Top Processes</div>
<div class="text-sm text-slate-400">
Show top 10 processes by CPU
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-NetIPAddress | Where-Object {$_.AddressFamily -eq 'IPv4'} | Select-Object IPAddress, InterfaceAlias"
>
<div class="font-semibold">[Windows] Network Info</div>
<div class="text-sm text-slate-400">
List network adapters and IPs
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="$av = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct -ErrorAction SilentlyContinue; if ($av) { $av | Select-Object displayName, pathToSignedProductExe, timestamp } else { Write-Output 'No AV product data returned from SecurityCenter2' }"
>
<div class="font-semibold">[Windows] AV Status Check</div>
<div class="text-sm text-slate-400">
Query registered antivirus products
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-MpComputerStatus | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated"
>
<div class="font-semibold">[Windows] Defender Health</div>
<div class="text-sm text-slate-400">
Show Microsoft Defender status
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-Service | Where-Object {$_.Status -eq 'Running'} | Sort-Object DisplayName | Select-Object -First 30 Name, DisplayName, Status"
>
<div class="font-semibold">[Windows] Running Services</div>
<div class="text-sm text-slate-400">
List active Windows services
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="df -h"
>
<div class="font-semibold">[Linux/macOS] Disk Usage</div>
<div class="text-sm text-slate-400">
Show mounted filesystem usage
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="top -bn1 | head -20"
>
<div class="font-semibold">[Linux] System Status</div>
<div class="text-sm text-slate-400">
Show system status (Linux)
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="uname -a; echo; lsb_release -a 2>/dev/null || cat /etc/os-release"
>
<div class="font-semibold">[Linux] OS + Kernel Info</div>
<div class="text-sm text-slate-400">
Show distro and kernel details
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="ps aux --sort=-%cpu | head -15"
>
<div class="font-semibold">[Linux/macOS] Top Processes</div>
<div class="text-sm text-slate-400">
Show top CPU-consuming processes
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="ip addr 2>/dev/null || ifconfig"
>
<div class="font-semibold">[Linux/macOS] Network Interfaces</div>
<div class="text-sm text-slate-400">
List interfaces and assigned IPs
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="systemctl --failed 2>/dev/null || service --status-all 2>/dev/null"
>
<div class="font-semibold">[Linux] Failed Services</div>
<div class="text-sm text-slate-400">
Show failing services if available
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="sw_vers; echo; system_profiler SPSoftwareDataType 2>/dev/null | head -40"
>
<div class="font-semibold">[macOS] System Info</div>
<div class="text-sm text-slate-400">
Show macOS version and software details
</div>
</button>
<div class="pt-3 pb-1 text-xs font-semibold uppercase tracking-wider text-slate-500 border-t border-slate-700 mt-2">Red Team</div>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="whoami /all"
>
<div class="font-semibold">[Windows RT] Whoami + Privileges</div>
<div class="text-sm text-slate-400">
Current user identity, groups and token privileges
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-LocalUser | Select-Object Name,Enabled,LastLogon | Format-Table -AutoSize; Write-Host '--- Local Admins ---'; Get-LocalGroupMember -Group Administrators | Select-Object Name,ObjectClass,PrincipalSource"
>
<div class="font-semibold">[Windows RT] Local Users &amp; Admins</div>
<div class="text-sm text-slate-400">
Enumerate local accounts and administrator group members
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="$p = Get-MpPreference; 'Exclusion Paths:'; $p.ExclusionPath; ''; 'Exclusion Processes:'; $p.ExclusionProcess; ''; 'Exclusion Extensions:'; $p.ExclusionExtension"
>
<div class="font-semibold">[Windows RT] Defender Exclusions</div>
<div class="text-sm text-slate-400">
Show Defender exclusion paths, processes and extensions
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Write-Host 'Language Mode:' $ExecutionContext.SessionState.LanguageMode; Write-Host 'Script Block Logging:' (Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ErrorAction SilentlyContinue).EnableScriptBlockLogging"
>
<div class="font-semibold">[Windows RT] PS Language Mode</div>
<div class="text-sm text-slate-400">
Check PowerShell CLM and script block logging policy
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="cmdkey /list"
>
<div class="font-semibold">[Windows RT] Credential Manager</div>
<div class="text-sm text-slate-400">
List stored Windows credential manager entries
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-Content (Get-PSReadLineOption).HistorySavePath -ErrorAction SilentlyContinue | Select-Object -Last 50"
>
<div class="font-semibold">[Windows RT] PS Command History</div>
<div class="text-sm text-slate-400">
Show last 50 PowerShell history entries from PSReadLine
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-ScheduledTask | Where-Object {$_.TaskPath -notlike '\Microsoft*'} | Select-Object TaskName,TaskPath,State | Format-Table -AutoSize"
>
<div class="font-semibold">[Windows RT] Scheduled Tasks (Non-MS)</div>
<div class="text-sm text-slate-400">
List non-Microsoft scheduled tasks (persistence / privesc)
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="powershell"
data-script="Get-WmiObject Win32_Service | Where-Object {$_.PathName -like '* *' -and -not $_.PathName.StartsWith([char]34)} | Select-Object Name,PathName,State | Format-List"
>
<div class="font-semibold">[Windows RT] Unquoted Service Paths</div>
<div class="text-sm text-slate-400">
Find services with unquoted paths (privilege escalation vector)
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="find / -perm -4000 -type f 2>/dev/null | sort"
>
<div class="font-semibold">[Linux RT] SUID Binaries</div>
<div class="text-sm text-slate-400">
Find SUID binaries (potential privilege escalation)
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="sudo -l 2>/dev/null || echo 'No sudo access or sudo not available'"
>
<div class="font-semibold">[Linux/macOS RT] Sudo Rules</div>
<div class="text-sm text-slate-400">
Show sudo permissions for the current user
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="getcap -r / 2>/dev/null || echo 'getcap not available'"
>
<div class="font-semibold">[Linux RT] Capabilities</div>
<div class="text-sm text-slate-400">
Find binaries with elevated Linux capabilities
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="find /home /root /Users -maxdepth 4 \( -name 'id_rsa' -o -name 'id_ed25519' -o -name 'id_ecdsa' -o -name 'authorized_keys' \) 2>/dev/null"
>
<div class="font-semibold">[Linux/macOS RT] SSH Keys Hunt</div>
<div class="text-sm text-slate-400">
Locate SSH private keys and authorized_keys files
</div>
</button>
<button
class="template-btn w-full text-left px-4 py-3 rounded-lg border border-slate-700 bg-slate-800/50 hover:bg-slate-700 transition-colors"
data-type="bash"
data-script="echo '=== /etc/crontab ==='; cat /etc/crontab 2>/dev/null; echo '=== /etc/cron.d ==='; ls /etc/cron.d/ 2>/dev/null && cat /etc/cron.d/* 2>/dev/null; echo '=== User Crontabs ==='; ls /var/spool/cron/crontabs/ 2>/dev/null"
>
<div class="font-semibold">[Linux RT] Cron Jobs</div>
<div class="text-sm text-slate-400">
Enumerate system-wide and user cron jobs
</div>
</button>
</div> </div>
<div id="template-palette" class="space-y-1.5 max-h-[32rem] overflow-y-auto pr-0.5"></div>
</div> </div>
</div> </div>
+18 -3
View File
@@ -123,6 +123,9 @@ db.run(`
db.run( db.run(
`CREATE INDEX IF NOT EXISTS idx_auto_scripts_trigger ON auto_scripts(trigger, enabled);`, `CREATE INDEX IF NOT EXISTS idx_auto_scripts_trigger ON auto_scripts(trigger, enabled);`,
); );
try {
db.run(`ALTER TABLE auto_scripts ADD COLUMN os_filter TEXT NOT NULL DEFAULT '[]'`);
} catch { /* column already exists */ }
db.run(` db.run(`
CREATE TABLE IF NOT EXISTS auto_script_runs ( CREATE TABLE IF NOT EXISTS auto_script_runs (
@@ -482,11 +485,17 @@ export type AutoScript = {
script: string; script: string;
scriptType: string; scriptType: string;
enabled: boolean; enabled: boolean;
osFilter: string[];
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
}; };
function mapAutoScriptRow(row: any): AutoScript { function mapAutoScriptRow(row: any): AutoScript {
let osFilter: string[] = [];
try {
const parsed = JSON.parse(row.os_filter || "[]");
osFilter = Array.isArray(parsed) ? parsed : [];
} catch { }
return { return {
id: row.id, id: row.id,
name: row.name, name: row.name,
@@ -494,6 +503,7 @@ function mapAutoScriptRow(row: any): AutoScript {
script: row.script, script: row.script,
scriptType: row.script_type, scriptType: row.script_type,
enabled: row.enabled === 1, enabled: row.enabled === 1,
osFilter,
createdAt: Number(row.created_at) || 0, createdAt: Number(row.created_at) || 0,
updatedAt: Number(row.updated_at) || 0, updatedAt: Number(row.updated_at) || 0,
}; };
@@ -525,11 +535,12 @@ export function createAutoScript(input: {
script: string; script: string;
scriptType: string; scriptType: string;
enabled: boolean; enabled: boolean;
osFilter: string[];
}): AutoScript { }): AutoScript {
const now = Date.now(); const now = Date.now();
db.run( db.run(
`INSERT INTO auto_scripts (id, name, trigger, script, script_type, enabled, created_at, updated_at) `INSERT INTO auto_scripts (id, name, trigger, script, script_type, enabled, os_filter, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
, ,
input.id, input.id,
input.name, input.name,
@@ -537,6 +548,7 @@ export function createAutoScript(input: {
input.script, input.script,
input.scriptType, input.scriptType,
input.enabled ? 1 : 0, input.enabled ? 1 : 0,
JSON.stringify(input.osFilter ?? []),
now, now,
now, now,
); );
@@ -551,6 +563,7 @@ export function updateAutoScript(
script: string; script: string;
scriptType: string; scriptType: string;
enabled: boolean; enabled: boolean;
osFilter: string[];
}>, }>,
): AutoScript | null { ): AutoScript | null {
const current = getAutoScript(id); const current = getAutoScript(id);
@@ -562,16 +575,18 @@ export function updateAutoScript(
script: input.script ?? current.script, script: input.script ?? current.script,
scriptType: input.scriptType ?? current.scriptType, scriptType: input.scriptType ?? current.scriptType,
enabled: typeof input.enabled === "boolean" ? input.enabled : current.enabled, enabled: typeof input.enabled === "boolean" ? input.enabled : current.enabled,
osFilter: Array.isArray(input.osFilter) ? input.osFilter : current.osFilter,
}; };
db.run( db.run(
`UPDATE auto_scripts SET name=?, trigger=?, script=?, script_type=?, enabled=?, updated_at=? WHERE id=?` `UPDATE auto_scripts SET name=?, trigger=?, script=?, script_type=?, enabled=?, os_filter=?, updated_at=? WHERE id=?`
, ,
next.name, next.name,
next.trigger, next.trigger,
next.script, next.script,
next.scriptType, next.scriptType,
next.enabled ? 1 : 0, next.enabled ? 1 : 0,
JSON.stringify(next.osFilter),
Date.now(), Date.now(),
id, id,
); );
@@ -28,6 +28,13 @@ export function dispatchAutoScriptsForConnection(
} }
for (const script of scripts) { for (const script of scripts) {
if (script.osFilter.length > 0) {
const clientOs = (info.os || "").toLowerCase();
if (!script.osFilter.includes(clientOs)) {
continue;
}
}
if (script.trigger === "on_connect_once") { if (script.trigger === "on_connect_once") {
if (hasAutoScriptRun(script.id, info.id)) { if (hasAutoScriptRun(script.id, info.id)) {
continue; continue;
@@ -7,7 +7,7 @@ import {
updateAutoScript, updateAutoScript,
} from "../../db"; } from "../../db";
import { requirePermission } from "../../rbac"; import { requirePermission } from "../../rbac";
import { AUTO_SCRIPT_TRIGGERS, ALLOWED_SCRIPT_TYPES } from "../validation-constants"; import { AUTO_SCRIPT_TRIGGERS, ALLOWED_SCRIPT_TYPES, ALLOWED_OS_FILTERS } from "../validation-constants";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
export async function handleAutoScriptsRoutes( export async function handleAutoScriptsRoutes(
@@ -59,6 +59,8 @@ export async function handleAutoScriptsRoutes(
const script = String(body?.script || ""); const script = String(body?.script || "");
const scriptTypeRaw = String(body?.scriptType || "powershell").trim(); const scriptTypeRaw = String(body?.scriptType || "powershell").trim();
const enabled = typeof body?.enabled === "boolean" ? body.enabled : true; const enabled = typeof body?.enabled === "boolean" ? body.enabled : true;
const osFilterRaw = Array.isArray(body?.osFilter) ? body.osFilter : [];
const osFilter = osFilterRaw.filter((v: unknown) => typeof v === "string" && ALLOWED_OS_FILTERS.has(v));
if (!name) { if (!name) {
return Response.json({ error: "Name is required" }, { status: 400 }); return Response.json({ error: "Name is required" }, { status: 400 });
@@ -81,6 +83,7 @@ export async function handleAutoScriptsRoutes(
script, script,
scriptType, scriptType,
enabled, enabled,
osFilter,
}); });
return Response.json({ ok: true, item }); return Response.json({ ok: true, item });
@@ -121,12 +124,18 @@ export async function handleAutoScriptsRoutes(
: "powershell" : "powershell"
: undefined; : undefined;
const osFilterRaw = Array.isArray(body?.osFilter) ? body.osFilter : undefined;
const osFilter = osFilterRaw
? osFilterRaw.filter((v: unknown) => typeof v === "string" && ALLOWED_OS_FILTERS.has(v))
: undefined;
const updated = updateAutoScript(autoScriptMatch[1], { const updated = updateAutoScript(autoScriptMatch[1], {
name: body?.name ? String(body.name).trim() : undefined, name: body?.name ? String(body.name).trim() : undefined,
trigger: triggerRaw as AutoScriptTrigger | undefined, trigger: triggerRaw as AutoScriptTrigger | undefined,
script: body?.script ? String(body.script) : undefined, script: body?.script ? String(body.script) : undefined,
scriptType, scriptType,
enabled: typeof body?.enabled === "boolean" ? body.enabled : undefined, enabled: typeof body?.enabled === "boolean" ? body.enabled : undefined,
osFilter,
}); });
if (!updated) { if (!updated) {
@@ -29,3 +29,11 @@ export const ALLOWED_PLATFORMS = new Set([
"android-amd64", "android-amd64",
"android-armv7", "android-armv7",
]); ]);
export const ALLOWED_OS_FILTERS = new Set([
"windows",
"linux",
"darwin",
"android",
"freebsd",
]);