Эта статья о том, как правильно передавать секреты запускаемым программам.
Бывает встречаются 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).
Подведем итоги
Кто-то скажет: "Да кому это все нужно, на мою систему все равно никто не сможет зайти ни под каким пользователем". Тут не стоит заблуждаться. В запущенных сервисах на вашей системе могут быть уязвимости. Какие-то уже известны, какие-то еще нет. И чем больше препятствий будет на пути у злоумышленника, тем больше времени будет у безопасников, чтобы заметить проникновение и подозрительную активность, чтобы успеть отрезать канал и провести расследование до того, как будут удалены ваши резервные копии и зашифрованы ваши данные.