dap 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dap/filter/ldap.rb +3 -1
- data/lib/dap/proto/ldap.rb +147 -18
- data/lib/dap/version.rb +1 -1
- data/spec/dap/filter/ldap_filter_spec.rb +1 -0
- data/spec/dap/proto/ldap_proto_spec.rb +72 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b61714c9b3553759bb7c726aad187acbf2adddf
|
4
|
+
data.tar.gz: e5f0ea147aa9a6f9fbcbb09854221b3d247eb467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53c0c68b19babf428a673063963122bb57f1e185a8205423f29c5e66ee9b4e2e3f2cfa5e905741a2bcf742358b2c5ddaef689edb862cb152a68ba453c7e8f592
|
7
|
+
data.tar.gz: fa2601b3d4bcaa99f0e3c4087e101e80f423a42228b95d53325c9bf31355aa8b186c3801f2a20b06a8cc1f4791ac6dfd1bc0b631fc829d4afa7e97b211fad25f
|
data/lib/dap/filter/ldap.rb
CHANGED
@@ -36,9 +36,11 @@ class FilterDecodeLdapSearchResult
|
|
36
36
|
begin
|
37
37
|
elem_decoded = OpenSSL::ASN1.decode(element)
|
38
38
|
rescue Exception => e
|
39
|
-
err_msg = 'FilterDecodeLdapSearchResult - Unable to decode
|
39
|
+
err_msg = 'FilterDecodeLdapSearchResult - Unable to decode ASN.1 element'
|
40
40
|
$stderr.puts "#{err_msg}: #{e}"
|
41
41
|
$stderr.puts e.backtrace
|
42
|
+
$stderr.puts "Element:\n\t#{element.inspect}"
|
43
|
+
$stderr.puts "Element hex:\n\t#{element.unpack('H*')}\n\n"
|
42
44
|
info['Error'] = { 'errorMessage' => err_msg }
|
43
45
|
next
|
44
46
|
end
|
data/lib/dap/proto/ldap.rb
CHANGED
@@ -2,6 +2,44 @@ module Dap
|
|
2
2
|
module Proto
|
3
3
|
class LDAP
|
4
4
|
|
5
|
+
# LDAPResult element resultCode lookup
|
6
|
+
# Reference: https://tools.ietf.org/html/rfc4511#section-4.1.9
|
7
|
+
# https://ldapwiki.willeke.com/wiki/LDAP%20Result%20Codes
|
8
|
+
RESULT_DESC = {
|
9
|
+
0 => 'success',
|
10
|
+
1 => 'operationsError',
|
11
|
+
2 => 'protocolError',
|
12
|
+
3 => 'timeLimitExceeded',
|
13
|
+
4 => 'sizeLimitExceeded',
|
14
|
+
5 => 'compareFalse',
|
15
|
+
6 => 'compareTrue',
|
16
|
+
7 => 'authMethodNotSupported',
|
17
|
+
8 => 'strongerAuthRequired',
|
18
|
+
9 => 'reserved',
|
19
|
+
10 => 'referral',
|
20
|
+
11 => 'adminLimitExceeded',
|
21
|
+
12 => 'unavailableCriticalExtension',
|
22
|
+
13 => 'confidentialityRequired',
|
23
|
+
14 => 'saslBindInProgress',
|
24
|
+
16 => 'noSuchAttribute',
|
25
|
+
17 => 'undefinedAttributeType',
|
26
|
+
18 => 'inappropriateMatching',
|
27
|
+
19 => 'constraintViolation',
|
28
|
+
20 => 'attributeOrValueExists',
|
29
|
+
21 => 'invalidAttributeSyntax',
|
30
|
+
32 => 'noSuchObject',
|
31
|
+
34 => 'invalidDNSyntax',
|
32
|
+
48 => 'inappropriateAuthentication',
|
33
|
+
49 => 'invalidCredentials',
|
34
|
+
50 => 'insufficientAccessRights',
|
35
|
+
51 => 'busy',
|
36
|
+
52 => 'unavailable',
|
37
|
+
53 => 'unwillingToPerform',
|
38
|
+
64 => 'namingViolation',
|
39
|
+
80 => 'other',
|
40
|
+
82 => 'localError (client response)',
|
41
|
+
94 => 'noResultsReturned (client response)',
|
42
|
+
}
|
5
43
|
|
6
44
|
#
|
7
45
|
# Parse ASN1 element and extract the length.
|
@@ -25,11 +63,15 @@ class LDAP
|
|
25
63
|
len_bytes = length - 128
|
26
64
|
return unless data.length > len_bytes + 2
|
27
65
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
66
|
+
# This shouldn't happen...
|
67
|
+
return unless len_bytes > 0
|
68
|
+
|
69
|
+
length = 0
|
70
|
+
len_bytes.times do |i|
|
71
|
+
temp_len = data.byteslice(2 + i).unpack('C')[0]
|
72
|
+
length = ( length << 8 ) + temp_len
|
32
73
|
end
|
74
|
+
|
33
75
|
elem_start += len_bytes
|
34
76
|
end
|
35
77
|
|
@@ -61,6 +103,49 @@ class LDAP
|
|
61
103
|
messages
|
62
104
|
end
|
63
105
|
|
106
|
+
#
|
107
|
+
# Parse an LDAPResult (not SearchResult) ASN.1 structure
|
108
|
+
# Reference: https://tools.ietf.org/html/rfc4511#section-4.1.9
|
109
|
+
#
|
110
|
+
# @param data [OpenSSL::ASN1::ASN1Data] LDAPResult structure
|
111
|
+
# @return [Hash] Hash containing decoded LDAP response
|
112
|
+
#
|
113
|
+
def self.parse_ldapresult(ldap_result)
|
114
|
+
results = {}
|
115
|
+
|
116
|
+
# Sanity check the result code element
|
117
|
+
if ldap_result.value[0] && ldap_result.value[0].value
|
118
|
+
code_elem = ldap_result.value[0]
|
119
|
+
return results unless code_elem.tag == 10 && code_elem.tag_class == :UNIVERSAL
|
120
|
+
results['resultCode'] = code_elem.value.to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
# These are probably safe if the resultCode validates
|
124
|
+
results['resultDesc'] = RESULT_DESC[ results['resultCode'] ] if results['resultCode']
|
125
|
+
results['resultMatchedDN'] = ldap_result.value[1].value if ldap_result.value[1] && ldap_result.value[1].value
|
126
|
+
results['resultdiagMessage'] = ldap_result.value[2].value if ldap_result.value[2] && ldap_result.value[2].value
|
127
|
+
|
128
|
+
# Handle optional elements that may be returned by certain
|
129
|
+
# LDAP application messages
|
130
|
+
ldap_result.value.each do |element|
|
131
|
+
next unless element.tag_class && element.tag && element.value
|
132
|
+
next unless element.tag_class == :CONTEXT_SPECIFIC
|
133
|
+
|
134
|
+
case element.tag
|
135
|
+
when 3
|
136
|
+
results['referral'] = element.value
|
137
|
+
when 7
|
138
|
+
results['serverSaslCreds'] = element.value
|
139
|
+
when 10
|
140
|
+
results['responseName'] = element.value
|
141
|
+
when 11
|
142
|
+
results['responseValue'] = element.value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
results
|
147
|
+
end
|
148
|
+
|
64
149
|
#
|
65
150
|
# Parse an LDAP SearchResult entry.
|
66
151
|
#
|
@@ -88,29 +173,73 @@ class LDAP
|
|
88
173
|
results['objectName'] = data.value[1].value[0].value
|
89
174
|
end
|
90
175
|
|
91
|
-
|
176
|
+
if data.value[1].value[1]
|
177
|
+
attrib_hash = {}
|
92
178
|
|
93
|
-
|
94
|
-
|
179
|
+
# Handle PartialAttributeValues
|
180
|
+
data.value[1].value[1].each do |partial_attrib|
|
95
181
|
|
96
|
-
|
97
|
-
|
182
|
+
value_array = []
|
183
|
+
attrib_type = partial_attrib.value[0].value
|
98
184
|
|
99
|
-
|
100
|
-
|
185
|
+
partial_attrib.value[1].each do |part_attrib_value|
|
186
|
+
value_array.push(part_attrib_value.value)
|
187
|
+
end
|
188
|
+
|
189
|
+
attrib_hash[attrib_type] = value_array
|
101
190
|
end
|
102
191
|
|
103
|
-
|
192
|
+
results['PartialAttributes'] = attrib_hash
|
104
193
|
end
|
105
194
|
|
106
|
-
|
107
|
-
|
108
|
-
elsif data.value[1].tag == 5
|
195
|
+
elsif data.value[1] && data.value[1].tag == 5
|
109
196
|
# SearchResultDone found..
|
110
197
|
result_type = 'SearchResultDone'
|
111
|
-
|
112
|
-
|
113
|
-
|
198
|
+
ldap_result = data.value[1]
|
199
|
+
|
200
|
+
if ldap_result.value[0] && ldap_result.value[0].class == OpenSSL::ASN1::Sequence
|
201
|
+
# Encoding of the SearchResultDone seems to vary, this is RFC format
|
202
|
+
# of an LDAPResult ASN.1 structure in which the data is contained in a
|
203
|
+
# Sequence
|
204
|
+
results = parse_ldapresult(ldap_result.value[0])
|
205
|
+
elsif ldap_result.value[0]
|
206
|
+
# LDAPResult w/o outer Sequence wrapper, used by MS Windows
|
207
|
+
results = parse_ldapresult(ldap_result)
|
208
|
+
end
|
209
|
+
if data.value[2] && data.value[2].tag == 10
|
210
|
+
# Unknown structure for providing a response, looks like LDAPResult
|
211
|
+
# but placed at a higher level in the response, salvage what we can..
|
212
|
+
results['resultCode'] = data.value[2].value.to_i if data.value[2].value
|
213
|
+
results['resultDesc'] = RESULT_DESC[ results['resultCode'] ] if results['resultCode']
|
214
|
+
results['resultMatchedDN'] = data.value[3].value if data.value[3] && data.value[3].value
|
215
|
+
results['resultdiagMessage'] = data.value[4].value if data.value[4] && data.value[4].value
|
216
|
+
end
|
217
|
+
|
218
|
+
elsif data.value[1] && data.value[1].tag == 1
|
219
|
+
result_type = 'BindResponse'
|
220
|
+
results = parse_ldapresult(data.value[1])
|
221
|
+
|
222
|
+
elsif data.value[1] && data.value[1].tag == 2
|
223
|
+
result_type = 'UnbindRequest'
|
224
|
+
|
225
|
+
elsif data.value[1] && data.value[1].tag == 3
|
226
|
+
# There is no legitimate use of application tag 3
|
227
|
+
# in this context per RFC 4511. Try to figure
|
228
|
+
# out what the intent is.
|
229
|
+
resp_data = data.value[1]
|
230
|
+
if resp_data.value[0].tag == 10 && resp_data.value[2].tag == 4
|
231
|
+
# Probably an incorrectly tagged BindResponse
|
232
|
+
result_type = 'BindResponse'
|
233
|
+
results = parse_ldapresult(resp_data)
|
234
|
+
else
|
235
|
+
result_type = 'UnhandledTag'
|
236
|
+
results['tagNumber'] = data.value[1].tag.to_i if data.value[1].tag
|
237
|
+
end
|
238
|
+
|
239
|
+
elsif data.value[1] && data.value[1].tag == 24
|
240
|
+
result_type = 'ExtendedResponse'
|
241
|
+
results = parse_ldapresult(data.value[1])
|
242
|
+
|
114
243
|
else
|
115
244
|
# Unhandled tag
|
116
245
|
result_type = 'UnhandledTag'
|
data/lib/dap/version.rb
CHANGED
@@ -4,6 +4,7 @@ describe Dap::Proto::LDAP do
|
|
4
4
|
describe '.decode_elem_length' do
|
5
5
|
context 'testing lengths shorter than 128 bits' do
|
6
6
|
data = ['301402'].pack('H*')
|
7
|
+
|
7
8
|
let(:decode_len) { subject.decode_elem_length(data) }
|
8
9
|
it 'returns a Fixnum' do
|
9
10
|
expect(decode_len.class).to eq(::Fixnum)
|
@@ -15,6 +16,7 @@ describe Dap::Proto::LDAP do
|
|
15
16
|
|
16
17
|
context 'testing lengths greater than 128 bits' do
|
17
18
|
data = ['308400000bc102010'].pack('H*')
|
19
|
+
|
18
20
|
let(:decode_len) { subject.decode_elem_length(data) }
|
19
21
|
it 'returns a Fixnum' do
|
20
22
|
expect(decode_len.class).to eq(::Fixnum)
|
@@ -24,8 +26,21 @@ describe Dap::Proto::LDAP do
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
29
|
+
context 'testing with 3 byte length' do
|
30
|
+
data = ['3083015e0802010764'].pack('H*')
|
31
|
+
|
32
|
+
let(:decode_len) { subject.decode_elem_length(data) }
|
33
|
+
it 'returns a Fixnum' do
|
34
|
+
expect(decode_len.class).to eq(::Fixnum)
|
35
|
+
end
|
36
|
+
it 'returns value correctly' do
|
37
|
+
expect(decode_len).to eq(89613)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
27
41
|
context 'testing invalid length' do
|
28
42
|
data = ['308400000bc1'].pack('H*')
|
43
|
+
|
29
44
|
let(:decode_len) { subject.decode_elem_length(data) }
|
30
45
|
it 'returns nil as expected' do
|
31
46
|
expect(decode_len).to eq(nil)
|
@@ -77,6 +92,44 @@ describe Dap::Proto::LDAP do
|
|
77
92
|
end
|
78
93
|
end
|
79
94
|
|
95
|
+
describe '.parse_ldapresult' do
|
96
|
+
|
97
|
+
context 'testing valid data' do
|
98
|
+
hex = ['300c02010765070a010004000400']
|
99
|
+
data = OpenSSL::ASN1.decode(hex.pack('H*'))
|
100
|
+
|
101
|
+
let(:parse_ldapresult) { subject.parse_ldapresult(data.value[1]) }
|
102
|
+
it 'returns Hash as expected' do
|
103
|
+
expect(parse_ldapresult.class).to eq(::Hash)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns results as expected' do
|
107
|
+
test_val = { 'resultCode' => 0,
|
108
|
+
'resultDesc' => 'success',
|
109
|
+
'resultMatchedDN' => '',
|
110
|
+
'resultdiagMessage' => ''
|
111
|
+
}
|
112
|
+
expect(parse_ldapresult).to eq(test_val)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'testing invalid data' do
|
117
|
+
hex = ['300702010765020400']
|
118
|
+
data = OpenSSL::ASN1.decode(hex.pack('H*'))
|
119
|
+
|
120
|
+
let(:parse_ldapresult) { subject.parse_ldapresult(data.value[1]) }
|
121
|
+
it 'returns Hash as expected' do
|
122
|
+
expect(parse_ldapresult.class).to eq(::Hash)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns empty Hash as expected' do
|
126
|
+
test_val = {}
|
127
|
+
expect(parse_ldapresult).to eq(test_val)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
80
133
|
describe '.parse_messages' do
|
81
134
|
|
82
135
|
context 'testing SearchResultEntry' do
|
@@ -115,6 +168,7 @@ describe Dap::Proto::LDAP do
|
|
115
168
|
it 'returns SearchResultDone value as expected' do
|
116
169
|
test_val = ['SearchResultDone', {
|
117
170
|
'resultCode' => 0,
|
171
|
+
'resultDesc' => 'success',
|
118
172
|
'resultMatchedDN' => '',
|
119
173
|
'resultdiagMessage' => ''
|
120
174
|
}]
|
@@ -122,6 +176,24 @@ describe Dap::Proto::LDAP do
|
|
122
176
|
end
|
123
177
|
end
|
124
178
|
|
179
|
+
context 'testing SearchResultDone - edge case #1' do
|
180
|
+
hex = ['300802010765000a0101']
|
181
|
+
data = OpenSSL::ASN1.decode(hex.pack('H*'))
|
182
|
+
|
183
|
+
let(:parse_message) { subject.parse_message(data) }
|
184
|
+
it 'returns Array as expected' do
|
185
|
+
expect(parse_message.class).to eq(::Array)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'returns operationsError as expected' do
|
189
|
+
test_val = ['SearchResultDone', {
|
190
|
+
'resultCode' => 1,
|
191
|
+
'resultDesc' => 'operationsError'
|
192
|
+
}]
|
193
|
+
expect(parse_message).to eq(test_val)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
125
197
|
context 'testing UnhandledTag' do
|
126
198
|
hex = ['300c02010767070a010004000400']
|
127
199
|
data = OpenSSL::ASN1.decode(hex.pack('H*'))
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rapid7 Research
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -248,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
248
248
|
version: '0'
|
249
249
|
requirements: []
|
250
250
|
rubyforge_project:
|
251
|
-
rubygems_version: 2.5
|
251
|
+
rubygems_version: 2.2.5
|
252
252
|
signing_key:
|
253
253
|
specification_version: 4
|
254
254
|
summary: 'DAP: The Data Analysis Pipeline'
|
@@ -257,3 +257,4 @@ test_files:
|
|
257
257
|
- spec/dap/proto/ipmi_spec.rb
|
258
258
|
- spec/dap/proto/ldap_proto_spec.rb
|
259
259
|
- spec/spec_helper.rb
|
260
|
+
has_rdoc:
|