Coverage for /root/GitHubProjects/impacket/impacket/ldap/ldaptypes.py: 80%

# Coverage for /root/GitHubProjects/impacket/impacket/ldap/ldaptypes.py : 80%

## 181 statements

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# Impacket - Collection of Python classes for working with network protocols.

2#

3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.

4#

5# This software is provided under a slightly modified version

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

7# for more information.

8#

9# Description:

10# Structures and types used in LDAP

11# Contains the Structures for the NT Security Descriptor (non-RPC format) and

12# all ACL related structures

13#

14# Author:

15# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com)

16#

17 from struct import unpack, pack

18 from impacket.structure import Structure

19

20# Global constant if the library should recalculate ACE sizes in objects that are decoded/re-encoded.

21# This defaults to True, but this causes the ACLs to not match on a binary level

22# since Active Directory for some reason sometimes adds null bytes to the end of ACEs.

23# This is valid according to the spec (see 2.4.4), but since impacket encodes them more efficiently

24# this should be turned off if running unit tests.

25 RECALC_ACE_SIZE = True

26

27# LDAP SID structure - based on SAMR_RPC_SID, except the SubAuthority is LE here

28 class LDAP_SID_IDENTIFIER_AUTHORITY(Structure):

29 structure = (

30 ('Value','6s'),

31 )

32

33 class LDAP_SID(Structure):

34 structure = (

35 ('Revision','<B'),

36 ('SubAuthorityCount','<B'),

37 ('IdentifierAuthority',':',LDAP_SID_IDENTIFIER_AUTHORITY),

38 ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'),

39 ('SubAuthority',':'),

40 )

41

42 def formatCanonical(self):

43 ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5:6]))

44 for i in range(self['SubAuthorityCount']):

45 ans += '-%d' % ( unpack('<L',self['SubAuthority'][i*4: i*4+4])[0])

46 return ans

47

48 def fromCanonical(self, canonical):

49 items = canonical.split('-')

50 self['Revision'] = int(items[1])

51 self['IdentifierAuthority'] = LDAP_SID_IDENTIFIER_AUTHORITY()

52 self['IdentifierAuthority']['Value'] = b'\x00\x00\x00\x00\x00' + pack('B',int(items[2]))

53 self['SubAuthorityCount'] = len(items) - 3

54 self['SubAuthority'] = b''

55 for i in range(self['SubAuthorityCount']):

56 self['SubAuthority'] += pack('<L', int(items[i+3]))

57

58"""

59 Self-relative security descriptor as described in 2.4.6

60 https://msdn.microsoft.com/en-us/library/cc230366.aspx

61"""

62 class SR_SECURITY_DESCRIPTOR(Structure):

63 structure = (

64 ('Revision','c'),

65 ('Sbz1','c'),

66 ('Control','<H'),

67 ('OffsetOwner','<L'),

68 ('OffsetGroup','<L'),

69 ('OffsetSacl','<L'),

70 ('OffsetDacl','<L'),

71 ('Sacl',':'),

72 ('Dacl',':'),

73 ('OwnerSid',':'),

74 ('GroupSid',':'),

75 )

76

77 def fromString(self, data):

78 Structure.fromString(self, data)

79 # All these fields are optional, if the offset is 0 they are empty

80 # there are also flags indicating if they are present

81 # TODO: parse those if it adds value

82 if self['OffsetOwner'] != 0: 82 ↛ 85line 82 didn't jump to line 85, because the condition on line 82 was never false

83 self['OwnerSid'] = LDAP_SID(data=data[self['OffsetOwner']:])

84 else:

85 self['OwnerSid'] = b''

86

87 if self['OffsetGroup'] != 0: 87 ↛ 90line 87 didn't jump to line 90, because the condition on line 87 was never false

88 self['GroupSid'] = LDAP_SID(data=data[self['OffsetGroup']:])

89 else:

90 self['GroupSid'] = b''

91

92 if self['OffsetSacl'] != 0: 92 ↛ 95line 92 didn't jump to line 95, because the condition on line 92 was never false

93 self['Sacl'] = ACL(data=data[self['OffsetSacl']:])

94 else:

95 self['Sacl'] = b''

96

97 if self['OffsetDacl'] != 0: 97 ↛ 100line 97 didn't jump to line 100, because the condition on line 97 was never false

98 self['Dacl'] = ACL(data=data[self['OffsetDacl']:])

99 else:

100 self['Sacl'] = b''

101

102 def getData(self):

103 headerlen = 20

104 # Reconstruct the security descriptor

105 # flags are currently not set automatically

106 # TODO: do this?

107 datalen = 0

108 if self['Sacl'] != b'': 108 ↛ 112line 108 didn't jump to line 112, because the condition on line 108 was never false

109 self['OffsetSacl'] = headerlen + datalen

110 datalen += len(self['Sacl'].getData())

111 else:

112 self['OffsetSacl'] = 0

113

114 if self['Dacl'] != b'': 114 ↛ 118line 114 didn't jump to line 118, because the condition on line 114 was never false

115 self['OffsetDacl'] = headerlen + datalen

116 datalen += len(self['Dacl'].getData())

117 else:

118 self['OffsetDacl'] = 0

119

120 if self['OwnerSid'] != b'': 120 ↛ 124line 120 didn't jump to line 124, because the condition on line 120 was never false

121 self['OffsetOwner'] = headerlen + datalen

122 datalen += len(self['OwnerSid'].getData())

123 else:

124 self['OffsetOwner'] = 0

125

126 if self['GroupSid'] != b'': 126 ↛ 130line 126 didn't jump to line 130, because the condition on line 126 was never false

127 self['OffsetGroup'] = headerlen + datalen

128 datalen += len(self['GroupSid'].getData())

129 else:

130 self['OffsetGroup'] = 0

131 return Structure.getData(self)

132

133"""

134 ACE as described in 2.4.4

135 https://msdn.microsoft.com/en-us/library/cc230295.aspx

136"""

137 class ACE(Structure):

138 # Flag constants

139 CONTAINER_INHERIT_ACE = 0x02

140 FAILED_ACCESS_ACE_FLAG = 0x80

141 INHERIT_ONLY_ACE = 0x08

142 INHERITED_ACE = 0x10

143 NO_PROPAGATE_INHERIT_ACE = 0x04

144 OBJECT_INHERIT_ACE = 0x01

145 SUCCESSFUL_ACCESS_ACE_FLAG = 0x40

146

147 structure = (

148 #

149 # ACE_HEADER as described in 2.4.4.1

150 # https://msdn.microsoft.com/en-us/library/cc230296.aspx

151 #

152 ('AceType','B'),

153 ('AceFlags','B'),

154 ('AceSize','<H'),

155 # Virtual field to calculate data length from AceSize

156 ('AceLen', '_-Ace', 'self["AceSize"]-4'),

157 #

158 # ACE body, is parsed depending on the type

159 #

160 ('Ace',':')

161 )

162

163 def fromString(self, data):

164 # This will parse the header

165 Structure.fromString(self, data)

166 # Now we parse the ACE body according to its type

167 self['TypeName'] = ACE_TYPE_MAP[self['AceType']].__name__

168 self['Ace'] = ACE_TYPE_MAP[self['AceType']](data=self['Ace'])

169

170 def getData(self):

171 if RECALC_ACE_SIZE or 'AceSize' not in self.fields: 171 ↛ 173line 171 didn't jump to line 173, because the condition on line 171 was never false

172 self['AceSize'] = len(self['Ace'].getData())+4 # Header size (4 bytes) is included

173 if self['AceSize'] % 4 != 0: 173 ↛ 175line 173 didn't jump to line 175, because the condition on line 173 was never true

174 # Make sure the alignment is correct

175 self['AceSize'] += self['AceSize'] % 4

176 data = Structure.getData(self)

177 # For some reason ACEs are sometimes longer than they need to be

178 # we fill this space up with null bytes to make sure the object

179 # we create is identical to the original object

180 if len(data) < self['AceSize']: 180 ↛ 181line 180 didn't jump to line 181, because the condition on line 180 was never true

181 data += '\x00' * (self['AceSize'] - len(data))

182 return data

183

184 def hasFlag(self, flag):

185 return self['AceFlags'] & flag == flag

186

187"""

188 ACCESS_MASK as described in 2.4.3

189 https://msdn.microsoft.com/en-us/library/cc230294.aspx

190"""

191 class ACCESS_MASK(Structure):

192 # Flag constants

193 GENERIC_READ = 0x80000000

194 GENERIC_WRITE = 0x40000000

195 GENERIC_EXECUTE = 0x20000000

196 GENERIC_ALL = 0x10000000

197 MAXIMUM_ALLOWED = 0x02000000

198 ACCESS_SYSTEM_SECURITY = 0x01000000

199 SYNCHRONIZE = 0x00100000

200 WRITE_OWNER = 0x00080000

201 WRITE_DACL = 0x00040000

202 READ_CONTROL = 0x00020000

203 DELETE = 0x00010000

204

205 structure = (

206 ('Mask', '<L'),

207 )

208

209 def hasPriv(self, priv):

210 return self['Mask'] & priv == priv

211

212 def setPriv(self, priv):

213 self['Mask'] |= priv

214

215 def removePriv(self, priv):

216 self['Mask'] ^= priv

217

218"""

219 ACCESS_ALLOWED_ACE as described in 2.4.4.2

220 https://msdn.microsoft.com/en-us/library/cc230286.aspx

221"""

222 class ACCESS_ALLOWED_ACE(Structure):

223 ACE_TYPE = 0x00

224 structure = (

225 ('Mask', ':', ACCESS_MASK),

226 ('Sid', ':', LDAP_SID)

227 )

228

229"""

230 ACCESS_ALLOWED_OBJECT_ACE as described in 2.4.4.3

231 https://msdn.microsoft.com/en-us/library/cc230289.aspx

232"""

233 class ACCESS_ALLOWED_OBJECT_ACE(Structure):

234 ACE_TYPE = 0x05

235

236 # Flag contstants

237 ACE_OBJECT_TYPE_PRESENT = 0x01

238 ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x02

239

240 # ACE type specific mask constants

241 # Note that while not documented, these also seem valid

242 # for ACCESS_ALLOWED_ACE types

243 ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100

244 ADS_RIGHT_DS_CREATE_CHILD = 0x00000001

245 ADS_RIGHT_DS_DELETE_CHILD = 0x00000002

246 ADS_RIGHT_DS_READ_PROP = 0x00000010

247 ADS_RIGHT_DS_WRITE_PROP = 0x00000020

248 ADS_RIGHT_DS_SELF = 0x00000008

249

250

251 structure = (

252 ('Mask', ':', ACCESS_MASK),

253 ('Flags', '<L'),

254 # Optional field

255 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'),

256 ('ObjectType', ':=""'),

257 # Optional field

258 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'),

259 ('InheritedObjectType', ':=""'),

260 ('Sid', ':', LDAP_SID)

261 )

262

263 @staticmethod

264 def checkInheritedObjectType(flags):

265 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT:

266 return 16

267 return 0

268

269 @staticmethod

270 def checkObjectType(flags):

271 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT:

272 return 16

273 return 0

274

275 def getData(self):

276 # Set the correct flags

277 if self['ObjectType'] != b'':

278 self['Flags'] |= self.ACE_OBJECT_TYPE_PRESENT

279 if self['InheritedObjectType'] != b'':

280 self['Flags'] |= self.ACE_INHERITED_OBJECT_TYPE_PRESENT

281 return Structure.getData(self)

282

283 def hasFlag(self, flag):

284 return self['Flags'] & flag == flag

285

286"""

287 ACCESS_DENIED_ACE as described in 2.4.4.4

288 https://msdn.microsoft.com/en-us/library/cc230291.aspx

289 Structure is identical to ACCESS_ALLOWED_ACE

290"""

291 class ACCESS_DENIED_ACE(ACCESS_ALLOWED_ACE):

292 ACE_TYPE = 0x01

293

294"""

295 ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.5

296 https://msdn.microsoft.com/en-us/library/gg750297.aspx

297 Structure is identical to ACCESS_ALLOWED_OBJECT_ACE

298"""

299 class ACCESS_DENIED_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE):

300 ACE_TYPE = 0x06

301

302"""

303 ACCESS_ALLOWED_CALLBACK_ACE as described in 2.4.4.6

304 https://msdn.microsoft.com/en-us/library/cc230287.aspx

305"""

306 class ACCESS_ALLOWED_CALLBACK_ACE(Structure):

307 ACE_TYPE = 0x09

308 structure = (

309 ('Mask', ':', ACCESS_MASK),

310 ('Sid', ':', LDAP_SID),

311 ('ApplicationData', ':')

312 )

313

314"""

315 ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.7

316 https://msdn.microsoft.com/en-us/library/cc230292.aspx

317 Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE

318"""

319 class ACCESS_DENIED_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE):

320 ACE_TYPE = 0x0A

321

322"""

323 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE as described in 2.4.4.8

324 https://msdn.microsoft.com/en-us/library/cc230288.aspx

325"""

326 class ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE):

327 ACE_TYPE = 0x0B

328 structure = (

329 ('Mask', ':', ACCESS_MASK),

330 ('Flags', '<L'),

331 # Optional field

332 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'),

333 ('ObjectType', ':=""'),

334 # Optional field

335 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'),

336 ('InheritedObjectType', ':=""'),

337 ('Sid', ':', LDAP_SID),

338 ('ApplicationData', ':')

339 )

340

341"""

342 ACCESS_DENIED_CALLBACK_OBJECT_ACE as described in 2.4.4.7

343 https://msdn.microsoft.com/en-us/library/cc230292.aspx

344 Structure is identical to ACCESS_ALLOWED_OBJECT_OBJECT_ACE

345"""

346 class ACCESS_DENIED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):

347 ACE_TYPE = 0x0C

348

349"""

350 SYSTEM_AUDIT_ACE as described in 2.4.4.10

351 https://msdn.microsoft.com/en-us/library/cc230376.aspx

352 Structure is identical to ACCESS_ALLOWED_ACE

353"""

354 class SYSTEM_AUDIT_ACE(ACCESS_ALLOWED_ACE):

355 ACE_TYPE = 0x02

356

357

358"""

359 SYSTEM_AUDIT_OBJECT_ACE as described in 2.4.4.11

360 https://msdn.microsoft.com/en-us/library/gg750298.aspx

361 Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE

362"""

363 class SYSTEM_AUDIT_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):

364 ACE_TYPE = 0x07

365

366

367"""

368 SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.12

369 https://msdn.microsoft.com/en-us/library/cc230377.aspx

370 Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE

371"""

372 class SYSTEM_AUDIT_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE):

373 ACE_TYPE = 0x0D

374

375"""

376 SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.13

377 https://msdn.microsoft.com/en-us/library/cc230379.aspx

378 Structure is identical to ACCESS_ALLOWED_ACE, but with custom masks and meanings.

379 Lets keep it separate for now

380"""

381 class SYSTEM_MANDATORY_LABEL_ACE(Structure):

382 ACE_TYPE = 0x11

383 structure = (

384 ('Mask', ':', ACCESS_MASK),

385 ('Sid', ':', LDAP_SID)

386 )

387

388"""

389 SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.14

390 https://msdn.microsoft.com/en-us/library/cc230378.aspx

391 Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE

392"""

393 class SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE):

394 ACE_TYPE = 0x0F

395

396"""

397 SYSTEM_RESOURCE_ATTRIBUTE_ACE as described in 2.4.4.15

398 https://msdn.microsoft.com/en-us/library/hh877837.aspx

399 Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE

400 The application data however is encoded in CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1

401 format as described in section 2.4.10.1

402 Todo: implement this substructure if needed

403"""

404 class SYSTEM_RESOURCE_ATTRIBUTE_ACE(ACCESS_ALLOWED_CALLBACK_ACE):

405 ACE_TYPE = 0x12

406

407

408"""

409 SYSTEM_SCOPED_POLICY_ID_ACE as described in 2.4.4.16

410 https://msdn.microsoft.com/en-us/library/hh877846.aspx

411 Structure is identical to ACCESS_ALLOWED_ACE

412 The Sid data MUST match a CAPID of a CentralAccessPolicy

413 contained in the CentralAccessPoliciesList

414 Todo: implement this substructure if needed

415 Also the ACCESS_MASK must always be 0

416"""

417 class SYSTEM_SCOPED_POLICY_ID_ACE(ACCESS_ALLOWED_ACE):

418 ACE_TYPE = 0x13

419

420# All the ACE types in a list

421 ACE_TYPES = [

422 ACCESS_ALLOWED_ACE,

423 ACCESS_ALLOWED_OBJECT_ACE,

424 ACCESS_DENIED_ACE,

425 ACCESS_DENIED_OBJECT_ACE,

426 ACCESS_ALLOWED_CALLBACK_ACE,

427 ACCESS_DENIED_CALLBACK_ACE,

428 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE,

429 ACCESS_DENIED_CALLBACK_OBJECT_ACE,

430 SYSTEM_AUDIT_ACE,

431 SYSTEM_AUDIT_OBJECT_ACE,

432 SYSTEM_AUDIT_CALLBACK_ACE,

433 SYSTEM_MANDATORY_LABEL_ACE,

434 SYSTEM_AUDIT_CALLBACK_OBJECT_ACE,

435 SYSTEM_RESOURCE_ATTRIBUTE_ACE,

436 SYSTEM_SCOPED_POLICY_ID_ACE

437]

438

439# A dict of all the ACE types indexed by their type number

440 ACE_TYPE_MAP = {ace.ACE_TYPE: ace for ace in ACE_TYPES}

441

442"""

443 ACL as described in 2.4.5

444 https://msdn.microsoft.com/en-us/library/cc230297.aspx

445"""

446 class ACL(Structure):

447 structure = (

448 ('AclRevision', 'B'),

449 ('Sbz1', 'B'),

450 ('AclSize', '<H'),

451 ('AceCount', '<H'),

452 ('Sbz2', '<H'),

453 # Virtual field to calculate data length from AclSize

454 ('DataLen', '_-Data', 'self["AclSize"]-8'),

455 ('Data', ':'),

456 )

457

458 def fromString(self, data):

459 self.aces = []

460 Structure.fromString(self, data)

461 for i in range(self['AceCount']):

462 # If we don't have any data left, return

463 if len(self['Data']) == 0: 463 ↛ 464line 463 didn't jump to line 464, because the condition on line 463 was never true

464 raise Exception("ACL header indicated there are more ACLs to unpack, but there is no more data")

465 ace = ACE(data=self['Data'])

466 self.aces.append(ace)

467 self['Data'] = self['Data'][ace['AceSize']:]

468 self['Data'] = self.aces

469

470 def getData(self):

471 self['AceCount'] = len(self.aces)

472 # We modify the data field to be able to use the

473 # parent class parsing

474 self['Data'] = b''.join([ace.getData() for ace in self.aces])

475 self['AclSize'] = len(self['Data'])+8 # Header size (8 bytes) is included

476 data = Structure.getData(self)

477 # Put the ACEs back in data

478 self['Data'] = self.aces

479 return data

480

481"""

482 objectClass mapping to GUID for some common classes (index is the ldapDisplayName).

483 Reference:

484 https://msdn.microsoft.com/en-us/library/ms680938(v=vs.85).aspx

485 Can also be queried from the Schema

486"""

487 OBJECTTYPE_GUID_MAP = {

488 b'group': 'bf967a9c-0de6-11d0-a285-00aa003049e2',

489 b'domain': '19195a5a-6da0-11d0-afd3-00c04fd930c9',

490 b'organizationalUnit': 'bf967aa5-0de6-11d0-a285-00aa003049e2',

491 b'user': 'bf967aba-0de6-11d0-a285-00aa003049e2',

492 b'groupPolicyContainer': 'f30e3bc2-9ff0-11d1-b603-0000f80367c1'

493}