Jeg har følgende oppsummerte pseudokode i C89 fra et ssh-serverbibliotek som bare gir tilgang til ting som git-shell (/bin/bash
er erstattet med programmet som skal kjøres, så det er ikke mulig å gjøre noe annet) :
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; }
Her hvordan kommandoen utføres senere (strengen command
er uendret etter 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.
Jeg kan ikke gjøre memcpy(command,string.st.data,0)
siden det tredje medlemmet av memcpy
minimumsstørrelse på 1, og i min sammenheng bruker size_t
et 64-biters heltall, jeg kan ikke utføre et bufferoverløp, siden det er len
.
Alt jeg kan gjøre er å sette len
til en verdi større enn den som er tildelt string.st.data
. Dette er en bufferunderstrømning som gjør at jeg kan lese ikke-allokert minne.
Jeg kan lese serverminnet, men jeg kan ikke se hvilke sensitive data en offentlig ssh-server kan ha (i mitt tilfelle listen over brukere som kan utføre ssh er offentlig) .
Så gjør en bufferunderstrømning på memcpy
tillat ekstern kodeutførelse?
Kommentarer
Svar
Generelt, nei, en bufferunderstrømning kan ikke brukes til ekstern kjøring av kode. Siden angriperstyrte data aldri forlater plassen som er tildelt det, har den aldri muligheten til å overta programmets kjøringsflyt.
En bufferstrømning har potensial for andre typer angrep, for eksempel informasjon (hvis programmet regner med at bufferens originale innhold blir utslettet av de nye dataene).
Kommentarer
- Eller som jeg så nylig i git, bli senere omgjort til et bufferoverløp.
Svar
Da jeg opprinnelig skrev dette svaret , det ser ut til at jeg gløste over noen misforståelser du har. Disse misforståelsene vil sannsynligvis forhindre deg i å forstå svaret mitt. For å være tydelig vil jeg gjøre denne teksten stor for å vektlegge, ikke for å være respektløs:
Mange av begrepene du bruker, betyr ikke det du ser ut til å tro at de betyr.
For eksempel:
- «det tredje medlemmet av
memcpy
» er ikke-eksisterende fordimemcpy
er en funksjon, ikke enstruct
ellerunion
. - «Jeg kan ikke utføre et bufferoverløp, siden det er
len
» og jeg kan ikke gå tom for bensin, siden det er bensin. Ber jeg om spørsmål? Det virker som en passende analogi for meg, spesielt siden koden din kunne påberope et bufferoverløp teoretisk sett. - «Alt jeg kan gjøre er å sette
len
til en verdi større enn den som er tildeltstring.st.data
. Dette er en bufferunderstrømning … « Nei, det er ikke definisjonen av en bufferstrømning. En bufferstrømning oppstår når du får tilgang til en matrise utenfor grensene ved hjelp av en negativ indeks, eller en indeks som kan forårsake peker aritmetisk innpakning å skje. Det siste kan være mulig for noen konfigurasjoner (selv på «64-biters systemer», uansett hva det betyr), men jeg tviler på at det er dette du mente da du skrev disse ordene, fordi du så fulgte dem opp med: - «… slik at jeg kan lese ikke-allokert minne.» Jeg tror kanskje du mente «uinitialisert minne». For å være tydelig, tror jeg du mente å si at du «har tildelt et overskudd og lot det overskytende være uinitialisert , og kanskje du vil gjøre noe med det (se
calloc
ellermemset
).
La oss vurdere char *fubar = NULL;
et øyeblikk … denne typen pekere har generelt null verdi.Dereferanse regnes som en nullpekereferanse , men hvis vi skriver noe sånt som 0["hello"]
får vi det samme som "hello"[0]
(altså "h"
). Dermed kan en nullpekerdereferanse brukes i angrep når angriperen kontrollerer uttrykket mellom de firkantede seler (som de gjør i din situasjon).
Kommer tilbake til fubar
ting; la oss si at vi memset(&fubar, UCHAR_MAX, sizeof fubar);
, nå er fubar
alle 1-bits, noe som betyr at det kan være en adresse det er den største adressen vårt system (teoretisk) kan ta imot. Hva om vi får tilgang til fubar[1]
? Hvordan får du tilgang til elementet etter den største adressen? Gjør adresse og deretter vikle seg rundt? Teknisk sett er alt dette udefinert oppførsel, men hvis jeg skulle nevne det på vanlig arkitektur, ville det være:
- Aritmetisk overløp av en peker som fører til …
- Null pekereferanse, og / eller potensielt en bufferunderstrømning .
tillater en bufferunderstrømning på
memcpy
ekstern kjøring av kode?
En buffer understrøm kan tillate en angriper å overskrive funksjonspekere som er plassert i områdene av minnet før den aktuelle matrisen. Hvis disse funksjonspekerne er laget for å peke på skallkode, kan den utføres når de senere påkalles.
I dette noe uklare kodestykket ser det ut som en risiko for bufferunderstrømning når int
har en bredere domene enn uint32_t
, da ntohl(string.st.len)+1
ville føre til at uint32_t
-verdien konverteres til en int
type. Tenk for eksempel på at INT_MIN
er -4294967296
(som er en mindre enn 0 - UINT32_MAX
) og INT_MAX
er 4294967295
… dette ville egentlig være en 33-bit int
med polstring til pad ut til bredden på en byte; uvanlig, men mulig. I denne omstendigheten er ikke uttrykket ntohl(string.st.len)+1
uint32_t
i typen; den «s int
i typen, og i stedet for å pakke tilbake til 0 når usignert heltalloverløp oppstår, brytes den sannsynligvis til -4294967296
når signert heltalloverløp oppstår.
Hvis du «ser etter en garanti mot bufferunderstrømning , bruk U
heltals bokstavssuffiks (dvs. ntohl(string.st.len)+1U
). Så i den situasjonen vil du ende opp med at uttrykket er enten en uint32_t
eller en unsigned int
(avhengig av hvilken type som har størst domene).
Hvis du mener at ntohl(string.st.len)
kan returnere en mindre enn maksimumsverdien for den usignerte typen (uansett hva den er), så len=ntohl(string.st.len)+1
vil føre til maksimal verdi, malloc(len+1)
vil føre til usignert innpakning slik at du ender med å påkalle malloc(0)
og deretter command[len]=0
vil skrive vel og virkelig utover slutten av matrisen. Så har du selvfølgelig «ll også et problem med memcpy(command,string.st.data,len);
(dette er et bufferoverløp).
Bufferstrømning og overløp er ikke den eneste risikoen. Hvis du ikke «t sjekk returverdien av malloc
, og malloc
returnerer NULL
, så kan en nullpekereferanse brukes til å forårsake vilkårlig kodeutførelse. Dette innebærer at du bør ha en metode for å kommunisere malloc
feil tilbake til den som ringer. Det innebærer også at du kan bruke den samme metoden for å kontrollere og kommunisere innpakningsproblemet tilbake til den som ringer.
command[len] = 0
så er det et bufferoverløp, siden maksindeksen for en buffer med lengdelen
erlen-1
. Alternativt, hvis den faktiske koden gjør enmalloc(len+1)
i stedet for enmalloc(len)
, kan du lage et massivt bufferoverløp ved å settelen
verdi til0xFFFFFFFF
.len+1
slik at innstillingen til 0 skal være gyldig.string.st.len
til −1.