Mapping Samba's S-1-22-[12]-* SID into names - c#

Samba3 uses SID's in the range S-1-22-1 for users and S-1-22-2 for groups. For instance, S-1-22-1-1-10042 is the UNIX user with uid 10042.
I would like to be either able to map such a SID into a name, like 'myunixaccount', similar to this functionality for Windows account mapping:
SecurityIdentifier sid = ...; // For instance from FileSystemAccessRule.
name = sid.Translate(typeof(NTAccount)).Value;
Windows itself is able to make this mapping, but I seem unable to find a mapping algorithm.
ADDED: Environment Description
Tested proposed solution on Convert SID to Username in C#. It did not help. Therefore some extra environment description:
Windows PC, either joined to a domain or standalone, running W7 Professional, x86.
File located on Samba-based drive. Samba authenticates to AD controller of domain.
Samba version: 4.0.3, running on Linux 2.6.18-238, x64.
PAM for Samba, interactive sessions etc.
AD controller is W2012 with some default UNIX extension attribute in the directory to allow mapping UID etc.
.NET Framework libraries 4.5.2.
ldap.conf:
piece of ldap.conf
nss_base_passwd=OU=nl,OU=xxx,dc=yyy,dc=local?sub(objectCategory=user)
nss_map_objectclass posixAccount User
nss_map_objectclass shadowAccount User
nss_map_attribute uid sAMAccountName
nss_map_attribute uidNumber uidNumber
nss_map_attribute gidNumber gidNumber
nss_map_attribute cn sAMAccountName
nss_map_attribute uniqueMember member
nss_map_attribute userPassword msSFUPassword
nss_map_attribute homeDirectory unixHomeDirectory
nss_map_attribute loginShell loginShell
nss_map_attribute gecos cn
nss_map_objectclass posixGroup Group
nss_map_attribute shadowLastChange pwdLastSet
Interactive logons on UNIX with Windows authentication work fine, dito for Samba shares. When using a PC on the domain, it doesn't ask for credentials.
Some samples, the user gle3 (highlighted in 1) also exists in the domain but with a different SID. The SID used here is the Samba SID, like S-1-22-1-1-10001.
In (2) you can see that the user exists in the used passwd configuration. The following of course yields no results: grep gle3 /etc/passwd, since the entries are used from remote server. The remote server maps the user SID of gle3 to UNIX uid 10001 and default group 10003.
In (3) you can see that the default group does not exist, and that is why the permissions can not resolve it to a name.
So obviously Windows somehow asks the file server: "give me the data on these SIDs" and the Samba file server respons somehow: Ok, that is "Unix User\gle3" and "Unix Group\10003" but I do not have a group name for that last one.

I've been toying around with this some time ago for building a local LAN crawler on a 2000+ computer network. I'm pretty sure that what you're asking is not part of the SMB protocol. You can actually see that: if Windows cannot resolve the credentials, it will show the SID's in the security properties.
Basically what happens is that a SID is an object ID (like a username / group) that's mapped to a unique ID. They work like GUID's. Usually PC's communicate in SID's, not in usernames.
Now, there's different kinds of SID you need to consider:
You have the Active Directory SID that you can resolve using the standard methods. Note that these include group and user SID's.
There's the local PC SID's that you can resolve using the standard methods. Again, group and user SID's. This probably works for Samba as well as Windows, although I've only tested it on Windows in the past.
There's SID's on remote PC's that normally cannot be resolved. Basically this is what happens if you get a NTLM token in any different way.
There's actually a lot more than this... certificate credentials, reserved users, etc are all also object ID's which can be used for logging in - but I'll just keep it simple. The key takeaway from this comment is that while all usernames have a SID, it's not true that all SID's also have a username.
If you have an AD somewhere (you seem to do), a proper setup contains all users here. The easiest way to get the complete mapping is to simply enumerate the complete active directory. That should contain all the mappings. Basically that works like this:
DirectoryEntry root = new DirectoryEntry("LDAP://dc=MyCompany,dc=com");
DirectorySearcher search = new DirectorySearcher(root);
search.Filter = "(objectCategory=Person)";
search.SearchScope = SearchScope.Subtree;
search.PropertiesToLoad.Add("objectSid");
search.PropertiesToLoad.Add("displayName");
foreach(SearchResult result in search.FindAll())
{
// grab the data - if present
if(result.Properties["objectSid"] != null && result.Properties["objectSid"].Count > 1)
{
var sid = result.Properties["objectSid"][0];
}
if(result.Properties["displayName"] != null && result.Properties["displayName"].Count > 0)
{
var userName = result.Properties["displayName"][0].ToString();
}
}

Related

Permissions required to retrieve local group members with ADSI

Using ADSI I can query the members of the local Administrator group on a given computer by doing (for example in PowerShell) :
([ADSI]"WinNT://computer-name/Administrators,Group").Invoke("members")
To do this, as far as I can tell, the user running the PowerShell script requires Administrator privileges on the target machine - that is, the user needs to be directly on indirectly in the local administrator group of computer-name (eg. by being a member of "Domain Admins").
This surprised me because a non-administrator account who can login to computer-name (eg. a user that's part of "Domain Users" and nothing else) can open the local users & groups application, and view the members of the local administrator group. No specific rights are required when doing it manually, yet ADSI seems to require it.
So my questions are:
Is it correct that using ADSI you need Administrator rights to access this information, or am I doing something wrong?
Is there a different approach to programmatically obtain this information, which requires less privileges than an Administrator account ? (If there are solutions that are not available in PowerShell that's fine, my targets are C#/.NET Core )
Please note I want to run this remotely on other workstations - not just on the local workstation.
ADSI is built on top of WMI. By default, only the local Administrators group is allowed to make remote WMI calls and read a computers local directory data.
You can change the permissions on the OS by going into Computer Management (local) -> Services and Applications -> WMI Control. Right click on WMI Control and choose Properties.
I've only experimented with allowing all reads, which you can set on the root folder. I did some research and you may be able to restrict this to just LDAP. On the Security tab drill down to Root -> directory -> LDAP. You'll want to adjust permissions on the LDAP entry (or maybe more?). The key permission is Remote Enable.
Update
To query WMI directly from PowerShell.
Remote WMI over PowerShell: https://learn.microsoft.com/en-us/windows/win32/wmisdk/connecting-to-wmi-on-a-remote-computer.
Custom PowerShell method for listing remote group membership through WMI: https://gallery.technet.microsoft.com/scriptcenter/List-local-group-members-c25dbcc4
I think your ADSI approach should work, at least when executed locally.
I used a c# snippet I grabbed from this SO answer: https://stackoverflow.com/a/8192062/3374994.
To test whether it could run from regular user permissions, I used
Runas /user:regularuser GetLocalUsers.exe.
I believe this shows that an ADSI approach would not necessarily require elevated privileges.
However, was your intention to run the code remotely?
var path = string.Format("WinNT://{0},computer", Environment.MachineName);
using (var computerEntry = new DirectoryEntry(path))
{
var userNames = from DirectoryEntry childEntry in computerEntry.Children
where childEntry.SchemaClassName == "User"
select childEntry.Name;
foreach (var name in userNames)
Console.WriteLine(name);
}

Finding last AD logon timestamp from remote machine

I'm trying to determine the last domain connection made by a user from a remote machine, regardless of if the machine is currently connected to the domain or not.
The closest I can get is using the System.DirectoryServices.ActiveDirectory and System.DirectoryServices.AccountManagement namespaces that do something similar to this:
Domain d = Domain.GetComputerDomain();
PrincipalContext c = new PrincipalContext(ContextType.Domain, d.Name);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, "johndoe");
And then using the LastLogon property of the UserPrincipal.
This works fine, as long as the machine my application is running on is connected to the domain. If it isn't, Domain.GetComputerDomain() returns null, and I'm out of luck (even if I hardcode the domain name to the PrincipalContext constructor, it throws an exception when not connected to the domain). Is there some other AD property or registry key that gets stored locally on the remote machine when it makes an AD connection to the server that I could use?
The following link describing the LSA Cache seems promising, but, to my knowledge, there is nothing regarding domain connection timestamps that gets cached.
Determine User Active Directory Groups from Local Machine off Network

ASP.NET MVC Active Directory code to retrieve all users stops working when deployed to IIS 7

I have an intranet ASP.NET MVC3 web application that is using Windows authentication with .NET Framework 4. All my code is working as far as authenticating the users. I am using Windows Authentication for the baseline authentication, and then link the Active Directory user to a user in a table in SQL Server 2008 for additional user properties specific to my application. All of that is working fine.
There is a section of the site where admins can select Active Directory users to add to the SQL user table (see above) and put in their site specific properties (IsAdmin, ShoeSize, etc.). On this screen, there is a drop down list that I populate to retrieve all Active Directory from a custom object I created that just holds the username and full name properties of the Active Directory user. Here is this code for that:
public IQueryable<ActiveDirectoryUser> ActiveDirectoryUsers
{
get
{
// get list of users in the Active Directory
DirectoryEntry dirEntry = new DirectoryEntry("WinNT://" + Environment.UserDomainName);
List<DirectoryEntry> activeDirectoryUsers = dirEntry.Children.Cast<DirectoryEntry>()
.Where(c => c.SchemaClassName == "User").ToList();
// populate a custom class I have to hold the active directory user's username and full name property values
return activeDirectoryUsers
.Where(u => !string.IsNullOrEmpty(u.Properties["FullName"].Value.ToString()))
.Select(u => new ActiveDirectoryUser()
{
NetworkId = String.Format(#"{0}\{1}", Environment.UserDomainName, u.Properties["Name"].Value),
FullName = u.Properties["FullName"].Value.ToString()
}).AsQueryable();
}
}
For some reason, this code returns no results when the web application is deployed to IIS 7. However, this works perfectly when running the site from IIS Express in Visual Studio. Any ideas of why this would be happening? I have been looking for IIS settings as the culprit, but have not found anything helpful. Do I need to change the way I am retrieving the Active Directory users? Thank you!
This issue ended up being a combination of Eric J. answer, and a change to my code for getting the Active Directory entries. I found that the DirectoryEntry method can be overloaded to take a username and password to use for the permissions (e.g. you don't have to rely on whatever account IIS is running under. Here is the code:
List<SearchResult> searchResults;
// use login account to access active directory (otherwise it will use the IIS user account, which does not have permissions)
using (DirectoryEntry root = new DirectoryEntry("LDAP://CN=Users,DC=test,DC=example,DC=com", "usernameCredential", "passwordCredential"))
using (DirectorySearcher searcher = new DirectorySearcher(root, "(&(objectCategory=person)(objectClass=user))"))
using (SearchResultCollection results = searcher.FindAll())
{
searchResults = results.Cast<SearchResult>().ToList();
}
// get the active directory users name and username
return searchResults
.Select(u => new ActiveDirectoryUser()
{
NetworkId = String.Format(#"{0}\{1}", this._domainName, u.Properties["sAMAccountName"][0]),
FullName = (string) u.Properties["cn"][0]
}).AsQueryable();
This allowed me to get the Active Directory entries, but only those are Users and person objects. Then I used LINQ to map it to my custom model.
When running in IIS Express under Visual Studio you have a much higher permission set than running in the default security context of IIS 7.
You will have to understand exactly which permissions are needed to query Active Directory in this way and ensure that the application pool your app runs in under IIS 7 has that right. Be careful to grant only the permissions needed for this operation, and make sure you review the implications of granting those rights carefully before proceeding.

Get windows users with C#

How can I get a list of all windows users of the local machine with the usage of .NET (C#) ?
Here is a blog post (with code) that explains how to do it:
http://csharptuning.blogspot.com/2007/09/how-to-get-list-of-windows-user-in-c.html
The author lists the following code (quoted from the above site):
DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName);
DirectoryEntry admGroup = localMachine.Children.Find("users","group");
object members = admGroup.Invoke("members", null);
foreach (object groupMember in (IEnumerable)members)
{
DirectoryEntry member = new DirectoryEntry(groupMember);
lstUsers.Items.Add(member.Name);
}
You need to add using System.DirectoryServices at the top of your code. To change machines, you would change the Environment.MachineName to be whatever machine you want to access (as long as you have permission to do so and the firewall isn't blocking you from doing so). I also modified the author's code to look at the users group instead of the administrators group.
It depends on what you are really 'after'... if you are on a windows domain (using active directory) then you can query Active Directory IF active directory is being used to limit the users who are "authorized" to use the local machine.
If your requirements are not as stringent then you can inspect the folders in the system UserProfiles where each folder except Default User and All Users represent a user profile that has logged into the local machine. caution this may include system and/or service accounts...

How to access HKCU registry of currently logged on user(s), from a service?

From within a windows service I want to check some user preferences that are stored within each users' HKCU registry area. How can I do this?
I see that HKEY_USERS has subkeys of each user that has logged in to the machine (or something like that?), and within these are the HKCU areas for each user. However, these subkeys are the SIDs of the users, so somehow I'd need to work out the SID of the currently logged in user(s).
I would then query HKEY_USERS\<the users SID>\whichever\key\i\need in place of querying HKEY_CURRENT_USER\whichever\key\i\need.
From this question I can get a list of the current users on the machine in DOMAIN\USER format. Is there a way to get the SID of a user from their windows login? Or is there a more direct way to get the registry path that is HKCU for the currently logged in user(s)?
In HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList you will find the SID's of the existing profiles. The ProfileImagePath will give the path of the profile.
Most of the time this path is the username. But it could be another path if a similar path already existed when the profile was created.
The short SID's like S-1-5-18 (=> Local System) are default local accounts (https://support.microsoft.com/en-us/kb/243330)
In order to do this you will need to do one of the following
Impersonate the users credentials and access HKCU from that impersonation context
Read the registry file directly off of disk (this has threading and data integrity implications).
I'm not 100% sure that #1 will work but I believe it will.
For either solution though you will need either the users credentials or access token in your process. This is not easily available because it's a security issue.
You can connect to their remote registry, then search the entire HKU key for their username (i.e. jsmith). Various entries reference their user profile; these will pop up then you can just look under which SID those entries are located. Bit of a roundabout way of doing it, but seems to work.
Using PowerShell you can match them up:
Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" | select ProfileImagePath, PSChildName
You can even search by username (eg john):
Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" | ? {$_.ProfileImagePath -match "john"} | select ProfileImagePath, PSChildName
Bonus: reverse SID lookup using PowerShell (will return DOMAIN\USERNAME)
$objSID = New-Object System.Security.Principal.SecurityIdentifier ("S-1-5-21-2139915555-1840087203-3974481593-26737")
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
$objUser.Value

Categories

Resources