Среда: Windows XP SP3, С#,.Net 4.0
Проблема:
Я пытаюсь добавить доступ к неактивному кусту реестра пользователей в классе олицетворения, и я сталкиваюсь с проблемами, основанными на типе персонифицированного пользователя (или, точнее, ограничение, похоже, у олицетворяющего пользователя).
Я изначально следовал пример олицетворения из CodeProject, который показал, что вызов LoadUserProfile()
происходит после запуска олицетворения с использованием дублированного токена, сгенерированного через вызов DuplcateToken()
из исходного токена, полученного из LogonUser()
. Мне не удалось заставить этот пример работать в моей среде, выдавая себя за ограниченного пользователя из учетной записи администратора (из снимков экрана, включенных в пример, похоже, что это было сделано в системе Windows Vista\7, и никаких подробностей о типы учетных записей).
Вызов LoadUserProfile()
вызывал ошибку "Access Denied". Глядя на userenv.log, показана строка "LoadUserProfile: не удалось включить привилегию восстановления. Error c0000022". Документация LoadUserProfile на MSDN показывает, что вызывающий процесс должен обладать привилегиями SE_RESTORE_NAME и SE_BACKUP_NAME, которые по умолчанию имеют только члены групп Администраторы и Операторы резервного копирования. (В качестве побочного примечания, когда я попытался добавить эти две привилегии позже в группу "Пользователи", я все еще получил "Отказ от доступа", но userenv.log показал, что "DropClientContext: Client [number] не имеет достаточного разрешения. Я не мог найти никакой информации)
Учитывая, что пользователь, которому я выдавал себя за работу, не имел этих привилегий, я переместил вызов LoadUserProfile()
до начала олицетворения, и на этот раз он загрузился без проблем, и я смог прочитать и написать ему в этом тесте, Думая, что я обнаружил свой ответ, я создал условную проверку для типа учетной записи, чтобы LoadUserProfile()
был вызван до олицетворения, если текущий пользователь был членом Администраторов или дождался после олицетворения, если член не был членом Администраторов (в более позднем случае я полагался бы на олицетворенного пользователя, имеющего эти привилегии). К сожалению, я ошибся; Я не нашел ответа. Когда я протестировал вызов с измененной ролью (Пользователь > Администратор), вызов LoadUserProfile()
по-прежнему не удался с ошибкой Access Denied, а userenv.log показал тот же самый "LoadUserProfile: не удалось включить привилегию восстановления. Error c0000061", но с другим номером ошибки на этот раз.
Думая, что привилегии не могут быть включены по умолчанию на токенах, возвращаемых из LogonUser()
и\или DuplicateToken()
, я добавил два вызова AdjustTokenPrivilege()
на токен пользователя (выполняемый после олицетворения), полученный из WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token
.
TokenAccessLevels.AdjustPrivileges
и TokenAccessLevels.Query
были указаны, потому что документация для AdjustTokenPrivilege в MSDN указывает, что они необходимы для корректируемого токена (я также попытался получить токен через вызов OpenProcessToken()
с помощью дескриптора, полученного из System.Diagnostics.Process.GetCurrentProcess().Handle
> но это не удалось при вызове от пользователя как внутри, так и за пределами олицетворения с помощью GetCurrentProcess()
, являющегося функцией, которая отказала в доступе)
AdjustTokenPrivilege()
успешно возвратился при использовании с WindowsIdentity...Token
, но LoadUserProfile()
по-прежнему приводил к отказу доступа (восстановить привилегию).
На данный момент я не был уверен, что AdjustTokenPrivilege()
выполняет эту работу, поэтому я решил определить, какие привилегии были доступны, и какое состояние они были для определенного токена с GetTokenInformation()
, в результате чего у него появился небольшой мешок удовольствия. Изучив некоторые новые вещи, я смог вызвать GetTokenInformation()
и распечатать список привилегий и их текущий статус, но результаты были несколько неубедительными, так как оба Restore and Backup показали атрибут 0 до и после вызова AdjustTokenPrivilege()
как в качестве администратор и пока выдает себя за администратора (как ни странно, три других привилегии менялись от 2 до 1 на токене при вызове AdjustTokenPrivilege()
, но не те, которые фактически были скорректированы, которые остались при значении 0)
Я удалил вызов на DuplicateToken()
и заменил все места, которые он использовал, на токен, возвращенный из LogonUser()
, чтобы узнать, поможет ли это при тестировании привилегий на токенах LogonUser()
и DuplicateToken()
жетоны были идентичны. Когда я изначально написал класс олицетворения, я без проблем использовал первичный токен в своем вызове WindowsImpersonationContext.Impersonate()
и решил, что стоит попробовать.
В приведенном ниже примере кода я могу олицетворять и обращаться к реестру пользователя при запуске в качестве администратора, но не наоборот. Любая помощь будет принята с благодарностью.
Предварительное сообщение:
Я также попытался использовать API RegOpenCurrentUser()
вместо LoadUserProfile()
и имел успех с помощью "Администратор" > "Я" и "Администратор" > "Выражение пользователя", но при выдаче себя за администратора из другой учетной записи администратора или пользователя RegOpenCurrentUser()
возвращает указатель на HKEY_USERS\S-1-5-18 (что бы это ни было) вместо фактического улья. Я предполагаю, потому что на самом деле он не загружен, что возвращает меня к квадрату с необходимостью использовать LoadUserProfile()
Из документации RegOpenCurrentUser (MSDN):
RegOpenCurrentUser использует токен потока для доступа к соответствующему ключу или по умолчанию, если профиль не загружен.
Фрагмент кода:
// Private variables used by class
private IntPtr tokenHandle;
private PROFILEINFO pInfo;
private WindowsImpersonationContext thisUser;
private string sDomain = string.Empty;
private string sUsername = string.Empty;
private string sPassword = string.Empty;
private bool bDisposed = false;
private RegistryKey rCurrentUser = null;
private SafeRegistryHandle safeHandle = null;
//Constants used for privilege adjustment
private const string SE_RESTORE_NAME = "SeRestorePrivilege";
private const string SE_BACKUP_NAME = "SeBackupPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
private const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
[StructLayout(LayoutKind.Sequential)]
protected struct PROFILEINFO {
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpPolicyPath;
public IntPtr hProfile;
}
protected struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID {
public uint LowPart;
public int HighPart;
}
// Private API calls used by class
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
protected static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Start() {
tokenHandle = IntPtr.Zero; // set the pointer to nothing
if (!LogonUser(sUsername, sDomain, sPassword, 2, 0, ref tokenHandle)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
} // end if !LogonUser returned false
try { //All of this is for loading the registry and is not required for impersonation to start
LUID LuidRestore = new LUID();
LUID LuidBackup = new LUID();
if(LookupPrivilegeValue(null, SE_RESTORE_NAME, ref LuidRestore) && LookupPrivilegeValue(null, SE_BACKUP_NAME, ref LuidBackup)) {
//Create the TokenPrivileges array to pass to AdjustTokenPrivileges
LUID_AND_ATTRIBUTES[] LuidAndAttributes = new LUID_AND_ATTRIBUTES[2];
LuidAndAttributes[0].Luid = LuidRestore;
LuidAndAttributes[0].Attributes = SE_PRIVILEGE_ENABLED;
LuidAndAttributes[1].Luid = LuidBackup;
LuidAndAttributes[1].Attributes = SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES TokenPrivileges = new TOKEN_PRIVILEGES();
TokenPrivileges.PrivilegeCount = 2;
TokenPrivileges.Privileges = LuidAndAttributes;
IntPtr procHandle = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token;
if(AdjustTokenPrivileges(procHandle, false, ref TokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) {
pInfo = new PROFILEINFO();
pInfo.dwSize = Marshal.SizeOf(pInfo);
pInfo.lpUserName = sUsername;
pInfo.dwFlags = 1;
LoadUserProfile(tokenHandle, ref pInfo); //this is not required to take place
if(pInfo.hProfile != IntPtr.Zero) {
safeHandle = new SafeRegistryHandle(pInfo.hProfile, true);
rCurrentUser = RegistryKey.FromHandle(safeHandle);
}//end if pInfo.hProfile
}//end if AdjustTokenPrivileges
}//end if LookupPrivilegeValue 1 & 2
}catch{
//We don't really care that this didn't work but we don't want to throw any errors at this point as it would stop impersonation
}//end try
WindowsIdentity thisId = new WindowsIdentity(tokenHandle);
thisUser = thisId.Impersonate();
} // end function Start