Caching impersonation in a C# web application - c#

I am working on an MVC web application which has to access files in various shared directories. Currently I am using this implementation of impersonation to access a directory, which works fine. My problem starts when I have to open multiple files from the same directory, since right now I initialize a new impersonator every time and I quickly reach the number of allowed connections to the target computer.
The code right now looks like:
// do stuff that does not require impersonation
using (Impersonation.LogonUser("domain", "username", "password", LogonType.NewCredentials))
{
// access **one** particular file
}
// continue doing stuff that does not require impersonation
However, if I use just one impersonation instance, I can access as many files as I want. This is though impractical for a web application, as I would have to run all my code under impersonation. Furthermore, different directories have different credentials (could also be located on different computers).
So my question is if it's possible to cache an impersonation, undo it for a while and re-enable it when I need to, so that I don't start a new one. Basically something like the following pseudo-code:
// do stuff that does not require impersonation
// get cached impersonation or create a new one
var imp = GetImpersonationFromStaticSharedCache("domain", "username", "password")
// access **one** particular file
// stop impersonation, but don't dispose it, so that I can re-use it later
imp.StopImpersonationForTheCurrentThread()
// continue doing stuff that does not require impersonation

Related

Run impersonated code along with wmi management scope [C#]

Scenario:
I've a remote computer without domain with a User, called hereafter Admin.
These are the steps I want to achive:
Connecting to that computer
Create a folder and give full control to Admin
Share the created folder so can be accessed via NFS
Run some code that underneath uses that directory to create temporary backup file and/or permanent ones.
So, I've used the functionalities exposed by the System.Management namespace, creating an object of type ManagementScope like this:
_managementScope = new ManagementScope($#"\\{_host}\root\cimv2",
new ConnectionOptions
{
Username = user,
Password = password
});
I think I can do the second and the third part using the Win32_Directory and Win32_Share class because they expose the ChangeSecurityPermissions and Create methods, respectively, and they seem to match my goal.
The problem to me is the last point, since the ManagementScope is configured to impersonate the user but it does no provide any object related to that so I could use it to run impersonated code.
In a nutshell, the ideal to me would be something like this:
if (_managementScope.Connected())
{
var directory = CreateDirectory(pathName);
SetFullPermission(directory, Admin); //managed with the Win32_Directory management class
ShareDirectory(directory); //managed with the Win32_Share management class
WindowsIdentity.RunImpersonated(_managementScope.Identity.AccessToken, () =>
{
//_managementScope.Identity is not available
Install(); //This method uses the directory and shall be managed by the user Admin
//so I need to run this code as Admin.
};
}
What's the best way to do so? Thanks

How can I create a protected directory that I can guarantee has only been modified by a specific user?

I am developing a .NET Windows Service using C# that runs as the SYSTEM user so that it has permissions to install software updates etc.
I want the service to download an executable file to a protected directory and launch it. However, I want to make sure that I've considered security and that it isn't possible for another user to copy a file into the directory that the service uses and then have the file executed with SYSTEM privileges.
I've looked into creating a directory that only the SYSTEM user has access to using an ACL as follows.
var localSystemIdentifier = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var directorySecurity = new DirectorySecurity();
directorySecurity.AddAccessRule(new FileSystemAccessRule(localSystemIdentifier, FileSystemRights.FullControl, AccessControlType.Allow));
directorySecurity.SetOwner(_localSystemIdentifier);
Directory.CreateDirectory(_pathToTempBootstrapperDirectory, directorySecurity);
Subsequent to this, I check that the owner of the directory is the SYSTEM user before I allow a cached copy of the executable file that has been downloaded to be used.
var acl = Directory.GetAccessControl(_pathToTempBootstrapperDirectory);
if (acl.GetOwner(typeof(SecurityIdentifier)) != localSystemIdentifier)
{
cache = false;
}
However, if a user with the right permissions was able to change the owner of the directory to themselves, copy in a file, and then change the owner back to the SYSTEM user, the above check would not be of any benefit.
Perhaps the only option is to always recreate the download folder with the strict ACL and redownload the file every time to prevent the possibility of the scenario above.
In short, my question is as follows; is there a way that I can create a protected directory that I can guarantee has only ever been created or modified by the SYSTEM user?
if a user with the right permissions was able to change the owner of the directory
If a user has administrative permissions he/she can just do whatever he/she wants without the help from your program. Raymond Chen calls this the "airtight hatchway", i.e. you need to protect against a user doing things he/she would not otherwise be allowed to, but there is no reason to protect against things the user already have permission to do.
I'm not an expert in windows permissions, but think that taking over ownership of a directory owned by SYSTEM requires admin permission.

NT AUTHORITY\Local Service is not listed in the Access Control List of a directory

I'm having this issue where I'm trying to check if NT\Authority Local Service has read\execute permissions on a directory (folder). The product that I work on REQUIRES that the folder the user is installing to has read\execute permissions set for Local Service.
The problem is that when I get the Access Control List (ACL) recursively (groups-within-groups), Local Service is not listed so I can't check if he has permissions to that folder or not.
By default, Local Service does not have read/execute permissions to user profiles (My Documents, Desktop, etc...) but I won't know if Local Service has access to other directories the user chooses to install to.
NOTE: Local Service DOES have access to Program Files, even though it is NOT listed in the ACL. Is it hidden somewhere else?
This is a short snippet on how I'm pulling the ACL:
GroupPrincipal groupPrincipal =
GroupPrincipal.FindByIdentity(principalContext, identityReferenceValue);
// GetMembers(true) is recursive (groups-within-groups)
foreach (var member in groupPrincipal.GetMembers(true)) {
if (member.SamAccountName.Equals("LOCAL SERVICE")) {
foundLocalService = true;
break;
}
}
Is there any other way I should be doing this? (Other than adding an access rule for Local Service on that directory)
Is Local Service just not listed in Directories ACL's?
Any help would be greatly appreciated.
It's notoriously difficult to calculate "effective permissions" for an account. But the simple answer to your question is that you will likely want to look for either on of:
The local Users group, sometimes shown as BUILTIN\Users or COMPUTERNAME\Users, or
Authenticated Users, sometimes shown as NT AUTHORITY\Authenticated Users.
Authenticated Users is one of the well-know SIDs. It is "a group that includes all users whose identities were authenticated when they logged on.". As long as you can prove who you are, you are included in Authenticated Users. The SID for this is always S-1-5-11 on every Windows computer.
However, it's not really considered a real group. To find it when adding permissions to a folder, you have to have "Built-in security principals" selected under "Select this object type":
The local Users group contains Authenticated Users by default. On my computer, I actually see both Users and Authenticated Users in the default permissions on the file system.
That's what you will most likely see, and that's likely all that matters.
But that's not the only way. You could see Everyone (S-1-1-0), which includes every user, authenticated or not.
Or, it could be a file or folder that has the LOCAL SERVICE account as the owner.
Or, there could be a local group that was created manually and LOCAL SERVICE was added to.
One way to get a more authoritative list of what you can look for is to run this under the LOCAL SERVICE account:
whoami /groups
That will tell you every group in the authentication token, which is every group that you are considered a member of for authentication purposes.
But you can't just open a command prompt as LOCAL SERVICE. So one way to do this is to open the Task Scheduler and create a task that runs under LOCAL SERVICE, with the action of:
Program: cmd
Arguments: /c "whoami /groups > C:\temp\localservice.txt"
Then run the task and, when it's done, look at C:\temp\localservice.txt. It will have a table of group names and their SIDs that you can look for.

OpenEvent/OpenFileMapping fails with ERROR_ACCESS_DENIED

I'm developing an open source .NET assembly (WinSCP .NET assembly) that spawns a native (C++) application and communicates with it via events and file mapping objects.
The assembly spawns the application using the Process class, with no special settings. The assembly creates few events (using the EventWaitHandle) and file mapping (using the PInvoked CreateFileMapping) and the application "opens" these using the OpenEvent and the OpenFileMapping.
It works fine in most cases. But now I'm having a user that uses the assembly from an ASPX application on Windows Server 2008 R2 64 bit.
In his case both the OpenEvent and the OpenFileMapping return NULL and the GetLastError returns the ERROR_ACCESS_DENIED.
I have tried to improve the assembly code by explicitly granting the current user necessary permissions to the event objects and the application code to require only the really needed access rights (instead of original EVENT_ALL_ACCESS) as per Microsoft Docs example. It didn't help. So I did not even bother to try the same for the file mapping object.
The C# code that creates the event is:
EventWaitHandleSecurity security = new EventWaitHandleSecurity();
string user = Environment.UserDomainName + "\\" + Environment.UserName;
EventWaitHandleAccessRule rule;
rule =
new EventWaitHandleAccessRule(
user, EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
security.AddAccessRule(rule);
rule =
new EventWaitHandleAccessRule(
user, EventWaitHandleRights.ChangePermissions, AccessControlType.Deny);
security.AddAccessRule(rule);
new EventWaitHandle(
false, EventResetMode.AutoReset, name, out createdNew, security);
The C++ code that "opens" the events is:
OpenEvent(EVENT_MODIFY_STATE, false, name);
(For other events the access level is SYNCHRONIZE, depending on needs).
I have also tried to add Global\ prefix to the object names. As expected this didn't solve the problem.
Does anyone have any idea what causes the "access denied" error in OpenEvent (or CreateFileMapping)?
My guess is that the event is created by either the anonymous user or the logged in user depending on how the website is setup. But the sub-process is being launched with the base process user. This can be checked by using process monitor and looking at the acl for the event handle to see who the creator is. Then look at the sub process to see who it is running as.
If this is the case then you can update the acl on the event to include the base process. In addition to this, you may still need to prefix with "global" to make sure that the event can be used across user boundaries.

Getting Error Querying Active Directory On The Server Only

I have the following block of code that queries Active Directory for users by Group Name using System.DirectoryServices.AccountManagement:
var domainContext = new PrincipalContext(ContextType.Domain, "company.container.internal");
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Name, "Lvl1Users");
if (groupPrincipal != null)
{
//Read the values
}
Now the site uses the following:
IIS7 on Win2k8
Windows Authentication
Impersonation = True
App Pool on .NET 4.0 using 'NETWORK SERVICE' as the account
On my local machine (you know how this goes) it all works great. My peers that try it locally also it works well. However once deployed to the server it shows the following:
An operations error occurred.
Everything I research says it's a permissions issue. 1 thing to note, on my local machine I'm on the MainNetwork domain which is the parent to company.container.internal domain which I am querying. The IIS machine is on company.container.internal and is querying the same domain. So honestly, I would think the more challenging situation is reading AD on my local machine which is on a different domain, but it works. On the server which is querying the same domain, it fails.
Here is what I've tried, and none of these has worked:
Change AppPool to 'LocalSystem'
Change AppPool to use a static super-duper Admin account
Used Impersonation in code to manipulate the context of the calls in a local block with an admin user on the MainNetwork domain.
Used Impersonation in code to manipulate the context of the calls in a local block with an admin user on the company.container.internal domain.
Adding in using (HostingEnvironment.Impersonate())
What gives here? I have tried impersonating every type of power admin on both domains, and used multiple AppPool settings, and I keep getting the same error. Is there anything that needs to change in the code with the declaration of the domains, or is there a permissions issue I'm missing?
I figured this out and it turned out that using HostingEnvironment.Impersonate() was still at the root to solve the problem. I had already tried this, but there was another issue with my code.
The issue is often that the context for which the Active Directory calls is made is under a user that does not have permissions (also can happen when identity impersonate="true" in ASP.NET, due to the fact that the users token is a "secondary token" that cannot be used when authenticating against another server from: http://bit.ly/1753RjA).
The following code will ensures that the block of code running, is run under the context of say the AppPool (i.e. NETWORKSERVICE) that the ASP.NET site is running under.
using (HostingEnvironment.Impersonate())
{
var domainContext = new PrincipalContext(ContextType.Domain, "myDomain.com");
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Name, "PowerUsers");
if (groupPrincipal != null)
{
//code to get the infomation
}
}
However, one super important detail is that all the code calling Active Directory must be in that block. I had used some code a team member of mine wrote that was returning a LINQ query results of type Users (custom class), but not evaluating the expression (bad practice). Therefore the expression tree was returned instead of the results.
What ended up happening is the calling code eventually evaluated the results and the An operations error occurred message still appeared. I though the code fix above didn't work. When in fact it did, but there was code evaluating the results outside the block.
In a nutshell, make sure all code to access Active Directory is inside that using block and the exception should be fixed one the service/app is deployed to the server.

Categories