В чем уязвимость access в С/C++?

Я находил похожий вопрос на stackoverflow, но я так и не понимаю в чем смысл подмены ? Вот мы хотим проверить что файл доступен для чтения и если доступен -> открыть. Отлично, мы проверили, файл доступен -> открыли. Другая ситуация: проверяем, доступен ли файл на чтение -> нам его подменяют -> отрываем другой файл(проблема). Чем вторая ситуация отличается от того, что я успею подменить файл в таком коде:

###Псевдокод 1###

int main()
{
  delay(5) #delay 5 sec
  open("substitutedFile", "r")
}

###Псевдокод 2###

int main()
{
  ....
  .... # Slow code, low performance
  open("substitutedFile", "r")
}

Вопрос в том, почему уязвимый именно access ? Когда такую подмену можно сделать в любом из выше приведенных сценариев. Зачем вообще нужен access если возвращаемое значение open даст мне знать, доступен ли файл на чтение/запись, а execv в возвращаемом значении даст понять, доступен ли файл на выполнение https://www.usenix.org/legacy/publications/library/proceedings/sec04/tech/full_papers/dean/dean_html/accessopen.html

https://stackoverflow.com/questions/7925177/access-security-hole


Ответы (2 шт):

Автор решения: Fat-Zer

Зачем вообще нужен access если возвращаемое значение open даст мне знать, доступен ли файл на чтение/запись, а execv в возвращаемом значении даст понять, доступен ли файл на выполнение

В абсолютном большинстве случаев так и надо поступать: просто пробовать открыть файл и обрабатывать ошибки, если это не удалось (с exec() это может быть затруднительно).

Как говорит тётя маня, access() в отличии от open() использует не эффективные, а реальные UID/GID, т.е. их результат может быть разным для процессов с suid битом и именно в таких случаях нужно использовать access():

Другими словами, access() не отвечает на вопрос «могу я прочитать/записать/выполнить этот файл?». Он отвечает на немного другой вопрос: «(предположим я исполняемый файл с setuid) может ли пользователь, запустивший меня прочитать/записать/выполнить этот файл?», что даёт программам с set-user-ID возможность не дать злонамеренным пользователям прочитать файлы, которые пользователи не должны читать.

Когда возникает уязвимость

Допустим есть процесс с suid, который должен проверить, есть ли у вызвавшего его пользователя право на чтение заданного файла:

if(access("/path/to/file", R_OK) == 0) {
  int fd = open("/path/to/file", O_RDONLY);
  assert(fd>=0);
  print_file(fd);
  close(fd)
}

В этом случае возможна следующая последовательность действий:

  • т.к. изначальный файл принадлежал пользователю, то проверка access() выполняется успешно.
  • Злоумышленник удаляет исходный файл и заменить его ссылкой на файл к которому у него нет доступа, например, ссылкой на /etc/shadow.
  • т.к. процесс работает с повышенными привилегиями, то вызов open() успешно открывает файл по ссылке и выводит его.

man в принципе рекомендует «избегать использования данного системного вызова».

→ Ссылка
Автор решения: Serge3leo

Зачем вообще нужен access если возвращаемое значение open даст мне знать, доступен ли файл на чтение/запись

Как было указано выше, по стандарту access() проверяет права исходя из реальных UID/GID, а результат open() зависит от эффективных.

Т.е. в программах с повышенными или пониженными привилегиями результат может отличаться.

Вопрос в том, почему уязвимый именно access ?

Сам access() - неуязвим. Но, программы с повышением или понижением привилегий, которые его неправильно используют, могут иметь весьма неопределённое поведение.

В частности, варианты связки access() и open()

https://www.usenix.org/legacy/publications/library/proceedings/sec04/tech/full_papers/dean/dean_html/accessopen.html Fixing Races for Fun and Profit: How to use access(2)

Выглядит корректным.

https://stackoverflow.com/questions/7925177/access-security-hole

Не выглядит полным, в части прав на поиск по пути.

Для однопоточных программ, вероятно, самым надёжным и простым является способ: ... seteuid(); setegid(); open(); ...

→ Ссылка