Готовим NSA SELinux

в 9:29, , рубрики: Без рубрики

NSA logoПривет! Данным постом я хочу немножно отвлечь многоуважаемое сообщество от пересуд на тему АНБ, и вместо этого заполнить пробел в описании одной их технологии, написав нечто среднее между «отключайте SELinux» и «посвятите ему лучшие годы чтобы понять малую часть». На самом деле, обе эти точки зрения одинаково далеки от правды — технология достаточно проста, прозрачна и позволяет сделать очень многое. Однако, хочу предупредить об огромном количестве букв, и достаточно узкой целевой аудитории, т.к. нижеописанное будет интересно далеко не всем. Если вы давно хотели понять, что такое SELinux, но не знали с какой стороны подступиться — эта статья для вас. Если вы давно все это знаете и успешно применяете, то я допустил достаточно неточностей, чтобы мы смогли обсудить это в комментариях. Ну а эксперты по ИБ с мировым именем могут смело проматывать в самый конец и начинать играть, у меня есть планы на продолжение :-)
Я не буду в статье касаться тем связанных с АНБ в целом, способностью расшифровывать RSA, прослушкой и прочими медийными аспектами — no hype, no FUD, only technology. Мы будем с разной степенью активности влезать в разные исходники, добавлять свои условия в само сердце MLS, возможно внедряя свои уязвимости (мы тоже делаем ошибки), и после этого попытаемся взлететь проведем тесты. Иными словами, я описываю что и как, а вы после этого уже не смотрите на SELinux как на неведому зверушку и обитель зла от вероятного противника, а смело начинаете использовать эту технологию во благо. Особенно учитывая, что она уже включена во всех ваших андроидах (>4.3) и многих дистрибутивах.
Итак, если вам все еще интересно, и вы не боитесь просидеть неделю в одном из многочисленных спойлеров, то

Предварительные чтения

Я подразумеваю, что у вас уже есть опыт работы с Linux в достаточном объёме, чтобы развернуть свой любимый дистрибутив в виртуальном окружении. Я буду делать все на примере Debian, но если вы решите повторить сей путь, то все это можно (и очень даже нужно) проделать на наиболее вам удобном и привычном дистрибутиве — в процессе вы узнаете про него много нового. Я постарался написать эту статью как обучающий материал, чтобы любой желающий мог пошагово повторить. Так же я подразумеваю, что вас не затруднит читать техническую документацию на английском — информации по SELinux на русском пока что крайне мало.

Общая информация по технологии

Вокруг SELinux вертится столько слухов, что вы будете удивлены, насколько небольшая по объему у нас вводная, всего три ссылки:

  1. RH Guide: если какая-то команда непонятна, с большой вероятностью вы найдете описание в нем. Откройте его в отдельном табе, пригодится.
  2. Конспект лекции Eli Billauer: рассматривайте как основной сборник фактов. По нему можно быстро понять что к чему, и знать, что именно спрашивать у гугла.
  3. Написание политик. Несмотря на десятилетнюю давность документа, в нем описано достаточно ключевых моментов для понимания внутреннего устройства SELinux, и как его ковырять.

Это основное, что я рекомендую к прочтению до того, как приступать к настройке, иначе вы постоянно будете возвращаться в этим документам. Есть множество других ресурсов, но вы до них обязательно дойдете, если захотите сделать что-то, отличное от включения/выключения булевых переменных.

Итак, когда вы все это прочитали, можем проверить себя простыми вопросами:

  1. Что такое unconfined_t/unconfined_u, и почему SELinux нельзя на нем тестировать?
  2. Что является частным случаем, MLS или MCS?
  3. Чем отличается *.te от *.if от *.fc?
Ответы

  1. Неограниченный домен/пользователь. С таким-же успехом можно настраивать SELinux на другой машине.
  2. MCS. MLS == MCS при MLS_SENS=1.
  3. Принципиально — ничем. Хоть в txt пишите, главное Makefile поправить не забудьте.

Постановка задачи и предварительная настройка

Теперь, когда мы уже знаем что мы хотим, но не знаем как мы это будем реализовывать, можем сформулировать цели эксперимента:

  • Хотим настроить SELinux MLS (раз уж взялись, давайте по максимуму, а не готовое из репозитория next->next->agree);
  • За основу хотим взять RefPolicy;
  • Ну и после этого хотим проверить 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, все настройки максимально по умолчанию.
    • внутри остатка — все под LVM.
  • Вот сразу fstab
    root@sandbox:~# cat /etc/fstab
    # /etc/fstab: static file system information.
    # <file system> <mount point>   <type>  <options>       <dump>  <pass>
    /dev/vda1               /boot   ext2    defaults        0       2
    /dev/mapper/vg0-root    /       btrfs   defaults        0       1
    /dev/mapper/vg0-usr     /usr    btrfs   defaults        0       2
    /dev/mapper/vg0-var     /var    btrfs   defaults        0       2
    /dev/mapper/vg0-tmp     /tmp    btrfs   defaults        0       2
    /dev/mapper/vg0-rhome   /root   btrfs   defaults        0       2
    /dev/mapper/vg0-swap    none    swap    sw              0       0
    
  • Отдельные разделы вынесены для последующего удобства тестирования.
  • Ставим минимальную систему с SSH-сервером, больше ничего.
  • Перед завершением установки, вызываем сразу шелл, и запоминаем ключ системы:
    root@sandbox:~# ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key
    256 f6:9b:ad:dd:93:cb:3d:c2:83:76:45:c3:02:e8:6a:1d  root@sandbox (ECDSA)
    

После установки, заходим по ssh, и приводим систему к базовой для наших экспериментов версии, в моем случа было примерно так:

sed -i 's/wheezy/jessie/g' /etc/apt/sources.list  # that's no bloody enterprise
aptitude update && aptitude dist-upgrade -VR # let's go testing, it's stable enough
aptitude install vim bash-completion deborphan -VR # a little comfort couldn't hurt
aptitude install policycoreutils auditd setools selinux-basics -VR # last is just helper scripts, optional
vim /etc/network/interfaces # make interfaces static
aptitude purge isc-dhcp-client console-setup console-setup-linux kbd iproute module-init-tools $(deborphan)

Создаем ключи для ssh, прописываем их на сервере для root:

@local$ ssh-keygen -b 521 -t ecdsa -f selinux-test
@remote# mkdir /root/.ssh && cat selinux-test.pub > /root/.ssh/authorized_keys2 && chown && chmod

Ну и под занавес, собираем и ставим свое ядро — мы же хотим поддержку последней версии политики, минимально необходимого набора модулей, экспериментировать с патчами 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!):selinux kernel opts, sorry for imageshack, habrastorage is not ok with my id

.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) выдает нам, что нам надо добавить в политике, чтобы данные сообщения в логе больше не появлялись.Так вот, если вы где-либо (а это практически везде) в интернете встретите рекомендацию

grep something-something /var/log/audit/audit.log | audit2allow -M mymegamodule
semodule -i mymegamodule

то следуйте ей только если вы отдаете себе отчет, что именно вы сейчас сотворите — этот набор команд означает, что 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.

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

суть нумеро дуо, на этот раз без плохих аналогий

Если вы внимательно читали предварительную документацию, то наверняка помните порядок принятия решений:

  1. Сначала DAC. Если запрещено, то до SELinux дело даже не дойдет, permission denied будет обычный, юниксовый, всем нам знакомый по временам, когда мы только-только знакомились со своей первой *nix системой.
  2. Потом MAC. Если не найдено ни одного подходящего разрешающего правила, permission denied будет уже от SELinux. В некоторых дистрибутивах (RH)в логах появятся строки, содержащие "SELinux is preventing", в некоторых нет, но во всех будет что-то в audit.log.

Итого, скорее всего в RefPolicy просто нет чего-то, что есть в политике дистрибутива. Давайте найдем это и добавим.

Ах да, забыл сказать, что начиная с этого момента, вам понадобится доступ к серверу не только по ssh, он может не работать. Благо, в нашем случае это виртуальный сервер, всегда есть VNC/SPICE/etc (ссылка спецом для ФСКН). Пробуем зайти локально — не пускает. Отличная ситуация, чтобы сразу проиллюстрировать, как из нее

выходить

  1. Don't panic.
  2. Перегружаемся — например, послав Ctrl+Alt+Del, acpid все сделает за нас.
  3. Ловим grub на этапе загрузки, меняем selinux=1 на selinux=0
  4. Грузимся, заходим под root.

На этом этапе, audit.log содержит в себе все причины наших неудач, почему мы не смогли зайти. Т.к. сейчас мы загрузились с отключенным SELinux, первое, что имеет смысл сделать, это скопировать audit.log с прошлой загрузки для последующего анализа, ведь при включенном SELinux у нас это сделать просто так не получится.

cp /var/log/audit/audit.log /root
wc -l /root/audit.log
195

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

  • Как читать логи

    type=DAEMON_START msg=audit(1383338997.597:1957): auditd start, ver=2.3.2 format=raw kernel=3.10.17-vm-slnx auid=4294967295 pid
    =1319 subj=system_u:system_r:auditd_t:s3:c0.c31 res=success
    

    Первая же строчка говорит нам о том, что 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 есть дополнительные ограничения, но об этом меня просили молчать далее.

    type=SYSCALL msg=audit(1383338997.620:219): arch=40000003 syscall=102 success=no exit=-13 a0=3 a1=afbe1c10 a2=a779b000 a3=ffffffc8 items=0 ppid=1338 pid=1346 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 ses=4294967295 tty=(none) comm="acpid" exe="/usr/sbin/acpid" subj=system_u:system_r:initrc_t:s0-s3:c0.c31 key=(null)
    

    Вторая строчка говорит нам о том, что демон 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 не самый главный, есть и поважнее :-)
    Загадка для пытливых умов — к какому сокету он обратился?*

    type=AVC msg=audit(1383338997.810:233): avc:  denied  { search } for  pid=1470 comm="restorecond" name="/" dev="tmpfs" ino=376 scontext=system_u:system_r:restorecond_t:s0-s3:c0.c31 tcontext=system_u:object_r:var_run_t:s3:c0.c31 tclass=dir
    

    Ну и возьмем третью строчку, из серединки. Логи 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:~# ls -laZ /lib/systemd/systemd-udevd 
      -rwxr-xr-x. 1 root root system_u:object_r:bin_t:s0 210380 Sep 23 12:24 /lib/systemd/systemd-udevd
      @local$ grep systemd-udevd custom/policy/ -R
      custom/policy/modules/system/udev.fc:/usr/lib/systemd/systemd-udevd -- gen_context(system_u:object_r:udev_exec_t,s0)
      

      В дебиане /lib, в RefPolicy /usr/lib. Правим:

      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). Другой вариант — переопределяем у себя локально, пересобираем политику, накатываем (и заодно ставим политику).

    • решение для отсутствия разрешающего правила

      Возьмем вот эту строку, для примера:

      type=AVC msg=audit(1383338997.860:251): avc:  denied  { module_request } for  pid=1524 comm="sshd" kmod="net-pf-10" scontext=system_u:system_r:sshd_t:s0-s3:c0.c31 tcontext=system_u:system_r:kernel_t:s3:c0.c31 tclass=system
      

      Расшифровка лога проста, но постоянное занятие этим со временем утомляет. Скинем ее в log, и пусть за нас поработает машина:

      root@sandbox:~# audit2allow -Rev -i /root/log
      
      require {
              type kernel_t;
              type sshd_t;
              class system module_request;
      }
      
      #============= sshd_t ==============
      # audit(1383338997.860:251):
      #  scontext="system_u:system_r:sshd_t:s0-s3:c0.c31" tcontext="system_u:system_r:kernel_t:s3:c0.c31"
      #  class="system" perms="module_request"
      #  comm="sshd" exe="" path=""
      #  message="type=AVC msg=audit(1383338997.860:251): avc:  denied  {
      #   module_request } for  pid=1524 comm="sshd" kmod="net-pf-10"
      #   scontext=system_u:system_r:sshd_t:s0-s3:c0.c31
      #   tcontext=system_u:system_r:kernel_t:s3:c0.c31 tclass=system "
      allow sshd_t kernel_t:system module_request;
      

      А вот тут уже, начинаем думать — задаем себе простые вопросы:

      • Что происходит? 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):

      the problem

      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
      type=SYSCALL msg=audit(1383338997.630:221): arch=40000003 syscall=102 success=no exit=-13 a0=3 a1=afbe15d0 a2=a779b000 a3=ffffffc8 items=0 ppid=1 pid=1351 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 ses=4294967295 tty=(none) comm="acpid" exe="/usr/sbin/acpid" subj=system_u:system_r:initrc_t:s0-s3:c0.c31 key=(null)
      

      Вот описание:

      the decription

      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;
      

      Вот существующие правила:

      the sesearch

      root@sandbox:~# sesearch --allow -s initrc_t -t syslogd_t -c unix_dgram_socket
      Found 2 semantic av rules:
         allow initrc_t syslogd_t : unix_dgram_socket sendto ; 
         allow unconfined_domain_type domain : unix_dgram_socket { ioctl read write create getattr setattr lock relabelfrom relabelto append bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind } ;
      

      Как видим, разрешающее правило уже есть. Более того, не в 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:

      root@sandbox:~# ps -auxZ  | grep [r]syslog
      system_u:system_r:syslogd_t:s3:c0.c31 root 1338 0.0  0.3  30784   972 ?        Ssl  Nov01   0:00 /usr/sbin/rsyslogd
      

      Следите за руками: 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» по инстаграммам :-)
В рамках данного эксперимента я использовал вот такие уровни и категории:

root@sandbox:~# cat /etc/selinux/custom/setrans.conf
Domain=Playbox

# levels
s0=SystemLow
s3:c0.c31=SystemHigh
s0-s3:c0.c31=SystemLow-SystemHigh

s1=Confidential
s2=Secret

# employee categories
s1.c0=Ninjas
s1.c1=Pirates
s1.c2=Jesuses
# secret stuff
s2.c0=Aliens
s2.c1=BigBrother

Теперь настроим наш веб-сервер, как будто бы он предназначен исключительно для внутреннего доступа, т.е. работает строго на уровне s1 (Confidential). Это не необходимо для демонстрации, но полезно для общего развития. Разумеется, IPSec и маркировку пакетов настраивать не будем, иначе никто его не увидит, ограничимся локальным контекстом. Поскольку у нас сейчас на тестовой машине настроен только ssh, давайте выберем сервер, который не описан в RefPolicy:

nginx

В интернетах есть модуль для nginx, но он нам не совсем подходит, поскольку он написан для MCS (только s0). Поэтому воспользуемся возможностью почесать NIH и написать модуль с нуля. Для начала, определяемся с контекстами, используя dpkg -L и lsof, я отобрал вот такие:

/usr/sbin/nginx         --      gen_context(system_u:object_r:nginx_exec_t,s1:c0.c2)
/etc/init.d/nginx               gen_context(system_u:object_r:nginx_initrc_exec_t,s1:c0.c2)
/etc/nginx(/.*)?                gen_context(system_u:object_r:nginx_etc_t,s1:c0.c2)
/var/log/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_log_t,s1:c0.c2)
/var/run/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_run_t,s1:c0.c2)
/var/www(/.*)?                  gen_context(system_u:object_r:nginx_var_www_t,s1:c0.c2)
/var/lib/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_lib_t,s1:c0.c3)

Все, что не попало сюда, но принадлежит пакету (доки, маны, и т.п.), будет наследовать контексты родительских каталогов. Сам же сервис будет работать на уровне 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 };

Для socket:

# 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 };

И так далее. Большую часть работы сделает 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. Не надо убивать все рутовые процессы и мешать другим, пользователь — один на всех.

Примечания

* © Tim Minchin, Lullaby

Автор: iaf

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js