#!/usr/bin/env ruby # frozen_string_literal: true require 'pwn' require 'optparse' require 'htmlentities' opts = {} OptionParser.new do |options| options.banner = "USAGE: #{File.basename($PROGRAM_NAME)} [opts] " options.on('-uGITURI', '--uri-source-root=GITURI', '') do |u| opts[:uri_source_root] = u end options.on('-dDIR', '--dir-path=DIR', '') do |d| opts[:dir_path] = d end options.on('-tTHREADS', '--max-threads=THREADS', '') do |t| opts[:max_threads] = t end options.on('-TCASE', '--test-cases=CASE', '') do |c| opts[:chosen_test_cases] = c end options.on('-l', '--[no-]list-test-cases', '') do |l| opts[:list_test_cases] = l end options.on('-nREPORTNAME', '--report-name=REPORTNAME', '') do |n| opts[:report_name] = n end options.on('-s', '--[no-]start-reporting-server', '') do |s| opts[:start_reporting_server] = s end end.parse! if opts.empty? puts `#{File.basename($PROGRAM_NAME)} --help` exit 1 end begin pwn_provider = 'ruby-gem' # pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.select { |s| s == 'PWN_PROVIDER' }.any? pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.any? { |s| s == 'PWN_PROVIDER' } green = "\e[32m" end_of_color = "\e[0m" dir_path = opts[:dir_path] dir_path ||= '.' uri_source_root = opts[:uri_source_root].to_s.scrub max_threads = opts[:max_threads] max_threads ||= 25 chosen_test_cases = opts[:chosen_test_cases] list_test_cases = opts[:list_test_cases] report_name = opts[:report_name] report_name ||= File.basename(Dir.pwd) start_reporting_server = opts[:start_reporting_server] # Define Test Cases to Run & Start Thread Pool if chosen_test_cases && !list_test_cases test_cases = chosen_test_cases.to_s.scrub.chomp.strip.delete("\s").split(',').map(&:to_sym) else test_cases = %i[ AMQPConnectAsGuest ApacheFileSystemUtilAPI AWS BannedFunctionCallsC Base64 BeefHook CmdExecutionJava CmdExecutionPython CmdExecutionRuby CmdExecutionScala CSRF DeserialJava Emoticon Eval Factory HTTPAuthorizationHeader InnerHTML Keystore LocalStorage LocationHash Log4J Logger MD5 OuterHTML PaddingOracle Password PHPInputMechanisms PHPTypeJuggling PomVersion Port PostMessage PrivateKey Redirect ReDOS Shell Signature SQL SSL Sudo TaskTag ThrowErrors Token TypeScriptTypeJuggling Version WindowLocationHash ].sort.uniq end if list_test_cases test_cases.each { |tc| puts "#{green}#{tc}#{end_of_color}" } exit end raise "ERROR: Invalid Directory #{dir_path}" unless File.directory?(dir_path) results_hash = { report_name: HTMLEntities.new.encode( report_name.to_s.scrub.strip.chomp ), data: [] } mutex = Mutex.new PWN::Plugins::ThreadPool.fill( enumerable_array: test_cases, max_threads: max_threads ) do |test_case| sca_arr = PWN::SAST.const_get( test_case.to_s.scrub ).scan( dir_path: dir_path, git_repo_root_uri: uri_source_root ) sca_arr.each do |hash_line| mutex.synchronize do results_hash[:data].push(hash_line) end end end # Generate HTML Report print "#{File.basename($PROGRAM_NAME)} Generating Report..." PWN::Reports::SAST.generate( dir_path: dir_path, results_hash: results_hash ) puts 'complete.' # Start Simple HTTP Server (If Requested) if start_reporting_server listen_port = PWN::Plugins::Sock.get_random_unused_port.to_s if pwn_provider == 'docker' listen_ip = '0.0.0.0' else listen_ip = '127.0.0.1' end puts "For Scan Results Navigate to: http://127.0.0.1:#{listen_port}/#{report_name}.html" Dir.chdir(dir_path) system( 'pwn_simple_http_server', '-i', listen_ip, '-p', listen_port ) end rescue SystemExit, Interrupt puts "\nGoodbye." end