Привет! Данным постом я хочу немножно отвлечь многоуважаемое сообщество от пересуд на тему АНБ, и вместо этого заполнить пробел в описании одной их технологии, написав нечто среднее между «отключайте SELinux» и «посвятите ему лучшие годы чтобы понять малую часть». На самом деле, обе эти точки зрения одинаково далеки от правды — технология достаточно проста, прозрачна и позволяет сделать очень многое. Однако, хочу предупредить об огромном количестве букв, и достаточно узкой целевой аудитории, т.к. нижеописанное будет интересно далеко не всем. Если вы давно хотели понять, что такое SELinux, но не знали с какой стороны подступиться — эта статья для вас. Если вы давно все это знаете и успешно применяете, то я допустил достаточно неточностей, чтобы мы смогли обсудить это в комментариях. Ну а эксперты по ИБ с мировым именем могут смело проматывать в самый конец и начинать играть, у меня есть планы на продолжение :-)
Я не буду в статье касаться тем связанных с АНБ в целом, способностью расшифровывать RSA, прослушкой и прочими медийными аспектами — no hype, no FUD, only technology. Мы будем с разной степенью активности влезать в разные исходники, добавлять свои условия в само сердце MLS, возможно внедряя свои уязвимости (мы тоже делаем ошибки), и после этого попытаемся взлететь проведем тесты. Иными словами, я описываю что и как, а вы после этого уже не смотрите на SELinux как на неведому зверушку и обитель зла от вероятного противника, а смело начинаете использовать эту технологию во благо. Особенно учитывая, что она уже включена во всех ваших андроидах (>4.3) и многих дистрибутивах.
Итак, если вам все еще интересно, и вы не боитесь просидеть неделю в одном из многочисленных спойлеров, то
Предварительные чтения
Я подразумеваю, что у вас уже есть опыт работы с Linux в достаточном объёме, чтобы развернуть свой любимый дистрибутив в виртуальном окружении. Я буду делать все на примере Debian, но если вы решите повторить сей путь, то все это можно (и очень даже нужно) проделать на наиболее вам удобном и привычном дистрибутиве — в процессе вы узнаете про него много нового. Я постарался написать эту статью как обучающий материал, чтобы любой желающий мог пошагово повторить. Так же я подразумеваю, что вас не затруднит читать техническую документацию на английском — информации по SELinux на русском пока что крайне мало.
Общая информация по технологии
Вокруг SELinux вертится столько слухов, что вы будете удивлены, насколько небольшая по объему у нас вводная, всего три ссылки:
RH Guide: если какая-то команда непонятна, с большой вероятностью вы найдете описание в нем. Откройте его в отдельном табе, пригодится.
Конспект лекции Eli Billauer: рассматривайте как основной сборник фактов. По нему можно быстро понять что к чему, и знать, что именно спрашивать у гугла.
Написание политик. Несмотря на десятилетнюю давность документа, в нем описано достаточно ключевых моментов для понимания внутреннего устройства SELinux, и как его ковырять.
Это основное, что я рекомендую к прочтению до того, как приступать к настройке, иначе вы постоянно будете возвращаться в этим документам. Есть множестводругихресурсов, но вы до них обязательно дойдете, если захотите сделать что-то, отличное от включения/выключения булевых переменных.
Итак, когда вы все это прочитали, можем проверить себя простыми вопросами:
Что такое unconfined_t/unconfined_u, и почему SELinux нельзя на нем тестировать?
Что является частным случаем, MLS или MCS?
Чем отличается *.te от *.if от *.fc?
Ответы
Неограниченный домен/пользователь. С таким-же успехом можно настраивать SELinux на другой машине.
MCS. MLS == MCS при MLS_SENS=1.
Принципиально — ничем. Хоть в txt пишите, главное Makefile поправить не забудьте.
Постановка задачи и предварительная настройка
Теперь, когда мы уже знаем что мы хотим, но не знаем как мы это будем реализовывать, можем сформулировать цели эксперимента:
Хотим настроить SELinux MLS (раз уж взялись, давайте по максимуму, а не готовое из репозитория next->next->agree);
Ну и после этого хотим проверить worst case scenario — нас поломали, и не просто поломали, а получили UID=0, да не просто получили, а с постоянным shell доступом, а root мы на user_u перемапить то и забыли. Я намеренно специально делаю ряд подобных допущений, рассматривать будем наихудший сценарий;
Мы будем настраивать минимально необходимый экземпляр, иначе будет не статья, а сага о пятиста страницах;
Сервер
С вашего позволения, уберу под спойлер. YMMV, у вас может быть и не Debian, да и установка в KVM ничем особенным не отличается. Подойдет любой дистрибутив, установленный в минимальной конфигурации в виртуальном окружении. Виртуальном — потому что удобнее, минимальном — потому что быстрее.
Детали
Обычная экспертная установка Debian, небольшие нюансы:
Разбивка дискa (целых 4GB!):
/dev/vda1 64MB as /boot, ext2.
rest as LUKS: aes256:cbc-essiv:passphrase, все настройки максимально по умолчанию.
Ну и под занавес, собираем и ставим свое ядро — мы же хотим поддержку последней версии политики, минимально необходимого набора модулей, экспериментировать с патчами PaX и GRSecurity (которые кстати отлично уживаются с SELinux, но это наверное в другой раз опишу). В общем, vanilla kernel нам подходит лучше всего на текущем этапе. Да, голос из зала, говорящий про Debian way, я тебя слышу — но сегодня путь самурая не ограничивается подобными рамками. В этом эксперименте мы пока еще UID=0 без ограничений, и мы делаем все, что хотим. Итак, нагреем немножко Аризону (или локальную виртуалку):
mkdir src && cd src && wget -c http://kernel.org/pub/linux/kernel/v3.0/linux-3.10.18.tar.bz2 && tar jxf linux*tar.bz2 && cd linux* && make menuconfig && make -j$((2* $(grep processor /proc/cpuinfo | wc -l))) deb-pkg && make clean
На этапе конфигурации, включаем SELinux (yes, this pun is intended!):
.config
# if you are lazy to configure yourself, here's my .config, usable on KVM+libvirt
wget -O - $aboveimage | dd bs=1 skip=3991 | xzcat
Считаем, что основа для экспериментов готова.
Автоматизируем сборку политик
Политики мне было удобнее собирать на локальной машине и в виде deb-пакета уже устанавливать на сервер. Поэтому я пошел по пути наименьшего сопротивления.
up'n'enter style
wget http://oss.tresys.com/files/refpolicy/refpolicy-2.20130424.tar.bz2
tar jxf refpolicy-2.20130424.tar.bz2
cp -rp refpolicy custom #all our modifications
asroot# mkdir /usr/share/selinux/custom # so we can 'make install' here
asroot# mkdir /etc/selinux/custom
asroot# chown $USER:$USER /etc/selinux/custom /usr/share/selinux/custom
asroot# touch /etc/selinux/custom/setrans.conf && chown $USER:$USER /etc/selinux/custom/setrans.conf # we'll need it later
asroot# aptitude install selinux-utils python-selinux policycoreutils checkpolicy # these are for policy build
Далее, скрипт сборки пакета:
#!/bin/bash
# sample deb build for custom selinux policy
# harvests policy from local system
version='0.0.1'
name='selinux-policy-custom'
description='Custom MLS SELinux policy'
cf="${name}-control"
cc="${name}-Copyright"
# depends and conflicts shamessly ripped from selinux-policy-mls
read -d '' cheader << EOF
Section: non-free
Priority: optional
Homepage: http://selinux/
Standards-Version: 3.9.2
Package: ${name}
Version: ${version}
Maintainer: secadm_r <here.can+be@your.email>
Pre-Depends:
Depends: policycoreutils (>= 2.1.0), libpam-modules (>= 0.77-0.se5), python, libselinux1 (>= 2.0.35), libsepol1 (>= 2.1.0)
Conflicts: cron (<= 3.0pl1-87.2sel), fcron (<= 2.9.3-3), logrotate (<= 3.7.1-1), procps (<= 1:3.1.15-1), selinux-policy-refpolicy-strict, selinux-policy-refpolicy-targeted, sysvinit (<= 2.86.ds1-1.se1)
Architecture: all
Copyright: ./selinux-policy-custom-Copyright
Description: ${description}
EOF
read -d '' postinst << "EOF"
File: postinst 755
#!/bin/sh -e
set -e
if [ "$1" = configure ]; then
/usr/sbin/semodule -s custom -b /usr/share/selinux/custom/base.pp $(find /usr/share/selinux/custom/ -type f ! -name base.pp | xargs -r -n1 echo -n " -i")
fi
#DEBHELPER#
exit 0
EOF
function make_policy() {
cd custom
make clean
rm -rf /usr/share/selinux/custom/*
make install
cd ..
}
function make_files() {
echo 'SELinux custom policy copyright:TODO' > ${cc}
echo -e "$cheader" > ${cf}
echo -e "$postinst" >> ${cf}
echo -en "nFiles: " >> ${cf}
# our setrans file
echo -e " /etc/selinux/custom/setrans.conf /etc/selinux/custom" >> ${cf}
# /etc/selinux dir
find /etc/selinux/custom -type f ! -name *LOCK | xargs -r -n1 -If -- sh -c 'echo " f $(dirname f)"' >> ${cf}
# /usr/share/selinux/custom dir
find /usr/share/selinux/custom -type f | xargs -r -n1 -If -- sh -c 'echo " f $(dirname f)"' >> ${cf}
}
function cleanup() {
rm -f ${cc} ${cf}
}
function build_deb() {
equivs-build ${cf}
[ $? -eq 0 ] && cleanup
}
rm ./${name}*deb # glob is ok
make_policy
make_files
build_deb
scp -P 22 -i ~/.ssh/selinux-test selinux*deb root@selinux:/tmp/
Время полной пересборки оказалось ~30 секунд, поэтому выбран общий принцип работы скрипта — «в лоб», что называется, я думаю, адаптировать для сборки rpm труда не составит:
Чистим все (make clean)
Собираем и ставим политики (make install)
Находим все, что установилось (мы знаем, где искать), собираем пакет
Заливаем на сервер в /tmp
В postinst он уже сам найдет что у него обновилось, дернет semodule и перезагрузит политики
SELinux, первое знакомство.
Сервер готов, система сборки готова, reference policy загружена, вот теперь можно приступать к самому интересному. (Примерно на этом этапе, оценив уже существующий объем статьи, закралась крамольная мысль разделить ее на 25 :-).
Для первой сборки, определимся с параметрами, я выбрал такие:
$ sed '/^#/d;/^$/d' build.conf
TYPE = mls
NAME = custom
DISTRO = debian
UNK_PERMS = reject
DIRECT_INITRC = n
MONOLITHIC = n
UBAC = y
CUSTOM_BUILDOPT =
MLS_SENS = 4
MLS_CATS = 32
MCS_CATS = 32
QUIET = n
Отличия от апстрима минимальны: включен MLS (значит при сборке будут включаться все параметры из policy/mls и config/appconfig-mls); включены дистро-специфичные макросы для debian, что на самом деле не обязательно; политика не будет загружаться, если в ядре будут определены разрешения, не отраженные в политике — вдруг у нас ядро намного новее; ну и я существенно снизил количество уровней и категорий — у нас будет всего 4 уровня секретности, в каждом по 32 категории. Пока что нам этого хватит.
суть нумеро уно
В качестве эксперимента, попробуйте выставить MONOLITHIC = y и собрать политику, не устанавливая ее — make policy. Результатом будет policy.conf, текстовое представление политики. Вот как раз тут, в простом виде, любезно развернутом m4 из всего нагромождения макросов, описано все, что будет разрешать SELinux. Иными словами (Warning: bad analogy time!): если secadm_r это навроде начальника СБ, утверждающий уровни доступа и допуски, то SELinux это рядовой сотрудник охраны, проверяющий эти списки, а в policy.conf, собственно, списки с полями:
1. кто(scontext) — куда(tcontext) — к кому(class) — зачем(call) (плюс, в случае MLS: покажите-ка еще и ваш уровень допуска, и если он меньше положенного, я в правила даже не посмотрю.)
Создаем все необходимые конфиги, которые мы будем править под свои нужды: make conf. Вначале правим правим появившийся policy/modules.conf — я отключил (modulename=off) почти все модули в группе contrib. Плюс — быстрее сборка, меньше модулей. Минус — возможное недоопределение контекстов. Поясню на примере:
Контекст /dev/xconsole, хоть и относится больше к логгированию, определяется в модуле xserver;
Отключив его, контекст стал наследоваться с директории /dev/;
И с большой вероятностью все, что хотело писать в /dev/xconsole, и было учтено в RefPolicy, тут же сломалось. Поправить — на ваш выбор: либо включаем модуль xserver, либо переопределяем контекст в любом своем локальном модуле.
contrib_off
grep -A5 contrib policy/modules.conf | grep "= module$" | wc -l # total number
grep -A5 contrib policy/modules.conf | grep "= module$" | sed 's/ = module//' | xargs -r -n1 -I__n -- sh -c 'sed -i "s/^__n = module$/__n = off/" policy/modules.conf' # kekeke
# turn some servicess off too (xserver + postgresql)
# turn _on_ logrotate,mta,postfix,ulogd, and whatever you think you need
Как только мы начали править modules.conf, мы прошли точку невозврата, после которой мы должны понимать, что мы делаем и почему. Возможное недоопределение контекстов — как раз первый пример того, как наши действия влияют на систему.
Забегая вперед сразу скажу немного о замечательной утилите audit2allow: она кушает audit.log, и в достаточно понятной форме (особенно с ключами -Rev) выдает нам, что нам надо добавить в политике, чтобы данные сообщения в логе больше не появлялись.Так вот, если вы где-либо (а это практически везде) в интернете встретите рекомендацию
то следуйте ей только если вы отдаете себе отчет, что именно вы сейчас сотворите — этот набор команд означает, что SELinux будет разрешать все, до чего (потенциально жадное) something-something попросило доступ, и даже немножко больше. Более того, в случае MLS данный метод вообще не сработает — потому что в MLS недостаточно создать разрешающее правило, нужно чтобы доступ удовлетворял всем наложенным ограничениям по допускам и категориям. Подобные действия равнозначны чистосердечному признанию: «да, я сегодня совсем не хочу думать головой, мне проще разрешить все». Не делайте из своей системы театр, и не настраиватёте SELinux подобным образом — это все равно что отлавливать все пакеты на фаерволе и скриптом превращать их в разрешающие правила.
Теперь самое время запустить make install, и если все хорошо, то собрать наш пакет и поставить на сервер:
dpkg -i /tmp/selinux-policy-custom*deb
sed -i 's/^SELINUX=.*$/SELINUX=enforcing/;s/^SELINUXTYPE=.*$/SELINUXTYPE=custom/' /etc/selinux/config
selinux-activate # if you installed helper package selinux-basics
# if not: touch /.autorelabel
# add 'selinux=1 security=selinux' to cmdline
reboot # let's rock!
Система перезагрузится, применит контексты согласно определенным в установленной политике (/etc/selinux/custom/contexts/files/*), перезагрузится еще раз и любезно предложит зайти.
When is rocking «rocking» and when is it «shaking» *
Шеф, все пропало. Ничего не работает. Мы даже не можем зайти по ssh — connection closed by host. Знакомьтесь, SELinux. Как замечательно точно сформулировал Eli Billauer:
What is SELinux?
In a nutshell: a machine that tells you permission is denied.
Тем не менее, хорошо, если вы дошли до этого момента. Это именно то поведение, которое нам нужно, и сейчас мы начнем разбираться, почему нас не пускает.
суть нумеро дуо, на этот раз без плохих аналогий
Если вы внимательно читали предварительную документацию, то наверняка помните порядок принятия решений:
Сначала DAC. Если запрещено, то до SELinux дело даже не дойдет, permission denied будет обычный, юниксовый, всем нам знакомый по временам, когда мы только-только знакомились со своей первой *nix системой.
Потом MAC. Если не найдено ни одного подходящего разрешающего правила, permission denied будет уже от SELinux. В некоторых дистрибутивах (RH)в логах появятся строки, содержащие "SELinux is preventing", в некоторых нет, но во всех будет что-то в audit.log.
Итого, скорее всего в RefPolicy просто нет чего-то, что есть в политике дистрибутива. Давайте найдем это и добавим.
Ах да, забыл сказать, что начиная с этого момента, вам понадобится доступ к серверу не только по ssh, он может не работать. Благо, в нашем случае это виртуальный сервер, всегда есть VNC/SPICE/etc (ссылка спецом для ФСКН). Пробуем зайти локально — не пускает. Отличная ситуация, чтобы сразу проиллюстрировать, как из нее
выходить
Don't panic.
Перегружаемся — например, послав Ctrl+Alt+Del, acpid все сделает за нас.
Ловим grub на этапе загрузки, меняем selinux=1 на selinux=0
Грузимся, заходим под root.
На этом этапе, audit.log содержит в себе все причины наших неудач, почему мы не смогли зайти. Т.к. сейчас мы загрузились с отключенным SELinux, первое, что имеет смысл сделать, это скопировать audit.log с прошлой загрузки для последующего анализа, ведь при включенном SELinux у нас это сделать просто так не получится.
Первая же строчка говорит нам о том, что auditd успешно (res) запустился, причем от имени system_u, роли system_r, в домене auditd_t, и относится ко всем категориям (c0.c31) нашего максимального уровня (s3). Согласно BLP, это означает что информация с любого уровня может успешно попадать в лапы auditd (write up), а сам он может читать с любого уровня (read down). Если не совсем понятно, то давайте вспомним, кем эта архитектура разрабатывалась, и что они имели ввиду под записью информации — передача информации от источника (кто пишет) к получателю (куда/кому пишет). И тогда все становится на свои места — уровень Top Secret действительно не может записать свои данные в уровень Secret (т.е. вниз, down) — они станут скомпрометированы, отсюда "no write down". Про "read up", надеюь, более очевидно. Так же в MLS есть дополнительные ограничения, но об этом меня просили молчать далее.
Вторая строчка говорит нам о том, что демон acpid, со всеми возможными максимальными регалиями (uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0), рут рутов и бофх сисопов, используя контекст initrc_t (стартовал) обратился (type=SYSCALL) к сокету (syscall=102), и (внезапно) был не опознан, не зван, и, как следствие, послан (success=no exit=-13). Хотя, это не должно удивлять, ведь мы все знаем, что в Linux root не самый главный, есть и поважнее :-)
Загадка для пытливых умов — к какому сокету он обратился?*
Ну и возьмем третью строчку, из серединки. Логи AVC (Access Vector Cache) это для нас самое интересное. Вышеуказанная, например, говорит о том, что в установленной политике нет разрешающего правила для того, чтобы источник (scontext) c вышеприведенным допуском, работающий в домене restorecond_t, выполнил поиск ({search} и tclass=dir) в директории c номером inode=376 с контекстом var_run_t. Иллюстрация чего? Правильно, no read up. Чего искал? На этот вопрос ответит find /var/run -inum 376. Как раз из подобной строчки audit2allow сделает разрешающее правило.
И так далее. Как видите, ничего сложного нет в этих логах нет. SELinux это не сложно качественно, это сложно количественно, и поначалу непривычно, но не более того. Опять-же, если что-то непонятно, всегда можно вбить обезличенную строку в гугл или поискать тут.Итак, считаем что мы теперь умеем читать и понимать логи.
* Отгадку давать не буду, напишите в комменты.
Как исправлять
Есть два основных варианта, с которыми вы можете столкнуться:
Некорректный контекст
Отсутствие разрешающего правила
Они покрывают 90% всех случаев permission denied, и в них прекрасно работает audit2allow. Во многих случаях, выбор, как именно поправить, первым вариантом или вторым, остается за вами.
Третий вариант, редко встречаемый но самые неочевидный, это нарушение ограничений MLS (policy constrain violation), и добавление разрешающего правила в этом случае не поможет, придется лезть в самое сердце MLS и править ограничения. Вот тут уже каждое изменение должно делаться с полным пониманием, зачем оно делается и что именно оно должно решать. Бездумные изменения гарантированно снизят вам уровень безопасности. You have been warned (again).
Теперь полотна про про методы решения, подкат в подкате ввиду размера:
root@sandbox:~# semanage fcontext -m -t udev_exec_t /lib/systemd/systemd-udevd # try to modify
/usr/sbin/semanage: File context for /lib/systemd/systemd-udevd is not defined
root@sandbox:~# semanage fcontext -a -t udev_exec_t /lib/systemd/systemd-udevd # ok, add
root@sandbox:~# grep udev /etc/selinux/custom/contexts/files/file_contexts.local
/lib/systemd/systemd-udevd system_u:object_r:udev_exec_t:s0
semanage — один из способов. Подобное изменение целесообразно, но в нашем случае оно может не пережить обновление политики (если мы начнем поставлять свой /etc/selinux/custom/contexts/files/file_contexts.local). Другой вариант — переопределяем у себя локально, пересобираем политику, накатываем (и заодно ставим политику).
А вот тут уже, начинаем думать — задаем себе простые вопросы:
Что происходит? sshd попросил загрузить модуль в ядро. Ок, net-pf-10 не сильно нужен, т.к. ipv6 у нас нет.
Что нам предложили? Разрешить домену sshd_t загружать модули в ядро. Разумеется, если мы разрешим, то подобной ошибки не будет. А если он попросит вражеский модуль?
Что пишут в интернетах? Хе-хе. Спасибо, но нет, наличие булевой переменной для разрешения подобного функционала нам тоже не сильно нужно.
Что делаем? Да запрещаем sshd попрошайничать в этом направлении, пусть работает на том, что дали. Когда нам понадобится ipv6, мы его сами загрузим, еще до старта ssh.
Решаем путем написания собственного минимодуля, это просто. Читаем описание структуры. Заодно создаем каркас для всех наших модулей (локально):
mkdir policy/modules/local && cd policy/modules/local
echo '<summary>Local layer -- differences from reference policy.</summary>' > metadata.xml
echo '## <summary>sshd local policy</summary>' > sshd_local.if
echo '## no file contexts redefined here' > sshd_local.fc
cat > sshd_local.te <<EOF
> policy_module(sshd_local, 0.0.1)
> ##################################################################
> require {
> type kernel_t;
> type sshd_t;
> class system module_request;
> }
> #============= sshd_t ==============
> # dont audit requests for module load
> # NOTE: this may hide some denials in the future
> dontaudit sshd_t kernel_t:system module_request;
>
> EOF
Как видите, правило мы поменяли на dontaudit sshd_t kernel_t:system module_request; — это значит запретить, и в лог не писать. Кстати, если вы столкнетесь с тем, что какой-либо функционал не работает, а в логе пусто, то скорее всего это как раз правило dontaudit. Просто пересоберите политику без них: semodule -DB, и готовьтесь к потоку сообщений в лог.
Указываем наш модуль в modules.conf, cобираем политику, заливаем на сервер, смотрим:
root@sandbox:/tmp# sesearch --allow -s sshd_t -t kernel_t | grep system
root@sandbox:/tmp# sesearch --dontaudit -s sshd_t -t kernel_t | grep system
root@sandbox:/tmp# dpkg -i selinux-policy-custom_0.0.1_all.deb
(Reading database ... 20371 files and directories currently installed.)
Preparing to replace selinux-policy-custom 0.0.1 (using selinux-policy-custom_0.0.1_all.deb) ...
Unpacking replacement selinux-policy-custom ...
Setting up selinux-policy-custom (0.0.1) ...
root@sandbox:/tmp# sesearch --dontaudit -s sshd_t -t kernel_t | grep system
dontaudit sshd_t kernel_t : system module_request ;
root@sandbox:/tmp# semodule -l | grep sshd_local
sshd_local 0.0.1
Правило появилось, модуль загружен. Сложно? Да ну. Долго и муторно? О да.
решение для ограничений MLS
Вот проблема (the level of spoilers is over nine thousand!!1one):
root@sandbox:~# audit2allow -Rev -i /tmp/x
require {
type syslogd_t;
type initrc_t;
class unix_dgram_socket sendto;
}
#============= initrc_t ==============
# audit(1383338997.630:221):
# scontext="system_u:system_r:initrc_t:s0-s3:c0.c31" tcontext="system_u:system_r:syslogd_t:s3:c0.c31"
# class="unix_dgram_socket" perms="sendto"
# comm="acpid" exe="" path=""
# message="type=AVC msg=audit(1383338997.630:221): avc: denied { sendto } for
# pid=1351 comm="acpid" path="/dev/log"
# scontext=system_u:system_r:initrc_t:s0-s3:c0.c31
# tcontext=system_u:system_r:syslogd_t:s3:c0.c31 tclass=unix_dgram_socket "#!!!! This avc is a constraint violation. You will need to add an attribute to either the source or target type to make it work.
#Constraint rule:
# Possible cause source context and target context 'level' differ
allow initrc_t syslogd_t:unix_dgram_socket sendto;
Как видим, разрешающее правило уже есть. Более того, не в MLS варианте данный доступ был бы разрешен.
offtopic
Заодно можете оценить всю прелесть unconfined домена. Можно все, на то он и «неограничен». Именно поэтому, тестирование SELinux на чем-то, отличном от strict, смысла особого не имеет. И даже если у вас strict, но тестируемый объект — unconfined, то, в общем-то, выводы о нужности и надежности SELinux делать рановато, даже если очень-очень хочется :-)
Решение. Находим искомое ограничение, читаем:
mlsconstrain unix_dgram_socket sendto
(( l1 eq l2 ) or
(( t1 == mlsnetwriteranged ) and ( l1 dom l2 ) and ( l1 domby h2 )) or
(( t1 == mlsnetwritetoclr ) and ( h1 dom l2 ) and ( l1 domby l2 )) or
( t1 == mlsnetwrite ) or
( t2 == mlstrustedobject ));
# scontext=system_u:system_r:initrc_t:s0-s3:c0.c31
# tcontext=system_u:system_r:syslogd_t:s3:c0.c31
Итого, sendto в сокет (t1 пишет в t2) разрешено если:
нижний уровень доступа t1 равен таковому у t2 (s0 != s3), или
t1 помечен как mlsnetwriteranged (нет, смотрим список seinfo -amlsnetwriteranged -x), остальные условия уже не важны, или
t1 помечен как mlsnetwritetoclr (нет, аналогично), или
t1 помечен как mlsnetwrite (нет, такой только setrans_t), или
t2 помечен как mlstrustedobject (нет, syslogd_t там нет, но есть devlog_t)
Как видим, ничего из этого не выполняется. Кстати, в последних версиях команды audit2allow она нам сама развернет все условия, подставит все метки и проверит на правду. Теперь смотрим сам файл /dev/log:
root@sandbox:~# ls -laZ /dev/log
srw-rw-rw-. 1 root root system_u:object_r:devlog_t:s3:c0.c31 0 Nov 1 23:06 /dev/log
«WTF?!» — скажет внимательный читатель. В логе же tcontext~syslogd_t, а у файла devlog_t? Посмотрим ps:
Следите за руками: rsyslog, запущеный в домене syslogd_t, создает сокет, который наследует его домен, по адресу /dev/log; но файл по адресу /dev/log имеет контекст devlog_t согласно определенным контекстам для файлов. Иными словами, мы sendto не в файл делаем, а в сокет, и именно это нарушает ограничения. Вот подробно на этот вопрос ответил автор SELinux, Stephen Smalley. А вот один из вариантов решения, сразу с патчами. А вот тут обсуждается нежелательность объявления домена syslogd_t как mlstrustedobject, поскольку все объекты /proc/`pidof rsyslog`/ тоже станут mlstrustedobject. Но, несмотря на это, в Fedora именно так это и решили. Чтож, не будем сопротивляться — это вполне продемонстрирует как решать задачу, описываемую в данном спойлере, а провалиться детали мы и так можем на каждом шагу, надеюсь, я это тоже показал. Создаем свой второй модуль, вот содержимое файлов:
$ grep '' syslogd_local.*
syslogd_local.fc:# no file contexts redefined here
syslogd_local.if:## <summary>syslogd local policy</summary>
syslogd_local.te:policy_module(syslogd_local, 0.0.1)
syslogd_local.te:##################################################################
syslogd_local.te:require {
syslogd_local.te: type syslogd_t;
syslogd_local.te:}
syslogd_local.te:
syslogd_local.te:#============= syslogd_t ==============
syslogd_local.te:# mark syslogd_t as mlstrustedobject
syslogd_local.te:# this is possible security hole, TODO: get some heavy brain augmentation and investigate
syslogd_local.te:mls_trusted_object(syslogd_t);
Указанный макрос все сделает за нас. Не забываем добавить в modules.conf перед сборкой политики.
Что исправлять
Все что не работает, у вас список может отличаться. Наперво, переключаемся в режим permissive (/etc/selinux/config), и добиваемся того, чтобы audit.log c момента загрузки вышеописанных ошибок не содержал, особенно проверяем сам вход, newrole, ssh. Потом переключаемся в enforcing, и проверяем что все ок. Вот как выглядел auditd лог у меня после загрузки и входа по ssh:
root@sandbox:~# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: custom
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: denied
Max kernel policy version: 28
root@sandbox:~# cat /var/log/audit/audit.log
type=DAEMON_START msg=audit(1383360996.062:2774): auditd start, ver=2.3.2 format=raw kernel=3.10.17-vm-slnx auid=4294967295 pid=1278 subj=system_u:system_r:auditd_t:s3:c0.c31 res=success
type=CONFIG_CHANGE msg=audit(1383360996.180:20): audit_backlog_limit=320 old=64 auid=4294967295 ses=4294967295 subj=system_u:system_r:auditctl_t:s0-s3:c0.c31 res=1
type=LOGIN msg=audit(1383361036.430:21): login pid=1568 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=1
type=LOGIN msg=audit(1383361038.410:22): login pid=1571 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=2
root@sandbox:~# id -Z
root:secadm_r:secadm_t:s0-s3:c0.c31
Все, что мне пришлось для этого поменять, я выложу патчем на самом сервере, здесь не привожу, так как, во первых, многовато, и во-вторых, попробуйте сами. Не пожалеете.
Итак, мы все настроили, система загружается в режиме enforcing. К этому моменту, как правило, внимательный читатель уже обладает обширными знаниями доработке модулей, бегло ориентируется в структуре политики, искренне любит (или не менее искренне ненавидит) синтаксис m4, подписан на рассылку NSA, знает два десятка источников информации по SELinux и дюжину разработчиков поименно.
Настало время залезть еще немного глубже, на территорию, не сильно описанную в документациях.
MLS
Если вы немного знакомы с всякими критериямиоценкизащищенности, то наверняка знаете, что их, во первых, превеликое множество (c отдельными(PDF) профилями(PDF), большая часть из которых разработана разного рода околовоенными организациями), и что на выходе получается некое количество попугаев, характеризующее жесткость предъявленных требований при прохождении. MLS добавляет к уже существующим ограничениям SELinux еще два уровня контроля, вертикальный (levels) и горизонтальный (categories). Первый — это ни что иное как «допуски», где вышестоящий допуск подразумевает доступ к нижестоящему («совершенно секретно» может читать документы с грифом «секретно»), а второй — различные категории одного и того-же уровня, где разрешение читать одну категорию вовсе не означает разрешения читать остальные.
Поскольку оба этих уровня контроля могут быть назначены любым объектам, с которыми работает SELinux, то это позволяет реализовывать практически любые требования по классификации информации и ее потоков:
Иерархический доступ, TopSecret -> Secret -> Unclassified, для любых объектов. Полный список есть в директории flask;
Маркировка как файлов, так и, например, сетевых соединений или таблиц в БД;
Предотвращение утечки информации на низлежащие уровни независимо от прав пользователя в системе;
Ограничение доступов по умолчанию для любых пользователей (в том числе и root), с дальнейшим разграничением ролей в зависимости от аутентификации.
И прочий оверкилл для 99% систем.
Разумеется, все это требует тщательной проработки, прежде всего, архитектуры системы, иначе у нас потом контрактные работники будут раскладывать документы с грифом «Top Secret» по инстаграммам :-)
В рамках данного эксперимента я использовал вот такие уровни и категории:
Теперь настроим наш веб-сервер, как будто бы он предназначен исключительно для внутреннего доступа, т.е. работает строго на уровне s1 (Confidential). Это не необходимо для демонстрации, но полезно для общего развития. Разумеется, IPSec и маркировку пакетов настраивать не будем, иначе никто его не увидит, ограничимся локальным контекстом. Поскольку у нас сейчас на тестовой машине настроен только ssh, давайте выберем сервер, который не описан в RefPolicy:
nginx
В интернетах есть модуль для nginx, но он нам не совсем подходит, поскольку он написан для MCS (только s0). Поэтому воспользуемся возможностью почесать NIH и написать модуль с нуля. Для начала, определяемся с контекстами, используя dpkg -L и lsof, я отобрал вот такие:
Все, что не попало сюда, но принадлежит пакету (доки, маны, и т.п.), будет наследовать контексты родительских каталогов. Сам же сервис будет работать на уровне s1 (Confidential), и в категориях с первой по третью. Для простоты все контексты я назначил одинаковыми, но вы можете сделать по другому. Сперва собираем политику с модулем, содержащим только контексты, меняем роль (newrole -r secadm_r), переходим в premissive режим (setenforce 0), ставим пакет и принудительно обновляем контексты (restorecon -RFvv /), после чего стартуем nginx из под sysadm_r (run_init /etc/init.d/nginx start). Теперь у нас в audit.log достаточно информации, чтобы настроить правила. Можно их собрать в modname.if, создав каркас, чтобы потом нагенерить много серверов, это «красивый» способ:
template(`web_server_template',`
type $1_t, web_server;
allow blah blah;
# so we can call web_server_template(nginxN) in modname.te
')
А можно оставить modname.if пустым и написать все по мере анализа, это «понятный» способ. Я для наглядности пошел вторым путем. Сначала определяем все необходимые нам типы, попутно обвешав их самыми простыми макросами, это сильно сократит нам количество правил в дальнейшем:
root@sandbox:~# cat nginx_local.te
policy_module(nginx_local, 0.0.1)
##################################################################
type nginx_t;
type nginx_exec_t;
type nginx_initrc_exec_t;
type nginx_etc_t;
type nginx_var_log_t;
type nginx_var_run_t;
type nginx_var_www_t;
type nginx_var_lib_t;
corecmd_executable_file(nginx_exec_t);
init_script_file(nginx_initrc_exec_t)
files_type(nginx_etc_t)
logging_log_file(nginx_var_log_t)
files_pid_file(nginx_var_run_t)
files_type(nginx_var_www_t)
files_type(nginx_var_lib_t)
init_ranged_daemon_domain(nginx_t, nginx_exec_t, s1:c0.c2)
Большинство из этих макросов определены в файле corecommands.if, там можно подробно прочитать во что они раскроются. Последняя строчка — макрос, поддерживающий MLS, и определяющий уровень и категории, на которые nginx будет телепортирован инитом при запуске.
После этого, пробегаемся по логу, маленькими частями вытаскивая запросы (grep nginx /var/log/audit/audit.log | grep 'sysctl'), анализируя их и добавляя, например, для sysctl:
# /read kernel sysctl values
require {
type sysctl_kernel_t;
class dir { search };
class file { open read };
}
allow nginx_t sysctl_kernel_t:dir { search };
allow nginx_t sysctl_kernel_t:file { open read };
И так далее. Большую часть работы сделает audit2allow, в последних версиях программа даже дает исчерпывающие подсказки с случаях нарушений ограничений MLS. Я намеренно пишу каждый раз блок require перед очередной порцией правил, мне так удобнее, но все это можно свернуть в пару страниц, используя соответствующие макросы. В конечном счете, у вас получится что-то вроде
такого
policy_module(nginx_local, 0.0.1)
##################################################################
type nginx_t;
type nginx_exec_t;
type nginx_initrc_exec_t;
type nginx_etc_t;
type nginx_var_log_t;
type nginx_var_run_t;
type nginx_var_www_t;
type nginx_var_lib_t;
corecmd_executable_file(nginx_exec_t);
init_script_file(nginx_initrc_exec_t)
files_type(nginx_etc_t)
logging_log_file(nginx_var_log_t)
files_pid_file(nginx_var_run_t)
files_type(nginx_var_www_t)
files_type(nginx_var_lib_t)
init_ranged_daemon_domain(nginx_t, nginx_exec_t, s1:c0.c2)
# rules
# /sys and /sys/devices/systemcpu/online
require {
type sysfs_t;
class dir { search };
class file { read open };
}
allow nginx_t sysfs_t:dir { search };
allow nginx_t sysfs_t:file { read open };
# /read kernel sysctl values
require {
type sysctl_kernel_t;
type sysctl_t;
class dir { search };
class file { open read };
}
allow nginx_t sysctl_kernel_t:dir { search };
allow nginx_t sysctl_kernel_t:file { open read };
allow nginx_t sysctl_t:dir search;
# self configs and symlinks
require {
type nginx_etc_t;
class dir { open read search };
class file { open read getattr };
class lnk_file { read };
}
allow nginx_t nginx_etc_t:dir { open read search };
allow nginx_t nginx_etc_t:file { open read getattr };
allow nginx_t nginx_etc_t:lnk_file { read };
# /etc/localtime, /etc/passwc, etc (no pun intended)
require {
type locale_t;
type etc_t;
class file { read open getattr };
}
allow nginx_t locale_t:file { read open getattr };
allow nginx_t etc_t:file { read open getattr };
# pid file
require {
type var_run_t;
class dir { search write add_name remove_name } ;
class file { write read create open unlink };
}
allow nginx_t var_run_t: dir { search };
allow nginx_t nginx_var_run_t: file { read write create open unlink };
allow nginx_t nginx_var_run_t: dir { search write add_name remove_name };
# libs
require {
type var_lib_t;
class dir { search getattr };
}
allow nginx_t var_lib_t:dir search;
allow nginx_t nginx_var_lib_t: dir { search getattr };
# socket bind
require {
type node_t;
type http_port_t;
class tcp_socket { name_bind setopt bind create listen node_bind };
class capability { net_bind_service setuid setgid };
}
allow nginx_t http_port_t:tcp_socket { name_bind };
allow nginx_t node_t:tcp_socket { node_bind };
allow nginx_t self:tcp_socket { bind create setopt listen };
allow nginx_t self:capability { net_bind_service setuid setgid };
# socket accept
require {
class tcp_socket { read write accept };
}
allow nginx_t self:tcp_socket { read write accept };
# logs
require {
type var_log_t;
class dir { search };
class file { open append };
}
allow nginx_t var_log_t:dir { search };
allow nginx_t nginx_var_log_t:dir { search };
allow nginx_t nginx_var_log_t:file { open append };
# www
require {
class dir { search getattr };
class file { read getattr open };
}
allow nginx_t nginx_var_www_t:dir { search getattr };
allow nginx_t nginx_var_www_t:file { read getattr open };
, что и является нашей финальной целью.
Заводим пользователей и роли, например так:
root/sysadm_r@sandbox:~# adduser alice
...skipped...
root/sysadm_r@sandbox:~# adduser bob
...skipped...
root/secadm_r@sandbox:~# semanage user -a -R user_r -L s1 -r s1-s1:c0 ninjas
root/secadm_r@sandbox:~# semanage user -a -R user_r -L s2 -r s2-s2:c0 aliens
root/secadm_r@sandbox:~# semanage login -a -s ninjas alice
root/secadm_r@sandbox:~# semanage login -a -s aliens bob # or, ninjas to supervise alice
root/secadm_r@sandbox:~# restorecon -RFvv /home/
# thats all, folks.
Итого, получаем:
оба пользователя не могут писать данные в директории ниже своего минимального уровня;
оба пользователя не могут читать из объектов выше своего уровня допуска;
оба пользователя ограничены своей категорией. При добавлении разрешющих правил на чтение других доменов они смогут читать только файлы с категорией c0;
root не может читать файлы пользователей, не изменив контекст;
если у боба будет тот-же SELinux ID, что и у alice, он сможет читать ее файлы (если DAC разрешает) и видеть ее процессы;
веб сервер не сможет писать никуда, кроме своих директорий, даже если его попросят выложить core — у нас практически вся система в s0, а он — s1.
Funky time
Ну и теперь, наконец-то, будут слайды. Для полноценной демонстрации, я купил под эту статью небольшую впску, рядышком с АНБ, и все проделанное быстренько развернул на ней. Непосредственно на этой системе можно посмотреть, что такое SELinux, зайти под рутом и первым делом набрать rm -rf /* всенепременнейше, позапускать всяких скриптов/сплойтов и руткитов, в общем, показать этим АНБ кузькину мать. Но прежде, чем вы займетесь этим увлекательным делом, давайте еще раз пробежимся как по допущениям, так и по ограничениям:
В рамках этого обучающего курса, мы:
Считаем, что root доступ к серверу получил кто-угодно.
Считаем, что ему можно входить по ssh и запускать интерактивный shell.
Считаем, что root у нас незамаплен на user_u, как это сделал Russell Coker в своей play machine. Разумеется, этого допущения не рекомендуется делать в production (как и все предыдущие, конечно-же :-)
Считаем, что мы не кастомизировали ядро (нет grsec, это я решил не включать в статью и тесты)
Считаем, что у нас почти нет фаервола.
Если и есть в ИБ термин для состояния безопасности, в описании которого есть слова «раздвигать» и «булки», то это именно оно. Все, что отделяет от полной компрометации, это SELinux. Компрометация неизбежна, но очень интересно, за какое время.
Но так-же, есть то, для чего SELinux не преднасначен, а именно:
SELinux это не средство ограничения ресурсов. Он не спасет от :(){ :|:& };:. Поэтому мне пришлось настроить небольшую защиту от fork bombs, но все-же — не пробуйте; в лучшем случае, вас кикнет и вы больше не сможете зайти, в худшем — если будете проявлять упрство — то и другие не смогут зайти, пока я все не почищу.
SELinux это не средство защиты от атак на другие ресурсы. Поэтому мне пришлось ограничить доступ наружу с демо сервера. Если вы сможете продемонстрировать, как вы отключаете SELinux или iptables — вы большой молодец, но к следующей версии я это поправлю. Скорее всего, это косяк не SELinux, а мой :-)
Мы рассматриваем сервер в минимальной конфигурации, там нет компиляторов/дебагеров и всего того, чего обычно на проде итак не бывает. Версия полноценной MLS Play Machine будет позже, когда я разверну это не на VPS, а на более контроллируемой инфраструктуре. Зато есть scp — можно интересное копировать.
И да, в лучших традициях организации, разработавшей SELinux, на сервере заодно тестируется запись консольных сеансов :-) А то сами понимаете, NSA, Аризона, Area51 недалеко, а тут рутовый доступ. Надо писать, вдруг мне туда центральных процессоров понапихают вагон. Снимете запись — вы тоже большой молодец, и тоже напишите в комментарии.
0day — на ваше усмотрение. Если всплывет, я конечно буду польщен. Хотя, кому я рассказываю :-)
Домен я не заводил, это для игрушки версии 0.0.2. Версия 0.0.1
тут
специально ссылку не даю: http://162.213.198.69
И да, отдельная просьба — please behave. Не надо убивать все рутовые процессы и мешать другим, пользователь — один на всех.