Handle LSA Account Rights
Hi !
How assign to a service account (local or domain) local rights ? Obviously multi scenarios ! This is the cool. The first one is to make it manually, but let be clear, this is not what you/me are looking for, our goal in life is to automatize EVERYTHING possible.
No need to speicify but many privileges can be granted to services running on close systems, this is a little list about those rights:
- SeAssignPrimaryTokenPrivilege
- SeAuditPrivilege
- SeBackupPrivilege
- SeBatchLogonRight
- SeChangeNotifyPrivilege
- SeCreatePagefilePrivilege
- SeCreatePermanentPrivilege
- SeCreateTokenPrivilege
- SeDebugPrivilege
- SeIncreaseBasePriorityPrivilege
- SeIncreaseQuotaPrivilege
- SeInteractiveLogonRight
- SeLoadDriverPrivilege
- SeLockMemoryPrivilege
- SeMachineAccountPrivilege
- SeNetworkLogonRight
- SeProfileSingleProcessPrivilege
- SeRemoteShutdownPrivilege
- SeRestorePrivilege
- SeSecurityPrivilege
- SeServiceLogonRight
- SeShutdownPrivilege
- SeSystemEnvironmentPrivilege
- SeSystemProfilePrivilege
- SetSystemtimePrivilege
- SeTakeOwnershipPrivilege
- SeTcbPrivilege
- SeUnsolicitedInputPrivilege
At first glance, this is not possible by default in all PowerShell version. All bing search send me in only 3 ways:
- ntrights.exe
- Set-Privilege (from PSCX module)
- LsaAddAccountRights (C# API)
Let’s have a look about how it works
NTRights.exe
The simplest solution, this legacy utility was firstly publied in Windows Ressource Kit, but it works perfectly in Windows 2008 R2… didn’t know about 2012.. but why not.
Microsoft says that you use it from cmd line like this
ntrights +r user_right -u "nom_compte" [-m\nom_ordinateur]
In PowerShell it could be something like this
$Accounts @('Test1','Test2') $Rights = @("SeTcbPrivilege","SeIncreaseQuotaPrivilege","SeCreateTokenPrivilege",`"SeImpersonatePrivilege","SeBatchLogonRight","SeServiceLogonRight", "SeAssignPrimaryTokenPrivilege","SeManageVolumeNamePrivilege","SeLockMemoryPrivilege","SeChangeNotifyPrivilege") Foreach ($Account in $Accounts) { Foreach ($Right in $Rights) { $cmd = "ntrights.exe +r $Right -u $Account" Invoke-Expression $cmd } }
Honestly this method is really simple, but who wants to use third party legacy utilities in their script if it’s not mandatory ?
PSCX / Set-Privilege
Module Powershell everyone should have ! it adds many powerfull features to your PowerShell. You can download it here. This is a module, so again you need to add it to your script… But let’s see how it works… nothing more simple !
$p = Get-Privilege $p.Enable('SeTimeZonePrivilege') Set-Privilege $p Get-Privilege | ft Name, Status -a
We gather privileges for current user, we enable a strict privilege and we assign privileges. The is always the WindowsIdentity parameter we can be use to provide to specific user as a target ! But there is a cooler method 🙂
LsaAddAccountRights
For me the best ! This Windows API contains multiple functions to handle accounts privileges and by default it’s a part of advapi32.dll que l’on trouve au minimum sur Windows 2008 R2 (not tested on 2012+) ! I’m not gonna make a training about how use Windows API, but i’ll show you about how compile an assembly to use it on your scripts 🙂
$LSAType = @' using System; using System.Collections.Generic; using System.Text; namespace MyLsaWrapper { using System.Runtime.InteropServices; using System.Security; using System.Management; using System.Runtime.CompilerServices; using System.ComponentModel; using LSA_HANDLE = IntPtr; [StructLayout(LayoutKind.Sequential)] struct LSA_OBJECT_ATTRIBUTES { internal int Length; internal IntPtr RootDirectory; internal IntPtr ObjectName; internal int Attributes; internal IntPtr SecurityDescriptor; internal IntPtr SecurityQualityOfService; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct LSA_UNICODE_STRING { internal ushort Length; internal ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] internal string Buffer; } sealed class Win32Sec { [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaOpenPolicy( LSA_UNICODE_STRING[] SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, int AccessMask, out IntPtr PolicyHandle ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaAddAccountRights( LSA_HANDLE PolicyHandle, IntPtr pSID, LSA_UNICODE_STRING[] UserRights, int CountOfRights ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern int LsaLookupNames2( LSA_HANDLE PolicyHandle, uint Flags, uint Count, LSA_UNICODE_STRING[] Names, ref IntPtr ReferencedDomains, ref IntPtr Sids ); [DllImport("advapi32")] internal static extern int LsaNtStatusToWinError(int NTSTATUS); [DllImport("advapi32")] internal static extern int LsaClose(IntPtr PolicyHandle); [DllImport("advapi32")] internal static extern int LsaFreeMemory(IntPtr Buffer); } /// /// This class is used to grant "Log on as a service", "Log on as a batchjob", "Log on localy" etc. /// to a user. /// public sealed class LsaWrapper : IDisposable { [StructLayout(LayoutKind.Sequential)] struct LSA_TRUST_INFORMATION { internal LSA_UNICODE_STRING Name; internal IntPtr Sid; } [StructLayout(LayoutKind.Sequential)] struct LSA_TRANSLATED_SID2 { internal SidNameUse Use; internal IntPtr Sid; internal int DomainIndex; uint Flags; } [StructLayout(LayoutKind.Sequential)] struct LSA_REFERENCED_DOMAIN_LIST { internal uint Entries; internal LSA_TRUST_INFORMATION Domains; } enum SidNameUse : int { User = 1, Group = 2, Domain = 3, Alias = 4, KnownGroup = 5, DeletedAccount = 6, Invalid = 7, Unknown = 8, Computer = 9 } enum Access : int { POLICY_READ = 0x20006, POLICY_ALL_ACCESS = 0x00F0FFF, POLICY_EXECUTE = 0X20801, POLICY_WRITE = 0X207F8 } const uint STATUS_ACCESS_DENIED = 0xc0000022; const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a; const uint STATUS_NO_MEMORY = 0xc0000017; IntPtr lsaHandle; public LsaWrapper() : this(null) { } // // local system if systemName is null public LsaWrapper(string systemName) { LSA_OBJECT_ATTRIBUTES lsaAttr; lsaAttr.RootDirectory = IntPtr.Zero; lsaAttr.ObjectName = IntPtr.Zero; lsaAttr.Attributes = 0; lsaAttr.SecurityDescriptor = IntPtr.Zero; lsaAttr.SecurityQualityOfService = IntPtr.Zero; lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); lsaHandle = IntPtr.Zero; LSA_UNICODE_STRING[] system = null; if (systemName != null) { system = new LSA_UNICODE_STRING[1]; system[0] = InitLsaString(systemName); } uint ret = Win32Sec.LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle); if (ret == 0) return; if (ret == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY)) { throw new OutOfMemoryException(); } throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret)); } public void AddPrivileges(string account, string privilege) { IntPtr pSid = GetSIDInformation(account); LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; privileges[0] = InitLsaString(privilege); uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, pSid, privileges, 1); if (ret == 0) return; if (ret == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY)) { throw new OutOfMemoryException(); } throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret)); } public void Dispose() { if (lsaHandle != IntPtr.Zero) { Win32Sec.LsaClose(lsaHandle); lsaHandle = IntPtr.Zero; } GC.SuppressFinalize(this); } ~LsaWrapper() { Dispose(); } // helper functions IntPtr GetSIDInformation(string account) { LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1]; LSA_TRANSLATED_SID2 lts; IntPtr tsids = IntPtr.Zero; IntPtr tdom = IntPtr.Zero; names[0] = InitLsaString(account); lts.Sid = IntPtr.Zero; Console.WriteLine("String account: {0}", names[0].Length); int ret = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids); if (ret != 0) throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret)); lts = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(tsids, typeof(LSA_TRANSLATED_SID2)); Win32Sec.LsaFreeMemory(tsids); Win32Sec.LsaFreeMemory(tdom); return lts.Sid; } static LSA_UNICODE_STRING InitLsaString(string s) { // Unicode strings max. 32KB if (s.Length > 0x7ffe) throw new ArgumentException("String too long"); LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING(); lus.Buffer = s; lus.Length = (ushort)(s.Length * sizeof(char)); lus.MaximumLength = (ushort)(lus.Length + sizeof(char)); return lus; } } public class LsaWrapperCaller { public static void AddPrivileges(string account, string privilege) { using (LsaWrapper lsaWrapper = new LsaWrapper()) { lsaWrapper.AddPrivileges(account, privilege); } } } } '@ Add-Type $LSAType -PassThru [MyLsaWrapper.LsaWrapperCaller]::AddPrivileges("andy", "SeServiceLogonRight")
Here we go! For me this is the best solution to assign LSA rights without any thirt party or legacy utilities.
Regards
Salut,
un point de départ pour créer un module binaire. Si j’ai le temps je l’ajouterais dans mon prochain tutoriel sur les modules. Mais à priori l’usage de module n’est pas ta tasse de thè 😉
Ceci dit, à mon avis, dés que l’on commence à utiliser ce genre de code, mieux vaut faciliter sa maintenance via un projet C#. Et plus tu masques l’implémentation, meilleure sera le taux d’acceptation de ce type de solution.
Je suis curieux de savoir combien de ‘c’est compliqué TON truc/TON script/TA solution/…” tu vas recevoir 🙂
Non non non, j’aime bien les modules, le problème c’est qu’ils ne sont pas utilisables dans notre environnement de production car pas validé blah blah blah pour les intégrer dans des templates…
Je te passes les détails inutiles mais ça oblige à rajouter dans tous mes scripts des fonctions identiques qui pourraient être intégrées dans des modules 😉
Un jour on arrêtera de travailler dans l’urgence.. un jour…
>>c’est qu’ils ne sont pas utilisables dans notre environnement de production car pas validé …
Je comprend mieux 🙂
>>Un jour on arrêtera de travailler dans l’urgence.. un jour…
+1
Hi. I come from the future and report that in the year 2015 we still have to resort t oeither the win API or a tool from the server 2003 resource pack. Thanks for posting this. It was very helpful!
Hi,
great post, this solution was exactly what I was looking for. I also love the French to English translation – kudos for the word “automatize” and “this is the cool” 😀
AHAHA I’m trying my best to write a smooth english… but you know.. 😀