&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);