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

Бывает встречаются Unix-системы, на которых некоторые администраторы передают процессам пароли в открытом виде, совершенно не заботясь о том, что их видят все пользователи данной системы.

Если вы смогли зайти в систему под непривилегированным пользователем, то вы можете набрать команду, отображающую список запущенных процессов:

ps -ef 

и, возможно, увидеть некоторые секреты, которых видеть не должны, например, у одного из процессов ниже открыт пароль basicAuth.password (пароль в тексте изменен):

$ strings /proc/1101/cmdline 
/usr/local/bin/vmagent
--remoteWrite.url=http://vm-cluster.local:1234/api/v1/write
--remoteWrite.basicAuth.username=user-rw
--remoteWrite.basicAuth.password=123456
--promscrape.config=/usr/local/etc/vmagent-config.yml

Как же быть?

Есть несколько способов этого избежать.

Способ первый

Пароль можно прочитать из файла. Читаем документацию к запускаемой программе. Например, vmagent знает такую опцию --remoteWrite.basicAuth.passwordFile.
Файл необходимо разместить в доступной только нужному пользователю папке и забрать у других пользователей права на доступ к этому файлу:

chmod 600 /my/secrets/passwordFile

Способ второй

Можно прочитать пароль из переменной окружения, если наш процесс это умеет. Снова читаем документацию.

Часто в описании ПО указывается название переменной, в которую можно записать пароль. Иногда разработчики идут на какую-нибудь хитрость, например, дают ссылку на переменную окружения прямо из файла конфигурации:

The file pointed by config may contain %{ENV_VAR} placeholders which are substituted by the corresponding ENV_VAR environment variable values.

При использовании переменных окружения также нельзя забывать про безопасность.
Если наш сервис запускается через systemd, нельзя указывать пароль в манифесте my.service через:

Environment=my_password=My_1St_Secret

Необходимо использовать:

EnvironmentFile=/my/secrets/passwordFile

И также устанавливать безопасные права доступа к файлу с переменными окружения.

Способ третий

Можно прочитать пароль при запуске непосредственно с консоли. Например, если вы что-то пишете на bash, то это будет:

read -s pass

При чтении пароля он не будет выводиться на экран.
А вот в такой вариации на экран будут выводиться звездочки:

pass=""
prompt="Enter VPN password for user ${vpnuser}: "
while IFS= read -p "${prompt}" -r -s -n 1 char
do
        [[ ${char} == $'\0' ]] && break
        prompt='*'
        pass+="${char}"
done 

Способ четвертый

На случай, если вы хотите сделать хорошую программу, но хотите перестраховаться от ее небезопасного использования.

Мы разрешаем использовать программу с указанием пароля как аргумент запуска.
Но реально сразу же после запуска мы преобразуем секретный аргумент в свою переменную окружения и перезапускаемся с другими параметрами запуска, например, так:

#!/bin/bash

#check if it is the first running or re-forking by env vars 'my_password' 
#and 'null_password' 
[[ -z "$my_password" ]] && [[ -z "$null_password" ]] && {
	#copy input arguments to new array 'args[]'
	args=("$@")
	#for every array element do
	for ((i=0; i<${#args[@]}; i++)); do
		[[ "${args[$i]}" == "--password" ]] && {
			 #if we found --password element, switch to the next element
			 let i++
			 #and save it to 'my_password' variable
			 my_password="${args[$i]}"
			 #replace array element by mask symbols 'top_secret'
			 args[$i]="top_secret"
		}
	done
	#check if 'my_password' var is null (it is a special case, it is permited),
    #set 'null_password' flag
	[[ -z "$my_password" ]] && null_password=true
	export my_password
	export null_password
	#create new fork in place of current process (with new args)
	exec $0 "${args[@]}"
	#current process has finished, this string will newer run
}

#Insert your code here
echo -e "\n\nYour password var is '$my_password'"
for arg in "${@}"; do let i++; echo "Argument${i}  is '$arg'"; done
echo "Look at proc info:"
ps -ef | pgrep -fa $0
sleep 180

При запуске такой программы пароль будет виден другим пользователям очень недолго и заменится сам на словосочетание top_secret:

$ ./get_password.sh --user "t e s t" --password "my strong password" --dir "/opt/my dir" &
[1] 5121
$ 

Your password var is 'my strong password'
Argument1  is '--user'
Argument2  is 't e s t'
Argument3  is '--password'
Argument4  is 'top_secret'
Argument5  is '--dir'
Argument6  is '/opt/my dir'
Look at proc info:
5121 /bin/bash /home/myuser/get_password.sh --user t e s t --password top_secret --dir /opt/my dir

$ ps -ef | grep get_password.sh
myuser        5121    1790  0 01:20 pts/0    00:00:00 /bin/bash /home/myuser/get_password.sh --user t e s t --password top_secret --dir /opt/my dir
myuser        5129    1790  0 01:21 pts/0    00:00:00 grep --color=auto get_password.sh

Подобным же способом пользуется известный многим VPN-клиент sstp-client (написан на языке C) - он заменяет пароль в аргументах запуска на символы "x", затем записывает сохраненную копию пароля во временный файл и перезапускает себя (по ссылкам в тексте можно посмотреть, как это происходит в коде).

Способ пятый

Если вы используете ядро новее чем 3.3, то вам доступны новые опции монтирования файловой системы /proc. Вот, что сказано в документации man -s 5 proc:

hidepid=n

Опция контролирует, кто может иметь доступ к информации в директориях /proc/pid.
Аргумент n может быть одним из следующих:

0:
Все могут иметь доступ ко всем директориям /proc/pid. Это традиционное поведение и оно работает по умолчанию (если опция монтирования не указана).

1:
Пользователи могут не иметь доступа к файлам и поддиректориям внутри любой директории /proc/pid, кроме тех, которыми они владеют (директории /proc/pid остаются видимыми). Чувствительные файлы, такие как /proc/pid/cmdline и /proc/pid/status теперь защищены от других пользователей. В результате невозможно узнать, запущена ли у какого-то пользователя конкретная программа (до тех пор, пока программа иным образом не проявит себя).

2:
Все, что описано в опции при значении 1, но в дополнение /proc/pid, принадлежащие другим пользователям, становятся невидимыми. Это означает, что записи /proc/pid больше нельзя использовать для обнаружения PID в системе. Это не скрывает того факта, что процесс с определенным значением PID существует (его можно узнать другими способами, например, с помощью "kill -0 $PID"), но он скрывает UID процесса и GID, который в противном случае можно было бы узнать, используя stat(2) в каталоге /proc/pid. Это значительно усложняет задачу злоумышленника по сбору информации о запущенных процессах (например, обнаружению того, запущен ли какой-либо демон с повышенными привилегиями, запускает ли другой пользователь какую-либо конфиденциальную программу, запускают ли другие пользователи вообще какую-либо программу и так далее).

Важно сразу обратить внимание еще на одну опцию монтирования, она пригодится для тех процессов, которые занимаются сбором метрик мониторинга (агент zabbix, node_exporter и другие):

gid=gid (since Linux 3.3)
Указывает идентификатор группы, члены которой авторизованы для получения информации о процессе, в противном случае запрещенной hidepid (т.е. пользователи в этой группе ведут себя так, как если бы /proc был подключен с hidepid=0). Следует использовать эту группу вместо таких подходов, как помещение пользователей, не являющихся root, в файл sudoers(5).

Как попробовать работу hidepid? Очень быстро:

ps -ef	#от обычного пользователя - видим все процессы в системе
sudo mount -o remount,hidepid=2 /proc		#перемонтируем с новой опцией
ps -ef	#от обычного пользователя - теперь НЕ видим все процессы в системе
sudo mount -o remount,hidepid=0 /proc		#вернули обратно

Как подключить, чтобы работало и после перезагрузки системы?

Заглянув в /etc/fstab, например, на CentOS7/AlmaLinux8 вы неожиданно НЕ обнаружите строчки с /proc. Дело в том, что это монтирование теперь делает systemd.

Но вы можете добавить эту опцию монтирования по старинке как дополнительную строчку в /etc/fstab. Тогда файловая система будет перемонтирована автоматически в конце старта системы сервисом systemd-remount-fs.service.

Тем не менее, не спешите радостно добавлять строчку в /etc/fstab на все ваши боевые системы. Дело в том, что не все системные процессы еще достаточно адаптировались к нововведениям, например Red Hat не рекомендует применять эту фичу в RHEL7 (там ядро 3.10.0, но они сделали backport).

Подведем итоги

Кто-то скажет: "Да кому это все нужно, на мою систему все равно никто не сможет зайти ни под каким пользователем". Тут не стоит заблуждаться. В запущенных сервисах на вашей системе могут быть уязвимости. Какие-то уже известны, какие-то еще нет. И чем больше препятствий будет на пути у злоумышленника, тем больше времени будет у безопасников, чтобы заметить проникновение и подозрительную активность, чтобы успеть отрезать канал и провести расследование до того, как будут удалены ваши резервные копии и зашифрованы ваши данные.

Комментарии (9)


  1. qrdl
    26.04.2024 11:25
    +3

    Мне кажется, тут просто обязательно надо упомянуть pass (https://www.passwordstore.org/)


  1. 0Bannon
    26.04.2024 11:25
    +1

    Спасибо


  1. prm_prg
    26.04.2024 11:25
    +2

    Спасибо. Кейс с просмотром пароля, переданного через аргумент запуска, при помощи чтения /proc удивил. Взял на вооружение, что так делать нельзя.


    1. Tony-Sol
      26.04.2024 11:25

      К слову, почти таким же образом можно прочитать и секрет переданный как переменная окружения, через /proc/%pid%/environ


      1. n27051538 Автор
        26.04.2024 11:25

        Без привелегии CAP_SYS_PTRACE - нельзя. У обычных пользователей её нет.


  1. akovalenko
    26.04.2024 11:25
    +1

    Совет про переменную окружения удивил. Секрет пропадает из /proc/PID/cmdline, но появляется в /proc/PID/environ, и чего мы этим добились?

    По трюку с перезапуском и изменением cmdline -- мы на мгновение всё-таки засветим секрет в списке процессов, а это обычно нежелательно, атакующему может повести. Ну и когда на файл с паролем вы делаете chmod 600, это обычно означает, что файл уже полежал с неправильными правами, и пароль лучше сменить. Вместо chmod я бы советовал umask на процессе, или каталог с правами 0700 (chmod на пустой файл до добавления туда пароля — нерабочий вариант, атакующий мог бы открыть этот файл заранее и прочитать после chmod'а)


    1. n27051538 Автор
      26.04.2024 11:25

      Если коротко, я с Вами не согласен.

      Вам разрешено читать environ файлы другого пользователя, только если у вас есть привелегия CAP_SYS_PTRACE. У обычных пользователей ее нет.

      Про создание файла - по тексту 'Файл необходимо разместить в доступной только нужному пользователю папке', что исключает компрометацию.

      Трюк с перезапуском - по тексту 'При запуске такой программы пароль будет виден другим пользователям очень недолго', вы или принимаете этот риск, или не принимаете.

      Извините за дизлайк, но какой-то трольный коммент у Вас.


      1. akovalenko
        26.04.2024 11:25
        +1

        Да, свою ошибку с cmdline уже осознал.

        По поводу риска временных засветов остаюсь при своём мнении.


      1. Tony-Sol
        26.04.2024 11:25

        Вам разрешено читать environ файлы другого пользователя, только если у вас есть привелегия CAP_SYS_PTRACE. У обычных пользователей ее нет.

        Если только не произошла эскалация привилегий, например в ходе lotl атаки