foo/wosuid/README.NO.md
2026-09-29 09:39:24 +02:00

309 lines
No EOL
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `wosuid`-laboratoriet — root-RCE **uten** setuid-bit
```
foowosd en bevisst sårbar daemon som er root fordi den ble *STARTET* som
root (port 2344, bare loopback som standard)
foowosc exploitet: forvandler et stack-overløp til en **root**-shell ved å
utføre shellcode — de samme 23 bytene som knekte user-level-
`food`-daemonen i hovedlaboratoriet
```
Det er det tredje laboratoriet i serien. Samme exploit-verktøykjede, samme
stil, én fundamental forskjell:
| Laboratorium | hvordan målprosessen blir root | `uid=0(root)`-shell? |
|----------|------------------------------------------------|----------------------|
| food/fooc| aldri — det er en vanlig user-daemon | nei |
| foosd/foosc | SUID-biten (`chmod u+s`) — euid 0, ruid 1000 | ja (krever `setreuid` i shellcoden, fordi bash nullstiller euid→ruid) |
| **foowosd/foowosc** | **ingen — root *starter* daemonen** (sudo / systemd `User=root`) | **ja (vanlig `execve`-shellcode)** |
Setuid-biten er et *transportmiddel* for privilegier — ikke privilegiene selv.
En daemon startet av root har reell, effektiv og lagret uid alle lik 0. For
kjernen er det root, punktum; den kan og vil ikke vite om prosessen kom dit via
`+s` på en fil eller via `sudo ./foowosd`. Overløpet i en root-startet daemon er
altså et root-exploit — *«jeg har ingen SUID-binærfiler» er ikke det samme som
«jeg er ikke utnyttbar».*
Det er hele leksjonen i dette laboratoriet. Alt nedenfor er maskineriet.
---
## Rask start (det brukeren ba om)
```
cd wosuid
make # bygger daemonen, exploitet og test-harnessen
```
### Det ekte — kjør daemonen som **root**
```
sudo make run-root # starter foowosd som uid 0 (prosess, ikke filtillstand)
make test-root # hver teknikk skal nå gi uid=0(root)
```
### Ikke sudo? Den identiske kjernesti via en user-namespace
```
make run-root-ns # uid 0 i en user-namespace — ingen passord nødvendig
make test-root # samme dommer; brukes av CI og alle uten sudo
```
### Baseline — daemon som din vanlige bruker (intet root noen steder)
```
make run # foowosd kjører med dine uid-er
make test # exploits lander shells, men `root` forventes MISSING
```
### Oppryddingsritual (alltid: dette er et root-shell-laboratorium)
```
make stop
```
`foowosd` er *ikke* setuid, og intet i dette biblioteket gjør noen gang
`chmod +s` — det er poenget. Den farlige tilstanden er **prosessen**, ikke
filen.
---
## Når du blir bedt om å sette SUID-biten
Det blir du **ikke**. Dette laboratoriet har bevisst ingen SUID-bit:
- `foowosd` bygges, eies og har vanlige tillstander som ethvert annet program.
- Det blir root, som ekte daemoner gjør — ved å bli *startet* av root.
- `make run-root` bruker `sudo` til nøyaktig det, og `make run-root-ns` skaffer
en ekte uid-0-prosess helt uten noe av det.
Suid-biten tilhører *søster*-laboratoriet (`foosd`). Kontrasten mellom de to
er pensum:
1. SUID-laboratoriet: biten gir **euid 0, men ruid 1000** →
`execve("/bin/sh")` nedgraderes av bashs vakt (`euid != ruid` → reset) →
shellcoden må først kalle `setreuid(0,0)` (32-byte-payload).
2. Dette laboratoriet: root **starter** prosessen → **ruid == euid == 0** →
vakten har ingenting å nullstille → den vanlige 23-byte `execve`-shellcoden
beholder root.
Samme overløp. Samme teknikk. Forskjellig *opprinnelse* av privilegier,
annerledes payload-form. Det er leksjonen i miniatyr.
---
## Protokollen
Uansett hvilken slags klient som kobler til, hilser foowosd på den med:
```
FOOWOSD 1.0 ids=0/0 leak stack=0x7ffd… libc=0x7f… (banneret + leaks)
BUF=0x7ffd… (bufferadressen)
```
`ids=euid/ruid` er *«er jeg root?»*-sidekanalen. foowosc skriver ut en høy
advarsel når euid ikke er 0 (dvs. du startet daemonen som en vanlig bruker):
payloaden lander fortsatt, men shellen blir en bruker-shell, og å kalle
exploitet «i stykker» ville vært feil — det eskalerer bare ikke.
> Staveform-merknad: `ids=`, ikke `euid=`/`ruid=`. Test-harnessen beviser en
> levende `id` ved å matche den bokstavelige formen `uid=NNN(`, så banneret må
> aldri inneholde en delstreng som selv oppfyller sjekken. (I SUID-laboratoriet
> produserte nøyaktig den fellen et spektakulært falskt positivt.)
---
## Exploit-teknikkene (`foowosc -t …`)
Alle fire exploit-stiene nedenfor virker mot foowosd. Når daemonen er root,
gir **alle som spawner noe, root** — i motsetning til SUID-laboratoriet der
ret2win/ret2libc stille og rolig ble nedgradert til uid 1000 av bashs vakt. Her
er det intet mismatch å vokte mot.
| `-t` | hva som skjer | når daemonen er root |
|---------------|---------------------------------------------------------------------|---------------------|
| `shellcode` | 23-byte-`execve("/bin/sh", NULL, NULL)` kjører på stacken. | **root-shell** (standard) |
| `ret2win` | hopp til `win()` → `execl("/bin/sh")` | **root-shell** |
| `ret2libc` | ROP: `pop rdi; ret` → `"/bin/sh"` → `system()` | **root-shell** |
| `demo` | bare søppel-overløp — forvent et SIGSEGV i daemonloggen | krasj, etter design |
| `leak` | skriver bare ut leaks, sender ingen payload | n/a |
```
./foowosc -t shellcode # interaktiv; standard-target 127.0.0.1:2344
./foowosc -t shellcode -n # send og rapporter, ingen interaktiv økt
```
En vellykket interaktiv økt videresender terminalen din til shellen *på
offeret* — det er nøyaktig én shell i bildet, og det er `/bin/sh` som kjører
som root inne i foowosd. Skriv `id` for å se `uid=0(root)`.
### Hvorfor det ikke finnes noen `ret2win-root`-teknikk her
foosc hadde én — den hoppet til et `win_root()` som kalte `setreuid(0,0)` før
exec, fordi en setuid-prosess kjørte med en reell uid som fortsatt sa 1000. En
root-*startet* prosess har den reelle uid-en allerede 0; det er ingenting å
rydde, så den ekstra funksjonen og teknikken ville ikke lært noe. Fjernet.
---
## Hva foowosc gjør, steg for steg
1. **Statisk analyse** — `objdump -d` av `./foowosd`. Finner
`vulnerable_handler`, `win()`, det `lea -0x50(%rbp)` som adresserer `buf`,
og det første nakne `ret`. Ut fra forskyvningen beregner det
`rip_off = 80 + 8 = 88`. Ingenting er hardkodet; det overlever en ombygging.
2. **Selv-introspeksjon** — leser sitt eget `/proc/self/maps` og `dlsym()`er
`system`/`read` for å lære libc-*offsets*. Targetets libc-base er
`leaked_read − off_read`, deretter `system = base + off_system`, osv. Denne
delta-aritmetikken er grunnen til at exploits overlever libc-versjoner.
3. **Koble til** — leser banneret/leaks (`ids=`, `stack=`, `libc=`, `BUF=`).
4. **Bygg payloaden** — for `shellcode`: 23 bytes maskinkode, padding til
`rip_off`, deretter den lagrede RIP = `buf` (så `ret` hopper inn på koden).
For `ret2win`/`ret2libc`: adresser beregnet ut fra analysen — ingen
utførelse av stacken nødvendig.
5. **Justeringsfixen** — et kapret nakent `ret` gir callee-en
`rsp ≡ 8 (mod 16)`, og glibcs SSE2-kode `movaps`-feiler på en ikke-justert
stack (crash-reporteren logger `si_addr=(nil)` — fingerpeket). foowosc
setter inn ett ekstra `ret`-gadget før det ekte målet og gjenoppretter
invarianten. Silkehansketeknikk i et shellcode-laboratorium, men det er
forskjellen mellom en payload som «noen ganger virker» og en som alltid
virker.
6. **Send, deretter videresend** — offerprosessen *er* shellen; denne prosessen
splisser bare bytes. Ingen lokal shell, ingen annen leser —
single-read-cursor-feilen (ett spist byte per chunk) er dokumentert i
`become_shell()`.
---
## Daemonens bevisste feil (alle i `foowosd.c`, alle ekte CWE-klasser)
| # | feil | CWE | notat |
|---|-----|-----|------|
| 1 | `read(fd, buf, 512)` inn i et 64-byte stack-buffer | CWE-120 | overløpet: 448 bytes forbi `buf`, lagret RIP ved +88 |
| 2 | bare `snprintf(line, …, "%.*s", …)`; men angriper-`%` i ekko-stien | CWE-134 | leaket er her den reelle payloaden; et `%n` i en *root*-prosess ville vært write-what-where som root |
| 3 | barn beholder root mens de håndterer upålitelige inputs | CWE-271 | det korrekte `drop_privs()` (setgroups→setgid→setuid, i den rekkefølgen, med verifikasjon) står i filen, kommentert, *bevisst aldri kalt* |
| 4 | `ids=`, `stack=`, `libc=`, `BUF=` avslørt til enhver klient | CWE-200 | uten disse leaks kunne ikke shellcode- og ret2libc-teknikkene beregne adresser (ASLR ville beseiret dem) |
Handleren har nøyaktig samme `buf[64]`/`read(512)`-form som de to andre
laboratoriene, så den felles objdump-baserte oppdagelsespipelinen virker
uendret.
---
## Slik inspiserer du daemonen (læringssti)
```
make status # kjører den? som hvilken uid? filtillstand vises
make run-root # eller run / run-root-ns
./foowosc -t leak # se banneret og leaks, send ingenting
./foowosc -t demo # søppel-overløp -> SIGSEGV, logget med RIP/rsp
./foowosc -t shellcode # den interaktive root-shellen
make test-root # full matrise, alle teknikkene, --must-root
```
Crash-reporter: Ved SIGSEGV logger daemonen feiladressen, RIP og RSP. Et `ret`
inn i en ikke-kanonisk `0x4141…` feiler ved `ret`-et selv (RIP som `0x4028xx`,
`si_addr=(nil)`) — verdt å vite før du feilleser en logglinje som en
NULL-dereferanse.
---
## Hvorfor shellcoden er 23 bytes, ikke 32
```
31 f6 xor esi, esi ; argv = NULL
31 d2 xor edx, edx ; envp = NULL
48 bf 2f62696e2f736800 movabs rdi, "/bin/sh\\0"
57 push rdi
48 89 e7 mov rdi, rsp
6a 3b push 0x3b ; 59 = execve
58 pop rax
0f 05 syscall
```
SUID-laboratoriet trenger `setreuid(0,0)` foran dette. Dette laboratoriet ikke,
av den grunnen som gjentas overalt: **ruid er allerede 0**, fordi root startet
prosessen. `make verify` beviser at bytene i `foowosc.c` er byte-for-byte det
`shellcode.S` assemblerer til.
---
## Mottiltak — hva `make hardened` endrer
Hardet build (`-fstack-protector-strong -fPIE -pie -z noexecstack`):
| teknikk | sårbar `foowosd` | hardet `foowosd_hardened` |
|-----------|----------------------|------------------------------|
| `shellcode` | root-shell (kjørbar stack) | SIGSEGV ved canary-sjekken / NX |
| `ret2win` / `ret2libc` | root-shell | canary aborter `ret` — men merk: en *PIE*-build gjør også disse adressene tilfeldige |
| `demo` | SIGSEGV, logget | SIGSEGV, logget |
`make test-hardened` demonstrerer det live. Den viktige observasjonen er ikke
bare at mottiltakene drepte teknikkene — det er at de **ikke** gjorde daemonen
til «ikke-root». En hardet build som fortsatt *startes* som root, er fortsatt en
root-daemon; mottiltaket hever bare lista for angriperen. Minste privilegium
(`drop_privs()`) og minnesikkerhet er to forskjellige feil, og en daemon som
ikke trenger root, bør ikke ha det.
---
## Sikkerhetsgelendere (samme politikk som SUID-laboratoriet)
- **Bare loopback.** foowosd nekter å binde noe annet enn `127.0.0.1` /
`localhost` / `::1`, med mindre du gir `-L`. En root-daemon på et ekte
grensesnitt er en *fjern* root-tjeneste. `-L` finnes bare for å vise vakten;
ikke bruk den på noe som betyr noe.
- **Tilstanden logges høyt.** Ved start skriver den ut `ruid/euid` og om dette
er en root-prosess, så du alltid vet hvilket exploit-resultat du skal vente.
- **Dommer kommer fra exit-status** i `make test*` (pty-harnessens
returkode), aldri fra å greppe stdout-en dens — grepbar utdata lyver.
- **pty-en må kjøre cooked + ECHO off**, ellers ekkoer harnessen sin egen
kommandolinje og forfalsker markøren. Harnessen slår av ECHO og beholder
ECHONL på.
- **Oppryddingsritual:** `make stop` etter hver økt. Hvis daemonen er
root-eid, sier `stop` deg å kjøre `sudo pkill -x foowosd`.
- Kjør aldri dette på en vert du bryr deg om. Det finnes for å dele ut
`uid=0`-shells over loopback-grensesnittet.
---
## Øvelser
1. Kjør `make run` (user-daemon), deretter `./foowosc -t shellcode`. Hvorfor er
shellen ikke root? (Sjekk `ids=` i banneret — foowosc sier deg det før du i
det hele tatt kobler til.)
2. `make stop && sudo make run-root && make test-root`. Forklar ut fra
bannerlinjen hvorfor alle fire teknikkene nå gir `uid=0(root)`.
3. Finn i `foowosd.c` `drop_privs()` og les *hvorfor rekkefølgen* av
`setgroups → setgid → setuid` betyr noe. Bestem hvor i `main()` den ville
hørt hjemme, og hva laboratoriets angrepsflate blir når den faktisk kalles.
4. Beregn `rip_off` for hånd ut fra `objdump -d foowosd`: finn `buf`s
`lea -0xNN(%rbp)` inne i `vulnerable_handler`, deretter `NN + 8`. foowosc
gjør nøyaktig det; sjekk regnestykket dens mot ditt eget.
5. `make hardened && make test-hardened`. Hvilken teknikk faller for canaryen,
og hvilken for NX? Hvorfor endrer ikke hærdning hva `make status` rapporterer
om *prosessen*?
6. Sammenlign de to laboratorienes shellcodes: 23 bytes her, 32 for foosd. Hva
gjør de ekstra 9 bytene, og hvorfor trengs de bare i SUID-tilfellet?
7. Les `become_shell()`s kommentar om den enkelte lese-cursoren. Gjenskap
feiltilstanden mentalt: to lesere på én socket betyr at login-shellen spiser
ett byte per chunk — «uid=1000…» ankommer som «id=1000…». Hvorfor kan en
relay-prosess aldri ha denne feilen?
---
## Filer
```
foowosd.c den sårbare root-daemonen (hver linje kommentert)
foowosc.c exploitet (hver linje kommentert)
shellcode.S referanse-assembly for 23-byte-payloaden
tests/pty_wosuid_test.c pty-harnessen (markør + streng id-form-sjekk)
Makefile build / run / run-root / run-root-ns / test /
test-root / verify / hardened / clean …
```
Søster-laboratorier: `../food.c`/`../fooc.c` (user-level-baseline, port 2342)
og `../suid/` (SUID-root-daemon `foosd`/`foosc`, port 2343). Portene er bevisst
forskjellige — du kan kjøre alle tre samtidig og kryssjekke `ids=`-linjene
deres i banneret.