Merhaba ben saldırı timlerinden Bunjo, bu konuda sizlere WordPress'te bulunan bir pluginin güvenlik zafiyeti içeren versiyonuna exploit yazacağız.
Diğer Exploit Konularım:
Exploit Eğitimi #8 (Shellshock (Bash Bug))
Exploit Eğitimi #7 (WordPress)
Exploit Eğitimi #6 (FTP Fuzzer)
Exploit Eğitimi #5 (SMTP Enumeration)
Exploit Eğitimi #4 (Her Türden)
Exploit Eğitimi #3 (Gerçek Uygulama)(Apache DOS)
Exploit Eğitimi #2 (FTP DOS)
Exploit Eğitimi #1 (ANON-FTP)
CVE-2020-35489
Bu güvenlik açığı, WordPress İletişim Formu 7 eklentisinin 5.3.2'den önceki sürümlerindeki bir güvenlik açığından kaynaklanmaktadır.
Yetersiz giriş doğrulama ve temizleme nedeniyle kimliği doğrulanmamış saldırganların form alanları aracılığıyla kötü amaçlı komut
dosyaları yüklemesine olanak tanır ve potansiyel olarak etkilenen sitede uzaktan kod yürütülmesine yol açar.
Genel İşleyiş:
Kullanıcı programa URL listesi verir.
Program bu URL listesini teker teker okur.
Plugin tarafından otomatik oluşturulan readme.txt dosyasının varlığını kontrol eder.
Eğer varsa bu dosya içerisinden versiyon kontrol eder.
Versiyon zafiyet içeren versiyon ile uyuşuyorsa zafiyetli olarak kayıt eder.
Gerekli Kütüphaneler:
Ruby:
require 'httparty'
require 'optparse'
require 'eventmachine'
- httparty: HTTP istekleri yapmak için kullanılan bir gem.
- optparse: Komut satırı seçeneklerini ayrıştırmak için kullanılan bir standart Ruby kütüphanesi.
- eventmachine: Olay odaklı programlama için kullanılan bir Ruby kütüphanesi.
Sınıf Tanımı: WP_Upload_Check:
Ruby:
class WP_Upload_Check
Bu sınıf, verilen URL'ler temelinde WordPress eklenti sürümlerini kontrol etmekten sorumludur.
Başlatma:
Ruby:
def initialize
@headers = {
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'
}
@vuln_urls = []
@params = {
input_file: nil,
output_file: "output_file.txt",
}
end
- @headers: HTTP istekleri için User-Agent başlığını içerir.
- @vuln_urls: Savunmasız URL'leri depolamak için bir dizi.
- @params: Giriş ve çıkış dosya yolları için varsayılan değerlere sahip bir hash.
-
Ruby:
def detect_plugin_version(readme)
start_tag = "Stable tag: "
version_start_index = readme.index(start_tag)
raise "Invalid readme format: 'Stable tag: ' not found." if version_start_index.nil?
version_end_index = readme.index("\n", version_start_index)
raise "Invalid readme format: Version end tag not found." if version_end_index.nil?
plugin_version = readme[version_start_index + start_tag.length, version_end_index - version_start_index - start_tag.length]
raise "Plugin version is missing." if plugin_version.empty?
plugin_version
end
detect_plugin_version fonksiyonu başlatılır ve readme parametresiyle çağrılır. Bu parametre, eklentinin readme dosyasının içeriğini içerir.
start_tag değişkeni, eklentinin sürüm numarasının başladığı etiketi belirtir. Bu örnekte, etiket "Stable tag: " olarak tanımlanmıştır.
version_start_index değişkeni, readme metnindeki start_tag'in ilk indeksini bulur. Eğer bulunamazsa (nil ise), "Invalid readme format: 'Stable tag: ' not found." hata mesajını fırlatır.
version_end_index değişkeni, version_start_index sonrasındaki ilk satır sonunu (newline karakterini) bulur.
Eğer bulunamazsa (nil ise), "Invalid readme format: Version end tag not found." hata mesajını fırlatır.plugin_version değişkeni, readme metnindeki sürüm numarasını içerir. Bu, version_start_index'den start_tag.length kadar ilerleyerek başlar ve version_end_index - version_start_index - start_tag.length kadar devam eder.
Eğer plugin_version boşsa (yoksa), yani sürüm numarası bulunamamışsa, "Plugin version is missing." hata mesajını fırlatır.
Fonksiyon, geçerli bir sürüm numarası bulunduğunda bu numarayı döndürür. Bu sayede, eklentinin sürümü başarıyla belirlenmiş olur.
Ruby:
def control_domain(domain)
domain.start_with?('http') ? domain : "https://#{domain}"
end
def opt_parse
begin
OptionParser.new do |opts|
opts.on "-i", "--input_file INPUT_FILE" do |input_file|
if File.exist?(input_file)
@params[:input_file] = input_file
else
puts("File not found: #{input_file}".red)
exit(1)
end
end
opts.on "-o", "--output_file OUTPUT_FILE" do |output_file|
@params[:output_file] = output_file
end
end.parse!
rescue Exception => exception
puts("Error: #{exception}".red)
end
end
control_domain: Bu fonksiyon, bir alan adını kontrol eder. Eğer alan adı 'http' ile başlıyorsa, aynı şekilde bırakılır; ancak başlamıyorsa, "https://" ön ekini ekleyerek düzenlenir. Yani, bu fonksiyonun amacı, sağlanan bir alan adını 'http' ile başlatmak veya 'https://' ön eki eklemektir.
opt_parse:
Bu fonksiyon, OptionParser kullanarak komut satırı seçeneklerini ayrıştırır. Şu adımları içerir:
OptionParser.new ile yeni bir OptionParser örneği oluşturulur.
-i veya --input_file seçeneği kullanıcı tarafından belirlendiyse, input_file değişkenine atanır. Eğer dosya mevcut değilse, hata mesajı yazdırılır ve program çıkış yapar (exit(1)).
-o veya --output_file seçeneği kullanıcı tarafından belirlendiyse, output_file değişkenine atanır.
parse! metodu ile komut satırı seçenekleri ayrıştırılır.
Hata durumlarına karşı bir begin ve rescue bloğu kullanılarak hata durumları ele alınır ve hata mesajı yazdırılır.
Bu fonksiyonlar, kullanıcı tarafından sağlanan alan adlarını ve komut satırı seçeneklerini düzenlemek ve kontrol etmek için kullanılır.
Ruby:
def check_site(domain, output_file)
domain = control_domain(domain)
plugin_url = "#{domain}/wp-content/plugins/contact-form-7/readme.txt"
begin
response = HTTParty.get(plugin_url, headers: @headers, timeout: 3)
if response.code == 200
plugin_version = detect_plugin_version(response.body)
is_vuln = Gem::Version.new(plugin_version) < Gem::Version.new('5.3.2')
if output_file and is_vuln
unless @vuln_urls.include?(domain)
@vuln_urls << domain
puts("#{plugin_url} --> Vuln".green)
File.open(output_file, 'a+') do |file|
file.puts(domain)
end
end
else
puts("#{domain} --> Fail".red)
end
end
rescue Net::OpenTimeout
puts("#{domain} --> Fail".red)
rescue StandardError => err
puts("#{domain} --> Fail".red)
end
end
Bu fonksiyon, verilen bir alan adındaki WordPress eklentisinin sürümünü kontrol eder ve belirli bir sürümden eski ise savunmasız kabul eder.
Domain Kontrolü ve Plugin URL Oluşturma:
domain kontrol edilir ve gerekirse 'https://' ön eki eklenir.
WordPress eklentinin readme dosyasının URL'si oluşturulur.
HTTP İstek ve Cevap Kontrolü:
Oluşturulan URL'ye HTTP GET isteği yapılır.
Aldığı cevap kontrol edilir.
Cevap Durumu ve Sürüm Kontrolü:
Eğer HTTP cevabı başarılı ise devam edilir.
Readme dosyasından eklenti sürümü çekilir.
Eklenti sürümü belirli bir sürümden küçük mü kontrol edilir.
Vulnerability Kontrolü ve Loglama:
Bir çıkış dosyası belirlenmişse ve eklenti savunmasızsa:
Daha önce eklenmemişse, savunmasız URL yazdırılır ve çıkış dosyasına eklenir.
Eğer çıkış dosyası belirlenmemişse veya eklenti savunmasız değilse, hata mesajı yazdırılır.
Hata Durumlarına Karşı İstisna Yönetimi:
Eğer HTTP isteği belirlenen sürede yanıt almazsa veya başka bir hata olursa, hata mesajı yazdırılır.
Bu fonksiyon, verilen bir alan adının eklentisinin sürümünü kontrol eder ve sonucu çıkış dosyasına veya ekrana yazdırır.
Ruby:
def print_help
help_text = <<-'HELP_TEXT'
USAGE: ruby upload_checker.rb [options]
OPTIONS:
-i, --input_file FILE: Define the path to the URL file.
-o, --output_file FILE: Define the name of the output log file.
HELP_TEXT
puts(help_text.magenta)
end
- Bu fonksiyon, komut satırında kullanılabilir seçenekleri ve kullanım talimatlarını içeren bir metin bloğunu oluşturur.
- puts(help_text.magenta) ifadesi ile bu metni ekrana yazdırır. magenta renk kodu, metni pembe renkte gösterir.
Ruby:
def parse_lines(group)
group.each do |line|
check_site(line.strip, @params[:output_file])
end
end
Bu fonksiyon, bir grup satırı alır ve her bir satırı check_site fonksiyonuna geçirir. line.strip ifadesi ile her satırdaki boşlukları temizler.
Ruby:
def main
opt_parse
unless @params[:input_file].nil?
lines = File.readlines(@params[:input_file])
lines.each_slice(4) do |group_lines|
parse_lines(group_lines)
end
puts("Completed.".magenta)
EM.stop
else
print_help
end
end
main fonksiyonu, betiğin ana çalışma mantığını içerir.
opt_parse fonksiyonu çağrılarak komut satırı seçenekleri ayrıştırılır.
Eğer bir giriş dosyası belirlenmişse:
Dosyadaki satırlar okunur ve her dört satırı bir grup olarak ele alır.
Her grup satırları parse_lines fonksiyonuna geçirilir.
İşlem tamamlandığında "Completed." mesajı yazdırılır ve EventMachine event loop'u kapatılır (EM.stop).
Eğer giriş dosyası belirlenmemişse, print_help fonksiyonu çağrılarak kullanım talimatları ekrana yazdırılır.
Ruby:
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
- Bu kod bloğu, String sınıfına üç ayrı renk metodunu ekler: red, green, ve magenta.
-
- Bu metodlar, metin içinde bu renklere sahip çıktı üretmek için kullanılabilir. \e[31m, \e[32m, ve \e[35m ANSI renk kodları sırasıyla kırmızı, yeşil ve pembe renkleri temsil eder.
-
- \e[0m ise renkli çıktıyı sıfırlar, yani sonrasında gelecek metin normal renkte olur.
-
Ruby:
EM.run do
begin
upload_checker = WP_Upload_Check.new
upload_checker.main
rescue => error
puts("Error: #{error}")
end
end
- EM.run do ile EventMachine olay döngüsü başlatılır.
-
- begin ve rescue blokları ile olası hatalara karşı bir hata yönetimi sağlanır.
-
- WP_Upload_Check sınıfından bir örnek (upload_checker) oluşturulur ve main fonksiyonu çağrılır.
-
- Eğer herhangi bir hata oluşursa (örneğin, WP_Upload_Check sınıfındaki main fonksiyonunda bir hata), hata mesajı ekrana yazdırılır.
Tüm Kod:
Ruby:
require 'httparty'
require 'optparse'
require 'eventmachine'
class WP_Upload_Check
def initialize
@headers = {
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'
}
@vuln_urls = []
@params = {
input_file: nil,
output_file: "output_file.txt",
}
end
def detect_plugin_version(readme)
start_tag = "Stable tag: "
version_start_index = readme.index(start_tag)
raise "Invalid readme format: 'Stable tag: ' not found." if version_start_index.nil?
version_end_index = readme.index("\n", version_start_index)
raise "Invalid readme format: Version end tag not found." if version_end_index.nil?
plugin_version = readme[version_start_index + start_tag.length, version_end_index - version_start_index - start_tag.length]
raise "Plugin version is missing." if plugin_version.empty?
plugin_version
end
def control_domain(domain)
domain.start_with?('http') ? domain : "https://#{domain}"
end
def opt_parse
begin
OptionParser.new do |opts|
opts.on "-i", "--input_file INPUT_FILE" do |input_file|
if File.exist?(input_file)
@params[:input_file] = input_file
else
puts("File not found: #{input_file}".red)
exit(1)
end
end
opts.on "-o", "--output_file OUTPUT_FILE" do |output_file|
@params[:output_file] = output_file
end
end.parse!
rescue Exception => exception
puts("Error: #{exception}".red)
end
end
def check_site(domain, output_file)
domain = control_domain(domain)
plugin_url = "#{domain}/wp-content/plugins/contact-form-7/readme.txt"
begin
response = HTTParty.get(plugin_url, headers: @headers, timeout: 3)
if response.code == 200
plugin_version = detect_plugin_version(response.body)
is_vuln = Gem::Version.new(plugin_version) < Gem::Version.new('5.3.2')
if output_file and is_vuln
unless @vuln_urls.include?(domain)
@vuln_urls << domain
puts("#{plugin_url} --> Vuln".green)
File.open(output_file, 'a+') do |file|
file.puts(domain)
end
end
else
puts("#{domain} --> Fail".red)
end
end
rescue Net::OpenTimeout
puts("#{domain} --> Fail".red)
rescue StandardError => err
puts("#{domain} --> Fail".red)
end
end
def print_help
help_text = <<-'HELP_TEXT'
USAGE: ruby upload_checker.rb [options]
OPTIONS:
-i, --input_file FILE: Define the path to the URL file.
-o, --output_file FILE: Define the name of the output log file.
HELP_TEXT
puts(help_text.magenta)
end
def parse_lines(group)
group.each do |line|
check_site(line.strip, @params[:output_file])
end
end
def main
opt_parse
unless @params[:input_file].nil?
lines = File.readlines(@params[:input_file])
lines.each_slice(4) do |group_lines|
parse_lines(group_lines)
end
puts("Completed.".magenta)
EM.stop
else
print_help
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
begin
upload_checker = WP_Upload_Check.new
upload_checker.main
rescue => error
puts("Error: #{error}")
end
end
Emeğe karşılık konuyu beğenebilirsiniz. Örnek olması amacıyla açılmıştır.
Saatin biraz geç olması dolayısıyla gözden kaçan şeyler olabilir
İyi forumlar...
Github: GitHub - thebunjo/CVE-2020-35489
Son düzenleme: