Docker container là 1 tiến trình sandbox. Nó cô lập với tất cả các tiến trình khác trên host. Sự cô lập này lợi dụng tính năng cgroup và namespace (đã nói ở trên), 2 tính năng tồn tại trên Linux kernel từ lâu. Docker sử dụng docker engine để tiếp cận và sử dụng 2 tính năng này dễ dàng hơn.
– Có thể chạy ở máy local, máy ảo hoặc trên môi trường cloud.
– Có thể chạy trên bất kỳ hệ điều hành nào.
– Cô lập với các container khác, chạy ứng dụng riêng và chỉ cần các file binary, lib, file cấu hình đủ để chạy ứng dụng đó.
Giả sử bạn chạy một container có chức năng Ghi chép, những thông tin lưu ở phần Ghi chép sẽ được lưu ra 1 file nào đó trong container. Tuy nhiên khi khởi chạy lại container từ đầu, tất cả những thay đổi trước đó sẽ bị mất –> cần đến mount
Mount là điểm chia sẻ chung giữa host và container, khi ta thay đổi dữ liệu folder mount trên container như nào thì dữ liệu trong folder mount trên host cũng bị thay đổi như thế.
Mount type bind tương tự như mount type volume, tuy nhiên ta sẽ chỉ định được source mount path
Toàn bộ data trong folder /tmp trên host sẽ được đồng bộ với folder /tmp trên container
Attack vector 1: Mounted Docker Socket Escape/Exposed Docker Socket
Docker hoạt động theo mô hình client/server, docker daemon đóng vai trò server nhận các yêu cầu từ client docker cli gửi đến và giao tiếp qua docker socket.Như mình đã đề cập ở trên mount point là điểm chia sẻ dữ liệu chung giữa host và container, khi docker socket được mount trong container, từ trong container attacker khi đó có thể giao tiếp với docker server thông qua socket này. Việc mount docker socket này thường được dùng trong trường hợp quản trị hoặc developer muốn chạy container bên trong 1 container hoặc muốn kiểm tra thông tin, log event của container nên cần giao tiếp với docker deamon từ trong container.
Kiểm tra docker.socket được mount trong container
Kiểm tra xem docker cli có được cài trong container không
Trong trường hợp không thể cài đặt được docker trong container, ta sẽ nghĩ đến việc giao tiếp với docker socket bằng cách khác, dùng TCP socket hoặc Docker HTTP API. Tham khảo tại:
– https://i.blackhat.com/USA-19/Thursday/us-19-Edwards-Compendium-Of-Container-Escapes-up.pdf (page 42)
– https://docs.docker.com/engine/api/v1.41/
Ví dụ command sau tương đương với docker container ls
Sau khi cài đặt docker cli, ta tạo mới một container, mount toàn bộ thư mục / của host vào thư mục /host trong container, thay đổi root folder và gọi lệnh bash
Attack vector 2: Capabilities Abuse Escape
Sử dụng command capsh –print để kiểm tra capabilities của container. Nếu có 1 trong số các capability sau thì có thể breakout container:
CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_MODULE, DAC_READ_SEARCH,
DAC_OVERRIDE, CAP_SYS_RAWIO, CAP_SYSLOG, CAP_NET_RAW, CAP_NET_ADMIN
CAP_SYS_MODULE
Các container là các tiến trình cô lập, đảm bảo 1 tiến trình không thể truy cập vào tài nguyên của tiến trình khác và tài nguyên của host, thế nhưng các container vẫn sử dụng chung kernel với host.
Khi 1 container có cap_sys_module capability, nó có thể inject – load/unload kernel module bất kỳ vào kernel của host sử dụng 3 system call: init_module, finit_module, delete_module
Kiểm tra xem container có cap_sys_module capability không, nếu có, ta có thể inject malicious module vào linux kernel sử dụng command insmod. Để build reverse-shell.ko tham khảo tại https://book.hacktricks.xyz/linux-hardening/privilege-escalation/linux-capabilities#cap_sys_module
Như vậy ta đã lấy được reverse shell của host.
DAC_READ_SEARCH
Discretionary access control (DAC) kiểm soát quyền truy cập linh hoạt, sử dụng ACL access control list để kiểm soát quyền ghi, đọc, xóa,… của 1 object này đối với 1 object khác. Khi 1 container có cap_dac_read_search, nó có thể path traversal và đọc toàn bộ file của host. Nó cũng có permission gọi đến syscal open_by_handle_at để thực hiện việc này. Kiểm tra xem container có dac_read_search capability không
Sử dụng exploit poc (tham khảo https://book.hacktricks.xyz/linux-hardening/privilege-escalation/linux-capabilities#cap_dac_read_search) để đọc file bất kỳ trên host
DAC_OVERRIDE
Tiếp tục ở lab trên, lợi dụng cap_dac_override để ghi đè file bất kỳ trên host, tuy nhiên cap_dac_override yêu cầu cần phải có cap_dac_read_search capability. Kiểm tra container có cap_dac_read_search và cap_dac_override capability không
Khi có capability này, để breakout container, ta thường thêm user + password vào file /etc/passwd, sửa password của user trong file /etc/shadow, thêm user vào file /etc/sudoers, hoặc xóa ký tự x cạnh user name trong file /etc/passwd để ssh user đó mà không cần mật khẩu,…
Sử dụng mã khai thác (tham khảo tại https://book.hacktricks.xyz/linux-hardening/privilege-escalation/linux-capabilities#cap_dac_override) để ghi đè file /etc/passwd và file /etc/shadow trên host. Ta có thể sử dụng lab trước để download các file này từ host, sau đó thêm user, password để tránh trường hợp bị conflict user.
Sau khi ghi đè 2 file /etc/passwd và /etc/shadow, ta ssh đến host sử dụng user root, password user root là password của user root trong container (ta kiểm soát được giá trị này)
CAP_SYS_PTRACE
Khi một container có cap_sys_ptrace nó có thể debug và inject vào một tiến trình khác.
Khi đứng trong 1 container, để inject được tiến trình ngoài host thì container phải cùng namespace
pid với host (–pid=host)
Trong trường hợp host có enable AppArmor, container cần disable tính năng này, vì AppArmor chặn
1 số system call từ trong container. Nếu AppArmor enable thì ta không thể inject được process của host.
Trên host ta chạy 1 tiến trình test để làm tiến trình target cho việc inject
Trong container, inject shellcode vào tiến trình test trên
Shellcode là payload mở cổng 5600 để lắng nghe shell trên host (https://www.exploit-db.com/exploits/41128) hoặc có thể sử dụng msfvenom để tạo shellcode với payload tùy ý.
Code c để inject shellcode vào 1 tiến trình khác: https://github.com/W3ndige/linux-process-injection/blob/master/inject.c
Sau khi inject, kiểm tra trên host port 5600 đã được mở
Từ trong container, sử dụng netcat kết nối đến cổng này trên host để lấy shell của host
Attack vector 3: Escape from Privileged Containers
Bình thường khi 1 container được tạo, thì nó sẽ được quản lí bởi Docker engine. Docker engine như là 1 cầu nối trung gian giữa host và container, chuyển các syscall từ container đến kernel và giúp container sử dụng tài nguyên của host một cách hợp lí, hiệu quả.
Khi một container chạy với mode privileged, nó có thể tương tác trực tiếp với host, kế thừa toàn bộ capabilities root user và filesystem trên host.
Chi tiết sự khác biệt giữa container với privileged container trong đường dẫn: https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-privileged
Privileged container có thể được tạo ra bằng cách thêm flag –privileged hoặc bằng 1 số option sau:
–cap-add=ALL –> thêm capabilities cho container
–security-opt apparmor=unconfined –> disable apparmor
–security-opt seccomp=unconfined –> disable seccomp
–security-opt label:disable –> disable selinux
–pid=host –> sử dụng pid namespace giống host
–userns=host –> sử dụng user namespace giống host
–uts=host –> sử dụng uts namespace giống host
–cgroupns=host –> sử dụng cgroup namespace giống host
Mount /dev (mount phân vùng đĩa cứng vào container)
case 1: privileged container + –pid=host
pid namespace của tiến trình trong container giống với pid namespace của tiến trình trên host
Khi đó ta có thể chuyển tới namespace của process chạy trên host bằng command nsenter
case 2: privileged container
Kiểm tra xem container có capabilities cap_sys_admin không? cap_sys_admin cho phép ta mount phân vùng đĩa cứng (/dev/sda*) của host vào container
Sau đó sử dụng lệnh fdisk -l để list các đĩa cứng và phân vùng đĩa cứng. Đĩa cứng phát hiện đầu tiên sẽ là /dev/sda, thứ 2 sẽ là /dev/sdb,…. đĩa CD ROM phát hiện thứ nhất tên sẽ là /dev/scd0 (hoặc /dev/sr0),..
Trong trường hợp container không hỗ trợ command fdisk, ta có thể tìm phân vùng đĩa cứng như sau
Khi đó ta có thể mount đĩa cứng /dev/sda1 này lên container và truy cập vào filesystem của host
cmd: mount /dev/sda1 /mnt
Attack vector 4: Share network namespace
Mặc định container sẽ chạy với interface docker0, địa chỉ ip sẽ bắt đầu với 172.17.0
Nên khi 1 container chạy với option –network host, nó sẽ sử dụng network interfaces của host, do đó sẽ cùng network namespace và cùng hostname với host.
Hướng tấn công của trường hợp này là đi tìm và khai thác các dịch vụ khác chạy trên interface này, khi RCE được từ 1 dịch vụ thì đó cũng là RCE host.
Mình dựng 1 php web server đơn giản trên host, trên đó có 1 shell php, để kiểm tra từ container gọi command, xem kết quả trả về sẽ là gì.
Trong trường hợp container không cùng network namespace với host
Trong trường hợp container cùng network namespace với host
Kết quả mình đã RCE được host từ container cùng network namespace.