суббота, 13 февраля 2010 г.

Потенциально опасное использование ldd(1)

Прочитал тут на RSDN интересную тему об особенностях работы ldd(1). Там же, в одном из сообщений обнаружил ссылку на статью "ldd arbitrary code execution". Если в кратце, то опасность заключается в возможности исполнения кода программы, путь к которой передаётся в параметрах ldd(1). Решил проверить во FreeBSD, так ли всё опасно на самом деле? Оказалось - да.

Действительно, если скомпилировать программу с указанием специального загрузчика для неё, то получаем не совсем то, что хочется увидеть при исследовании её зависимостей. Так как на самом деле зависимости показывает не сама утилита ldd(1), а загрузчик, который эти зависимости ищет и загружает при нормальном запуске программы. ldd(1) всего лишь выставляет переменную окружения, при обнаружении которой ld-elf.so.1 выводит список зависимостей и завершает выполнение программы. Как написано в статьях по ранее приведённым ссылкам, в linux ldd вообще представляет собой shell скрипт.

Так вот, если злонамеренный пользователь скомпилирует свой загрузчик, который не будет завершать исполнение программы после вывода списка зависимостей, а так же скомпилирует своё приложение, привязав его к этому загрузчику, то программа будет просто выполнена при попытке просмотра её зависимостей.

Там же, в статье, есть пример сценария по которому, можно "уговорить" системного администратора запустить эту программу. И если он будет так неосторожен, что сделает это с правами суперпользователя, то...

Я не стал тестировать с uClibc, просто скопировал себе в домашний каталог исходники rtld. Слегка подправив Makefile и код rtld.c, (по аналогии с тем, как это сделано в статье) и взяв пример программы для теста, вот что я получил:

> ./test
Nothing.
> ldd ./test
./test:
All your box are belong to me.

Из этого я для себя сделал такой вывод: прежде чем что-то проверять утилитой ldd(1), сначала надо воспользоваться утилитой readelf(1) или objdump(1). Например так:

> readelf -l ./test

Elf file type is EXEC (Executable file)
Entry point 0x80483a0
There are 6 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
  INTERP         0x0000f4 0x080480f4 0x080480f4 0x0001e 0x0001e R   0x1
      [Requesting program interpreter: /home/butcher/bin/ld-elf.so.1]
  LOAD           0x000000 0x08048000 0x08048000 0x005c3 0x005c3 R E 0x1000
  LOAD           0x0005c4 0x080495c4 0x080495c4 0x000f4 0x000fc RW  0x1000
  DYNAMIC        0x0005d4 0x080495d4 0x080495d4 0x000b0 0x000b0 RW  0x4
  NOTE           0x000114 0x08048114 0x08048114 0x00018 0x00018 R   0x4

> objdump -sj .interp ./test

./test:     file format elf32-i386-freebsd

Contents of section .interp:
 80480f4 2f686f6d 652f6275 74636865 722f6269  /home/butcher/bi
 8048104 6e2f6c64 2d656c66 2e736f2e 3100      n/ld-elf.so.1
Обращать внимение нужно на выделенные строки, если они отличаются от /libexec/ld-elf.so.1, то лучше не испытывать судьбу и не использовать ldd(1) с этой программой.
А так же вывод №2: никогда не запускать утилиту ldd(1) от суперпользователя.