Am următorul pseudo cod rezumat în C89 dintr-o bibliotecă server ssh care oferă acces doar la lucruri precum git-shell ( este înlocuit cu programul de rulat, deci nu este posibil să faceți altceva) :
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; }
Iată modul în care comanda este executată mai târziu (șirul command
este neschimbat după 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.
Nu pot face memcpy(command,string.st.data,0)
deoarece al treilea membru al memcpy
are dimensiunea minimă de 1 și, în contextul meu, size_t
folosește un număr întreg pe 64 de biți, nu pot efectua o depășire a bufferului, deoarece există len
.
Tot ce pot face este să setez len
la o valoare mai mare decât cea alocată string.st.data
. Acesta este un flux de memorie tampon care îmi permite să citesc memoria nealocată.
Pot citi memoria serverului, totuși, nu văd ce date sensibile poate deține un server ssh public (în cazul meu, lista de utilizatori care poate efectua ssh este public) .
La fel, un subfluor tampon pe memcpy
permite telecomanda executarea codului?
Comentarii
Răspuns
În general, nu, nu se poate utiliza un flux de memorie tampon pentru executarea codului la distanță. Deoarece datele controlate de atacatori nu părăsesc niciodată spațiul alocat pentru aceasta, nu are niciodată capacitatea de a prelua fluxul de execuție al programului.
Un sub-flux de tampon are potențialul pentru alte tipuri de atacuri, cum ar fi divulgarea informațiilor (dacă programul se bazează pe conținutul original al bufferului șters de noile date).
Comentarii
- Sau ca I văzut recent în git, transformat mai târziu într-un buffer overflow.
Răspuns
Când am scris inițial acest răspuns , se pare că am analizat câteva neînțelegeri pe care le dețineți. Aceste neînțelegeri v-ar împiedica probabil să înțelegeți răspunsul meu. Pentru a fi clar, voi face ca acest text să fie mare pentru a atrage accentul, nu pentru a fi lipsit de respect:
Mulți dintre termenii pe care îi folosiți nu înseamnă ceea ce credeți că înseamnă.
De exemplu:
- „al treilea membru al
memcpy
” este inexistent deoarecememcpy
este o funcție, nu unstruct
sauunion
. - „Nu pot efectua o depășire a tamponului, deoarece există
len
” și nu pot rămâne fără benzină, deoarece există benzină. Cerșesc Întrebare? Mi se pare o analogie potrivită, mai ales că teoretic vorbind, codul tău ar putea invoca o depășire a bufferului. - „Tot ce pot face este să setez
len
la o valoare mai mare decât cea alocatăstring.st.data
. Acesta este un buffer underflow … „ Nu, aceasta nu este definiția unui buffer underflow. Un buffer underflow apare atunci când accesați în afara unei limite o matrice utilizând un index negativ sau un index care ar cauza Pointer aritmetic se va înfășura. Acesta din urmă poate fi posibil pentru anumite configurații (chiar și pe „sisteme pe 64 de biți”, indiferent ce înseamnă asta), dar mă îndoiesc că asta v-ați referit atunci când scriați aceste cuvinte, pentru că le-ați urmat apoi cu: - „… permițându-mi să citesc memoria nealocată.” Cred că probabil ai vrut să spui „memorie neinitializată”. Pentru a fi clar, cred că ai vrut să afirmi că tu „Am alocat un exces și am lăsat excesul neinițializat și poate doriți să faceți ceva în acest sens (consultați
calloc
saumemset
).
Să luăm în considerare char *fubar = NULL;
pentru o clipă … acest tip de pointer are în general un zero valoare.Dereferențierea este considerată o dereferere a indicatorului nul , dar dacă scriem ceva de genul 0["hello"]
obținem același lucru ca "hello"[0]
(adică "h"
). Astfel, o dereferință a indicatorului nul poate fi utilizată în atacuri atunci când atacatorul controlează expresia dintre paranteze pătrate (așa cum se întâmplă în situația dvs.).
Revenind la fubar
lucru; să spunem că memset(&fubar, UCHAR_MAX, sizeof fubar);
, acum fubar
este tot pe 1 biți, ceea ce înseamnă că ar putea fi o adresă aceasta este cea mai mare adresă pe care sistemul nostru ar putea să o găzduiască (teoretic). Ce se întâmplă dacă accesăm fubar[1]
? Cum puteți accesa elementul după cea mai mare adresă? Adresă apoi înfășurată? Din punct de vedere tehnic, toate acestea sunt un comportament nedefinit, dar dacă aș numi asta pe arhitectura comună, ar fi:
- Revărsarea aritmetică a un pointer, care duce în …
- Nereferențierea indicatorului nul și / sau potențial un buffflow underflow .
un buffer underflow pe
memcpy
permite executarea codului la distanță?
Un buffer underflow-ul poate permite unui atacator să suprascrie pointerele funcționale situate în regiunile de memorie dinaintea matricei în cauză. Dacă acele pointeri funcționale sunt îndreptate către shellcode, acel shellcode poate să fie executate atunci când sunt „invocate mai târziu.
În această bucată de cod oarecum obscură, apare„ un risc de subfluență a tamponului atunci când int
are un domeniu decât uint32_t
, deoarece ntohl(string.st.len)+1
ar face ca valoarea uint32_t
să fie convertită la int
tip. Luați în considerare, de exemplu, dacă INT_MIN
este -4294967296
(care este cu unul mai puțin decât 0 - UINT32_MAX
) și INT_MAX
este 4294967295
… acesta ar fi în esență un int
cu 33 de biți umplutură pentru a tampona până la lățimea unui octet; neobișnuit, dar posibil. În această circumstanță, expresia ntohl(string.st.len)+1
nu este de tip uint32_t
; este „de tip int
și, mai degrabă decât să se întoarcă la 0 când apare depășire de număr nesemnat , probabil se înfășoară la -4294967296
atunci când apare overflow de număr semnat .
Dacă „căutați o garanție împotriva buffflow underflow , utilizați U
sufix literal întreg (adică ntohl(string.st.len)+1U
). Apoi, în acea situație, veți termina cu expresia fie uint32_t
, fie unsigned int
(în funcție de tipul care are cel mai mare domeniu).
Dacă considerați că ntohl(string.st.len)
poate returna cu mai puțin decât valoarea maximă pentru acel tip nesemnat (oricare ar fi acesta), atunci len=ntohl(string.st.len)+1
va avea ca rezultat valoarea maximă, malloc(len+1)
va provoca împachetarea nesemnată așa că veți ajunge să invocați malloc(0)
și apoi command[len]=0
va scrie bine și cu adevărat dincolo de sfârșitul matricei. Apoi, bineînțeles, veți de asemenea să aveți o problemă cu memcpy(command,string.st.data,len);
(aceasta este o depășire a bufferului).
Subfluxurile bufferului și deversările nu sunt singurul risc. Dacă nu „t verificați valoarea returnată a malloc
și malloc
returnează NULL
apoi se poate utiliza o dereferență a indicatorului nul pentru a provoca executarea arbitrară a codului. Aceasta implică faptul că ar trebui să aveți o metodă de comunicare malloc
eșuează înapoi către apelant. Aceasta implică, de asemenea, că ați putea utiliza aceeași metodă pentru a verifica și a comunica acea problemă de împachetare înapoi către apelant.
command[len] = 0
atunci acesta este un depășire a bufferului, deoarece indicele maxim pentru un buffer de lungimelen
estelen-1
. Alternativ, dacă codul real face unmalloc(len+1)
în loc de unmalloc(len)
, atunci puteți face o depășire masivă a bufferului setândlen
valoarea la0xFFFFFFFF
.len+1
, astfel încât setarea la 0 ar trebui să fie validă.string.st.len
la -1.