Files
html/api/incident-postgres-cron-scan.php

88 lines
4.5 KiB
PHP

<?php
// WEVIA Intent Helper : incident-postgres-cron-scan
// Wired by Opus 20 Apr - supervisor level
// Doctrine #2 zero regression, #3 GOLD-first, NO deletion - pure forensics
// Trigger : malicious cron postgres detected curl pastebin | sh since 2 Mar 2026
header('Content-Type: application/json');
$TS = date('Ymd-His');
$VAULT = '/opt/wevads/vault/incident-20260420-postgres-cron';
$out = [];
// Create vault dir for incident artifacts
@mkdir($VAULT, 0755, true);
// === 1. BACKUP crontab postgres INTACT ===
$cron_raw = @shell_exec('sudo cat /var/spool/cron/crontabs/postgres 2>&1');
@file_put_contents("$VAULT/crontab-postgres-{$TS}.txt", $cron_raw);
$out['crontab_backup'] = "$VAULT/crontab-postgres-{$TS}.txt";
$out['crontab_content'] = $cron_raw;
// === 2. List ALL cron jobs system-wide ===
$out['all_user_crontabs'] = trim(@shell_exec('sudo ls -la /var/spool/cron/crontabs/ 2>&1'));
$out['etc_cron_files'] = trim(@shell_exec('sudo find /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly /etc/crontab 2>/dev/null -type f -exec ls -la {} +'));
// === 3. Files owned by postgres outside PG data dir (prévention scope) ===
$out['postgres_files_outside_pgdata'] = trim(@shell_exec(
'sudo find / -user postgres -type f 2>/dev/null | grep -v "^/var/lib/postgresql" | grep -v "^/proc" | grep -v "^/sys" | head -50'
));
// === 4. Authorized_keys / SSH persistence ===
$out['postgres_ssh'] = trim(@shell_exec('sudo ls -la /var/lib/postgresql/.ssh 2>&1'));
$out['postgres_authorized_keys'] = trim(@shell_exec('sudo cat /var/lib/postgresql/.ssh/authorized_keys 2>/dev/null || echo NONE'));
$out['all_authorized_keys'] = trim(@shell_exec('sudo find / -name authorized_keys -type f 2>/dev/null | head -20'));
// === 5. Systemd user services (persistence vector) ===
$out['systemd_user_services'] = trim(@shell_exec('sudo find /etc/systemd/system /var/lib/systemd /home -name "*.service" 2>/dev/null | grep -iE "postgres|miner|crypto|kworker|kdevtmpfs|kinsing" | head -20'));
// === 6. LD_PRELOAD + /etc/ld.so.preload (rootkit vector) ===
$out['ld_preload_env'] = trim(@shell_exec('env | grep -i preload ; cat /etc/ld.so.preload 2>/dev/null || echo NO_PRELOAD'));
// === 7. Hidden binaries in /tmp /var/tmp /dev/shm ===
$out['hidden_binaries'] = trim(@shell_exec('sudo find /tmp /var/tmp /dev/shm -type f \( -name ".*" -o -executable \) 2>/dev/null | head -30'));
// === 8. Listening ports not in WEVAL expected set ===
$out['listening_ports'] = trim(@shell_exec('sudo ss -ltnp 2>/dev/null | head -40'));
// === 9. Processes started by postgres (non-pg DB) ===
$out['postgres_non_db_procs'] = trim(@shell_exec(
"ps -eo pid,user,etimes,cmd 2>/dev/null | awk '\$2==\"postgres\" && \$4 !~ /^postgres/ && \$4 !~ /^\\/usr\\/lib\\/postgres/ {print}' | head -20"
));
// === 10. Recent files (<7 days) in /tmp /var/tmp - typical dropper locations ===
$out['recent_tmp_files'] = trim(@shell_exec('sudo find /tmp /var/tmp -type f -mtime -7 2>/dev/null | head -30'));
// === 11. Pastebin URL fetch (read only, log content seen) ===
$pb = @shell_exec('curl -s --max-time 10 https://pastebin.com/raw/C0Y31fxq 2>/dev/null');
$out['pastebin_current_content_len'] = strlen((string)$pb);
$out['pastebin_current_content_md5'] = md5((string)$pb);
$out['pastebin_current_content_snippet'] = substr((string)$pb, 0, 500);
@file_put_contents("$VAULT/pastebin-content-{$TS}.txt", $pb);
// === 12. Crowdsec / fail2ban events ===
$out['crowdsec_decisions'] = trim(@shell_exec('sudo cscli decisions list 2>/dev/null | head -20'));
// === 13. Syslog last 500 lines grep cron postgres ===
$out['syslog_cron_postgres'] = trim(@shell_exec('sudo grep -E "CRON.*postgres" /var/log/syslog /var/log/syslog.1 2>/dev/null | tail -30'));
// === Save full dump ===
$full_path = "$VAULT/scan-{$TS}.json";
@file_put_contents($full_path, json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
// === Summary ===
echo json_encode([
'tool' => 'incident-postgres-cron-scan',
'status' => 'diagnostic_only_no_deletion',
'vault_dir' => $VAULT,
'full_dump' => $full_path,
'ts' => date('c'),
'summary' => [
'malicious_cron_present' => (strpos($cron_raw, 'pastebin.com/raw/C0Y31fxq') !== false),
'pastebin_content_md5' => $out['pastebin_current_content_md5'],
'pastebin_size' => $out['pastebin_current_content_len'],
'files_postgres_outside_pg' => substr_count($out['postgres_files_outside_pgdata'], "\n") + 1,
],
'next_step' => 'Review vault dump, decide removal (Option B) or escalate',
], JSON_PRETTY_PRINT);