Files
beef/extensions/social_engineering/mass_mailer/mass_mailer.rb
jcrew99 486a9bb329 Update copyright 2023 (#2675)
* updated copyright

* reverted gemfile lock changes
2022-12-31 15:36:07 +10:00

239 lines
8.8 KiB
Ruby

#
# Copyright (c) 2006-2023 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module SocialEngineering
class MassMailer
require 'net/smtp'
require 'base64'
include Singleton
def initialize
@config = BeEF::Core::Configuration.instance
@config_prefix = 'beef.extension.social_engineering.mass_mailer'
@templates_dir = "#{File.expand_path('../../../extensions/social_engineering/mass_mailer/templates', __dir__)}/"
@user_agent = @config.get("#{@config_prefix}.user_agent")
@host = @config.get("#{@config_prefix}.host")
@port = @config.get("#{@config_prefix}.port")
@helo = @config.get("#{@config_prefix}.helo")
@auth = @config.get("#{@config_prefix}.auth")
@password = @config.get("#{@config_prefix}.password")
end
# tos_hash is an Hash like:
# 'antisnatchor@gmail.com' => 'Michele'
# 'ciccio@pasticcio.com' => 'Ciccio'
def send_email(template, fromname, fromaddr, subject, link, linktext, tos_hash)
# create new SSL context and disable CA chain validation
if @config.get("#{@config_prefix}.use_tls")
@ctx = OpenSSL::SSL::SSLContext.new
unless @config.get("#{@config_prefix}.verify_ssl")
@ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # In case the SMTP server uses a self-signed cert, we proceed anyway
end
@ctx.ssl_version = 'TLSv1'
end
n = tos_hash.size
x = 1
print_info "Sending #{n} mail(s) from [#{fromaddr}] - name [#{fromname}] using template [#{template}]:"
print_info "subject: #{subject}"
print_info "link: #{link}"
print_info "linktext: #{linktext}"
# create a new SMTP object, enable TLS with the previous instantiated context, and connects to the server
smtp = Net::SMTP.new(@host, @port)
smtp.enable_starttls(@ctx) unless @config.get("#{@config_prefix}.use_tls") == false
if @config.get("#{@config_prefix}.use_auth")
smtp.start(@helo, @auth, @password, :login) do |smtp|
tos_hash.each do |to, name|
message = compose_email(fromname, fromaddr, to, name, subject, link, linktext, template)
smtp.send_message(message, fromaddr, to)
print_info "Mail #{x}/#{n} to [#{to}] sent."
x += 1
end
end
else
smtp.start(@helo) do |smtp|
tos_hash.each do |to, name|
message = compose_email(fromname, fromaddr, to, name, subject, link, linktext, template)
smtp.send_message(message, fromaddr, to)
print_info "Mail #{x}/#{n} to [#{to}] sent."
x += 1
end
end
end
end
def compose_email(fromname, fromaddr, to, name, subject, link, linktext, template)
begin
msg_id = random_string(50)
boundary = "------------#{random_string(24)}"
rel_boundary = "------------#{random_string(24)}"
header = email_headers(fromaddr, fromname, @user_agent, to, subject, msg_id, boundary)
plain_body = email_plain_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.plain", template), boundary)
rel_header = email_related(rel_boundary)
html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template), rel_boundary)
images = ''
@config.get("#{@config_prefix}.templates.#{template}.images").each do |image|
images += email_add_image(image, "#{@templates_dir}#{template}/#{image}", rel_boundary)
end
attachments = ''
unless @config.get("#{@config_prefix}.templates.#{template}.attachments").nil?
@config.get("#{@config_prefix}.templates.#{template}.attachments").each do |attachment|
attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}", rel_boundary)
end
end
close = email_close(boundary)
rescue StandardError => e
print_error 'Error constructing email.'
raise
end
message = header + plain_body + rel_header + html_body + images + attachments + close
print_debug "Raw Email content:\n #{message}"
message
end
def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary)
<<~EOF
From: "#{fromname}" <#{from}>
Reply-To: "#{fromname}" <#{from}>
Return-Path: "#{fromname}" <#{from}>
X-Mailer: #{user_agent}
To: #{to}
Message-ID: <#{msg_id}@#{@host}>
X-Spam-Status: No, score=0.001 required=5
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary=#{boundary}
This is a multi-part message in MIME format.
--#{boundary}
EOF
end
def email_plain_body(plain_text, boundary)
<<~EOF
Content-Type: text/plain; charset="utf8"
Content-Transfer-Encoding:8bit
#{plain_text}
--#{boundary}
EOF
end
def email_related(rel_boundary)
<<~EOF
Content-Type: multipart/related;
boundary="#{rel_boundary}"
--#{rel_boundary}
EOF
end
def email_html_body(html_body, rel_boundary)
<<~EOF
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
#{html_body}
--#{rel_boundary}
EOF
end
def email_add_image(name, path, rel_boundary)
file_encoded = [File.read(path)].pack('m') # base64 encoded
<<~EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-ID: <#{name}>
Content-Disposition: inline;
filename="#{name}"
#{file_encoded}
--#{rel_boundary}
EOF
end
def email_add_attachment(name, path, rel_boundary)
file_encoded = [File.read(path)].pack('m') # base64 encoded
<<~EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="#{name}"
#{file_encoded}
--#{rel_boundary}
EOF
end
def email_close(boundary)
<<~EOF
--#{boundary}--
EOF
end
# Replaces placeholder values from the plain/html email templates
def parse_template(name, link, linktext, template_path, template)
result = ''
img_config = "#{@config_prefix}.templates.#{template}.images_cids"
img_count = 0
File.open(template_path, 'r').each do |line|
# change the Recipient name
if line.include?('__name__')
result += line.gsub('__name__', name)
# change the link/linktext
elsif line.include?('__link__')
result += if line.include?('__linktext__')
line.gsub('__link__', link).gsub('__linktext__', linktext)
else
line.gsub('__link__', link)
end
# change images cid/name/alt
elsif line.include?('src="cid:__')
img_count += 1
result += if line.include?('name="img__') || line.include?('alt="__img')
line.gsub("__cid#{img_count}__",
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
@config.get("#{img_config}.cid#{img_count}"))
else
line.gsub("__cid#{img_count}__", @config.get("#{img_config}.cid#{img_count}"))
end
else
result += line
end
end
result
end
def get_mime(file_path)
result = ''
IO.popen(['file', '--mime', '-b', file_path.to_s], 'r+') do |io|
result = io.readlines.first.split(';').first
end
result
end
def random_string(length)
output = (0..length).map { rand(36).to_s(36).upcase }.join
end
end
end
end
end