mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 08:30:20 +00:00
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):
- brain/ LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
dispatch, A51 channels, and the OSINT cluster
- knowledge/ LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
MITRE ATT&CK data
- reference/ defensive threat-reference (C3, shhbruh doc) + AdaYaml parser
License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.
Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.
https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for analyzing security logs with Splunk using splunk-sdk."""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
import splunklib.client as client
|
|
import splunklib.results as results
|
|
|
|
|
|
def connect_splunk(host, port, username, password):
|
|
"""Establish connection to Splunk instance."""
|
|
service = client.connect(
|
|
host=host,
|
|
port=port,
|
|
username=username,
|
|
password=password,
|
|
autologin=True,
|
|
)
|
|
return service
|
|
|
|
|
|
def run_search(service, query, earliest="-24h", latest="now"):
|
|
"""Execute a Splunk search and return parsed results."""
|
|
kwargs_search = {
|
|
"earliest_time": earliest,
|
|
"latest_time": latest,
|
|
"search_mode": "normal",
|
|
"exec_mode": "blocking",
|
|
}
|
|
job = service.jobs.create(f"search {query}", **kwargs_search)
|
|
reader = results.JSONResultsReader(job.results(output_mode="json"))
|
|
rows = [row for row in reader if isinstance(row, dict)]
|
|
job.cancel()
|
|
return rows
|
|
|
|
|
|
def detect_brute_force(service, threshold=10, earliest="-24h"):
|
|
"""Detect brute force attacks via failed logon events (EventCode 4625)."""
|
|
query = (
|
|
'index=windows sourcetype="WinEventLog:Security" EventCode=4625 '
|
|
f"| stats count as failed_attempts, dc(src_ip) as unique_sources, "
|
|
f"values(src_ip) as source_ips by TargetUserName "
|
|
f"| where failed_attempts > {threshold} "
|
|
f"| sort -failed_attempts"
|
|
)
|
|
return run_search(service, query, earliest=earliest)
|
|
|
|
|
|
def detect_lateral_movement(service, earliest="-24h"):
|
|
"""Detect lateral movement via Type 3 network logons to multiple hosts."""
|
|
query = (
|
|
'index=windows sourcetype="WinEventLog:Security" EventCode=4624 '
|
|
"Logon_Type=3 "
|
|
"| stats dc(ComputerName) as unique_targets, values(ComputerName) as targets "
|
|
"by TargetUserName, src_ip "
|
|
"| where unique_targets > 3 "
|
|
"| sort -unique_targets"
|
|
)
|
|
return run_search(service, query, earliest=earliest)
|
|
|
|
|
|
def detect_suspicious_powershell(service, earliest="-24h"):
|
|
"""Detect encoded or download-cradle PowerShell execution via Sysmon."""
|
|
query = (
|
|
'index=sysmon EventCode=1 Image="*\\\\powershell.exe" '
|
|
'(CommandLine="*-enc*" OR CommandLine="*-encodedcommand*" '
|
|
'OR CommandLine="*downloadstring*" OR CommandLine="*iex*") '
|
|
"| table _time, host, User, ParentImage, CommandLine "
|
|
"| sort _time"
|
|
)
|
|
return run_search(service, query, earliest=earliest)
|
|
|
|
|
|
def detect_lsass_access(service, earliest="-24h"):
|
|
"""Detect credential dumping via LSASS process access (Sysmon Event 10)."""
|
|
query = (
|
|
'index=sysmon EventCode=10 TargetImage="*\\\\lsass.exe" '
|
|
"GrantedAccess=0x1010 "
|
|
"| table _time, host, SourceImage, SourceUser, GrantedAccess"
|
|
)
|
|
return run_search(service, query, earliest=earliest)
|
|
|
|
|
|
def build_incident_timeline(service, hosts, users, earliest="-24h", latest="now"):
|
|
"""Build a unified incident timeline across multiple log sources."""
|
|
host_filter = " OR ".join(f'host="{h}"' for h in hosts)
|
|
user_filter = " OR ".join(f'user="{u}"' for u in users)
|
|
query = (
|
|
f"index=windows OR index=sysmon OR index=proxy OR index=firewall "
|
|
f"({host_filter} OR {user_filter}) "
|
|
'| eval event_summary=case('
|
|
' sourcetype=="WinEventLog:Security" AND EventCode==4624, '
|
|
' "Logon: ".TargetUserName." from ".src_ip, '
|
|
' sourcetype=="WinEventLog:Security" AND EventCode==4625, '
|
|
' "Failed logon: ".TargetUserName, '
|
|
' EventCode==1, "Process: ".Image." by ".User, '
|
|
' 1==1, sourcetype.": ".EventCode) '
|
|
"| table _time, sourcetype, host, event_summary "
|
|
"| sort _time"
|
|
)
|
|
return run_search(service, query, earliest=earliest, latest=latest)
|
|
|
|
|
|
def generate_report(findings):
|
|
"""Format investigation findings into a structured report."""
|
|
report = {
|
|
"report_type": "SPLUNK INVESTIGATION REPORT",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"findings": findings,
|
|
}
|
|
return json.dumps(report, indent=2, default=str)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Splunk Security Log Analysis Agent")
|
|
parser.add_argument("--host", default=os.getenv("SPLUNK_HOST", "localhost"))
|
|
parser.add_argument("--port", type=int, default=int(os.getenv("SPLUNK_PORT", "8089")))
|
|
parser.add_argument("--username", default=os.getenv("SPLUNK_USERNAME", "admin"))
|
|
parser.add_argument("--password", default=os.getenv("SPLUNK_PASSWORD", ""))
|
|
parser.add_argument("--earliest", default="-24h", help="Search earliest time")
|
|
parser.add_argument("--action", choices=[
|
|
"brute_force", "lateral_movement", "powershell",
|
|
"lsass_access", "timeline", "full_investigation"
|
|
], default="full_investigation")
|
|
parser.add_argument("--hosts", nargs="*", default=[], help="Target hosts for timeline")
|
|
parser.add_argument("--users", nargs="*", default=[], help="Target users for timeline")
|
|
parser.add_argument("--threshold", type=int, default=10)
|
|
args = parser.parse_args()
|
|
|
|
service = connect_splunk(args.host, args.port, args.username, args.password)
|
|
findings = {}
|
|
|
|
if args.action in ("brute_force", "full_investigation"):
|
|
findings["brute_force"] = detect_brute_force(service, args.threshold, args.earliest)
|
|
print(f"[+] Brute force: {len(findings['brute_force'])} accounts targeted")
|
|
|
|
if args.action in ("lateral_movement", "full_investigation"):
|
|
findings["lateral_movement"] = detect_lateral_movement(service, args.earliest)
|
|
print(f"[+] Lateral movement: {len(findings['lateral_movement'])} suspicious paths")
|
|
|
|
if args.action in ("powershell", "full_investigation"):
|
|
findings["suspicious_powershell"] = detect_suspicious_powershell(service, args.earliest)
|
|
print(f"[+] Suspicious PowerShell: {len(findings['suspicious_powershell'])} events")
|
|
|
|
if args.action in ("lsass_access", "full_investigation"):
|
|
findings["lsass_access"] = detect_lsass_access(service, args.earliest)
|
|
print(f"[+] LSASS access: {len(findings['lsass_access'])} events")
|
|
|
|
if args.action == "timeline" and args.hosts:
|
|
findings["timeline"] = build_incident_timeline(
|
|
service, args.hosts, args.users, args.earliest
|
|
)
|
|
print(f"[+] Timeline: {len(findings['timeline'])} events")
|
|
|
|
print(generate_report(findings))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|