I need to find a way to guarantee that people using my library have set the requestedExecutionLevel to a minimum of highestAvailable in the application manifest, as follows:
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
</requestedPrivileges>
When they haven't set up the manifest correctly, I want to throw an exception so the developer is aware about this need, without having to read the documentation.
The main question: Is it possible to access this information, if so how?
Instead of checking this setting I already thought about checking the result of this setting. When set to highestAvailable I would expect any user part of the administrators group, to be running as administrator.
This would be possible using:
WindowsPrinciple.IsInRole() to check the currently used role.
Seemingly more difficult to find, a method to check whether a user is in a given group, or more particularly the administrator group. The IsUserInAdminGroup() method listed on the UAC code samples might do the trick.
Your approach is flawed, the dev that uses your library is going to dislike it greatly. The issue is that the manifest isn't the only way to get a process to run elevated. And in fact is almost never the way it is done when you debug code, the Visual Studio Hosting Process is the EXE you run and has the active manifest, without "highestAvailable". The dev will instead elevate Visual Studio itself, the program he starts inherits the security token and will run elevated as well.
What you really want to know is whether the process is elevated when it uses your library and that the user account belongs to the Administrators group. That's rather hard to come by, if the process does not run elevated then the security token is one of a normal user.
You need the code available here. Throw the exception if IsUserInAdminGroup is true and IsProcessElevated is false.
As Hans Passant points out in his answer, the real underlying question - why in this scenario you would want to check the manifest - is:
How to check whether the current user can run the process with elevated privileges?
As suggested in the question, the following would work:
var myPrincipal = new WindowsPrincipal( WindowsIdentity.GetCurrent() );
if ( !myPrincipal.IsInRole( WindowsBuiltInRole.Administrator ) &&
IsUserInAdminGroup() )
{
throw new NotSupportedException( "Some useful comments ..." );
}
The main question is thus, how do you write IsUserInAdminGroup()? The code listed in the UAC self-elevation sample albeit useful, doesn't explain what is going on, and why it is needed.
Hans Passant replied in a comment "Windows emulates a non-elevated process too well, no way to tell from .NET that the user account is in fact an admin account. Falling back to interrogating the token with pinvoke is the workaround used in the linked code.".
In short, you'll need to rely on P/Invoke in order to implement IsUserInAdminGroup(), of which the code can be found in the UAC sample.
More interesting perhaps, is why?
In order to find out I refactored the sample code and incorporated the function into my library. The result in my opinion is a bit more clear. Below you can find the outline, the comments are probably more relevant than the code since it depends on other classes etc ...
Starting from Windows Vista, you have different token types as expressed by TOKEN_ELEVATION_TYPE. Although you can access WindowsIdentity.Token through .NET, this isn't the token we need to check whether someone is administrator. This is a limited token. It has a linked elevated token attached to it, but this isn't exposed in .NET.
Pretty much all the (semi-pseudo) code below does is look up whether there is such an elevated token attached to the original token, and use that to check IsInRole() instead.
// Default token's received aren't impersonation tokens,
// we are looking for an impersonation token.
bool isImpersonationToken = false;
// Open the access token of the current process.
SafeTokenHandle processToken;
if ( !AdvApi32.OpenProcessToken( ..., out processToken ) )
{
MarshalHelper.ThrowLastWin32ErrorException();
}
// Starting from Vista linked tokens are supported which need to be checked.
if ( EnvironmentHelper.VistaOrHigher )
{
// Determine token type: limited, elevated, or default.
SafeUnmanagedMemoryHandle elevationTypeHandle = ...;
if ( !AdvApi32.GetTokenInformation( ... elevationTypeHandle ) )
{
MarshalHelper.ThrowLastWin32ErrorException();
}
var tokenType = (AdvApi32.TokenElevationType)Marshal.ReadInt32(
elevationTypeHandle.DangerousGetHandle() );
// If limited, get the linked elevated token for further check.
if ( tokenType == AdvApi32.TokenElevationType.TokenElevationTypeLimited )
{
// Get the linked token.
SafeUnmanagedMemoryHandle linkedTokenHandle = ...;
if ( !AdvApi32.GetTokenInformation( ... linkedTokenHandle ) )
{
MarshalHelper.ThrowLastWin32ErrorException();
}
processToken = new SafeTokenHandle(
Marshal.ReadIntPtr( linkedTokenHandle.DangerousGetHandle() ) );
// Linked tokens are already impersonation tokens.
isImpersonationToken = true;
}
}
// We need an impersonation token in order
// to check whether it contains admin SID.
if ( !isImpersonationToken )
{
SafeTokenHandle impersonatedToken;
if ( !AdvApi32.DuplicateToken( ..., out impersonatedToken ) )
{
MarshalHelper.ThrowLastWin32ErrorException();
}
processToken = impersonatedToken;
}
// Check if the token to be checked contains admin SID.
var identity= new WindowsIdentity( processToken.DangerousGetHandle() );
var principal = new WindowsPrincipal( identity );
return principal.IsInRole( WindowsBuiltInRole.Administrator );
Related
Ok, first of all: my task is, to map a network drive programmatically from a C# program that runs as administrator.
I managed to map a drive as my normal admin user without elevated privileges and it was still visible after logoff/reboot (even though it didn't reconnect, but that's a different story). I did this with the WNetAddConnection2 WinApi function and also with the net use command in cmd, just to check.
Sadly, both didn't work with elevated privileges. In this case the drive is added as it schould, but after a reboot it is completely gone.
Is it even possible to achieve this with elevated privileges or is there some Windows account magical stuff I don't know about that prevents that?
Here is the Code I used, setting all flags that should usually make the drive be remembered and also reconnected:
uint flags = (uint)(Flags.CONNECT_CMD_SAVECRED |
Flags.CONNECT_INTERACTIVE |
Flags.CONNECT_COMMANDLINE |
Flags.CONNECT_UPDATE_PROFILE);
NETRESOURCE NetworkResource = new NETRESOURCE();
oNetworkResource.dwType = ResourceType.RESOURCETYPE_DISK;
oNetworkResource.lpLocalName = Console.ReadLine() + ":";
oNetworkResource.lpRemoteName = #"\\[Server]\foo";
oNetworkResource.lpProvider = null;
Console.WriteLine(WNetAddConnection2(NetworkResource, "[Password]", #"[Domain]\[Username]", flags));
it was still visible after logoff/reboot
this is because, when CONNECT_UPDATE_PROFILE flag used - called exported, but undocumented function I_MprSaveConn (from mpr.dll) which save in registry, under HKEY_CURRENT_USER\Network\<lpLocalName> information which you pass to WNetAddConnection2. but I_MprSaveConn at very begin call function bool IsElevatedCaller(PLUID ) and if function return true - it just exit, without saving in registry. so you absolute correct - when you call WNetAddConnection2 from elevated process (without impersonation) - this connection not persist (info not saved in registry)
solution: you need got not elevated token (say from explorer) - open/duplicate (for TokenImpersonation type) and call SetThreadToken. in this case IsElevatedCaller (can) return false (it first try open thread token (only if it not exist - process token) ) and query opened token for TokenElevationType (and return true if TokenElevationTypeFull )
so this of course not documented, but current (i test) if you impersonate self thread with not elevated token (how you got it separate question) flag CONNECT_UPDATE_PROFILE will be worked well
I was reading about delegates on MSDN and saw a line that says
"Note:
Delegates run under the caller's security permissions, not the declarer's permissions"
What does that mean?
Assuming the question is about Windows permissions, not .Net Code Access Security (CAS).
Irrespective what account code was run under when you created delegate (i.e. box admin) Windows permissions will be computed at the moment of actual invocation - which may be different from the one where at creation time.
Imagine that you run code that performs impersonation of an account (Windows user) to access some files:
// run under "account1" - has access to c:\myFile.txt
// current Environment.UserName = "account2"
Func<string,string> readAllFile = fileName => File.ReadAllText(fileName);
// start impersonation of account2 - has access to c:\otherFile.txt,
// but not c:\myFile.txt
ImpersonateAccount("account2", readAllFile);
....
...ImpersonateAccout(string name, Func<string,string> readAllFile)
{
// .... impersonation code omitted
// current Environment.UserName = "account2"
var text1 = readAllFile(#"c:\otherFile.txt"); // success
var text2 = readAllFile(#"c:\myFile.txt"); // failure
....
In above sample readAllFile created when code was running under account1, but it does not "capture" permission of that account and hence later delegate can't read c:\myFile.txt which "account2" has no permissions to.
Note that delegates "capture" C# level context like local variables, which may bring assumption that other kinds of contexts are captured too. It is not the case for Window security context as well as for .Net execution context (current thread's culture for example).
In my current C# code I'm able to lock a Windows user session programmatically (same as Windows + L).
Since the app would still be running, is there any way to unlock the session from that C# program. User credentials are known. The app is running on Windows 7.
You'll need a custom windows credential provider to log in for you. Also, you'll need to save the user's credentials somewhere to log in. There are some samples in Windows SDK 7 https://www.microsoft.com/en-us/download/details.aspx?id=8279
There's a bunch of projects to get you started under Samples\security\credentialproviders.
To unlock the screen:
set the username / password in CSampleCredential::Initialize
set autologin to true in CSampleCredential::SetSelected
search the hardware provider sample for WM_TOGGLE_CONNECTED_STATUS message to see how to trigger the login
build some way to communicate with your app to trigger the unlock (local tcp server for example)
It's a pain in the ass, but it works.
Here is some hackery to do that: http://www.codeproject.com/Articles/16197/Remotely-Unlock-a-Windows-Workstation
Didn't test it myself though.
Not for .NET part, but you could also make your own custom Logon UI and inject some mechanism there. It can easily become security problem though.
var path = new ManagementPath();
path.NamespacePath = "\\ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption"; path.ClassName = "Win32_EncryptableVolume";
var scope = new ManagementScope(path, new ConnectionOptions() { Impersonation = ImpersonationLevel.Impersonate });
var management = new ManagementClass(scope, path, new ObjectGetOptions());
foreach (ManagementObject vol in management.GetInstances())
{
Console.WriteLine("----" + vol["DriveLetter"]);
switch ((uint)vol["ProtectionStatus"])
{
case 0:
Console.WriteLine("not protected by bitlocker");
break;
case 1:
Console.WriteLine("unlocked");
break;
case 2:
Console.WriteLine("locked");
break;
}
if ((uint)vol["ProtectionStatus"] == 2)
{
Console.WriteLine("unlock this driver ...");
vol.InvokeMethod("UnlockWithPassphrase", new object[] { "here your pwd" });
Console.WriteLine("unlock done.");
}
}
Note: this only works if you run Visual Studio as an administrator.
No, there is no way to do this, by design. What's your scenario and why do you need to lock/unlock the workstation?
Of course you can't unlock it. Unlocking a session requires the user physically be there to enter their account credentials. Allowing software to do this, even with saved credentials, would be a security issue for many of the other situations where workstation locking is used.
When installing a service, there is a helpful .NET class called ServiceProcessInstaller. This class has a property Account, which is a ServiceAccount enumeration with possible values LocalService, LocalSystem, NetworkService and User.
This is fine at install-time, but does anybody know how I can change this value for an existing service?
I assuming that I need to move away from the actual install-type classes, and have been researching hooking into the advapi32 ChangeServiceConfig method, WMI and ManagementObjects etc.
Indeed I have found code which will actually change the account under which the service runs,
ManagementObject mo = new ManagementObject("Win32_Service.Name='" + myService + "'");
object[] configParams = new object[11];
configParams[6] = userName;
configParams[7] = password;
object result = mo.InvokeMethod("Change", configParams);
(which on its own looks a bit like black magic but makes sense when viewed with the ChangeServiceConfig signature)
However when I apply this code to a service which happens to be installed as LocalSystem, it has no effect (although when I interpret result the call is reporting success). This doesn't really surprise me since I am only setting a username and password, I am not saying "rather than running as a local service, this service needs to run under a specific user account".
Now, my gut feel is that I am heading along the right lines here. The problem is that none of the parameters in ChangeServiceConfig appear to offer the opportunity to do this.
Any ideas? TIA, Pete
Error code 16 means "Service marked for deletion". Sometimes when you change service parameter, in particular when you delete / re-create a service you need to reboot your PC for operation to complete. While it's still pending, you can't manipulate service and you get error code 16.
Also, it might not be the case, that you problem has something to do with the fact that the call is inside a dll. If you put you code in a test rig dll and call it from a test rig exe (the same way you tested it in a test rig exe) and don't create / delete service in between I think it will work anyway.
The reason it does not working in your application on my opinion has to do with what you did with the service before (and this something most likely is not described in your question).
You need Impersonate an thread to run at context of user.
Try this class :
A small C# Class for impersonating a User
or this one :
Impersonate User
Return code is 21: "Invalid Parameter".
I ran into the same issue: Problem occurs when trying to apply a new user/password to a service which currently has "LocalSystem" with "Allow Service to interact with desktop" enabled.
To resolve, set the "DesktopInteract" flag in the "Change" query
var query = new ManagementPath(string.Format("Win32_Service.Name='{0}'", serviceName)); // string.Format("SELECT * FROM Win32_Service where Name='{0}'", serviceName);
using (ManagementObject service = new ManagementObject(query))
{
object[] wmiParams = new object[10];
//WMI update doesn't work if the service's user is currently set to LocalSystem
// with Interact with desktop on
wmiParams[5] = false;
wmiParams[6] = serviceUserName;
wmiParams[7] = password;
//update credentials for the service
var rtn = service.InvokeMethod("Change", wmiParams);
}
I've noticed if you change the security settings for a particular directory, you can make that folder no longer "browsable" in windows. In particular, changing the "Read" permission for Administrators to "Deny" will make that folder inaccessible.
The question I now have, is how do I figure this out in code? I following gets me close, but it still ain't right:
/// <summary>
/// Takes in a directory and determines if the current user has read access to it (doesn't work for network drives)
/// THIS IS VERY HACKY
/// </summary>
/// <param name="dInfo">directoryInfo object to the directory to examine</param>
/// <returns>true if read access is available, false otherwise</returns>
public static bool IsDirectoryReadable(DirectoryInfo dInfo)
{
try
{
System.Security.AccessControl.DirectorySecurity dirSec = dInfo.GetAccessControl();
System.Security.Principal.WindowsIdentity self = System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal selfGroup = new System.Security.Principal.WindowsPrincipal(self);
// Go through each access rule found for the directory
foreach (System.Security.AccessControl.FileSystemAccessRule ar in dirSec.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)))
{
if (selfGroup.IsInRole((System.Security.Principal.SecurityIdentifier)ar.IdentityReference))
{
// See if the Read right is included
if ((ar.FileSystemRights & System.Security.AccessControl.FileSystemRights.Read) == System.Security.AccessControl.FileSystemRights.Read)
{
if (ar.AccessControlType == System.Security.AccessControl.AccessControlType.Allow)
{
// If all of the above are true, we do have read access to this directory
return true;
}
else
{
return false;
}
}
}
}
// If we didn't find anything
return false;
}
catch
{
// If anything goes wrong, assume false
return false;
}
}
I'm close with the above, but I'm still missing something huge. If I right click on a folder to set permissions, I see (in my example) 3 groups or user names: "Administrators, myUserName and SYSTEM". If I set the "Read" to Deny for either "Administrators" or "myUserName" I can no longer browse the directory. If I only set "System" to "Deny", I can still browse it.
There seems to be some sort of permission hierarchy implied, where either myUserName or Administrator supersedes the SYSTEM group/user.
The code above looks for the first Allow for "Read" it finds for my user identity and returns true. I could also write code that looks for the first "Deny" for Read and returns false.
I can set a folder to be Read - "Deny" for SYSTEM and Read - "Allow" for the other two accounts and still read the folder. If I change the code to look for Deny's and it encounters the SYSTEM user identity first, my function will return "false", which is... false. It might very well be Read - "Allow" for the other two accounts.
The problem that I still can't figure out is, how can I determine which user identity permission has priority over all of the others?
It gets very tricky because ACL's allow inheritance, but they also have a model of most restrictive access. In other words, if you have a DENY anywhere in your user chain to a resource, no matter how many other groups may give you an ALLOW, you are denied. There is a good article on the subect on MSDN.
The system group is related to the O/S process and not directly related to your user account. It's what the O/S would use to access the filesystem if there was no user context. Since your application is running as your "username" the permissions come from it and the groups it's in. Don't think you need to be checking System in this case unless I'm missing something.
Update: Keep in mind that Directory.Exists() will also check that you have permissions to read the directory.
The issue isn’t a permission hierarchy. The problem is that your code is returning true or false based on the first matching Role. You really need to evaluate all permissions.
I recently had to deal with this issue... I posted my code here.