Extracting Active Directory SIDs with Ruby

Getting user and group information from Active Directory via LDAP is nearly easy, but I came across one hurdle. If you retrieve a user, you might get something like this:

{
  :smaccountname => ['jeremy'],
  :dn => ‘["CN=Jeremy Wells,CN=Users,DC=domain,DC=boost,DC=co,DC=nz"],
  :primarygroupid => ['1114'],
  :objectsid => ["010500000000000525000000\210B ?\23302r\255ոT040000"]
}

If you then wanted to get the users primary group, you’d need to search using that primary group id. But you’d find there is no corresponding id on the group object, which looks as thus:

{
  :samaccountname=>["Students"],
  :dn=>["CN=Students,OU=Groups,DC=domain,DC=boost,DC=co,DC=nz"],
  :objectsid=>["010500000000000525000000\210B ?\23302r\255ոZ040000"],
  :name=>["Students"]
}

But notice the binary field :objectsid. This is the binary form of the string you may see sometimes when using AD, called SID, and it looks something like “S-1-5-21-123-456-789″. In order to find the users group you would take the :primarygroupid and the users :objectsid to generate the groups SID.

Space Intruder Detector (SID)

Space Intruder Detector (SID) UFO © ITV Global Entertainment

Only the last group of numbers in the SID corresponds to the current object, so to find the SID for another object we can just take off the last number group and replace it with the :primarygroupid, 1114. However, first we need to convert the binary string into a SID string.

The binary form of the SID is as follows:

  • byte 1: SID structure revision (always 1, but it could change in the future). This becomes the first number group.
  • byte 2: The number of sub-authorities in the SID. This is discarded for the string, but you can use it to work out the number of number groups ahead.
  • byte 3 – 9: Identifier Authority. This field should be converted to hexadecimal as the second number group.
  • byte 10 onwards: A variable length list of unsigned 32bit integers, the number of which is defined in byte 2.

Here is ruby code for doing the conversion:

def get_sid_string(data)
  sid = []
  sid << data[0].to_s
 
  rid = ""
  (6).downto(1) do |i|
    rid += byte2hex(data[i,1][0])
  end
  sid << rid.to_i.to_s
 
  sid += data.unpack("bbbbbbbbV*")[8..-1]
  "S-" + sid.join('-')
end
 
def byte2hex(b)
  ret = '%x' % (b.to_i & 0xff)
  ret = '0' + ret if ret.length < 2
  ret
end

Note: Originally I was using unpack with L* instead of V*. My tests were passing fine on my machine, but not on our CI server. As it turns out the endiness of L is dependent on the processor, and the CI is an old PPC G4.

Using the SID string you can now do an LDAP search with Net::LDAP::Filter.eq(“objectSID”, sid_string) to find the user. Replace the last number group with that from :primarygroupid and it’ll find the group. So if the user SID is S-1-5-21-123-456-789 and the :primarygroupid is 1114, the group’s SID will be S-1-5-21-123-456-1114.

Related Posts:

Leave a Comment

*