Mám následující souhrnný pseudokód v C89 z knihovny serveru ssh, který poskytuje pouze přístup k věcem jako git-shell (/bin/bash
je nahrazen spuštěným programem, takže není možné dělat něco jiného) :
struct _raw_uncapped_ssh_string { // no limit on the size of the string; uint32_t len; char non_null_terminated_string[]; // by protocol design it have a minimum length of 1 }; typedef struct _raw_uncapped_ssh_string raw_ssh_string; union buffer { void * uncapped_zlib_decompressed_network_data; // yes, the size is uncapped, so it’s possible to put 4Gb of // data in it that would be copied later into memory. zlib // allow easily to turn some Mb in Gb of data, but it’s not // the point of the question. raw_ssh_string st; }; get_command (compressed_network_data) { size_t len; char * command; buffer string=uncompress_to_buffer(compressed_network_data); len=ntohl(string.st.len)+1; command=malloc(len+1); command[len]=0; // here’s the point, both the string length and content as // well it’s supplied size is controlled by the attacker. memcpy(command,string.st.data,len); return command; }
Zde je jak je příkaz proveden později (řetězec command
se nezmění po get_command()
) .
const char *args[]={"/bin/bash",command,NULL}; // /bin/bash isn’t the shell, it has been replaced by git‑shell. // redirect the program output to the network. dup2(stdin, 0); dup2(stdout,1); dup2(stdout,2); close(stdin); close(stdout); //if this return execution failed and print an error message return execv(args[0],(char * const *)args); // I don’t know which is the system, so I can’t know about the libc behaviour.
Nemohu dělat memcpy(command,string.st.data,0)
, protože třetí člen memcpy
má minimální velikost 1 a v mém kontextu size_t
používá 64bitové celé číslo, nemohu provést přetečení vyrovnávací paměti, protože existuje len
.
Jediné, co mohu udělat, je nastavit len
na hodnotu větší, než je hodnota přidělená string.st.data
. Toto je podtečení vyrovnávací paměti, které mi umožňuje číst nepřidělenou paměť.
Dokážu přečíst paměť serveru, ale nevidím, jaká citlivá data může veřejný server ssh obsahovat (v mém případě seznam uživatelů, kteří může provádět ssh je veřejné) .
Stejně tak podtečení vyrovnávací paměti na memcpy
umožňuje vzdálené provedení kódu?
Komentáře
Odpověď
Obecně ne, podtečení vyrovnávací paměti nelze použít pro vzdálené spuštění kódu. Jelikož data řízená útočníkem nikdy neopustí prostor, který jim byl přidělen, nikdy nemají schopnost převzít průběh provádění programu.
Podtečení vyrovnávací paměti má potenciál pro jiné typy útoků, například zpřístupnění informací (pokud program počítá s vymazáním původního obsahu vyrovnávací paměti novými daty).
Komentáře
- Nebo jako já viděl nedávno v git, později se změnil na přetečení vyrovnávací paměti.
Odpověď
Když jsem původně napsal tuto odpověď Zdá se, že jsem přečetl několik nedorozumění, která držíte. Tato nedorozumění by vám pravděpodobně zabránila porozumět mé odpovědi. Aby bylo jasné, tento text zvětším, abych zdůraznil, abych nebyl neúctivý:
Mnoho termínů, které používáte, neznamená to, co si myslíte, že znamenají.
Například:
- „třetí člen
memcpy
“ neexistuje, protožememcpy
je funkce, nikolistruct
nebounion
. - „Nemohu provést přetečení vyrovnávací paměti, protože tam je
len
“ a benzín mi nemůže dojít, protože tam je benzín. Prosím o Otázka? Zdá se mi to jako vhodná analogie, zejména proto, že teoreticky váš kód může vyvolat přetečení vyrovnávací paměti. - „Jediné, co můžu udělat, je nastavit
len
na hodnotu větší, než je hodnota přidělenástring.st.data
. Toto je podtečení vyrovnávací paměti … „ Ne, to není definice podtečení vyrovnávací paměti. K podtečení vyrovnávací paměti dochází, když přistupujete k mezím pole pomocí záporného indexu nebo indexu, který by způsobil aritmetické zalomení ukazatele může nastat. U některých konfigurací je to možné (dokonce i na „64bitových systémech“, ať už to znamená cokoli), ale pochybuji, že jste tím mysleli, když jste psali tato slova, protože jste je následovali s: - „… umožňující mi číst nepřidělenou paměť.“ Myslím, že jste možná mysleli „neinicializovanou paměť“. Aby bylo jasno, myslím, že jste chtěli říci, že vy „ve přidělil přebytek a ponechal přebytek neinicializovaný a možná s tím budete chtít něco udělat (viz
calloc
nebomemset
).
Zvažme na chvíli char *fubar = NULL;
… tento druh ukazatele má obecně nulu hodnota.Dereferencování je považováno za dereference nulového ukazatele , ale pokud napíšeme něco jako 0["hello"]
, dostaneme totéž jako "hello"[0]
(to je "h"
). Při útocích lze tedy použít dereference nulového ukazatele, když útočník ovládá výraz mezi hranatými závorkami (jako je tomu ve vaší situaci).
Vracíme se k fubar
věc; řekněme memset(&fubar, UCHAR_MAX, sizeof fubar);
, nyní fubar
jsou všechny 1 bity, což znamená, že může být adresa to je největší adresa, které by náš systém mohl (teoreticky) vyhovět. Co když přistoupíme k fubar[1]
? Jak můžete získat přístup k prvku za největší adresou? adresa se pak zabalí zpět? Technicky je to všechno nedefinované chování, ale kdybych měl pojmenovat to na běžné architektuře, bylo by to:
- Aritmetické přetečení ukazatel, vedoucí do …
- dereference nulového ukazatele a / nebo potenciálně podtečení vyrovnávací paměti .
Umožňuje podtečení vyrovnávací paměti na
memcpy
povolit vzdálené spuštění kódu?
Vyrovnávací paměť underflow může útočníkovi umožnit přepsat ukazatele funkcí umístěné v oblastech paměti před dotyčným polem. Pokud jsou tyto ukazatele funkcí vytvořeny tak, aby ukazovaly na shellcode, může tento shellcode být provedeny, když budou znovu vyvolány později.
V této poněkud nejasné části kódu se zdá, že existuje riziko podtečení vyrovnávací paměti, když má int
širší doména než uint32_t
, protože ntohl(string.st.len)+1
způsobí převod hodnoty uint32_t
na int
typ. Zvažte například, zda INT_MIN
je -4294967296
(což je o méně než 0 - UINT32_MAX
) a INT_MAX
je 4294967295
… v podstatě by to byl 33bitový int
s padding to pad out to the width of a byte; neobvyklé, ale možné. Za těchto okolností výraz ntohl(string.st.len)+1
není uint32_t
v typu; je to int
v typu a spíše než zalomení zpět na 0, když dojde k přetečení celého čísla bez znaménka , pravděpodobně se to zalomí na -4294967296
když dojde k přetečení celého čísla se znaménkem .
Pokud hledáte záruku proti podtečení vyrovnávací paměti , použijte U
celočíselná doslovná přípona (tj. ntohl(string.st.len)+1U
). Pak v této situaci „skončíte s tím, že výraz bude buď uint32_t
nebo unsigned int
(podle toho, který typ má největší doména).
Pokud se domníváte, že ntohl(string.st.len)
může vrátit o jednu méně, než je maximální hodnota pro tento nepodepsaný typ (ať je jakýkoli), pak len=ntohl(string.st.len)+1
bude mít za následek maximální hodnotu, malloc(len+1)
způsobí nepodepsané zabalení , takže nakonec vyvoláte malloc(0)
a poté command[len]=0
zapíše dobře a skutečně za konec pole. Pak samozřejmě budete „mít také problém s memcpy(command,string.st.data,len);
(jedná se o přetečení vyrovnávací paměti).
Podtečení vyrovnávací paměti a přetečení není jediným rizikem. Pokud don „t zkontrolujte návratovou hodnotu malloc
a malloc
vrací NULL
pak lze použít dereference nulového ukazatele, která způsobí spuštění libovolného kódu. To znamená, že byste měli mít způsob komunikace malloc
selhání zpět volajícímu. Z toho také vyplývá, že stejnou metodu můžete použít ke kontrole a sdělení problému s zabalením zpět volajícímu.
command[len] = 0
pak se jedná o přetečení vyrovnávací paměti, protože maximální index vyrovnávací paměti o délcelen
jelen-1
. Alternativně, pokud skutečný kód provedemalloc(len+1)
namístomalloc(len)
, můžete provést masivní přetečení vyrovnávací paměti nastavenímlen
hodnota0xFFFFFFFF
.len+1
, takže nastavení na 0 by mělo být platné.string.st.len
na –1.