Files
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

218 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""Osquery Endpoint Monitoring Agent - Generates configs, deploys queries, and analyzes results."""
import json
import os
import logging
import argparse
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
SECURITY_QUERIES = {
"process_not_on_disk": {
"query": "SELECT pid, name, path, cmdline, uid FROM processes WHERE on_disk = 0;",
"interval": 300,
"description": "Detect processes running from deleted binaries",
},
"listening_ports": {
"query": (
"SELECT lp.pid, lp.port, lp.protocol, lp.address, p.name, p.path "
"FROM listening_ports lp JOIN processes p ON lp.pid = p.pid "
"WHERE lp.port NOT IN (22, 80, 443, 3306, 5432);"
),
"interval": 600,
"description": "Monitor unexpected listening ports",
},
"outbound_connections": {
"query": (
"SELECT pid, remote_address, remote_port, local_port, p.name, p.path "
"FROM process_open_sockets pos JOIN processes p ON pos.pid = p.pid "
"WHERE remote_address NOT IN ('0.0.0.0', '127.0.0.1', '::1', '') "
"AND remote_address NOT LIKE '10.%' AND remote_address NOT LIKE '192.168.%';"
),
"interval": 300,
"description": "Monitor external outbound connections",
},
"cron_persistence": {
"query": "SELECT * FROM crontab WHERE command NOT LIKE '%logrotate%' AND command NOT LIKE '%anacron%';",
"interval": 3600,
"description": "Detect new cron job persistence",
},
"suid_binaries": {
"query": "SELECT path, mode, uid, gid FROM suid_bin WHERE path NOT LIKE '/usr/%' AND path NOT LIKE '/bin/%';",
"interval": 3600,
"description": "Detect SUID binaries outside standard paths",
},
"file_integrity_etc": {
"query": (
"SELECT path, mtime, size, sha256 FROM file "
"WHERE path LIKE '/etc/%%' AND mtime > (SELECT CAST(strftime('%s', 'now', '-1 hour') AS INTEGER));"
),
"interval": 600,
"description": "Monitor file changes in /etc",
},
"kernel_modules": {
"query": "SELECT name, size, status, address FROM kernel_modules WHERE status = 'Live';",
"interval": 3600,
"description": "Monitor loaded kernel modules",
},
"authorized_keys": {
"query": "SELECT uid, algorithm, key, key_file FROM authorized_keys;",
"interval": 3600,
"description": "Monitor SSH authorized keys",
},
"startup_items": {
"query": "SELECT name, path, source, status, username FROM startup_items;",
"interval": 3600,
"description": "Monitor startup/login items",
},
"shell_history": {
"query": "SELECT uid, command, history_file FROM shell_history WHERE command LIKE '%curl%pipe%sh%' OR command LIKE '%wget%';",
"interval": 1800,
"description": "Detect suspicious shell history entries",
},
}
def generate_osquery_config(queries, log_dir="/var/log/osquery"):
"""Generate osquery.conf with security monitoring queries."""
config = {
"options": {
"logger_plugin": "filesystem",
"logger_path": log_dir,
"disable_logging": "false",
"schedule_splay_percent": "10",
"events_expiry": "3600",
"database_path": "/var/osquery/osquery.db",
"verbose": "false",
"worker_threads": "2",
"enable_monitor": "true",
},
"schedule": {},
"file_paths": {
"etc": ["/etc/%%"],
"binaries": ["/usr/bin/%%", "/usr/sbin/%%", "/bin/%%", "/sbin/%%"],
"tmp": ["/tmp/%%"],
},
}
for name, query_def in queries.items():
config["schedule"][name] = {
"query": query_def["query"],
"interval": query_def["interval"],
"description": query_def["description"],
}
logger.info("Generated osquery config with %d scheduled queries", len(queries))
return config
def parse_osquery_results(results_dir):
"""Parse osquery differential result logs from the results directory."""
all_results = []
for filename in sorted(os.listdir(results_dir)):
if not filename.endswith(".log"):
continue
filepath = os.path.join(results_dir, filename)
with open(filepath, "r") as f:
for line in f:
try:
entry = json.loads(line.strip())
all_results.append(entry)
except json.JSONDecodeError:
continue
logger.info("Parsed %d result entries from %s", len(all_results), results_dir)
return all_results
def analyze_results(results):
"""Analyze osquery results for security findings."""
findings = []
for entry in results:
name = entry.get("name", "")
action = entry.get("action", "")
columns = entry.get("columns", {})
if name == "process_not_on_disk" and action == "added":
findings.append({
"type": "Process without binary",
"severity": "critical",
"details": columns,
"query": name,
})
elif name == "listening_ports" and action == "added":
port = int(columns.get("port", 0))
if port > 1024:
findings.append({
"type": "New listening port",
"severity": "high",
"details": columns,
"query": name,
})
elif name == "cron_persistence" and action == "added":
findings.append({
"type": "New cron job",
"severity": "high",
"details": columns,
"query": name,
})
elif name == "suid_binaries" and action == "added":
findings.append({
"type": "New SUID binary",
"severity": "critical",
"details": columns,
"query": name,
})
elif name == "authorized_keys" and action == "added":
findings.append({
"type": "New SSH authorized key",
"severity": "high",
"details": columns,
"query": name,
})
logger.info("Analysis: %d security findings from %d results", len(findings), len(results))
return findings
def generate_report(config, results, findings):
"""Generate osquery monitoring report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"scheduled_queries": len(config.get("schedule", {})),
"total_results_parsed": len(results),
"security_findings": len(findings),
"critical_findings": len([f for f in findings if f["severity"] == "critical"]),
"findings": findings[:50],
}
print(f"OSQUERY REPORT: {len(findings)} findings ({report['critical_findings']} critical)")
return report
def main():
parser = argparse.ArgumentParser(description="Osquery Endpoint Monitoring Agent")
parser.add_argument("--generate-config", help="Output path for osquery.conf")
parser.add_argument("--results-dir", help="Osquery results log directory")
parser.add_argument("--output", default="osquery_report.json")
args = parser.parse_args()
config = generate_osquery_config(SECURITY_QUERIES)
if args.generate_config:
with open(args.generate_config, "w") as f:
json.dump(config, f, indent=2)
logger.info("Config saved to %s", args.generate_config)
results = []
findings = []
if args.results_dir and os.path.isdir(args.results_dir):
results = parse_osquery_results(args.results_dir)
findings = analyze_results(results)
report = generate_report(config, results, findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()