Extracting Active Directory SIDs with Ruby

By Nathan Donaldson in Other on April 29, 2009

Image 0424 full 2x

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:

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.