In Part 1 and Part 2, we looked at various ways how to embed a password in your scripts so that the script could access privileged things. A typical example would be a regular user that needed to do some admin stuff.
The problem of course is that all of these approaches can only "obfuscate" the password, but it is still in your scripts. A smart and knowledgeable PowerShell user could still retrieve and abuse it. At first glance, there seems to be no safer way because Windows operating systems cannot assign "execute only" privileges. Execution always includes the right to read.
Use an "Escrow Agent" to elevate scripts
The only safe way for regular users to launch something elevated would be to not give them the admin password. Instead, you would need someone else, an escrow agent. This escrow agent would be a neutral authority. You could approach him and tell him the admin password secret and the script to launch. A regular user could also approach the escrow agent and tell him to launch the script. The escrow agent would do what both parties want but never reveal the secret to the regular user.
Scheduled Tasks: Secure Way to Elevation
There is already such an escrow agent: scheduled tasks! You can create a scheduled task and specify that the task should run under an Admin account and use maximum privileges. Then, someone else could trigger that task. However, Windows would not allow a regular user to launch a privileged task. You would get an "access denied". The real trick is to trigger the scheduled task by a system event. It is much easier than you may think.
This is what an Admin needs to do to set up such a task:
1. Create a PowerShell script that needs to be run as admin by a regular user
2. Secure that script with approproate NTFS privs so that a regular user cannot change it
3. Create a scheduled task that runs the script on behalf of a privileged account
4. Assign maximum privileges to that task
5. Assign an event trigger that launches the task on a specific event log entry
Then, the regular user could launch that task safely as admin by adding the required event log entry, for example by using Write-EventLog. That's a perfect way of launching elevated scripts because you also get a great logging who elevated when.
Autogenerate Scheduled Tasks
I wrote a function called Create-ScheduledTask which will automate all the steps above. I wrote it on Windows 7 so you may adjust some details on XP. You can download the script here: http://powershell.com/cs/media/p/8026.aspx
function Create-ScheduledTask {
param(
[Parameter(Mandatory=$true)]
$ScriptPath,
[Parameter(Mandatory=$true)]
$UserName,
$UserDomain = $env:userdomain,
[Parameter(Mandatory=$true)]
$Password,
[switch]
$EnableNTFSSecurity,
$EventLogName = 'PowerShell Elevation',
$EventSource = 'RunElevated',
$EventID = 999,
[switch]
$CreateEventLog
)
$taskname = [System.Guid]::NewGUID().Guid
$user = "$userdomain\$username"
if ((Test-Path $ScriptPath) -eq $false) {
Throw "Script '$ScriptPath' not found."
}
if ((Get-Service Schedule).Status -ne 4) {
Start-Service Schedule -ErrorAction Stop
}
if ($enableNTFSSecurity) {
$result = icacls.exe ('"{0}" /reset' -f $ScriptPath) 2>&1
if ($LASTEXITCODE -ne 0) {
Throw $result
}
$result = icacls.exe ('"{0}" /inheritance:r /grant:r {1}:F Administrator:F' -f $ScriptPath, $user) 2>&1
if ($LASTEXITCODE -ne 0) {
Throw $result
}
}
if ($CreateEventLog) {
if (([System.Diagnostics.EventLog]::Exists($EventLogName)-eq $false) -or ([System.Diagnostics.EventLog]::SourceExists($EventSource) -eq $false)) {
try {
New-EventLog $EventLogName -Source $EventSource -ErrorAction Stop | Out-Null
Write-EventLog $EventLogName -Source $EventSource -EventID 0 -EntryType Information -Message 'Created'
}
catch {
Throw $_
}
}
}
$r1 = schtasks /CREATE /RU $user /RP $password /TR "powershell.exe -noprofile -nologo -executionpolicy Bypass -file ""$ScriptPath""" /TN $taskname /EC $EventLogName /SC OnEvent /MO "*[System[Provider[@Name='$EventSource'] and EventID=$EventID]]" 2>&1
$r2 = schtasks /CHANGE /RU $user /RP $password /TN $taskname /RL Highest 2>&1
if ($LASTEXITCODE -eq 0) {
"Successfully created scheduled task '$taskname'"
"The scheduled task will fail to run if the machine is on battery power. Adjust scheduled task manually to change this behavior."
""
"Non-elevated users can run this task on behalf of $user with this code:"
"Write-EventLog $EventLogName -source $EventSource -EventID $EventID -message 'ElevationTask triggered' -EntryType Information"
"On Windows 7/Server 2008R2, this line is in your clipboard now, too."
try { "Write-EventLog $EventLogName -source $EventSource -EventID $EventID -message 'ElevationTask triggered' -EntryType Information" | clip } catch { }
} else {
"Creating scheduled task failed:"
$r1
$r2
}
}
When you run Create-ScheduledTask, you can submit a user name and password. You can also specify an event log, an event id and an event source name. Just make sure the eventlog you specify is writeable to regular users. You can of course specify your very own eventlog. It does not need to exist yet.
The script then protects your script with appropriate NTFS access (since it runs as admin, you do not want users to change it later). It creates the eventlog and eventsource if missing. It creates the scheduled task and sets it up appropriately. And it autogenerates the line of code a normal user would need to trigger the scheduled script. That's the line you'd hand out to your regular users. The elevation launch is triggered by a highly specific entry in one specific eventlog. Only the correct combination of eventlog, source and id will trigger.
Write-EventLog PSElevation -source ScriptLaunch -EventID 776
-message 'ElevationTask triggered' -EntryType Information
They could then launch the script, and the script would run under the account of the person you specified when you scheduled it. The regular users would not need to know any passwords, and they would not be able to actually see or change the script because they don't know where it is located, and the script is NTFS protected as well. The logfile used to trigger the elevated launch maintains a nice log of who elevated when what.
Note that the scheduled task is set up in a way that will prevent launches when a system is on battery power. If you must run elevated scripts on the road as well, make sure you change the settings of the scheduled task which you can do manually in control panel or programmatically using schtasks.exe. Changing the battery launch option is not surfaced by a switch in schtasks.exe, though. You would need to use schtasks to get the XML definition of the task, change the setting and then write the XML back using schtasks.exe. I did not want to go through that hassle because I need to go now.
If anyone happens to be at TechEd 2010 in Berlin in November, let me know! We could meet and have fun! Or pass by one of the PowerShell sessions or the Idera booth. We'll have exciting activity there...!
Tobias
Microsoft MVP PowerShell Germany
P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de
Posted
Oct 28 2010, 07:44 PM
by
Tobias