Initial commit
This commit is contained in:
commit
394e3be54d
41 changed files with 16315 additions and 0 deletions
311
wosuid/README.NL.md
Normal file
311
wosuid/README.NL.md
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
# Het `wosuid`-lab — root-RCE **zonder** setuid-bit
|
||||
|
||||
```
|
||||
foowosd een bewust kwetsbare daemon die root is omdat hij als *GESTART* als
|
||||
root (poort 2344, standaard alleen loopback)
|
||||
foowosc het exploit: verandert een stack-overloop in een **root**-shell door
|
||||
shellcode uit te voeren — dezelfde 23 bytes die de user-level-
|
||||
`food`-daemon in het hoofdlab kraakten
|
||||
```
|
||||
|
||||
Dit is het derde lab in de serie. Dezelfde exploit-werktuigketen, dezelfde stijl,
|
||||
één fundamenteel verschil:
|
||||
|
||||
| Lab | hoe het doelproces root wordt | `uid=0(root)`-shell? |
|
||||
|----------|------------------------------------------------|----------------------|
|
||||
| food/fooc | nooit — het is een gewone user-daemon | nee |
|
||||
| foosd/foosc | de SUID-bit (`chmod u+s`) — euid 0, ruid 1000 | ja (vereist `setreuid` in de shellcode, want bash reset euid→ruid) |
|
||||
| **foowosd/foowosc** | **geen — root *start* de daemon** (sudo / systemd `User=root`) | **ja (gewone `execve`-shellcode)** |
|
||||
|
||||
De setuid-bit is een *transportmiddel* voor privileges — niet de privileges
|
||||
zelf. Een daemon die door root is gestart, heeft reële, effectieve en
|
||||
opgeslagen uid allemaal 0. Voor de kernel is dat root, punt uit; hij kan en wil
|
||||
niet weten of het proces daar via `+s` op een bestand of via `sudo ./foowosd`
|
||||
is gekomen. De overloop in een root-gestarte daemon is dus een root-exploit — *"ik
|
||||
heb geen SUID-binaries" is niet hetzelfde als "ik ben niet exploiteerbaar".*
|
||||
|
||||
Dat is de hele les van dit lab. Al het andere hieronder is het mechanisme.
|
||||
|
||||
---
|
||||
|
||||
## Snelle start (wat de gebruiker vroeg)
|
||||
|
||||
```
|
||||
cd wosuid
|
||||
make # bouwt de daemon, het exploit en de test-harness
|
||||
```
|
||||
|
||||
### Het echte werk — draai de daemon als **root**
|
||||
|
||||
```
|
||||
sudo make run-root # start foowosd als uid 0 (proces, geen bestandstoestand)
|
||||
make test-root # elke techniek moet nu uid=0(root) geven
|
||||
```
|
||||
|
||||
### Geen sudo? Het identieke kernelpad via een user-namespace
|
||||
|
||||
```
|
||||
make run-root-ns # uid 0 in een user-namespace — geen wachtwoord nodig
|
||||
make test-root # dezelfde oordelen; gebruikt door CI en iedereen zonder sudo
|
||||
```
|
||||
|
||||
### Baseline — daemon als je normale gebruiker (nergens root)
|
||||
|
||||
```
|
||||
make run # foowosd draait met jouw uids
|
||||
make test # exploits landen shells, maar `root` wordt verwacht als MISSING
|
||||
```
|
||||
|
||||
### Opruimritueel (altijd: dit is een root-shell-lab)
|
||||
|
||||
```
|
||||
make stop
|
||||
```
|
||||
|
||||
`foowosd` is *niet* setuid, en niets in deze map doet ooit `chmod +s` — dat is
|
||||
het punt. De gevaarlijke toestand is **het proces**, niet het bestand.
|
||||
|
||||
---
|
||||
|
||||
## Wanneer je wordt gezegd de SUID-bit te zetten
|
||||
|
||||
Dat zul je **niet** worden. Dit lab heeft bewust geen SUID-bit:
|
||||
|
||||
- `foowosd` wordt gebouwd, is eigendom van jou en heeft gewone toestanden als
|
||||
elk ander programma.
|
||||
- Hij wordt root zoals echte daemons dat doen — door *gestart* te worden door
|
||||
root.
|
||||
- `make run-root` gebruikt `sudo` voor precies dat, en `make run-root-ns`
|
||||
regelt een echte uid-0-proces zonder ook maar iets daarvan.
|
||||
|
||||
De Suid-bit hoort bij het *zuster*-lab (`foosd`). Het contrast tussen de twee
|
||||
is de leerstof:
|
||||
|
||||
1. SUID-lab: de bit geeft **euid 0, maar ruid 1000** → `execve("/bin/sh")`
|
||||
wordt gedegradeerd door de wacht van bash (`euid != ruid` → reset) → de
|
||||
shellcode moet eerst `setreuid(0,0)` aanroepen (32-byte-payload).
|
||||
2. Dit lab: root **start** het proces → **ruid == euid == 0** → de wacht heeft
|
||||
niets om te resetten → de gewone 23-byte `execve`-shellcode houdt root.
|
||||
|
||||
Dezelfde overloop. Dezelfde techniek. Andere *oorsprong* van privileges, andere
|
||||
payload-vorm. Dat is de les in het klein.
|
||||
|
||||
---
|
||||
|
||||
## Het protocol
|
||||
|
||||
Welke client er ook verbindt, foowosd begroet hem met:
|
||||
|
||||
```
|
||||
FOOWOSD 1.0 ids=0/0 leak stack=0x7ffd… libc=0x7f… (banner + leaks)
|
||||
BUF=0x7ffd… (het bufferadres)
|
||||
```
|
||||
|
||||
`ids=euid/ruid` is het *"ben ik root?"*-kanaal. foowosc print een luide
|
||||
waarschuwing wanneer euid niet 0 is (dus je startte de daemon als gewone
|
||||
gebruiker): de payload landt nog steeds, maar de shell wordt een
|
||||
gebruiker-shell, en het exploit "kapot" noemen zou fout zijn — hij escaleert
|
||||
alleen niet.
|
||||
|
||||
> Spellingsnoot: `ids=`, niet `euid=`/`ruid=`. De test-harness bewijst een
|
||||
> levende `id` door de letterlijke vorm `uid=NNN(` te matchen, dus het banner
|
||||
> mag nooit een deelstring bevatten die zelf aan de controle voldoet. (In het
|
||||
> SUID-lab produceerde precies die val een spectaculaire fout-positief.)
|
||||
|
||||
---
|
||||
|
||||
## De exploit-technieken (`foowosc -t …`)
|
||||
|
||||
Alle vier de exploit-paden hieronder werken tegen foowosd. Wanneer de daemon
|
||||
root is, geeft **alles dat iets spawnt root** — in tegenstelling tot het
|
||||
SUID-lab, waar ret2win/ret2libc stilletjes door de wacht van bash naar uid 1000
|
||||
werden gedegradeerd. Hier is er geen mismatch om te bewaken.
|
||||
|
||||
| `-t` | wat er gebeurt | wanneer de daemon root is |
|
||||
|---------------|---------------------------------------------------------------------|---------------------|
|
||||
| `shellcode` | 23-byte `execve("/bin/sh", NULL, NULL)` draait op de stack. | **root-shell** (standaard) |
|
||||
| `ret2win` | sprong naar `win()` → `execl("/bin/sh")` | **root-shell** |
|
||||
| `ret2libc` | ROP: `pop rdi; ret` → `"/bin/sh"` → `system()` | **root-shell** |
|
||||
| `demo` | alleen rommel-overloop — verwacht een SIGSEGV in de daemonlog | crash, volgens ontwerp |
|
||||
| `leak` | print alleen leaks, stuurt geen payload | n.v.t. |
|
||||
|
||||
```
|
||||
./foowosc -t shellcode # interactief; standaardtarget 127.0.0.1:2344
|
||||
./foowosc -t shellcode -n # sturen en rapporteren, geen interactieve sessie
|
||||
```
|
||||
|
||||
Een succesvolle interactieve sessie schakelt je terminal door naar de shell *op
|
||||
het slachtoffer* — er is precies één shell in het beeld, en dat is `/bin/sh`
|
||||
die als root binnenin foowosd draait. Typ `id` om `uid=0(root)` te zien.
|
||||
|
||||
### Waarom er hier geen `ret2win-root`-techniek is
|
||||
|
||||
foosc had er één — die sprong naar een `win_root()` die `setreuid(0,0)` aanriep
|
||||
vóór exec, omdat een setuid-proces draaide met een reële uid die nog 1000 zei.
|
||||
Een root-*gestart* proces heeft de reële uid al 0; er valt niets op te ruimen,
|
||||
dus de extra functie en techniek zouden niets leren. Verwijderd.
|
||||
|
||||
---
|
||||
|
||||
## Wat foowosc doet, stap voor stap
|
||||
|
||||
1. **Statische analyse** — `objdump -d` van `./foowosd`. Vindt
|
||||
`vulnerable_handler`, `win()`, de `lea -0x50(%rbp)` die `buf` adresseert,
|
||||
en de eerste kale `ret`. Uit de verplaatsing berekent het
|
||||
`rip_off = 80 + 8 = 88`. Niets is hardcoded; het overleeft een herbouw.
|
||||
2. **Zelf-introspectie** — leest zijn eigen `/proc/self/maps` en `dlsym()`t
|
||||
`system`/`read` om de libc-*offsets* te leren. De libc-base van het
|
||||
doelwit is `leaked_read − off_read`, daarna `system = base + off_system`,
|
||||
enz. Die delta-rekenkunde is de reden dat exploits libc-versies overleven.
|
||||
3. **Verbind** — leest het banner/leaks (`ids=`, `stack=`, `libc=`, `BUF=`).
|
||||
4. **Bouw de payload** — voor `shellcode`: 23 bytes machinecode, padding tot
|
||||
`rip_off`, daarna de opgeslagen RIP = `buf` (zodat de `ret` op de code
|
||||
springt). Voor `ret2win`/`ret2libc`: adressen berekend uit de analyse —
|
||||
geen stackuitvoering nodig.
|
||||
5. **De uitlijnfix** — een gekaapte kale `ret` geeft de callee
|
||||
`rsp ≡ 8 (mod 16)`, en glibc's SSE2-code faalt met `movaps` op een
|
||||
niet-uitgelijnde stack (de crash-reporter logt `si_addr=(nil)` — de hint).
|
||||
foowosc plaatst één extra `ret`-gadget vóór het echte doelwit en herstelt de
|
||||
invariant. Een zijdenhandschoentechniek in een shellcode-lab, maar het is
|
||||
het verschil tussen een payload die "soms werkt" en een die altijd werkt.
|
||||
6. **Stuur, daarna schakel door** — het doelproces *is* de shell; dit proces
|
||||
splist alleen bytes. Geen lokale shell, geen andere lezer — de
|
||||
single-read-cursor-bug (één opgegeten byte per chunk) is gedocumenteerd in
|
||||
`become_shell()`.
|
||||
|
||||
---
|
||||
|
||||
## De bewuste bugs van de daemon (allemaal in `foowosd.c`, allemaal echte CWE-klassen)
|
||||
|
||||
| # | bug | CWE | notitie |
|
||||
|---|-----|-----|------|
|
||||
| 1 | `read(fd, buf, 512)` in een 64-byte stack-buffer | CWE-120 | de overloop: 448 bytes voorbij `buf`, opgeslagen RIP op +88 |
|
||||
| 2 | alleen `snprintf(line, …, "%.*s", …)`; maar aanvaller-`%` in het echo-pad | CWE-134 | het lek is hier de echte payload; een `%n` in een *root*-proces zou write-what-where als root zijn |
|
||||
| 3 | kinderen behouden root terwijl ze onbetrouwbare input afhandelen | CWE-271 | de correcte `drop_privs()` (setgroups→setgid→setuid, in die volgorde, met verificatie) staat in het bestand, gecommentarieerd, *bewust nooit aangeroepen* |
|
||||
| 4 | `ids=`, `stack=`, `libc=`, `BUF=` onthuld aan elke client | CWE-200 | zonder deze leaks konden de shellcode- en ret2libc-technieken geen adressen berekenen (ASLR zou ze verslaan) |
|
||||
|
||||
De handler heeft precies dezelfde `buf[64]`/`read(512)`-vorm als de twee andere
|
||||
labs, dus de gedeelde objdump-gebaseerde herkenningspipeline werkt ongewijzigd.
|
||||
|
||||
---
|
||||
|
||||
## Zo inspecteer je de daemon (leerpad)
|
||||
|
||||
```
|
||||
make status # draait hij? als welke uid? bestandstoestand wordt getoond
|
||||
make run-root # of run / run-root-ns
|
||||
./foowosc -t leak # zie het banner en leaks, stuur niets
|
||||
./foowosc -t demo # rommel-overloop -> SIGSEGV, gelogd met RIP/rsp
|
||||
./foowosc -t shellcode # de interactieve root-shell
|
||||
make test-root # volledige matrix, alle technieken, --must-root
|
||||
```
|
||||
|
||||
Crash-reporter: Bij SIGSEGV logt de daemon het foutadres, RIP en RSP. Een `ret`
|
||||
in een niet-canonieke `0x4141…` faalt bij de `ret` zelf (RIP als `0x4028xx`,
|
||||
`si_addr=(nil)`) — goed om te weten vóór je een logregel als NULL-dereferentie
|
||||
verkeerd leest.
|
||||
|
||||
---
|
||||
|
||||
## Waarom de shellcode 23 bytes is, niet 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
|
||||
```
|
||||
|
||||
Het SUID-lab heeft `setreuid(0,0)` vóór dit nodig. Dit lab niet, om de reden
|
||||
die overal herhaald wordt: **ruid is al 0**, omdat root het proces startte.
|
||||
`make verify` bewijst dat de bytes in `foowosc.c` byte-voor-byte zijn wat
|
||||
`shellcode.S` assembleren tot.
|
||||
|
||||
---
|
||||
|
||||
## Tegenmaatregelen — wat `make hardened` verandert
|
||||
|
||||
Geharde build (`-fstack-protector-strong -fPIE -pie -z noexecstack`):
|
||||
|
||||
| techniek | kwetsbare `foowosd` | geharde `foowosd_hardened` |
|
||||
|-----------|----------------------|------------------------------|
|
||||
| `shellcode` | root-shell (uitvoerbare stack) | SIGSEGV bij de canary-controle / NX |
|
||||
| `ret2win` / `ret2libc` | root-shell | canary aborted de `ret` — maar merk: een *PIE*-build maakt deze adressen ook willekeurig |
|
||||
| `demo` | SIGSEGV, gelogd | SIGSEGV, gelogd |
|
||||
|
||||
`make test-hardened` demonstreert het live. De belangrijke observatie is niet
|
||||
alleen dat de tegenmaatregelen de technieken doodden — het is dat ze de daemon
|
||||
**niet** "niet-root" maakten. Een geharde build die nog steeds als root
|
||||
*gestart* wordt, is nog steeds een root-daemon; de tegenmaatregel verhoogt
|
||||
alleen de lat voor de aanvaller. Minste privilege (`drop_privs()`) en
|
||||
geheugenveiligheid zijn twee verschillende bugs, en een daemon die geen root
|
||||
nodig heeft, zou het niet moeten hebben.
|
||||
|
||||
---
|
||||
|
||||
## Veiligheidsleuningen (zelfde beleid als het SUID-lab)
|
||||
|
||||
- **Alleen loopback.** foowosd weigert iets anders dan `127.0.0.1` /
|
||||
`localhost` / `::1` te binden, tenzij je `-L` geeft. Een root-daemon op een
|
||||
echte interface is een *externe* root-dienst. `-L` bestaat alleen om de
|
||||
wacht te tonen; gebruik het niet op iets dat ertoe doet.
|
||||
- **De toestand wordt luid gelogd.** Bij de start print hij `ruid/euid` en of
|
||||
dit een root-proces is, zodat je altijd weet welk exploit-resultaat je moet
|
||||
verwachten.
|
||||
- **Oordelen komen uit exit-status** in `make test*` (de retourcode van de
|
||||
pty-harness), nooit uit het greppen van zijn stdout — grepbare output liegt.
|
||||
- **De pty moet cooked + ECHO off draaien**, anders echoët de harness zijn
|
||||
eigen commandoregel en vervalst de markering. De harness zet ECHO uit en
|
||||
houdt ECHONL aan.
|
||||
- **Opruimritueel:** `make stop` na elke sessie. Als de daemon root-bezeten
|
||||
is, zegt `stop` je `sudo pkill -x foowosd` te draaien.
|
||||
- Draai dit nooit op een host waar je om geeft. Het bestaat om `uid=0`-shells
|
||||
over de loopback-interface uit te delen.
|
||||
|
||||
---
|
||||
|
||||
## Oefeningen
|
||||
|
||||
1. Draai `make run` (user-daemon), daarna `./foowosc -t shellcode`. Waarom is
|
||||
de shell niet root? (Check `ids=` in het banner — foowosc zegt het je vóór
|
||||
je überhaupt verbindt.)
|
||||
2. `make stop && sudo make run-root && make test-root`. Verklaar aan de hand
|
||||
van de bannerregel waarom alle vier de technieken nu `uid=0(root)` geven.
|
||||
3. Vind in `foowosd.c` `drop_privs()` en lees *waarom de volgorde* van
|
||||
`setgroups → setgid → setuid` ertoe doet. Bepaal waar in `main()` hij thuis
|
||||
zou horen, en wat het aanvalsoppervlak van het lab wordt wanneer hij
|
||||
daadwerkelijk wordt aangeroepen.
|
||||
4. Bereken `rip_off` met de hand uit `objdump -d foowosd`: vind de
|
||||
`lea -0xNN(%rbp)` van `buf` binnenin `vulnerable_handler`, daarna `NN + 8`.
|
||||
foowosc doet precies dat; controleer zijn rekensom tegen de jouwe.
|
||||
5. `make hardened && make test-hardened`. Welke techniek valt voor de canary,
|
||||
en welke voor NX? Waarom verandert harden niet wat `make status` over *het
|
||||
proces* rapporteert?
|
||||
6. Vergelijk de shellcodes van de twee labs: 23 bytes hier, 32 voor foosd. Wat
|
||||
doen de extra 9 bytes, en waarom zijn ze alleen in het SUID-geval nodig?
|
||||
7. Lees de commentaar van `become_shell()` over de enkele lees-cursor. Maak de
|
||||
faaltoestand mentaal na: twee lezers op één socket betekent dat de
|
||||
login-shell één byte per chunk eet — "uid=1000…" arriveert als "id=1000…".
|
||||
Waarom kan een relay-proces deze bug nooit hebben?
|
||||
|
||||
---
|
||||
|
||||
## Bestanden
|
||||
|
||||
```
|
||||
foowosd.c de kwetsbare root-daemon (elke regel gecommentarieerd)
|
||||
foowosc.c het exploit (elke regel gecommentarieerd)
|
||||
shellcode.S referentie-assembly voor de 23-byte-payload
|
||||
tests/pty_wosuid_test.c de pty-harness (markering + strikte id-vorm-controle)
|
||||
Makefile build / run / run-root / run-root-ns / test /
|
||||
test-root / verify / hardened / clean …
|
||||
```
|
||||
|
||||
Zuster-labs: `../food.c`/`../fooc.c` (user-level-baseline, poort 2342) en
|
||||
`../suid/` (SUID-root-daemon `foosd`/`foosc`, poort 2343). De poorten zijn
|
||||
bewust verschillend — je kunt alle drie tegelijk draaien en hun `ids=`-regels
|
||||
in het banner kruislings controleren.
|
||||
Loading…
Add table
Add a link
Reference in a new issue