
Аудитор функций libc во время выполнения, который обнаруживает состояния гонки при доступе к файлам и уязвимости символьных ссылок путем перехвата системных вызовов файловой системы через LD_PRELOAD, регистрируя потенциальные пути повышения привилегий.
PathAuditor — это инструмент для поиска уязвимостей, связанных с доступом к файлам, путём аудита функций libc.
Идея примерно следующая:
Мы используем LD_PRELOAD для перехвата всех библиотечных вызовов, связанных с файловой системой, и логирования всех обнаруженных нарушений в syslog.
Это неофициальный продукт Google, не поддерживаемый официально.
Рассмотрим пример уязвимости, которую может обнаружить этот инструмент. CVE-2019-3461 была ошибкой в tmpreaper — инструменте, который обходит /tmp и удаляет старые файлы. Обычно он запускается как cron-задача от root. Поскольку он не хочет удалять файлы за пределами tmp, он использовал следующий код для проверки, является ли каталог точкой монтирования:
if (S_ISDIR (sb.st_mode)) {
char *dst;
if ((dst = malloc(strlen(ent->d_name) + 3)) == NULL)
message (LOG_FATAL, "malloc failed.\n");
strcpy(dst, ent->d_name);
strcat(dst, "/X");
rename(ent->d_name, dst);
if (errno == EXDEV) {
free(dst);
message (LOG_VERBOSE,
"File on different device skipped: `%s/%s'\n",
dirname, ent->d_name);
continue;
}
// [...]
Короче говоря, этот код вызывает rename("/tmp/foo", "/tmp/foo/x"), которая вернёт EXDEV, если "/tmp/foo" является точкой монтирования.
PathAuditor пометил бы этот вызов как потенциальную уязвимость, если "/tmp/foo" принадлежит любому пользователю, кроме root.
Чтобы понять почему, нужно подумать о том, что происходит в ядре при выполнении системного вызова rename (упрощённо):
"/tmp/foo" для первого аргумента."/tmp/foo/x" для второго аргумента.Здесь присутствует состояние гонки, так как "/tmp/foo" будет разрешён дважды. Если этот путь контролируется пользователем, он может в любой момент заменить его на другой файл.
В частности, нам нужно, чтобы "/tmp/foo" сначала был каталогом, чтобы пройти проверку if(S_ISDIR) в коде tmpreaper. Затем мы заменяем его на файл непосредственно перед тем, как код войдёт в системный вызов. Когда ядро обрабатывает первый аргумент, оно увидит файл с содержимым, контролируемым пользователем.
Теперь снова заменяем его, на этот раз на символическую ссылку на произвольный каталог в той же файловой системе. Ядро второй раз разрешит путь, перейдёт по символической ссылке и переместит контролируемый файл в выбранный каталог.
Ограничение одной файловой системы связано с тем, что rename не работает между файловыми системами. Но в некоторых дистрибутивах Linux /tmp по умолчанию является просто папкой в корневой файловой системе, и вы могли бы использовать эту ошибку, чтобы переместить файл в /etc/cron, который будет выполнен от root.
Чтобы попробовать, нужно собрать libpath_auditor.so с помощью bazel и загрузить его в бинарный файл с помощью LD_PRELOAD. Все нарушения будут записаны в syslog, поэтому убедитесь, что он запущен.
bazel build //pathauditor/libc:libpath_auditor.so
LD_PRELOAD=/path/to/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
tail /var/log/syslog
Также возможно запустить это для всех процессов в системе, добавив его в /etc/ld.so.preload. Однако предупреждаем, что это рекомендуется только на тестовых системах, так как может привести к нестабильности.
Для быстрого старта вы можете попробовать Docker-контейнер, поставляемый с этим проектом:
docker build -t pathauditor-example .
docker run -it pathauditor-example
# LD_PRELOAD=/pathauditor/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
# cat /var/log/syslog