Pscan
http://www.striker.ottawa.on.ca/~aland/pscan/
Pscan (Problem Scanner) - это программа, которая позволяет сканировать код Си-программ на наличие общих ошибок в вызове и объявлении функций, которые часто приводят к такой распространенной ошибке как переполнению буфера. Сканер работает по следующему принципу: он ищет одну из "проблемных" функций (на основе своего списка) и применяет следующее правило. ЕСЛИ последний параметр функции - строка какого-либо формата, И формат строки НЕ постоянен, ТОГДА сравнивает и делает вывод об уязвимости. После того, как потенциальная уязвимость найдена, сканер выдает ее на консоль с предложением о коррекции данного куска кода.
Pscan является консольной утилитой, работает в Unix-системах, поставляется в tgz-архиве и собирается в бинарный файл стандартной командой make. На выходе, при передаче исполняемому файлу в качестве параметра си-файла, как и все подобные утилиты, сканер выводит номер строки, описание уязвимости и название функции, в которой найдена проблема. Программе можно передавать как отдельный файл, так и задавать сканирование по маске.
Запуск программы с ключом -v (verbose) даст более детальное описание уязвимостей. Ключ -w позволяет выводить предупреждения, когда строки непостоянного формата
(non-constant strings) используются как передаваемый в функцию параметр.
Рассмотрим применение программы, передавая ей для анализа файл test1.c
[root@localhost pscan-1.2]# ./pscan test1.c
test1.c:38 SECURITY: fprintf call should have "%s" as argument 1
test1.c:66 SECURITY: sprintf call should have "%s" as argument 1
test1.c:77 SECURITY: sprintf call should have "%s" as argument 1
test1.c:95 SECURITY: sprintf call should have "%s" as argument 1
test1.c:118 SECURITY: printf call should have "%s" as argument 0
test1.c:131 SECURITY: syslog call should have "%s" as argument 1
[root@localhost pscan-1.2]#
Как видим, отчет нам выдали в довольно лаконичной форме. Видим, что соответствующим функциям не хватает аргумента "%t" где t - тип передаваемых данных, т.е. это можно интерпретировать как неявно заданный формат строки. Затем, по указанному выше алгоритму, программа сравнивает, так ли на самом деле (т.е. присутствует "%t" или нет), и, в случае неудачи, возвращает 0, иначе - 1.
Таким образом, в программе test1.c найдена одна ошибка на переполнение буфера (строка 118).
Запуская программу с ключом -vv, получаем несколько более "разговорчивый" отчет:
[root@localhost pscan-1.2]# ./pscan -vv /root/scanz/test2.c
Scanning /root/scanz/test2.c ...
/root/scanz/test2.c:6 FUNC printf Last argument is variable or reference: BAD
/root/scanz/test2.c:17 FUNC sprintf Last argument is constant string: OK
/root/scanz/test2.c:18 FUNC sprintf Last argument is constant string: OK
/root/scanz/test2.c:19 FUNC sprintf format string with 1 parameters: OK
/root/scanz/test2.c:20 FUNC sprintf format string with 1 parameters: OK
/root/scanz/test2.c:21 FUNC sprintf format string with 1 parameters: OK
/root/scanz/test2.c:22 FUNC printf format string with 1 parameters: OK
/root/scanz/test2.c:23 FUNC scanf format string with 1 parameters: OK
/root/scanz/test2.c:24 FUNC scanf format string with 1 parameters: OK
/root/scanz/test2.c:25 FUNC scanf format string with 1 parameters: OK
/root/scanz/test2.c:26 FUNC scanf format string with 1 parameters: OK
/root/scanz/test2.c:29 FUNC printf Last argument is variable or reference: BAD
/root/scanz/test2.c:35 FUNC syslog format string with 2 parameters: OK
/root/scanz/test2.c:36 FUNC syslog Last argument is constant string: OK
/root/scanz/test2.c:38 FUNC syslog Last argument is constant string: OK
Total problems identified: 2
[root@localhost pscan-1.2]#
Суть та же самая, найдено две ошибки, в тех местах, где, как видим по отчету, аргумент функции printf варьируется (не постоянен) или зависит от чего-либо (потенциально не постоянен).
RATS
http://www.securesw.com/projects.html
RATS ("Грубая утилита для аудита безопасности", Rough Auditing Tool for Security) - утилита для сканирования исходных кодов программ на С/С++, Perl, PHP и Python и обнаружения уязвимостей, связанных с ошибками в программировании, таких как переполнения буфера и TOCTOU (Time Of Check, Time Of Use). Как подразумевает название программы, она предназначена только для грубого анализа исходника, и может обнаружить не все ошибки, или же наоборот, принять за ошибку корректный участок кода (интересно, что это - признание автора в несовершенстве алгоритма или скромность, граничащая с осторожностью?). То есть "ручную" проверку кода этот сканер не заменит, но серьезно поможет в сем трудном деле.
Как и подавляющее большинство *nix'овых консольных утилит, RATS поставляется в tar-файле, сжатом gzip'ом, и устанавливается, как и большинство собранных autoconf'ом программ, с помощью процедуры ./configure, make, make install. По умолчанию процедура установки происходит в /usr/local/bin.
Использование утилиты:
rats [-d <filename>] [-h] [-r] [-w <level>] [-x] [file1 file2 ... filen]
Где основные опции:
-d <filename>
Путь к альтернативной базе уязвимостей (если дефолтовая не устраивает)
-h
Список всех команд
-l <lang>
Явное указание языка исходника, даже не взирая на расширение файла, для более точного аудита
данного языка (позволяется выбирать "c", "perl", "php" и "python").
-r
Указание своих, нестандартных вызовов функций, для отражения их в отчете.
-w <level>
Установка уровня опасности уязвимостей. Уровень 1 включает только проверку на высокую степень уязвимости (несерьезные, мелкие потенциальные уязвимости игнорируются). Уровень 2 включает в себя проверку на среднюю степень уязвимости (это уровень по умолчанию). Уровень 3 включает также проверку на незначительные, мелкие возможные уязвимости.
-x
Указание не грузить стандартные базы уязвимостей (/usr/local/lib) по умолчанию.
Очевидно, то, какие уязвимости будут найдены, зависит от указанной базы уязвимостей. Для каждой уязвимости выводится номер строки, где она встретилась, уровень опасности, название исследуемой функции, краткое описание уязвимости, а также предлагаемые действия.
Запускаем утилиту, передавая ей файл test2.c
[root@localhost flawfinder-0.21]# rats -d /root/scanz/rats-1.3/vuln_db.c /root/scanz/Flaw-finder/flawfinder-0.21/test2.c
not well-formed (invalid token) at line 1
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:13: High: strcpy
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:14: High: strcpy
Check to be sure that argument 2 passed to this function call will not more
data than can be handled, resulting in a buffer overflow.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:17: High: sprintf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:18: High: sprintf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:19: High: sprintf
Check to be sure that the format string passed as argument 2 to this function
call doest not come from an untrusted source that could have added formatting
characters that the code is not prepared to handle. Additionally, the format
string could contain `%s' without precision that could result in a buffer
overflow.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:18: High: sprintf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:19: High: sprintf
Check to be sure that the non-constant format string passed as argument 2 to
this function call does not come from an untrusted source that could have added
formatting characters that the code is not prepared to handle.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:20: High: printf
Check to be sure that the non-constant format string passed as argument 1 to
this function call does not come from an untrusted source that could have added
formatting characters that the code is not prepared to handle.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:21: High: scanf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:22: High: scanf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:23: High: scanf
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:24: High: scanf
Check to be sure that the format string passed as argument 2 to this function
call doest not come from an untrusted source that could have added formatting
characters that the code is not prepared to handle. Additionally, the format
string could contain `%s' without precision that could result in a buffer
overflow.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:25: High: gets
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:28: High: gets
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:29: High: gets
Gets is unsafe!! No bounds checking is performed, buffer is easily overflowable by user. Use fgets(buf, size, stdin) instead.
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:32: High: syslog
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:33: High: syslog
/root/scanz/Flaw-finder/flawfinder-0.21/test2.c:35: High: syslog
Truncate all input strings to a reasonable length before
passing them to this function
В первой группе предупреждений контролируется отсутствие ограничение на длину передаваемого функции strcpy параметра, что может вызвать переполнение буфера. Во втором - та же история с sprintf, нам
предлагают убедиться, что вызов функции не идет из опасного источника и не произойдет т.н. "ошибка форматирования строки". При вызове функции gets программа предупреждает, что в данном контексте использование ее не рекомендуется, ибо здесь не используется хоть какой-нибудь проверки и "буфер легко переполняем пользователем". Вместо нее настойчиво требуют использовать более безопасную fgets.
Наконец, в вызове функции syslog, нам рекомендуют урезать все входные параметры (строки) до приемлемой длины (ограничить длину, другим словом), перед передачей их этой функции.
Quaudit
Qaudit.pl - это не совсем программа, а простой кроссплатформенный перл-скрипт для анализа исходных текстов си программ на уязвимости. Для его использования на машине должен быть установлен перл. Главный минус скрипта - отсутствие базы уязвимостей.
Рассмотрим анализ программы test1.c
[root@localhost bin]#perl /root/scanz/qaudit.pl
qaudit.pl::q(uick)audit: version[02]. by: vade79[v9@fakehalo.org].
--------------------------------------------------------------------------------
qaudit> help
interface commands: file audit exit.
qaudit> file /root/scanz/test1.c
source file set to: /root/scanz/test1.c.
qaudit> audit
executing audit on: /root/scanz/test1.c
--------------------------------------------------------------------------------
test1.c:*NOTICE:START: entering format bug check mode.
test1.c:FMT_CHK:38:WARNING: fprintf(stderr, variable); /* problematic */
test1.c:FMT_CHK:44:WARNING: fprintf(stderr, format, variable1, variable2);
test1.c:FMT_CHK:113:WARNING: printf((variable ? fmt1 : fmt2), string3); /* OK */
test1.c:FMT_CHK:118:WARNING: printf((variable ? string1 : string2)); /* problematic */
test1.c:*NOTICE:STOP: exiting format bug check mode.
test1.c:*NOTICE:START: entering bounds check mode.
test1.c:*NOTICE:STOP: exiting bounds check mode.
test1.c:*NOTICE:START: entering exec check mode.
test1.c:*NOTICE:STOP: exiting exec check mode.
test1.c:*NOTICE:START: entering environmental variable check mode.
test1.c:*NOTICE:STOP: exiting environmental variable check mode.
test1.c:*NOTICE:START: entering miscellaneous check mode.
test1.c:MSC_CHK:54:WARNING: sprintf(buffer, "string"); /* OK */
test1.c:MSC_CHK:61:WARNING: sprintf(buffer, "%s"); /* OK */
test1.c:MSC_CHK:66:WARNING: sprintf(buffer, variable); /* problematic */
test1.c:MSC_CHK:71:WARNING: sprintf(buffer, "%s", variable); /* OK */
test1.c:MSC_CHK:77:WARNING: sprintf(buffer, "%d", sprintf(buffer1, variable)); /* problematic! */
test1.c:MSC_CHK:95:WARNING: sprintf(buffer,
test1.c:MSC_CHK:103:WARNING: sprintf(buffer, "%s %s %s", one, two, three); /* OK */
test1.c:MSC_CHK:120:WARNING: // sprintf(buffer, variable); C++ comments get ignored, for good or for bad.
test1.c:MSC_CHK:122:WARNING: /* sprintf(buffer, variable); these comments get ignored, too */
test1.c:MSC_CHK:129:WARNING: sprintf(s, "PASV port %i assigned to %s", i, remoteident);
test1.c:*NOTICE:STOP: exiting miscellaneous check mode.
--------------------------------------------------------------------------------
qaudit> exit
exiting qaudit
[root@localhost scanz]#
Здесь мы сталкиваемся с преимуществом того, что программа реализована как скрипт. Она передает на
выход сразу целую строку, благодаря чему мы видим и комментарии в коде. Очевидна строгая проверка - программа выдает предупреждения даже там, где в исходнике комментарий "OK", но в то же время пропускает закомментированные функции. Минус - ни о какой базе уязвимостей не может быть и речи.
The конец
Итак, к озакномлению были представлены четыре популярные проги плюс один скрипт :). Я намеренно не стал анализировать качество работы каждой утилиты: исходники-тесты у тебя есть, репорты тоже есть, бери, сравнивай, анализируй и думай 🙂 В принципе, написать самому подобную прогу - не проблема, главное выработать эффективный алгоритм. Ну а потом - никто не мешает тебе запихнуть все это дело в ГУИ. И еще. Надеюсь, ты понимаешь, что далеко не каждая найденный сканнером баг - это баг (а не фича :), т.к. все они подходят к сканирования формально, в силу как раз используемых алгоритмов, и не являются панацеей, однако если твоя прога состоит из нескольких сот строк, и будет использоваться в потенциально небезопасном месте (aka в сети, например, на серваке, да еще при этом обрабатывать какие-либо запросы извне), то проверить ее на подобные баги - не излишество, а суровая необходимость.