Jag har följande sammanfattade pseudokod i C89 från ett ssh-serverbibliotek som bara ger tillgång till saker som git-shell (/bin/bash
ersätts med programmet som ska köras, så det är inte möjligt att göra något annat) :
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; }
Här är hur kommandot körs senare (strängen command
är oförändrad efter 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.
Jag kan inte göra memcpy(command,string.st.data,0)
eftersom den tredje medlemmen i memcpy
har minsta storlek 1, och i mitt sammanhang använder size_t
ett 64-bitars heltal, jag kan inte utföra ett buffertflöde, eftersom det finns len
.
Allt jag kan göra är att ställa in len
till ett värde som är större än det som tilldelats string.st.data
. Detta är ett buffertunderflöde som gör det möjligt för mig att läsa otilldelat minne.
Jag kan läsa serverminnet, men jag kan inte se vilka känsliga data en offentlig ssh-server kan innehålla (i mitt fall listan över användare som kan utföra ssh är offentligt) .
Så gör ett buffertunderflöde på memcpy
fjärr kodkörning?
Kommentarer
Svar
I allmänhet nej, ett buffertunderflöde kan inte användas för fjärrkörning av kod. Eftersom angriparkontrollerad data aldrig lämnar det utrymme som tilldelats för den har den aldrig förmågan att ta över programmets exekveringsflöde.
Ett buffertunderflöde har potential för andra typer av attacker, t.ex. avslöjande av information (om programmet räknar med att buffertens ursprungliga innehåll raderas av de nya uppgifterna).
Kommentarer
- Eller som jag såg nyligen i git, förvandlas senare till ett buffertöverflöde.
Svar
När jag ursprungligen skrev det här svaret , det verkar som jag glättade över några missförstånd som du har. Dessa missförstånd skulle sannolikt hindra dig från att förstå mitt svar. För att vara tydlig kommer jag att göra den här texten stor för att betona, inte för att vara respektlös:
Många av de termer du använder betyder inte vad du verkar tro att de menar.
Till exempel:
- ”den tredje medlemmen i
memcpy
” finns inte eftersommemcpy
är en funktion, inte enstruct
ellerunion
. - ”Jag kan inte utföra ett buffertöverflöde eftersom det finns
len
” och jag kan inte ta slut på bensin, eftersom det finns bensin. Ber jag fråga? Det verkar som en passande analogi för mig, speciellt eftersom din kod kan åberopa ett buffertöverflöde teoretiskt sett. - ”Allt jag kan göra är att ställa in
len
till ett värde som är större än det som tilldelatsstring.st.data
. Detta är ett buffertunderflöde … ” Nej, det är inte definitionen av ett buffertunderflöde. Ett buffertunderflöde inträffar när du får åtkomst till en array med ett negativt index eller ett index som kan orsaka pekare aritmetisk inslagning att inträffa. Det senare kan vara möjligt för vissa konfigurationer (även på ”64-bitars system”, vad det än betyder), men jag tvivlar på att det var detta du menade när du skrev dessa ord, för du följde sedan upp dem med: - ”… gör det möjligt för mig att läsa otilldelat minne.” Jag tror kanske att du menade ”oinitialiserat minne”. För att vara tydlig tror jag att du menade att du ”har tilldelat ett överskott och lämnat överskottet oinitialiserat , och kanske vill du göra något åt det (se
calloc
ellermemset
).
Låt oss överväga char *fubar = NULL;
ett ögonblick … den här typen av pekare har i allmänhet en noll värde.Dereferencing anses vara en nullpekare-avvikelse , men om vi skriver något som 0["hello"]
får vi samma som "hello"[0]
(det vill säga "h"
). Således kan en nollpekardereference användas i attacker när angriparen styr uttrycket mellan de hakparenteserna (som de gör i din situation).
Kommer tillbaka till fubar
sak; låt oss säga att vi memset(&fubar, UCHAR_MAX, sizeof fubar);
, nu är fubar
alla 1-bitar, vilket betyder att det kan vara en adress det är den största adressen som vårt system (teoretiskt) kan ta emot. Vad händer om vi får åtkomst till fubar[1]
? Hur kan du komma åt elementet efter den största adressen? Har adress sedan linda tillbaka? Tekniskt sett är allt detta odefinierat beteende, men om jag skulle namnge det på gemensam arkitektur, skulle det vara:
- Aritmetisk överflöd av en pekare som leder till …
- Nullpekare och / eller eventuellt ett buffertunderflöde .
tillåter ett buffertunderflöde på
memcpy
exekvering av fjärrkod?
En buffert underflöde kan tillåta en angripare att skriva över funktionspekare som ligger i minnesregionerna före arrayen i fråga. Om dessa funktionspekare görs för att peka på skalkod kan den köras när de ”åberopas senare.
I denna något obskyra kod visas det en risk för buffertunderflöde när int
har en bredare domän än uint32_t
, eftersom ntohl(string.st.len)+1
skulle orsaka att uint32_t
-värdet konverteras till ett int
typ. Tänk till exempel om INT_MIN
är -4294967296
(vilket är en mindre än 0 - UINT32_MAX
) och INT_MAX
är 4294967295
… detta skulle i huvudsak vara en 33-bitars int
med padding to pad out till bredden av en byte; ovanligt, men möjligt. Under denna omständighet är uttrycket ntohl(string.st.len)+1
inte uint32_t
i typ; det ”s int
i typ, och snarare än att slå tillbaka till 0 när osignerat heltal överflöd inträffar, sveper det troligen till -4294967296
när undertecknat heltalsflöde inträffar.
Om du letar efter en garanti mot buffertunderflöde använder du U
heltal bokstavssuffix (dvs. ntohl(string.st.len)+1U
). I den situationen kommer du att sluta med att uttrycket antingen är uint32_t
eller unsigned int
(beroende på vilken typ som har störst domän).
Om du anser att ntohl(string.st.len)
kan returnera ett mindre än det maximala värdet för den osignerade typen (oavsett vilken det är), så len=ntohl(string.st.len)+1
kommer att resultera i maximalt värde, malloc(len+1)
kommer att orsaka osignerad omslag så att du slutar åberopa malloc(0)
och sedan command[len]=0
skriver väl och riktigt bortom slutet av matrisen. Sedan har du naturligtvis ”ll också ett problem med memcpy(command,string.st.data,len);
(detta är ett buffertflöde).
Buffertunderflöde och överflöden är inte den enda risken. Om du inte ”t kontrollerar du returvärdet för malloc
och malloc
returnerar NULL
då kan en nollpekardereferens användas för att orsaka godtycklig kodkörning. Detta innebär att du bör ha en metod för att kommunicera malloc
misslyckas tillbaka till den som ringer. Det innebär också att du kan använda samma metod för att kontrollera och kommunicera omslagsproblemet till den som ringer.
command[len] = 0
då är det ett buffertflöde, eftersom maxindex för en buffert med längdlen
ärlen-1
. Alternativt, om den faktiska koden gör enmalloc(len+1)
istället för enmalloc(len)
, kan du göra ett massivt buffertflöde genom att ställa inlen
värde till0xFFFFFFFF
.len+1
så att inställningen till 0 ska vara giltig.string.st.len
till −1.