Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under under a slightly modified version 

4# of the Apache Software License. See the accompanying LICENSE file 

5# for more information. 

6# 

7# Author: Alberto Solino (beto@coresecurity.com) 

8# 

9# Description: 

10# SPNEGO functions used by SMB, SMB2/3 and DCERPC 

11# 

12from __future__ import division 

13from __future__ import print_function 

14from struct import pack, unpack, calcsize 

15 

16############### GSS Stuff ################ 

17GSS_API_SPNEGO_UUID = b'\x2b\x06\x01\x05\x05\x02' 

18ASN1_SEQUENCE = 0x30 

19ASN1_AID = 0x60 

20ASN1_OID = 0x06 

21ASN1_OCTET_STRING = 0x04 

22ASN1_MECH_TYPE = 0xa0 

23ASN1_MECH_TOKEN = 0xa2 

24ASN1_SUPPORTED_MECH = 0xa1 

25ASN1_RESPONSE_TOKEN = 0xa2 

26ASN1_ENUMERATED = 0x0a 

27MechTypes = { 

28b'+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider', 

29b'*\x86H\x82\xf7\x12\x01\x02\x02': 'MS KRB5 - Microsoft Kerberos 5', 

30b'*\x86H\x86\xf7\x12\x01\x02\x02': 'KRB5 - Kerberos 5', 

31b'*\x86H\x86\xf7\x12\x01\x02\x02\x03': 'KRB5 - Kerberos 5 - User to User', 

32b'\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism' 

33} 

34 

35TypesMech = dict((v,k) for k, v in MechTypes.items()) 

36 

37def asn1encode(data = ''): 

38 #res = asn1.SEQUENCE(str).encode() 

39 #import binascii 

40 #print '\nalex asn1encode str: %s\n' % binascii.hexlify(str) 

41 if 0 <= len(data) <= 0x7F: 

42 res = pack('B', len(data)) + data 

43 elif 0x80 <= len(data) <= 0xFF: 43 ↛ 44line 43 didn't jump to line 44, because the condition on line 43 was never true

44 res = pack('BB', 0x81, len(data)) + data 

45 elif 0x100 <= len(data) <= 0xFFFF: 45 ↛ 47line 45 didn't jump to line 47, because the condition on line 45 was never false

46 res = pack('!BH', 0x82, len(data)) + data 

47 elif 0x10000 <= len(data) <= 0xffffff: 

48 res = pack('!BBH', 0x83, len(data) >> 16, len(data) & 0xFFFF) + data 

49 elif 0x1000000 <= len(data) <= 0xffffffff: 

50 res = pack('!BL', 0x84, len(data)) + data 

51 else: 

52 raise Exception('Error in asn1encode') 

53 return res 

54 

55def asn1decode(data = ''): 

56 len1 = unpack('B', data[:1])[0] 

57 data = data[1:] 

58 if len1 == 0x81: 

59 pad = calcsize('B') 

60 len2 = unpack('B',data[:pad])[0] 

61 data = data[pad:] 

62 ans = data[:len2] 

63 elif len1 == 0x82: 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true

64 pad = calcsize('H') 

65 len2 = unpack('!H', data[:pad])[0] 

66 data = data[pad:] 

67 ans = data[:len2] 

68 elif len1 == 0x83: 68 ↛ 69line 68 didn't jump to line 69, because the condition on line 68 was never true

69 pad = calcsize('B') + calcsize('!H') 

70 len2, len3 = unpack('!BH', data[:pad]) 

71 data = data[pad:] 

72 ans = data[:len2 << 16 + len3] 

73 elif len1 == 0x84: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true

74 pad = calcsize('!L') 

75 len2 = unpack('!L', data[:pad])[0] 

76 data = data[pad:] 

77 ans = data[:len2] 

78 # 1 byte length, string <= 0x7F 

79 else: 

80 pad = 0 

81 ans = data[:len1] 

82 return ans, len(ans)+pad+1 

83 

84class GSSAPI: 

85# Generic GSSAPI Header Format  

86 def __init__(self, data = None): 

87 self.fields = {} 

88 self['UUID'] = GSS_API_SPNEGO_UUID 

89 if data: 89 ↛ 90line 89 didn't jump to line 90, because the condition on line 89 was never true

90 self.fromString(data) 

91 pass 

92 

93 def __setitem__(self,key,value): 

94 self.fields[key] = value 

95 

96 def __getitem__(self, key): 

97 return self.fields[key] 

98 

99 def __delitem__(self, key): 

100 del self.fields[key] 

101 

102 def __len__(self): 

103 return len(self.getData()) 

104 

105 def __str__(self): 

106 return len(self.getData()) 

107 

108 def fromString(self, data = None): 

109 # Manual parse of the GSSAPI Header Format 

110 # It should be something like 

111 # AID = 0x60 TAG, BER Length 

112 # OID = 0x06 TAG 

113 # GSSAPI OID 

114 # UUID data (BER Encoded) 

115 # Payload 

116 next_byte = unpack('B',data[:1])[0] 

117 if next_byte != ASN1_AID: 

118 raise Exception('Unknown AID=%x' % next_byte) 

119 data = data[1:] 

120 decode_data, total_bytes = asn1decode(data) 

121 # Now we should have a OID tag 

122 next_byte = unpack('B',decode_data[:1])[0] 

123 if next_byte != ASN1_OID: 

124 raise Exception('OID tag not found %x' % next_byte) 

125 decode_data = decode_data[1:] 

126 # Now the OID contents, should be SPNEGO UUID 

127 uuid, total_bytes = asn1decode(decode_data) 

128 self['OID'] = uuid 

129 # the rest should be the data 

130 self['Payload'] = decode_data[total_bytes:] 

131 #pass 

132 

133 def dump(self): 

134 for i in list(self.fields.keys()): 

135 print("%s: {%r}" % (i,self[i])) 

136 

137 def getData(self): 

138 ans = pack('B',ASN1_AID) 

139 ans += asn1encode( 

140 pack('B',ASN1_OID) + 

141 asn1encode(self['UUID']) + 

142 self['Payload'] ) 

143 return ans 

144 

145class SPNEGO_NegTokenResp: 

146 # https://tools.ietf.org/html/rfc4178#page-9 

147 # NegTokenResp ::= SEQUENCE { 

148 # negState [0] ENUMERATED { 

149 # accept-completed (0), 

150 # accept-incomplete (1), 

151 # reject (2), 

152 # request-mic (3) 

153 # } OPTIONAL, 

154 # -- REQUIRED in the first reply from the target 

155 # supportedMech [1] MechType OPTIONAL, 

156 # -- present only in the first reply from the target 

157 # responseToken [2] OCTET STRING OPTIONAL, 

158 # mechListMIC [3] OCTET STRING OPTIONAL, 

159 # ... 

160 # } 

161 # This structure is not prepended by a GSS generic header! 

162 SPNEGO_NEG_TOKEN_RESP = 0xa1 

163 SPNEGO_NEG_TOKEN_TARG = 0xa0 

164 

165 def __init__(self, data = None): 

166 self.fields = {} 

167 if data: 

168 self.fromString(data) 

169 pass 

170 

171 def __setitem__(self,key,value): 

172 self.fields[key] = value 

173 

174 def __getitem__(self, key): 

175 return self.fields[key] 

176 

177 def __delitem__(self, key): 

178 del self.fields[key] 

179 

180 def __len__(self): 

181 return len(self.getData()) 

182 

183 def __str__(self): 

184 return self.getData() 

185 

186 def fromString(self, data = 0): 

187 payload = data 

188 next_byte = unpack('B', payload[:1])[0] 

189 if next_byte != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true

190 raise Exception('NegTokenResp not found %x' % next_byte) 

191 payload = payload[1:] 

192 decode_data, total_bytes = asn1decode(payload) 

193 next_byte = unpack('B', decode_data[:1])[0] 

194 if next_byte != ASN1_SEQUENCE: 194 ↛ 195line 194 didn't jump to line 195, because the condition on line 194 was never true

195 raise Exception('SEQUENCE tag not found %x' % next_byte) 

196 decode_data = decode_data[1:] 

197 decode_data, total_bytes = asn1decode(decode_data) 

198 next_byte = unpack('B',decode_data[:1])[0] 

199 

200 if next_byte != ASN1_MECH_TYPE: 200 ↛ 202line 200 didn't jump to line 202, because the condition on line 200 was never true

201 # MechType not found, could be an AUTH answer 

202 if next_byte != ASN1_RESPONSE_TOKEN: 

203 raise Exception('MechType/ResponseToken tag not found %x' % next_byte) 

204 else: 

205 decode_data2 = decode_data[1:] 

206 decode_data2, total_bytes = asn1decode(decode_data2) 

207 next_byte = unpack('B', decode_data2[:1])[0] 

208 if next_byte != ASN1_ENUMERATED: 208 ↛ 209line 208 didn't jump to line 209, because the condition on line 208 was never true

209 raise Exception('Enumerated tag not found %x' % next_byte) 

210 item, total_bytes2 = asn1decode(decode_data2[1:]) 

211 self['NegState'] = item 

212 decode_data = decode_data[1:] 

213 decode_data = decode_data[total_bytes:] 

214 

215 # Do we have more data? 

216 if len(decode_data) == 0: 216 ↛ 217line 216 didn't jump to line 217, because the condition on line 216 was never true

217 return 

218 

219 next_byte = unpack('B', decode_data[:1])[0] 

220 if next_byte != ASN1_SUPPORTED_MECH: 220 ↛ 221line 220 didn't jump to line 221, because the condition on line 220 was never true

221 if next_byte != ASN1_RESPONSE_TOKEN: 

222 raise Exception('Supported Mech/ResponseToken tag not found %x' % next_byte) 

223 else: 

224 decode_data2 = decode_data[1:] 

225 decode_data2, total_bytes = asn1decode(decode_data2) 

226 next_byte = unpack('B', decode_data2[:1])[0] 

227 if next_byte != ASN1_OID: 227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true

228 raise Exception('OID tag not found %x' % next_byte) 

229 decode_data2 = decode_data2[1:] 

230 item, total_bytes2 = asn1decode(decode_data2) 

231 self['SupportedMech'] = item 

232 

233 decode_data = decode_data[1:] 

234 decode_data = decode_data[total_bytes:] 

235 next_byte = unpack('B', decode_data[:1])[0] 

236 if next_byte != ASN1_RESPONSE_TOKEN: 236 ↛ 237line 236 didn't jump to line 237, because the condition on line 236 was never true

237 raise Exception('Response token tag not found %x' % next_byte) 

238 

239 decode_data = decode_data[1:] 

240 decode_data, total_bytes = asn1decode(decode_data) 

241 next_byte = unpack('B', decode_data[:1])[0] 

242 if next_byte != ASN1_OCTET_STRING: 242 ↛ 243line 242 didn't jump to line 243, because the condition on line 242 was never true

243 raise Exception('Octet string token tag not found %x' % next_byte) 

244 decode_data = decode_data[1:] 

245 decode_data, total_bytes = asn1decode(decode_data) 

246 self['ResponseToken'] = decode_data 

247 

248 def dump(self): 

249 for i in list(self.fields.keys()): 

250 print("%s: {%r}" % (i,self[i])) 

251 def getData(self): 

252 ans = pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP) 

253 if 'NegState' in self.fields and 'SupportedMech' in self.fields and 'ResponseToken' in self.fields: 253 ↛ 255line 253 didn't jump to line 255, because the condition on line 253 was never true

254 # Server resp 

255 ans += asn1encode( 

256 pack('B', ASN1_SEQUENCE) + 

257 asn1encode( 

258 pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

259 asn1encode( 

260 pack('B',ASN1_ENUMERATED) + 

261 asn1encode( self['NegState'] )) + 

262 pack('B',ASN1_SUPPORTED_MECH) + 

263 asn1encode( 

264 pack('B',ASN1_OID) + 

265 asn1encode(self['SupportedMech'])) + 

266 pack('B',ASN1_RESPONSE_TOKEN ) + 

267 asn1encode( 

268 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 

269 elif 'NegState' in self.fields and 'SupportedMech' in self.fields: 269 ↛ 271line 269 didn't jump to line 271, because the condition on line 269 was never true

270 # Server resp 

271 ans += asn1encode( 

272 pack('B', ASN1_SEQUENCE) + 

273 asn1encode( 

274 pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

275 asn1encode( 

276 pack('B',ASN1_ENUMERATED) + 

277 asn1encode( self['NegState'] )) + 

278 pack('B',ASN1_SUPPORTED_MECH) + 

279 asn1encode( 

280 pack('B',ASN1_OID) + 

281 asn1encode(self['SupportedMech'])))) 

282 elif 'NegState' in self.fields: 282 ↛ 284line 282 didn't jump to line 284, because the condition on line 282 was never true

283 # Server resp 

284 ans += asn1encode( 

285 pack('B', ASN1_SEQUENCE) + 

286 asn1encode( 

287 pack('B', SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

288 asn1encode( 

289 pack('B',ASN1_ENUMERATED) + 

290 asn1encode( self['NegState'] )))) 

291 else: 

292 # Client resp 

293 ans += asn1encode( 

294 pack('B', ASN1_SEQUENCE) + 

295 asn1encode( 

296 pack('B', ASN1_RESPONSE_TOKEN) + 

297 asn1encode( 

298 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 

299 return ans 

300 

301class SPNEGO_NegTokenInit(GSSAPI): 

302 # https://tools.ietf.org/html/rfc4178#page-8 

303 # NegTokeInit :: = SEQUENCE { 

304 # mechTypes [0] MechTypeList, 

305 # reqFlags [1] ContextFlags OPTIONAL, 

306 # mechToken [2] OCTET STRING OPTIONAL, 

307 # mechListMIC [3] OCTET STRING OPTIONAL, 

308 # } 

309 SPNEGO_NEG_TOKEN_INIT = 0xa0 

310 def fromString(self, data = 0): 

311 GSSAPI.fromString(self, data) 

312 payload = self['Payload'] 

313 next_byte = unpack('B', payload[:1])[0] 

314 if next_byte != SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT: 

315 raise Exception('NegTokenInit not found %x' % next_byte) 

316 payload = payload[1:] 

317 decode_data, total_bytes = asn1decode(payload) 

318 # Now we should have a SEQUENCE Tag 

319 next_byte = unpack('B', decode_data[:1])[0] 

320 if next_byte != ASN1_SEQUENCE: 

321 raise Exception('SEQUENCE tag not found %x' % next_byte) 

322 decode_data = decode_data[1:] 

323 decode_data, total_bytes2 = asn1decode(decode_data) 

324 next_byte = unpack('B',decode_data[:1])[0] 

325 if next_byte != ASN1_MECH_TYPE: 

326 raise Exception('MechType tag not found %x' % next_byte) 

327 decode_data = decode_data[1:] 

328 remaining_data = decode_data 

329 decode_data, total_bytes3 = asn1decode(decode_data) 

330 next_byte = unpack('B', decode_data[:1])[0] 

331 if next_byte != ASN1_SEQUENCE: 

332 raise Exception('SEQUENCE tag not found %x' % next_byte) 

333 decode_data = decode_data[1:] 

334 decode_data, total_bytes4 = asn1decode(decode_data) 

335 # And finally we should have the MechTypes 

336 self['MechTypes'] = [] 

337 while decode_data: 

338 next_byte = unpack('B', decode_data[:1])[0] 

339 if next_byte != ASN1_OID: 

340 # Not a valid OID, there must be something else we won't unpack 

341 break 

342 decode_data = decode_data[1:] 

343 item, total_bytes = asn1decode(decode_data) 

344 self['MechTypes'].append(item) 

345 decode_data = decode_data[total_bytes:] 

346 

347 # Do we have MechTokens as well? 

348 decode_data = remaining_data[total_bytes3:] 

349 if len(decode_data) > 0: 

350 next_byte = unpack('B', decode_data[:1])[0] 

351 if next_byte == ASN1_MECH_TOKEN: 

352 # We have tokens in here! 

353 decode_data = decode_data[1:] 

354 decode_data, total_bytes = asn1decode(decode_data) 

355 next_byte = unpack('B', decode_data[:1])[0] 

356 if next_byte == ASN1_OCTET_STRING: 

357 decode_data = decode_data[1:] 

358 decode_data, total_bytes = asn1decode(decode_data) 

359 self['MechToken'] = decode_data 

360 

361 def getData(self): 

362 mechTypes = b'' 

363 for i in self['MechTypes']: 

364 mechTypes += pack('B', ASN1_OID) 

365 mechTypes += asn1encode(i) 

366 

367 mechToken = b'' 

368 # Do we have tokens to send? 

369 if 'MechToken' in self.fields: 369 ↛ 374line 369 didn't jump to line 374, because the condition on line 369 was never false

370 mechToken = pack('B', ASN1_MECH_TOKEN) + asn1encode( 

371 pack('B', ASN1_OCTET_STRING) + asn1encode( 

372 self['MechToken'])) 

373 

374 ans = pack('B',SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT) 

375 ans += asn1encode( 

376 pack('B', ASN1_SEQUENCE) + 

377 asn1encode( 

378 pack('B', ASN1_MECH_TYPE) + 

379 asn1encode( 

380 pack('B', ASN1_SEQUENCE) + 

381 asn1encode(mechTypes)) + mechToken )) 

382 

383 

384 self['Payload'] = ans 

385 return GSSAPI.getData(self)