* Предотвратяване на препълване на буфера
Публикувано на 30 ноември 2008 от Филип Петров. Записано в NetSec.
Това е както един от трудните за реализиране проблеми, така и един от най-трудните за предотвратяване. Има няколко основни правила, които трябва да имаме в предвид:
1. Не вярвайте на готовите библиотеки. Ето ви един непълен списък на вградени функции от С, които са доказано уязвими:
strcpy(), strcat(), printf(), sprintf(), vsprintf(), gets(), scanf(), fscanf(), sscanf(), vscanf(), vsscanf(), vfscanf(), realpath(), getopt(), getpass(), streadd(), strecpy(), strtrns() и др.
Ето ви и един eлементарен пример:
char buf[10]; gets(buf);
Въведете повече от 10 символа и ще видите неприятен резултат.
2. Ако директният достъп до паметта не ви е нужен, то се опитайте да стартирате проекта си чрез език от по-високо ниво (например тези, използващи виртуални машини – Java, .Net, …).
3. Не стартирайте програмата си в незащитена среда. Използвайте jail, SELinux или други подобни „защитени среди“.
4. Правете пълни проверки на всяка една информация, подадена от потребител. Става дума както за валидни (допустими) данни, така и за техният размер.
5. Винаги се грижете за поставянето на ‘\0′ в края на низовете!
Ако все пак ни се наложи да пишем на С или подобен език, позволяващ уязвимости от тип препълване на буфер – как все пак да пишем сигурен код? Когато става дума за буфери съществуват два подхода:
- Използване на статичен буфер (например масив): при отчитане на препълване моментално се отказва достъп.
- Използване на динамичен буфер (например вектор): автоматично разширява буфера, така че да резервира допълнително пространство за данни.
Въпреки, че статичния подход изглежда по-сигурен, той има своите недостатъци. На практика всеки минимален пропуск в сигурността на кода е достатъчен за постигане на препълване на буфера.
При динамичният подход имаме сериозно предимство – не позволяваме никога, никой буфер да бъде препълнен. За нещастие обаче ние сме ограничени от максималното количество памет на системата. При този подход се появяват два други популярни пробива – претоварването на stack или heap. С други думи тук решаваме проблема с препълване на буфера, но си създаваме значително по-лесните за постигане атаки като denial of system чрез обикновен flood с голямо количество данни.
Независимо кой подход сте избрали и с кой вид уязвимост се борите, ви препоръчваме да използвате само „сигурни“ функции:
- При статични буфери това са например bcopy(), fgets(), memcpy(), snprintf(), strccpy(), strcadd(), vsnprintf(), strncpy() и strncat() (те обаче също си имат свои специфични особености, например strncpy() и strncat() не терминират низовете с ‘\0′). Използвайте и sprintf() с особено внимание (задължително слагайте спецификатори за максимална дължина на изхода – напр. използвайте %.10s, вместо %s). Търсете и специфични за системата функции (например при BSD съществуват strlcpy() и strlcat(), които решават редица проблеми от strncpy() и strncat()).
- При динамични буфери разчитайте на вече популярни като „сигурни“ библиотеки. Например за С това е библиотеката SafeStr. При C++ е въведена библиотека std::string. За съжаление всеки път когато ви се наложи да ги преобразувате към char* се получава пробив в сигурността.
В заключение ще кажем – използвайте С/С++ и подобни езици с изключително повишено внимание. Особено при С предотвратяването на buffer overflow е изключително сложна задача.
Примери за добри практики:
a) От по-горния пример би било по-добре да използваме fgets():
#define BUFSIZE 10 char buf[BUFSIZE]; fgets(buf, BUFSIZE, stdin);
b) Ако имате възможност да знаете големината на входните данни винаги правете проверка:
if(strlen(source_data) >= destination_size){
... throw exception ...
}
c) Използвайте формат при sprintf():
void main(int argc, char **argv)
{
char buffer[1024];
sprintf(buffer, "%.1023s", argv[0]);
...
}
d) Добавяйте същата маска и при sscanf():
void main(int argc, char **argv)
{
char buf[1024];
sscanf(argv[0], "%1023s", &buf);
}
e) Въвеждайте броячи навсякъде:
char buf[10];
int i = 0;
char ch;
while((ch = getchar()) != '\n')
{
if(ch == '\0') break;
if(i==10) break;
buf[i++] = ch;
}
buf[i] = '\0';
Като заключение ще споменем един силен подход за решение на проблема. Използвайте т.нар. „тунели“. Това са малки програми, написани на „сигурен“ език и четящи информацията, подадена от протребителя, във виртуална среда. Те се използват за прочитане и почистване на входните данни, като препращат „сигурна“ информация към програмите с по-ниско ниво на достъп до паметта.
Един коментар за “Предотвратяване на препълване на буфера”
Trackback URI | RSS за коментарите
Пусни коментар
Страници
Категории
- C/C++ (45)
- DB (34)
- Dogs (37)
- Food (5)
- History (6)
- Java (33)
- Lada (22)
- Math (96)
- Metodos (23)
- NetSec (33)
- Other (66)
- Politics (20)
- Probability (8)
- VC++.Net (1)
- XHTML/JS (25)
Нови
- Предстоящи промени в средното образование
- Борбата със спам
- Лада с лого във Формула 1
- Рама за раздатка – продължение
- Проблема на Марков за иглата
10 януари 2009 на 0:18
polzvai strncpy(), vsnprintf() i t.n. *n*() f-ii