{"id":43513,"date":"2014-11-28T07:00:00","date_gmt":"2014-11-28T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2014\/11\/28\/a-users-sid-can-change-so-make-sure-to-check-the-sid-history\/"},"modified":"2014-11-28T07:00:00","modified_gmt":"2014-11-28T07:00:00","slug":"a-users-sid-can-change-so-make-sure-to-check-the-sid-history","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20141128-00\/?p=43513","title":{"rendered":"A user&#039;s SID can change, so make sure to check the SID history"},"content":{"rendered":"<p>\nIt doesn&#8217;t happen often,\nbut a user&#8217;s SID can change.\nFor example, when I started at Microsoft,\nmy account was in the SYS-WIN4 domain,\nwhich is where all the people on the Windows&nbsp;95 team\nwere placed.\nAt some point, that domain was retired,\nand my account moved to the REDMOND domain.\nWe saw\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/03\/15\/89753.aspx\">\nsome time ago<\/a>\nthat the format of a user SID is\n<\/p>\n<table BORDER=\"0\" CELLSPACING=\"3\">\n<tr>\n<td>S-1-<\/td>\n<td>version number (SID_REVISION)<\/td>\n<\/tr>\n<tr>\n<td>-5-<\/td>\n<td>SECURITY_NT_AUTHORITY<\/td>\n<\/tr>\n<tr>\n<td>-21-<\/td>\n<td>SECURITY_NT_NON_UNIQUE<\/td>\n<\/tr>\n<tr>\n<td>-w-x-y-<\/td>\n<td>the entity (machine or domain) that issued the SID<\/td>\n<\/tr>\n<tr>\n<td>-z<\/td>\n<td>the unique user ID for that entity<\/td>\n<\/tr>\n<\/table>\n<p>\nThe issuing entity for a local account on a machine is the machine\nto which the account belongs.\nThe issuing entity for a domain account is the domain.\n<\/p>\n<p>\nIf an account moves between domains,\nthe issuing entity changes,\nwhich means that the old SID is not valid.\nA new SID must be issued.\n<\/p>\n<p>\nWait, does this mean that if my account moves between domains,\nthen I lose access to all my old stuff?\nAll my old stuff grants access to my old SID, not my new SID.\n<\/p>\n<p>\nFortunately, this doesn&#8217;t happen,\nthanks to the\n<a HREF=\"http:\/\/technet.microsoft.com\/en-us\/library\/cc974384(v=WS.10).aspx\">\n<i>SID history<\/i><\/a>.\nWhen your account moves to the new domain, the new domain controller\nremembers all the previous SIDs you used to have.\nWhen you authenticate against the domain controller,\nit populates your token with your SID history.\nIn my example, it means that my token not only says\n&#8220;This is user number 271828 on the REDMOND domain&#8221;,\nit also says\n&#8220;This user used to be known as number 31415 on the SYS-WIN4 domain.&#8221;\nThat way, when the system sees an object whose ACL says,\n&#8220;Grant access to user 31415 on the SYS-WIN4 domain,&#8221;\nthen it should grant me access to that object.\n<\/p>\n<p>\nThe existence of SID history means that recognizing users when they\nreturn is more complicated than a simple\n<code>Equal&shy;Sid<\/code>,\nbecause <code>Equal&shy;Sid<\/code> will say that\n&#8220;No, S-1-5-21-REDMOND-271828 is not equal to\nS-1-5-21-SYS-WIN4-31415,&#8221;\neven though both SIDs refer to the same person.\n<\/p>\n<p>\nIf you are going to remember a SID and then try to recognize a user\nwhen they return, you need to search the SID history for a match,\nin case the user changed domains between the two visits.\nThe easiest way to do this is with the\n<code>Access&shy;Check<\/code> function.\nFor example, suppose I visited your site while I belong to the\nSYS-WIN4 domain,\nand you remembered my SID.\nWhen I return,\nyou create a security descriptor that grants access to\nthe SID you remembered,\nand then you ask <code>Access&shy;Check<\/code>,\n&#8220;If I had an object that granted access only to this SID,\nwould you let this guy access it?&#8221;\n<\/p>\n<p>\n(So far, this is just recapping\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2014\/08\/29\/10553610.aspx\">\nstuff I discussed a few months ago<\/a>.\nNow comes the new stuff.)\n<\/p>\n<p>\nThere are a few ways of building up the security descriptor.\nIn all the cases, we will create a security descriptor that\ngrants the specified SID some arbitrary access,\nand then we will ask the operating system whether the current\nuser has that access.\n<\/p>\n<p>\nMy arbitrary access shall be\n<\/p>\n<pre>\n#define FROB_ACCESS     1 \/\/ any single bit less than 65536\n<\/pre>\n<p>\nOne way to build the security descriptor\nis to let SDDL do the heavy lifting:\nGenerate the string\n<code>D:(A;;1;;;&lang;SID&rang;)<\/code>\nand then pass it to\n<code>String&shy;Security&shy;Descriptor&shy;To&shy;Security&shy;Descriptor<\/code>.\n<\/p>\n<p>\nAnother is to build it up with security descriptor functions.\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/aa446595(v=vs.85).aspx\">\nI defer to the sample code in MSDN<\/a>\nfor an illustration.\n<\/p>\n<p>\nThe hard-core way is just to build the security descriptor by hand.\nFor a security descriptor this simple, the direct approach involves\nthe least amount of code.\nGo figure.\n<\/p>\n<p>\nThe format of the security descriptor we want to build is\n<\/p>\n<pre>\nstruct ACCESS_ALLOWED_ACE_MAX_SIZE\n{\n    ACCESS_ALLOWED_ACE Ace;\n    BYTE SidExtra[SECURITY_MAX_SID_SIZE - sizeof(DWORD)];\n};\n<\/pre>\n<p>\nThe <code>ACCESS_ALLOWED_ACE_MAX_SIZE<\/code>\nstructure represents the maximum possible size of an\n<code>ACCESS_ALLOWED_ACE<\/code>.\nThe\n<code>ACCESS_ALLOWED_ACE<\/code>\nleaves a <code>DWORD<\/code> for the SID\n(<code>Sid&shy;Start<\/code>),\nso we add additional bytes afterward to accommodate the largest\nvalid SID.\nIf you wanted to be more C++-like, you could make\n<code>ACCESS_ALLOWED_ACE_MAX_SIZE<\/code>\nderive from\n<code>ACCESS_ALLOWED_ACE<\/code>.\n<\/p>\n<pre>\nstruct ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR\n{\n    SECURITY_DESCRIPTOR_RELATIVE Header;\n    ACL Acl;\n    ACCESS_ALLOWED_ACE_MAX_SIZE Ace;\n};\nconst ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR c_sdTemplate = {\n  \/\/ SECURITY_DESCRIPTOR_RELATIVE\n  {\n    SECURITY_DESCRIPTOR_REVISION,           \/\/ Revision\n    0,                                      \/\/ Reserved\n    SE_DACL_PRESENT | SE_SELF_RELATIVE,     \/\/ Control\n    FIELD_OFFSET(ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR, Ace.Ace.SidStart),\n                                            \/\/ Offset to owner\n    FIELD_OFFSET(ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR, Ace.Ace.SidStart),\n                                            \/\/ Offset to group\n    0,                                      \/\/ No SACL\n    FIELD_OFFSET(ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR, Acl),\n                                            \/\/ Offset to DACL\n  },\n  \/\/ ACL\n  {\n    ACL_REVISION,                           \/\/ Revision\n    0,                                      \/\/ Reserved\n    sizeof(ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR) -\n    FIELD_OFFSET(ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR, Acl),\n                                            \/\/ ACL size\n    1,                                      \/\/ ACE count\n    0,                                      \/\/ Reserved\n  },\n  \/\/ ACCESS_ALLOWED_ACE_MAX_SIZE\n  {\n    \/\/ ACCESS_ALLOWED_ACE\n    {\n      \/\/ ACE_HEADER\n      {\n        ACCESS_ALLOWED_ACE_TYPE,            \/\/ AceType\n        0,                                  \/\/ flags\n        sizeof(ACCESS_ALLOWED_ACE_MAX_SIZE),\/\/ ACE size\n      },\n      FROB_ACCESS,                          \/\/ Access mask\n    },\n  },\n};\n<\/pre>\n<p>\nOur template security descriptor says that it is a self-relative\nsecurity descriptor with an owner, group and DACL,\nbut no SACL.\nThe DACL consists of a single ACE.\nWe set up everything in the ACE except for the SID.\nWe point the owner and group to that same SID.\nTherefore, this security descriptor is all ready for action\nonce you fill in the SID.\n<\/p>\n<pre>\nBOOL IsInSidHistory(HANDLE Token, PSID Sid)\n{\n  DWORD SidLength = GetLengthSid(Sid);\n  if (SidLength &gt; SECURITY_MAX_SID_SIZE) {\n    \/\/ Invalid SID. That's not good.\n    \/\/ Somebody is playing with corrupted data.\n    \/\/ Stop before anything bad happens.\n    RaiseFailFastException(nullptr, nullptr, 0);\n  }\n  ALLOW_ONLY_ONE_SECURITY_DESCRIPTOR Sd = c_sdTemplate;\n  CopyMemory(&amp;Sd.Ace.Ace.SidStart, Sid, SidLength);\n<\/pre>\n<p>\nAs you can see, generating the security descriptor is a simple\nmatter of copying our template and then replacing the SID.\nThe next step is performing an access check of the token\nagainst that SID.\n<\/p>\n<pre>\n  const static GENERIC_MAPPING c_GenericMappingFrob = {\n    FROB_ACCESS,\n    FROB_ACCESS,\n    FROB_ACCESS,\n    FROB_ACCESS,\n  };\n  PRIVILEGE_SET PrivilegeSet;\n  DWORD PrivilegeSetSize = sizeof(PrivilegeSet);\n  DWORD GrantedAccess = 0;\n  BOOL AccessStatus = 0;\n  return AccessCheck(&amp;Sd, Token, FROB_ACCESS,\n    const_cast&lt;PGENERIC_MAPPING&gt;(&amp;c_GenericMappingFrob),\n    &amp;PrivilegeSet, &amp;PrivilegeSetSize,\n    &amp;GrantedAccess, &amp;AccessStatus) &amp;&amp;\n    AccessStatus;\n}\n<\/pre>\n<p>\nSo let&#8217;s take this guy out for a spin.\nSince I don&#8217;t know what is in your SID history,\nI&#8217;m going to pick something that should be in your token already\n(<i>Authenticated Users<\/i>)\nand something that shouldn&#8217;t\n(<i>Local System<\/i>).\n<\/p>\n<pre>\n\/\/ Note: Error checking elided for expository purposes.\nvoid CheckWellKnownSid(HANDLE Token, WELL_KNOWN_SID_TYPE type)\n{\n  BYTE rgbSid[SECURITY_MAX_SID_SIZE];\n  DWORD cbSid = sizeof(rgbSid);\n  CreateWellKnownSid(type, NULL, rgbSid, &amp;cbSid);\n  printf(\"Is %d in SID history? %d\\n\", type,\n         IsInSidHistory(Token, rgbSid));\n}\nint __cdecl wmain(int argc, wchar_t **argv)\n{\n  HANDLE Token;\n  \/\/ In real life you had better error-check these calls,\n  \/\/ to avoid a security hole.\n  ImpersonateSelf(SecurityImpersonation);\n  OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &amp;Token);\n  RevertToSelf();\n  CheckWellKnownSid(Token, WinAuthenticatedUserSid);\n  CheckWellKnownSid(Token, WinLocalSystemSid);\n  CloseHandle(Token);\n  return 0;\n}\n<\/pre>\n<p>\n<b>Related reading<\/b>:\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2010\/09\/12\/10060082.aspx\">\nHey there token, long time no see! (Did you do something with your hair?)<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>It doesn&#8217;t happen often, but a user&#8217;s SID can change. For example, when I started at Microsoft, my account was in the SYS-WIN4 domain, which is where all the people on the Windows&nbsp;95 team were placed. At some point, that domain was retired, and my account moved to the REDMOND domain. We saw some time [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-43513","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It doesn&#8217;t happen often, but a user&#8217;s SID can change. For example, when I started at Microsoft, my account was in the SYS-WIN4 domain, which is where all the people on the Windows&nbsp;95 team were placed. At some point, that domain was retired, and my account moved to the REDMOND domain. We saw some time [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/43513","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=43513"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/43513\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=43513"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=43513"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=43513"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}