foo/wosuid/README.NO.md
2026-09-29 10:04:38 +02:00

14 KiB
Raw Permalink Blame History

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-120 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 bufs 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.