dai che magari questa cosa la finisco
This commit is contained in:
parent
1b82a442b7
commit
073a4b3704
2 changed files with 46 additions and 72 deletions
114
README.md
114
README.md
|
@ -1,37 +1,38 @@
|
|||
## Light Hardened Container
|
||||
|
||||
#### Perché
|
||||
Voglio esporre su internet alcuni servizi da una board (Odroid C2).
|
||||
Giocando con i vari sistemi per creare container quello piú simile ai miei desideri é `systemd-nspawn` (si, systemd é una merda ed é il male). `systemd-nspawn` é tipo una chroot ma meglio.
|
||||
Voglio esporre su internet alcuni servizi da una board (Odroid C2) e farlo a modino, ogni servizio dentro un container.
|
||||
Giocando con i vari sistemi per creare container quello piú simile ai miei desideri é `runc`.
|
||||
|
||||
#### Come
|
||||
Uso [alpine linux](https://alpinelinux.org) come base perché la rootfs é scandalosamente piccola (1.9Mb), supporta arm64 (che é l'architettura della mia board casalinga), supporta un sistema di pacchetti degno di questo nome (apk) ed e' orientata alla sicurezza.
|
||||
|
||||
L'assunto di base é che i servizi che girano dentro il container sono bucabili e quindi bisogna limitare i danni di un sicuro pwn.
|
||||
Per fare questo ho pensato di far girare la root di ogni container dentro una partizione read-only.
|
||||
Per i dati necessari ai vari servizi, faccio un bind di una directory da una partizione no-exec in modo che
|
||||
Per __i dati__ necessari ai vari servizi, faccio un bind di una directory da una partizione no-exec in modo che
|
||||
anche se il servizio viene bucato, l'attaccante puó scrivere solamente sulla partizione dei dati (da cui non puo'
|
||||
peró avviare niente).
|
||||
|
||||
Per evitare di far usare interpreti e altre utilities utili all'attaccante, il container deve contenere esclusivamente i files strettamente necessari per far girare il servizio. Per fare questo, prima della fase di deploy il servizio verrá fatto girare con strace per controllare tutte le syscall a `open`: i file aperti saranno gli unici a rimanere nel container.
|
||||
Per evitare di far usare interpreti e altre utilities utili all'attaccante, il container deve contenere esclusivamente i files strettamente necessari per far girare il servizio. Come fare? Partendo dal binario che vogliamo lanciare (sia esso apache, nginx, dovecot o tor) prendiamo innanzitutto le librerie da cui dipende con un `ldd` (anche se sarebbe stato figo usare una distro [tutta statica](http://sta.li)) e poi scopriamo tutti i file da cui dipende cercando le syscall a `open` mentre gira (con `strace`).
|
||||
|
||||
Nella pratica, immaginiamo di voler far girare `nginx` in questa maniera, il setup a mano sarebbe:
|
||||
Nella pratica, immaginiamo di voler far girare [caddy](https://caddyserver.com/) in questa maniera, ecco come sarebbe il setup a mano:
|
||||
|
||||
Creo una partizione per i dati di 30Mb (a titolo di esempio lo facciamo in loop) e ne faccio il mount con l'opzione noexec
|
||||
|
||||
Creo una partizione per __i dati__ di 30Mb (a titolo di esempio lo facciamo in loop) e ne faccio il mount (per ora senza noexec)
|
||||
|
||||
```bash
|
||||
dd if=/dev/zero of=./datafs bs=1024 count=30720
|
||||
losetup /dev/loop0 datafs
|
||||
dd if=/dev/zero of=./testfs/datafs bs=1024 count=30720
|
||||
losetup /dev/loop0 ./testfs/datafs
|
||||
mkfs.ext4 /dev/loop0
|
||||
mkdir data
|
||||
mount -o noexec /dev/loop0 data
|
||||
mount /dev/loop0 data
|
||||
```
|
||||
|
||||
stessa cosa per la partizione dei containers (per ora non la monto in read-only)
|
||||
stessa cosa per la partizione dei container (per ora non la monto in read-only)
|
||||
|
||||
```bash
|
||||
dd if=/dev/zero of=containerfs bs=1024 count=30720
|
||||
losetup /dev/loop1 containerfs
|
||||
dd if=/dev/zero of=./testfs/containerfs bs=1024 count=30720
|
||||
losetup /dev/loop1 ./testfs/containerfs
|
||||
mkfs.ext4 /dev/loop1
|
||||
mkdir containers
|
||||
mount /dev/loop1 containers
|
||||
|
@ -40,82 +41,55 @@ mount /dev/loop1 containers
|
|||
creo le dir per il container
|
||||
|
||||
```bash
|
||||
mkdir containers/nginx
|
||||
mkdir data/nginx
|
||||
mkdir containers/caddy
|
||||
mkdir data/caddy
|
||||
```
|
||||
|
||||
e ci scompatto dentro la rootfs di alpine linux: `tar xvf alpine-minirootfs-3.5.1-aarch64.tar.gz -C containers/nginx`
|
||||
e ci scompatto dentro la rootfs di alpine linux: `tar xvf rootfs/alpine-minirootfs-3.5.1-aarch64.tar.gz -C containers/caddy`
|
||||
ora sará sufficiente `run spec` per creare un file di configurazione di runc e modificare i parametri del file che ci interessano:
|
||||
|
||||
a questo punto viene il bello, facciamo partire il container:
|
||||
|
||||
```bash
|
||||
lhc git:(master) ✗ systemd-nspawn -D containers/nginx --bind=`pwd`/data/nginx/:/data
|
||||
Spawning container nginx on /data/dev/lhc/containers/nginx.
|
||||
Press ^] three times within 1s to kill container.
|
||||
Timezone Europe/Rome does not exist in container, not updating container timezone.
|
||||
-sh: can't access tty; job control turned off
|
||||
nginx:~#
|
||||
```js
|
||||
// prima di tutto il path della rootfs (quindi cercando 'root')
|
||||
"root": {
|
||||
"path": "./containers/caddy",
|
||||
// per ora non lo mettiamo read-only perché dobbiamo prima installare
|
||||
// quello che ci interessa, per il deploy questo bisogna metterlo a true
|
||||
"readonly": false
|
||||
}
|
||||
|
||||
// poi dentro la voce "mounts" aggiungiamo la nostra partizione per i dati
|
||||
"mounts": [
|
||||
{
|
||||
"type": "bind",
|
||||
"destination": "/data",
|
||||
"source": "./data/caddy",
|
||||
"options": ["rbind", "rw", "noexec"]
|
||||
},
|
||||
```
|
||||
|
||||
e siamo dentro il container, quindi possiamo iniziare ad installare quello che dobbiamo:
|
||||
|
||||
```bash
|
||||
a questo punto possiamo far partire il container con `runc run caddy` e siamo dentro,
|
||||
non rimane che configurare il servizio che ci serve, in questo caso caddy.
|
||||
Potrei fare velocemente con un `apk update` e `apk add caddy` ma il pacchettizzato
|
||||
manca di alcune funzionalitá quindi scelgo di scaricarlo direttamente dal sito:
|
||||
|
||||
```
|
||||
apk update
|
||||
apk add nginx
|
||||
apk add wget
|
||||
wget 'https://caddyserver.com/download/build?os=linux&arch=arm64&features=git,filemanager,cors,expires,minify' -O caddy.tar.gz
|
||||
|
||||
```
|
||||
|
||||
da adesso in poi, dobbiamo cercare di configurare il servizio per usare /data/ come path per tutti i file che vuole utilizzare in scrittura. Per fare ció possiamo fare in tanti modi, iniziamo a vedere cosa succede ad avviarlo manualmente:
|
||||
|
||||
|
||||
```bash
|
||||
nginx: [emerg] open() "/run/nginx/nginx.pid" failed (2: No such file or directory)
|
||||
nginx3:~#
|
||||
```
|
||||
|
||||
ok, /run é svuotata da `systemd-nspawn` di proposito e quindi la directory 'nginx' al suo interno non possiamo mettercela, inoltre per l'assunto di prima (ovvero che questo fs sará read-only) dobbiamo metterlo dentro /data, quindi modifichiamo `nginx.conf` per fare in modo che questo accada e quindi `vi /etc/nginx/nginx.conf`:
|
||||
Ora ci serve far partire il container direttamente con `caddy` e non con la shell
|
||||
|
||||
```nginx.conf
|
||||
# aggiungiamo da qualche parte se non c'é giá
|
||||
pid /data/nginx.pid
|
||||
|
||||
# e giá che ci siamo cerchiamo anche gli altri path usati da nginx in scrittura e li cambiamo
|
||||
error_log /data/error.log;
|
||||
|
||||
access_log /data/access.log;
|
||||
|
||||
```
|
||||
|
||||
riproviamo:
|
||||
```bash
|
||||
nginx:/etc/nginx# nginx
|
||||
nginx:/etc/nginx# ps aux
|
||||
PID USER TIME COMMAND
|
||||
1 root 0:00 -sh
|
||||
26 root 0:00 nginx: master process nginx
|
||||
27 nginx 0:00 nginx: worker process
|
||||
28 nginx 0:00 nginx: worker process
|
||||
29 nginx 0:00 nginx: worker process
|
||||
30 nginx 0:00 nginx: worker process
|
||||
31 nginx 0:00 nginx: worker process
|
||||
32 nginx 0:00 nginx: worker process
|
||||
33 nginx 0:00 nginx: worker process
|
||||
34 nginx 0:00 nginx: worker process
|
||||
35 root 0:00 ps aux
|
||||
nginx:/etc/nginx#
|
||||
```
|
||||
|
||||
direi molto meglio, ora usciamo dal container (ctrl+] per tre volte oppure `exit`).
|
||||
Ora ci serve far partire il container direttamente con `nginx` e non con la shell e c'é un problema:
|
||||
per come é fatto systemd-nspawn il programma che prende il comando non deve uscire (é effettivamente PID 1 dentro il container) quindi bisogna dire a nginx di rimanere in foreground (basta aggiungere `daemon off;` in `containers/nginx/etc/nginx.conf`)
|
||||
|
||||
`systemd-nspawn -D containers/nginx --bind=\`pwd\`/data/nginx/:/data nginx`
|
||||
|
||||
per come é pacchettizzato dentro alpine linux, nginx cambia di suo l'utente con cui gira quindi per questo servizio non serve dire a systemd-nspawn di cambiare utente, negli altri casi bisogna usare il flag `-u`.
|
||||
|
||||
Ultima cosa, non voglio avere una shell dentro il container o altri tool (metti che appunto ci bucano, mica vogliamo fargli un favore), ma come fare? ci serve qualcosa che trovi tutti i file aperti da `nginx` e quello che mi viene in mente é `strace`, quindi da dentro il container installiamo strace (`apk add strace`) e lanciamo nginx con strace redirigendo `stderr` su un file (`strace nginx 2> strace.log`). Dobbiamo cercare di "attivare" tutte le funzionalitá del servizio in modo che becchiamo tutti i file che possono essere utilizzati da `nginx` (in questo caso quello che mi viene in mente é di lanciare una richiesta HTTP a nginx per dire, tipo dall'host faccio un `wget localhost`).
|
||||
Ora killiamo lo strace `kill -9 strace` e cerchiamo le syscall a open dentro il log:
|
||||
Ultima cosa, non voglio avere una shell dentro il container o altri tool (metti che appunto ci bucano, mica vogliamo fargli un favore), ma come fare? ci serve qualcosa che trovi tutti i file aperti da `caddy` e quello che mi viene in mente é `strace`, quindi da dentro il container installiamo strace (`apk add strace`) e lanciamo nginx con strace redirigendo `stderr` su un file (`strace nginx 2> strace.log`). Dobbiamo cercare di "attivare" tutte le funzionalitá del servizio in modo che becchiamo tutti i file che possono essere utilizzati da `nginx` (in questo caso quello che mi viene in mente é di lanciare una richiesta HTTP a nginx per dire, tipo dall'host faccio un `wget localhost`).
|
||||
Ora killiamo lo strace `kill -9 strace` e cerchiamo le syscallscall a open dentro il log:
|
||||
```bash
|
||||
nginx5:~# grep open strace.log
|
||||
open("/etc/ld-musl-x86_64.path", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
|
||||
|
|
|
@ -93,8 +93,8 @@ escaped_path=$(echo $FULL_CONTAINER_PATH | sed -e 's/\//\\\//g')
|
|||
echo "ESCAPED_PATH: $escaped_path"
|
||||
mkdir `pwd`/containers/$CONTAINER_NAME.tmp
|
||||
|
||||
files=`sed -rn "s/.* open (readwrite |writeonly )?$escaped_path(.*) \{.*/\2/p" < files_rs.log | sort | uniq`
|
||||
links=`sed -rn "s/.* readlink $escaped_path(.*) \{.*/\1/p" < files_rs.log | sort | uniq`
|
||||
files=`sed -rn "s/.* open (readwrite |writeonly )?$escaped_path(.*) \{.*/\2/p" < files_$CONTAINER_NAME.log | sort | uniq`
|
||||
links=`sed -rn "s/.* readlink $escaped_path(.*) \{.*/\1/p" < files_$CONTAINER_NAME.log | sort | uniq`
|
||||
|
||||
## ok, removing all file but ones in $CONTAINER_NAME.log
|
||||
cd $FULL_CONTAINER_PATH
|
||||
|
|
Loading…
Reference in a new issue