spnego.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. from __future__ import print_function
  2. # Copyright (c) 2003-2016 CORE Security Technologies
  3. #
  4. # This software is provided under under a slightly modified version
  5. # of the Apache Software License. See the accompanying LICENSE file
  6. # for more information.
  7. #
  8. # Author: Alberto Solino (beto@coresecurity.com)
  9. #
  10. # Description:
  11. # SPNEGO functions used by SMB, SMB2/3 and DCERPC
  12. #
  13. from struct import pack, unpack, calcsize
  14. ############### GSS Stuff ################
  15. GSS_API_SPNEGO_UUID = '\x2b\x06\x01\x05\x05\x02'
  16. ASN1_SEQUENCE = 0x30
  17. ASN1_AID = 0x60
  18. ASN1_OID = 0x06
  19. ASN1_OCTET_STRING = 0x04
  20. ASN1_MECH_TYPE = 0xa0
  21. ASN1_MECH_TOKEN = 0xa2
  22. ASN1_SUPPORTED_MECH = 0xa1
  23. ASN1_RESPONSE_TOKEN = 0xa2
  24. ASN1_ENUMERATED = 0x0a
  25. MechTypes = {
  26. '+\x06\x01\x04\x01\x827\x02\x02\x1e': 'SNMPv2-SMI::enterprises.311.2.2.30',
  27. '+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider',
  28. '*\x86H\x82\xf7\x12\x01\x02\x02': 'MS KRB5 - Microsoft Kerberos 5',
  29. '*\x86H\x86\xf7\x12\x01\x02\x02': 'KRB5 - Kerberos 5',
  30. '*\x86H\x86\xf7\x12\x01\x02\x02\x03': 'KRB5 - Kerberos 5 - User to User'
  31. }
  32. TypesMech = dict((v,k) for k, v in MechTypes.iteritems())
  33. def asn1encode(data = ''):
  34. #res = asn1.SEQUENCE(str).encode()
  35. #import binascii
  36. #print '\nalex asn1encode str: %s\n' % binascii.hexlify(str)
  37. if 0 <= len(data) <= 0x7F:
  38. res = pack('B', len(data)) + data
  39. elif 0x80 <= len(data) <= 0xFF:
  40. res = pack('BB', 0x81, len(data)) + data
  41. elif 0x100 <= len(data) <= 0xFFFF:
  42. res = pack('!BH', 0x82, len(data)) + data
  43. elif 0x10000 <= len(data) <= 0xffffff:
  44. res = pack('!BBH', 0x83, len(data) >> 16, len(data) & 0xFFFF) + data
  45. elif 0x1000000 <= len(data) <= 0xffffffff:
  46. res = pack('!BL', 0x84, len(data)) + data
  47. else:
  48. raise Exception('Error in asn1encode')
  49. return str(res)
  50. def asn1decode(data = ''):
  51. len1 = unpack('B', data[:1])[0]
  52. data = data[1:]
  53. if len1 == 0x81:
  54. pad = calcsize('B')
  55. len2 = unpack('B',data[:pad])[0]
  56. data = data[pad:]
  57. ans = data[:len2]
  58. elif len1 == 0x82:
  59. pad = calcsize('H')
  60. len2 = unpack('!H', data[:pad])[0]
  61. data = data[pad:]
  62. ans = data[:len2]
  63. elif len1 == 0x83:
  64. pad = calcsize('B') + calcsize('!H')
  65. len2, len3 = unpack('!BH', data[:pad])
  66. data = data[pad:]
  67. ans = data[:len2 << 16 + len3]
  68. elif len1 == 0x84:
  69. pad = calcsize('!L')
  70. len2 = unpack('!L', data[:pad])[0]
  71. data = data[pad:]
  72. ans = data[:len2]
  73. # 1 byte length, string <= 0x7F
  74. else:
  75. pad = 0
  76. ans = data[:len1]
  77. return ans, len(ans)+pad+1
  78. class GSSAPI:
  79. # Generic GSSAPI Header Format
  80. def __init__(self, data = None):
  81. self.fields = {}
  82. self['UUID'] = GSS_API_SPNEGO_UUID
  83. if data:
  84. self.fromString(data)
  85. pass
  86. def __setitem__(self,key,value):
  87. self.fields[key] = value
  88. def __getitem__(self, key):
  89. return self.fields[key]
  90. def __delitem__(self, key):
  91. del self.fields[key]
  92. def __len__(self):
  93. return len(self.getData())
  94. def __str__(self):
  95. return len(self.getData())
  96. def fromString(self, data = None):
  97. # Manual parse of the GSSAPI Header Format
  98. # It should be something like
  99. # AID = 0x60 TAG, BER Length
  100. # OID = 0x06 TAG
  101. # GSSAPI OID
  102. # UUID data (BER Encoded)
  103. # Payload
  104. next_byte = unpack('B',data[:1])[0]
  105. if next_byte != ASN1_AID:
  106. raise Exception('Unknown AID=%x' % next_byte)
  107. data = data[1:]
  108. decode_data, total_bytes = asn1decode(data)
  109. # Now we should have a OID tag
  110. next_byte = unpack('B',decode_data[:1])[0]
  111. if next_byte != ASN1_OID:
  112. raise Exception('OID tag not found %x' % next_byte)
  113. decode_data = decode_data[1:]
  114. # Now the OID contents, should be SPNEGO UUID
  115. uuid, total_bytes = asn1decode(decode_data)
  116. self['OID'] = uuid
  117. # the rest should be the data
  118. self['Payload'] = decode_data[total_bytes:]
  119. #pass
  120. def dump(self):
  121. for i in self.fields.keys():
  122. print("%s: {%r}" % (i,self[i]))
  123. def getData(self):
  124. ans = pack('B',ASN1_AID)
  125. ans += asn1encode(
  126. pack('B',ASN1_OID) +
  127. asn1encode(self['UUID']) +
  128. self['Payload'] )
  129. return ans
  130. class SPNEGO_NegTokenResp:
  131. # http://tools.ietf.org/html/rfc4178#page-9
  132. # NegTokenResp ::= SEQUENCE {
  133. # negState [0] ENUMERATED {
  134. # accept-completed (0),
  135. # accept-incomplete (1),
  136. # reject (2),
  137. # request-mic (3)
  138. # } OPTIONAL,
  139. # -- REQUIRED in the first reply from the target
  140. # supportedMech [1] MechType OPTIONAL,
  141. # -- present only in the first reply from the target
  142. # responseToken [2] OCTET STRING OPTIONAL,
  143. # mechListMIC [3] OCTET STRING OPTIONAL,
  144. # ...
  145. # }
  146. # This structure is not prepended by a GSS generic header!
  147. SPNEGO_NEG_TOKEN_RESP = 0xa1
  148. SPNEGO_NEG_TOKEN_TARG = 0xa0
  149. def __init__(self, data = None):
  150. self.fields = {}
  151. if data:
  152. self.fromString(data)
  153. pass
  154. def __setitem__(self,key,value):
  155. self.fields[key] = value
  156. def __getitem__(self, key):
  157. return self.fields[key]
  158. def __delitem__(self, key):
  159. del self.fields[key]
  160. def __len__(self):
  161. return len(self.getData())
  162. def __str__(self):
  163. return len(self.getData())
  164. def fromString(self, data = 0):
  165. payload = data
  166. next_byte = unpack('B', payload[:1])[0]
  167. if next_byte != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
  168. raise Exception('NegTokenResp not found %x' % next_byte)
  169. payload = payload[1:]
  170. decode_data, total_bytes = asn1decode(payload)
  171. next_byte = unpack('B', decode_data[:1])[0]
  172. if next_byte != ASN1_SEQUENCE:
  173. raise Exception('SEQUENCE tag not found %x' % next_byte)
  174. decode_data = decode_data[1:]
  175. decode_data, total_bytes = asn1decode(decode_data)
  176. next_byte = unpack('B',decode_data[:1])[0]
  177. if next_byte != ASN1_MECH_TYPE:
  178. # MechType not found, could be an AUTH answer
  179. if next_byte != ASN1_RESPONSE_TOKEN:
  180. raise Exception('MechType/ResponseToken tag not found %x' % next_byte)
  181. else:
  182. decode_data2 = decode_data[1:]
  183. decode_data2, total_bytes = asn1decode(decode_data2)
  184. next_byte = unpack('B', decode_data2[:1])[0]
  185. if next_byte != ASN1_ENUMERATED:
  186. raise Exception('Enumerated tag not found %x' % next_byte)
  187. item, total_bytes2 = asn1decode(decode_data)
  188. self['NegResult'] = item
  189. decode_data = decode_data[1:]
  190. decode_data = decode_data[total_bytes:]
  191. # Do we have more data?
  192. if len(decode_data) == 0:
  193. return
  194. next_byte = unpack('B', decode_data[:1])[0]
  195. if next_byte != ASN1_SUPPORTED_MECH:
  196. if next_byte != ASN1_RESPONSE_TOKEN:
  197. raise Exception('Supported Mech/ResponseToken tag not found %x' % next_byte)
  198. else:
  199. decode_data2 = decode_data[1:]
  200. decode_data2, total_bytes = asn1decode(decode_data2)
  201. next_byte = unpack('B', decode_data2[:1])[0]
  202. if next_byte != ASN1_OID:
  203. raise Exception('OID tag not found %x' % next_byte)
  204. decode_data2 = decode_data2[1:]
  205. item, total_bytes2 = asn1decode(decode_data2)
  206. self['SupportedMech'] = item
  207. decode_data = decode_data[1:]
  208. decode_data = decode_data[total_bytes:]
  209. next_byte = unpack('B', decode_data[:1])[0]
  210. if next_byte != ASN1_RESPONSE_TOKEN:
  211. raise Exception('Response token tag not found %x' % next_byte)
  212. decode_data = decode_data[1:]
  213. decode_data, total_bytes = asn1decode(decode_data)
  214. next_byte = unpack('B', decode_data[:1])[0]
  215. if next_byte != ASN1_OCTET_STRING:
  216. raise Exception('Octet string token tag not found %x' % next_byte)
  217. decode_data = decode_data[1:]
  218. decode_data, total_bytes = asn1decode(decode_data)
  219. self['ResponseToken'] = decode_data
  220. def dump(self):
  221. for i in self.fields.keys():
  222. print("%s: {%r}" % (i,self[i]))
  223. def getData(self):
  224. ans = pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP)
  225. if 'NegResult' in self.fields and 'SupportedMech' in self.fields:
  226. # Server resp
  227. ans += asn1encode(
  228. pack('B', ASN1_SEQUENCE) +
  229. asn1encode(
  230. pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) +
  231. asn1encode(
  232. pack('B',ASN1_ENUMERATED) +
  233. asn1encode( self['NegResult'] )) +
  234. pack('B',ASN1_SUPPORTED_MECH) +
  235. asn1encode(
  236. pack('B',ASN1_OID) +
  237. asn1encode(self['SupportedMech'])) +
  238. pack('B',ASN1_RESPONSE_TOKEN ) +
  239. asn1encode(
  240. pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
  241. elif 'NegResult' in self.fields:
  242. # Server resp
  243. ans += asn1encode(
  244. pack('B', ASN1_SEQUENCE) +
  245. asn1encode(
  246. pack('B', SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) +
  247. asn1encode(
  248. pack('B',ASN1_ENUMERATED) +
  249. asn1encode( self['NegResult'] ))))
  250. else:
  251. # Client resp
  252. ans += asn1encode(
  253. pack('B', ASN1_SEQUENCE) +
  254. asn1encode(
  255. pack('B', ASN1_RESPONSE_TOKEN) +
  256. asn1encode(
  257. pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
  258. return ans
  259. class SPNEGO_NegTokenInit(GSSAPI):
  260. # http://tools.ietf.org/html/rfc4178#page-8
  261. # NegTokeInit :: = SEQUENCE {
  262. # mechTypes [0] MechTypeList,
  263. # reqFlags [1] ContextFlags OPTIONAL,
  264. # mechToken [2] OCTET STRING OPTIONAL,
  265. # mechListMIC [3] OCTET STRING OPTIONAL,
  266. # }
  267. SPNEGO_NEG_TOKEN_INIT = 0xa0
  268. def fromString(self, data = 0):
  269. GSSAPI.fromString(self, data)
  270. payload = self['Payload']
  271. next_byte = unpack('B', payload[:1])[0]
  272. if next_byte != SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT:
  273. raise Exception('NegTokenInit not found %x' % next_byte)
  274. payload = payload[1:]
  275. decode_data, total_bytes = asn1decode(payload)
  276. # Now we should have a SEQUENCE Tag
  277. next_byte = unpack('B', decode_data[:1])[0]
  278. if next_byte != ASN1_SEQUENCE:
  279. raise Exception('SEQUENCE tag not found %x' % next_byte)
  280. decode_data = decode_data[1:]
  281. decode_data, total_bytes2 = asn1decode(decode_data)
  282. next_byte = unpack('B',decode_data[:1])[0]
  283. if next_byte != ASN1_MECH_TYPE:
  284. raise Exception('MechType tag not found %x' % next_byte)
  285. decode_data = decode_data[1:]
  286. remaining_data = decode_data
  287. decode_data, total_bytes3 = asn1decode(decode_data)
  288. next_byte = unpack('B', decode_data[:1])[0]
  289. if next_byte != ASN1_SEQUENCE:
  290. raise Exception('SEQUENCE tag not found %x' % next_byte)
  291. decode_data = decode_data[1:]
  292. decode_data, total_bytes4 = asn1decode(decode_data)
  293. # And finally we should have the MechTypes
  294. self['MechTypes'] = []
  295. while decode_data:
  296. next_byte = unpack('B', decode_data[:1])[0]
  297. if next_byte != ASN1_OID:
  298. # Not a valid OID, there must be something else we won't unpack
  299. break
  300. decode_data = decode_data[1:]
  301. item, total_bytes = asn1decode(decode_data)
  302. self['MechTypes'].append(item)
  303. decode_data = decode_data[total_bytes:]
  304. # Do we have MechTokens as well?
  305. decode_data = remaining_data[total_bytes3:]
  306. if len(decode_data) > 0:
  307. next_byte = unpack('B', decode_data[:1])[0]
  308. if next_byte == ASN1_MECH_TOKEN:
  309. # We have tokens in here!
  310. decode_data = decode_data[1:]
  311. decode_data, total_bytes = asn1decode(decode_data)
  312. next_byte = unpack('B', decode_data[:1])[0]
  313. if next_byte == ASN1_OCTET_STRING:
  314. decode_data = decode_data[1:]
  315. decode_data, total_bytes = asn1decode(decode_data)
  316. self['MechToken'] = decode_data
  317. def getData(self):
  318. mechTypes = ''
  319. for i in self['MechTypes']:
  320. mechTypes += pack('B', ASN1_OID)
  321. mechTypes += asn1encode(i)
  322. mechToken = ''
  323. # Do we have tokens to send?
  324. if 'MechToken' in self.fields:
  325. mechToken = pack('B', ASN1_MECH_TOKEN) + asn1encode(
  326. pack('B', ASN1_OCTET_STRING) + asn1encode(
  327. self['MechToken']))
  328. ans = pack('B',SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT)
  329. ans += asn1encode(
  330. pack('B', ASN1_SEQUENCE) +
  331. asn1encode(
  332. pack('B', ASN1_MECH_TYPE) +
  333. asn1encode(
  334. pack('B', ASN1_SEQUENCE) +
  335. asn1encode(mechTypes)) + mechToken ))
  336. self['Payload'] = ans
  337. return GSSAPI.getData(self)