|
Thursday, February 16, 2006
Groups Command in MSH: Get-WindowsIdentity cmdlet
here.
In Linux Bash shell, there is a handy groups command, which takes a username as input and return user associated groups. To get similar functionality, I wrote the Get-WindowsIdentity cmdlet, which take a MshCredential as input, call LogonUser Win32 API and return a System.Security. Principal.WindowsIdentity object. The bad news, you got to have a username and password. Example:
If you are using windows server 2003 and want to check a domain account, don't use this cmdlet. Just do something like this:
1. To find out a specific user (you known his/her password) belongs to which groups
2. To check a user's password without login as that user or using runas/su.msh. If you input wrong password, you will get an error:
C# code:
Have fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
(Added on Mar 6th) There is a follow up post on this topic In Linux Bash shell, there is a handy groups command, which takes a username as input and return user associated groups. To get similar functionality, I wrote the Get-WindowsIdentity cmdlet, which take a MshCredential as input, call LogonUser Win32 API and return a System.Security. Principal.WindowsIdentity object. The bad news, you got to have a username and password. Example:
>get-WindowsIdentity (get-credential)This cmdlet is designed for WindowsXP SP2. In Windows 2000, user required to have SeTcbPrivilege,a.k.a Act as part of the operating system, to call LogonUser. The reference (at the end of the post), provide a workaround to not call LongonUser: Use NegotiateStream. But it did not work for me.
AuthenticationType : MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
ImpersonationLevel : None
IsAuthenticated : True
IsGuest : False
IsSystem : False
IsAnonymous : False
Name : Domain\Administrator
Owner : S-1-5-21-XXXXXXXXX-XXXXXXX-XXXX-XX
User : S-1-5-21-XXXXXXXXX-XXXXXXX-XXXX-XX
Groups : {S-1-5-21-XXXXXXXXX-XXXXXXX-XXXX-XX, , , , , , , }
Token : XXXX
If you are using windows server 2003 and want to check a domain account, don't use this cmdlet. Just do something like this:
>$id = new-object System.Security.Principal.WindowsIdentity("Domain\User")This cmdlet could be useful if you are trying
1. To find out a specific user (you known his/her password) belongs to which groups
2. To check a user's password without login as that user or using runas/su.msh. If you input wrong password, you will get an error:
get Get-WindowsIdentity : Win32 API (LogonUser) Error: 1326
C# code:
using System;Reference: http://pluralsight.com/wiki/default.aspx/Keith.GuideBook/HowToGetATokenForAUser.html
using System.Security.Principal;
using System.Management.Automation;
using System.Net;
using System.Runtime.InteropServices;
namespace MSHForFun.Security
{
public class LogonUserHelper
{
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonTypes logonType,
LogonProviders logonProvider,
out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
enum LogonTypes : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
enum LogonProviders : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
public static WindowsIdentity LogonUser(MshCredential UserCre)
{
IntPtr token = IntPtr.Zero;
NetworkCredential Cre = UserCre.GetNetworkCredential();
bool result = LogonUser(
Cre.UserName, "", Cre.Password,
LogonTypes.Network,
LogonProviders.Default,
out token);
WindowsIdentity id;
if (result)
{
id = new WindowsIdentity(token);
CloseHandle(token);
}
else
{
throw new Exception("Win32 API (LogonUser) Error: " + Marshal.GetLastWin32Error().ToString());
}
return id;
}
}
/// <summary>
/// Get User WindowsIdentity
/// </summary>
[Cmdlet("Get", "WindowsIdentity", SupportsShouldProcess = true)]
public class GetWindowsIdentityCmd : Cmdlet
{
#region Parameters
private MshCredential userCre = null;
/// <summary>Process Arrary to work with</summary>
[Parameter(Mandatory = true, Position = 0)]
public MshCredential UserCredential
{
get { return userCre; }
set { userCre = value; }
}
#endregion
/// <summary>
/// Call LogonUser Return a WindowsIdentity Object
/// </summary>
protected override void ProcessRecord()
{
if (ShouldProcess("Get WindowsIdentity of User: " + userCre.UserName))
{
WriteObject(LogonUserHelper.LogonUser(userCre));
}
}
}
}
Have fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Tags: msh monad PowerShell
Add-ProcessOwner cmdlet (update)
here.
Rewrite Add-ProcessOwner cmdlet and build into a MshSnapin. Most intersting thing here: Wrap an object into MshObject, add a MshNoteProperty.
MshObject Constructor:
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
(Added on Mar 6th) There is a follow up post on this topic Rewrite Add-ProcessOwner cmdlet and build into a MshSnapin. Most intersting thing here: Wrap an object into MshObject, add a MshNoteProperty.
MshObject Constructor:
System.Management.Automation.MshObject..ctor(Object)MshNoteProperty Constructor:
System.Management.Automation.MshNoteProperty..ctor(String, Object)Example to use this cmdlet:
>$ps=get-process | add-processowner
>$ps | get-member
TypeName: System.Diagnostics.Process
>$ps[0].ProcessOwner | get-member
System.Security.Principal.WindowsIdentity
# Some processOwner could be null due to security policy
//Add-ProcessOwner.csFor MshSnapin
using System;
using System.Security.Principal;
using System.Diagnostics;
using System.Management.Automation;
using System.Runtime.InteropServices;
namespace MSHForFun.Security
{
/// <summary>
/// Add Process Owner infomation as NoteProperty
/// </summary>
[Cmdlet("add", "processowner", SupportsShouldProcess = true)]
public class AddProcessOwnerCmd : Cmdlet
{
#region Parameters
private Process[] allProcess = null;
/// <summary>
/// Arrary of Process to query
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
public Process[] ProcessArray
{
get { return allProcess; }
set { allProcess = value; }
}
#endregion
private SafeTokenHandle processToken = new SafeTokenHandle(IntPtr.Zero);
private MshObject psex;
private MshNoteProperty info;
private WindowsIdentity id;
/// <summary>
/// Get WindowsIdentity for each process
/// </summary>
protected override void ProcessRecord()
{
foreach (Process ps in allProcess)
{
if (ShouldProcess(ps.ProcessName))
{
psex = new MshObject(ps);
try
{
if (Win32Helper.OpenProcessToken(ps.Handle, TokenAccessLevels.Query, ref processToken))
{
if (processToken.IsInvalid)
{
WriteWarning("Invalid process token: " + ps.ProcessName);
info = new MshNoteProperty("ProcessOwner", null);
psex.Properties.Add(info);
}
else
{
id = processToken.GetWindowsIdenty();
WriteVerbose("Successfully get process owner information: " + ps.ProcessName);
info = new MshNoteProperty("ProcessOwner", id);
psex.Properties.Add(info);
}
}
else
{
WriteWarning("Unable to query process token (privilege not held): " + ps.ProcessName);
info = new MshNoteProperty("ProcessOwner", null);
psex.Properties.Add(info);
}
}
catch
{
WriteWarning("Unable to query process token (privilege not held): " + ps.ProcessName);
info = new MshNoteProperty("ProcessOwner", null);
psex.Properties.Add(info);
}
WriteObject(psex);
}
}
}
}
}
//MshSnapin.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Configuration.Install;
using System.ComponentModel;
namespace MSHForFun.Security
{
/// <summary> Defines the properties of MonadSecurity snapin</summary>
[RunInstaller(true)]
public class MonadSecurity : MshSnapIn
{
/// <summary>Creates an instance of MonadSecurity Snapin class.</summary>
public MonadSecurity() : base()
{
}
///<summary>The snapin name which is used for registration</summary>
public override string Name
{
get
{
return "MSHForFun.Security";
}
}
/// <summary>Gets vendor of the snapin.</summary>
public override string Vendor
{
get
{
return "http://mshforfun.blogspot.com/";
}
}
/// <summary>Gets description of the snapin. </summary>
public override string Description
{
get
{
return "Cmdlets for windows security related jobs";
}
}
}
}
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Tags: msh monad PowerShell
Tuesday, February 14, 2006
Peek into process token: Add-ProcessOwner cmdlet
In order to get process token, I have to call Win32API – OpenProcessToken, which return a System.IntPtr type token. This token can be used to construct a System.Security. Principal.WindowsIdentity object. (It is first time I use P/Invoke. Please tell me if I did anything wrong.)
Using add-member cmdlet, I add this WindowsIdentity object to System.Diagnostics.Process object as NoteProperty. To use it:
>$ps= Get-process –ProcessName exporler | Add-ProcessOwner.msh
>$ps | get-member
…
WaitForExit Method   System.Boolean WaitForExit(Int...
WaitForInputIdle Method System.Boolean WaitForInputIdl...
__NounName NoteProperty System.String __NounName=Process
ProcessOwner NoteProperty System.Security.Principal.Wind...
…
>$ps.ProcessOwner
AuthenticationType : NTLM
ImpersonationLevel : None
IsAuthenticated :
IsGuest : False
IsSystem : False
IsAnonymous : False
Name : Domain\tony
Owner : S-1-5-21-854245398-XXXXXXXXXX-XXXXXX-XXX
User : S-1-5-21-854245398--XXXXXXXXXX-XXXXXX-XXX
Groups : {S-1-5-21-854245398--XXXXXXXXXX, , , , , }
Token : XXXX
It is pretty sad that due to local security policy, I can not get owner of processes runing as System or Network service (which are those most intersting process). I might have to mount myself as a system service first than call OpenProcessToken. Well, that is to complicated for me.
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Collections;
using System.Diagnostics;
namespace Monad.Security
{
public class OpenProcessTokenHelper
{
[Flags]
enum TOKEN_ACCESS : uint
{
TOKEN_ASSIGN_PRIMARY = 0x0001,
TOKEN_DUPLICATE = 0x0002,
TOKEN_IMPERSONATE = 0x0004,
TOKEN_QUERY = 0x0008,
TOKEN_QUERY_SOURCE = 0x0010,
TOKEN_ADJUST_PRIVILEGES = 0x0020,
TOKEN_ADJUST_GROUPS = 0x0040,
TOKEN_ADJUST_DEFAULT = 0x0080,
TOKEN_ADJUST_SESSIONID = 0x0100,
TOKEN_READ = 0x00020000 | TOKEN_QUERY,
TOKEN_WRITE = 0x00020000 | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT,
TOKEN_EXECUTE = 0x00020000,
};
[DllImport("Advapi32.dll", SetLastError = true)]
extern static int OpenProcessToken(IntPtr processHandle, TOKEN_ACCESS desiredAccess, out IntPtr tokenHandle);
[DllImport("kernel32.dll", SetLastError = true)]
extern static bool CloseHandle(IntPtr handle);
public static WindowsIdentity GetProcessesIdentities(Process process)
{
try
{
IntPtr token = IntPtr.Zero;
if (OpenProcessToken(process.Handle, TOKEN_ACCESS.TOKEN_QUERY, out token) == 0)
{
throw new ApplicationException("Can't open process token for: " + process.ProcessName);
}
WindowsIdentity id = new WindowsIdentity(token);
CloseHandle(token);
return (id);
}
catch (Exception ex)
{
throw new ApplicationException("Open process token error", ex);
}
}
}
}
#################################################
## Add-ProcessOwner.msh
## Add System.Security.Principal.WindowsIdentity object
## to System.Diagnostics.Process object as NoteProperty
## using P/invoke OpenProcessToken Win32API
##
## from http://mshforfun.blogspot.com/
################################################
begin
{
[void][Reflection.Assembly]::LoadFile("D:\msh\OpenProcessTokenHelper.dll")
}
process
{
if ($_)
{
# Only working with process object
if ($_ -isnot [System.Diagnostics.Process]) {continue}
else
{
# Get WindowsIdentityObject
$id=[Monad.Security.OpenProcessTokenHelper]::GetProcessesIdentities($_)
add-member -InputObject $_ -Type NoteProperty -Name "ProcessOwner" -Value $id -ErrorAction "SilentlyContinue"
}
}
}
Reference: http://www.sellsbrothers.com/askthewonk/secure/default.aspx?content=howcanigetthesecurityprin.htm
Monday, February 13, 2006
Page load Activities increased!
monad team blog and Lee Holmes' blog. Thanks you, guys!
Although only small changes were made, their scripts delivered whole new user experience or better security compared to my (ugly) script. As you may have noticed, my daytime job is research scientist on immunology. I set up this blog for biology oriented readers and never thought IT professionals would be major readers. So please forgive my amateurish and feel free to comment on any errors or bad ideas in my posts.
PS,I need to mention is that Blastn is program for DNA sequence analysis. So input should match [ATGCN], otherwise you will get an error.
Have Fun.
My laptop was down for two days. When it came back, I was surprised to find out that visits to my blog increased dramatically. All visitors were referred from Although only small changes were made, their scripts delivered whole new user experience or better security compared to my (ugly) script. As you may have noticed, my daytime job is research scientist on immunology. I set up this blog for biology oriented readers and never thought IT professionals would be major readers. So please forgive my amateurish and feel free to comment on any errors or bad ideas in my posts.
PS,I need to mention is that Blastn is program for DNA sequence analysis. So input should match [ATGCN], otherwise you will get an error.
Have Fun.
Wednesday, February 08, 2006
Tired of Typing Administrator’s Password Repeatedly?
If you read my previous blog entry and were convinced to logon as non-privilege user, you must have discovered the beauty and power of runas.exe or our su.msh.
But being the only user of that laptop, isn't it annoying to type Administrator's password again and again just for some routing jobs? Why can't we just incorporate the Administrator's Password in our msh script?
Yes, we can. We are not dumb enough to put plain text password in our script. We have to store our password in an encrypted form. Monad beta3 contain three cmdlets to operate SecureString:
Name : export-SecureString
Definition : export-SecureString [-SecureString] SecureString [[-SecureKey] Sec
ureString] [-Verbose] [-Debug] [-ErrorAction ActionPreference] [-E
rrorVariable String] [-OutVariable String] [-OutBuffer Int32]
export-SecureString [-SecureString] SecureString [-Key Byte[]] [-V
erbose] [-Debug] [-ErrorAction ActionPreference] [-ErrorVariable S
tring] [-OutVariable String] [-OutBuffer Int32]
Name : import-SecureString
Definition : import-SecureString [-String] String [[-SecureKey] SecureString] [
-Verbose] [-Debug] [-ErrorAction ActionPreference] [-ErrorVariable
String] [-OutVariable String] [-OutBuffer Int32]
import-SecureString [-String] String [-Key Byte[]] [-Verbose] [-De
bug] [-ErrorAction ActionPreference] [-ErrorVariable String] [-Out
Variable String] [-OutBuffer Int32]
Name : new-SecureString
Definition : new-SecureString [-Verbose] [-Debug] [-ErrorAction ActionPreferenc
e] [-ErrorVariable String] [-OutVariable String] [-OutBuffer Int32]
Using export-SecureString, we can export a securestring (Administrator's Password) to a safe, persistable format (encrypted Administrator's Password). When needed, we can use import-SecureString to decrypt the output of export-securestring and restore it to a securestring.
> export-SecureString (new-SecureString)|out-file Admin.passNow we can write a script like following:
Enter secret: ******
> get-content Admin.pass
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000091700afb9271384c8d9d0f5fc5bbcad
d0000000002000000000003660000a8000000100000003ab5cb17d04880206425514877334d5600
…
(Clipped)
##########################################Tada! No more prompts for password!
# Defrag.msh
# Start Disk Defragmenter as Administrator
# From http://mshforfun.blogspot.com/
# This script is provided AS IS
# Use it at your own risk
##########################################
$Password=import-SecureString (get-content D:\msh\Admin.pass)
$Windir=(get-childitem Env:windir).Value
$StartInfo = new-object System.Diagnostics.ProcessStartInfo
$StartInfo.UserName = "Admin"
$StartInfo.Password = $Password
$StartInfo.FileName = $Windir + "\system32\mmc.exe"
$StartInfo.Arguments = $Windir + "\system32\dfrg.msc"
$StartInfo.WorkingDirectory = $Windir + "\system32"
$StartInfo.LoadUserProfile = $true
$StartInfo.UseShellExecute = $false
[System.Diagnostics.Process]::Start($StartInfo)
Something needed to mention:
1. Although I don't know the default key which export-SecureString used to encrypt password, I am pretty sure this key is account-specific and domain-specific. That is to say, another user on the same computer can not use import-SecureString to decrypt export-SecureString output. Even a user with same account name can't decrypt it if he/she is on another computer (domain). If an unauthorized user tried to import-SecureString, he/she will get an error:
import-SecureString : Key not valid for use in specified state.But that does not rule out bruce attack. So make sure to apply restricted access rule to you password file. It should be only readable by yourself and Administrators group.
2. Export-SecureString can take a key (a SecureString or byte[]) to encrypt SecureString. Import-SecureString will require the same key to decrypt. (Then we would have to type another password - the encrypt key) This method will output an encrypted password which can be used by another user. But this feature is yet not available in version beta 3. You will get an error:
> $key=new-securestringAdded on 14th, Feb 2006
Enter secret: *****
> export-securestring (new-securestring) -SecureKey $key
Enter secret: ******
export-SecureString : Cannot process argument because the value of argument "key" is invalid.
This is not a bug:
From Lee Holmes--When you use an encryption key on export-secureString, it must be of a length supported by the encryption algorithm. That is 128,192, or 256 bytes -- which is 8, 12, or 16 unicode characters if you type them. I'll file a doc bug to make this more clear.
Added on 14th, Feb 2006
3. Sign your script, and apply the AllSigned execution policy to prevent the script was tampered by malicious user.
4. DO NOT try to grant access to another user using this method. As you can see, if a non-privilege user got the content of Admin.pass (if Admin.pass properly encrypted for this specific user), he can do anything as an Administrator!
There are more in depth discuss about Cryptograpy in MSH on MoW's Blog: here and here
Have Fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Tags: msh monad PowerShell
Saturday, February 04, 2006
Give Monad a Voice: out-voice cmdlet
WindowsXP shipped with both speech recognition and Text To Speech Engine. TTS engine (sapi.dll) takes text as input and output that text as spoken audio. Although WinFX will contain the fully managed API for speech, at this moment, I have to use (ugly) com interop. To make things easier, I generate a managed API libaray (SpeechLib.dll) using tlbimp.exe, a tool in .NET 2.0 SDK.
set-alias tlbimp "C:\Program Files\Microsoft.NET\SDK\v2.0\bin\tlbimp.exe"You will see some warnings talking about type error. Just ignore them because we will not use those problemetic class today. Then we can load the libray, initialize a SpeechLib.SpVoiceClass object and call Speak method.
tlbimp "C:\Program Files\Common Files\Microsoft Shared\Speech\sapi.dll" `
/out:SpeechLib.dll /namespace:SpeechLib
[Reflection.Assembly]::LoadFile("D:\msh\SpeechLib.dll")After using this cmdlet for a while, I found out that call Speak method asynchronously is a good idea. While listen to your voice output (which could be lengthy and boring), you can still get a prompt and working with MSH.
$SpVoice = new-object SpeechLib.SpVoiceClass
$SpVoice.Speak("Hello", [SpeechLib.SpeechVoiceSpeakFlags]::SVSFDefault)
$SpVoice.Speak("Hello", [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)To get a .wav filename, I used System.Windows.Forms.SaveFileDialog object
$SaveFileD = new-object System.Windows.Forms.SaveFileDialogTo save to wav file, I use SpeechLib.SpFileStreamClass
$SaveFileD.Filter = "All files (*.*)|*.*|wav files (*.wav)|*.wav";
$SaveFileD.Title = "Save to a wave file";
$SaveFileD.FilterIndex = 2;
$SaveFileD.RestoreDirectory = true;
if ($SaveFileD.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
{
$FileName = $SaveFileD.FileName
}
$WaveFile = new-object SpeechLib.SpFileStreamClassI use $_ to get object from pipline and use out-string cmdlet to convert them to System.String object .
$WaveFile.Open($FileNameS, [SpeechLib.SpeechStreamFileMode]::SSFMCreateForWrite, $false)
$SpVoice.AudioOutputStream = $WaveFile
$SpVoice.Speak($StringToSay, [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
$SpVoice.WaitUntilDone([System.Threading.Timeout]::Infinite)
$WaveFile.Close()
$StringToSay = $_ | out-stringIf you want to try it, do something like
dir| select-object Name -first 4 | .\out-voice.mshMake sure the speaker on your computer is poweron and enabled. Make sure you use select to format your results first. Otherwise you won't know what your computer are talking about. There is no guarantee that it would be fun. As I said before it could be lengthy and boring.
get-eventlog Application -Newest 4 | .\out-voice.msh $true
###############################################################Reference: Code4Fun, C# Corner
#
# out-voice.msh
# This script takes objects from pipline and output as voice using TTS engine.
# If set $SaveToWav to $true, output to WAV file(s).
#
# Usage: "Hello", "Welcome to monad world" | out-voice
# "Hello", "Welcome to monad world" | out-voice $true
#
# from Tony http://mshforfun.blogspot.com/
#
################################################################
param ( [bool] $SaveToWav = $false )
begin
{
[void][Reflection.Assembly]::LoadFile("D:\msh\SpeechLib.dll")
$SpVoice = new-object SpeechLib.SpVoiceClass
$count = 0
if ($SaveToWav)
{
[void] [Reflection.Assembly]::Load("System.Windows.Forms, Version=2.0.0.0, `
Culture=neutral, PublicKeyToken=b77a5c561934e089")
$SaveFileD = new-object System.Windows.Forms.SaveFileDialog
$SaveFileD.Filter = "All files (*.*)|*.*|wav files (*.wav)|*.wav";
$SaveFileD.Title = "Save to a wave file";
$SaveFileD.FilterIndex = 2;
$SaveFileD.RestoreDirectory = true;
if ($SaveFileD.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
{
$FileName = $SaveFileD.FileName
}
else
{
"Must Specify a wav file"
break
}
}
}
process
{
if ($_)
{
$count++
$StringToSay = $_ | out-string
"$count: $StringToSay"
if ($SaveToWav)
{
$FileNameS = $FileName.Replace(".wav", $count.Tostring()+".wav")
$WaveFile = new-object SpeechLib.SpFileStreamClass
$WaveFile.Open($FileNameS, `
[SpeechLib.SpeechStreamFileMode]::SSFMCreateForWrite, $false)
$SpVoice.AudioOutputStream = $WaveFile
[void]$SpVoice.Speak($StringToSay, `
[SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
[void]$SpVoice.WaitUntilDone([System.Threading.Timeout]::Infinite)
$WaveFile.Close()
}
else
{
[void]$SpVoice.Speak($StringToSay, `
[SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
}
}
else
{
"null object"
}
}
end
{
"Number of objects successfully output: $count"
}
##############################################
Have Fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Tags: msh monad PowerShell
Thursday, February 02, 2006
Survival as a Non-Administrator: A Monad view
DON'T ROOT AROUND!
Remember the security rule NO.1 in unix world? It is also 100% rule NO.1 in windows world. Do you get some worms, viruses, spywares or adware in you windows box? You should blame yourself first if you logged in as an Adminstrator EVERYTIME EVERYDAY. It is you who let those malware bypass all windows security check and write themselves directly in system folder and registry. Do you really need Administrator privilege to read Emails or surf the internet? Or do you really need Administrator privilege to compile your code? If you want to write secure code, login as a limited user and find out how you code works. Don't rely on testing afterwards.
Reality check:
Since this is a blog about MSH, I am going to talk about some solutions in MSH.
1. -Credential Parameter
It is good to see that (although still in beta stage) monad designers kept "running at least privilege" in mind. For example, they plan to implemetation -Credential parameter for file/registry provider. There are some buildin cmdlet using -Credential parameter. For example those Item cmdlet:
Name : clear-item
Definition : clear-item [-Path] String[] [-Force] [-Filter String] [-Include St
ring[]] [-Exclude String[]] [-Credential MshCredential] [-Verbose]
[-Debug] [-ErrorAction ActionPreference] [-ErrorVariable String]
[-OutVariable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : copy-item
Definition : copy-item [-Path] String[] [[-Destination] String] [-Container] [-
Force] [-Filter String] [-Include String[]] [-Exclude String[]] [-
Recurse] [-PassThru] [-Credential MshCredential] [-Verbose] [-Debu
g] [-ErrorAction ActionPreference] [-ErrorVariable String] [-OutVa
riable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : get-item
Definition : get-item [-Path] String[] [-Filter String] [-Include String[]] [-E
xclude String[]] [-Force] [-Credential MshCredential] [-Verbose] [
-Debug] [-ErrorAction ActionPreference] [-ErrorVariable String] [-
OutVariable String] [-OutBuffer Int32]
Name : invoke-item
Definition : invoke-item [-Path] String[] [-Filter String] [-Include String[]]
[-Exclude String[]] [-Credential MshCredential] [-Verbose] [-Debug
] [-ErrorAction ActionPreference] [-ErrorVariable String] [-OutVar
iable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : move-item
Definition : move-item [-Path] String[] [[-Destination] String] [-Force] [-Filt
er String] [-Include String[]] [-Exclude String[]] [-PassThru] [-C
redential MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionP
reference] [-ErrorVariable String] [-OutVariable String] [-OutBuff
er Int32] [-WhatIf] [-Confirm]
Name : new-item
Definition : new-item [-Path] String[] [-Type String] [-Value Object] [-Force]
[-Credential MshCredential] [-Verbose] [-Debug] [-ErrorAction Acti
onPreference] [-ErrorVariable String] [-OutVariable String] [-OutB
uffer Int32] [-WhatIf] [-Confirm]
Name : remove-item
Definition : remove-item [-Path] String[] [-Filter String] [-Include String[]]
[-Exclude String[]] [-Recurse] [-Force] [-Credential MshCredential
] [-Verbose] [-Debug] [-ErrorAction ActionPreference] [-ErrorVaria
ble String] [-OutVariable String] [-OutBuffer Int32] [-WhatIf] [-C
onfirm]
Name : rename-item
Definition : rename-item [-Path] String [-Name] String [-Force] [-PassThru] [-C
redential MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionP
reference] [-ErrorVariable String] [-OutVariable String] [-OutBuff
er Int32] [-WhatIf] [-Confirm]
Name : set-item
Definition : set-item [-Path] String[] [[-Value] Object] [-Force] [-PassThru] [
-Filter String] [-Include String[]] [-Exclude String[]] [-Credenti
al MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionPreferen
ce] [-ErrorVariable String] [-OutVariable String] [-OutBuffer Int3
2] [-WhatIf] [-Confirm]
Unfortunately, if you try to use them now, you will get an error.
invoke-item : Dynamic parameters for the Cmdlet cannot be retrieved. Invocation of MakePath on the 'FileSystem' provider failed for path ''. Cannot call method. The provider does not support the use of credentials.
According to Abhishek Agrawal [MSFT] : "FileSystem and Registry provider do not supprt Credential parameter for V1. We plan to address this in V2. For now you will need to start an elevated MSH window using runas.exe (or Process.Start api) and carry out the required operations there. "
So you have to wait new version of monad which can utilize the -credential parameter for FileSystem and Registry provider.
2. System.Diagnostics.Process.Start() method
.NET framework 2.0 provide three new overloads of System.Diagnostics.Process.Start() method which (might use CreateProcessAsUser Win32 API to) start a process as different user:
They take string type username and SecureString type password. (ProcessStartInfo.Password is a SecureString) SecureString is a new creature in .NET 2.0. How we can get an instance of SecureString? Using get-credential cmdlet we can easily and securely package username and password and then pass them to System.Diagnostics.Process.Start() method. See my previous blog entry for details.
Armed with this tool, we can do almost anything in MSH command line. For example, set file system access control rule, kill a process, modify registry or start a service.
Remember you need to type:
3. Have to use GUI?
Well, I have to admit that sometimes command line is just not good enough. For example, you want to disable wireless network connection. There is no way to use runas.exe or do shift-click tricks. Even if you started msh.exe as an Administrator, invoking ncpa.cpl will not give you "Network Connections" in control panel. The only choice is to use other command line tools like: devcon.exe (from microsoft) or network.exe (from Wingnut Software) If you know how to do this in MSH script, please tell me, please!
ps: I found this funny story. The author had same problem as mine.
But there is always a work around: With help of monad, we can easily get a GUI shell(explorer.exe) as Administrator.
1) using su.msh to get a msh prompt as Adminstrator
2) run following commands
a) Explorer.exe will reload itself quickly after been killed, so you better write those command in msh script.
b) After you finish your work, you need to return to your original explorer environment. You can't kill the explorer.exe process within task manager because you are a limited user (remember?). Instead, you can
What? you have already closed that window? Run su.msh again!
then you either wait explorer.exe to reload itself or start explorer (Do this as a non-Administrator!) by
Added on 26th, Feb 2006
You can change registry to start exporler.exe in seperate process (so there is no need to kill your original explorer.exe process):
First, run su.msh
Then modify registry:
Without reboot, we can now start new explorer.exe windows as Administrator:
You can even use System.Diagnostics.Process.Start() method to start a explorer.exe process from your msh runing as limited user.
Added on 26th, Feb 2006
Have Fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Remember the security rule NO.1 in unix world? It is also 100% rule NO.1 in windows world. Do you get some worms, viruses, spywares or adware in you windows box? You should blame yourself first if you logged in as an Adminstrator EVERYTIME EVERYDAY. It is you who let those malware bypass all windows security check and write themselves directly in system folder and registry. Do you really need Administrator privilege to read Emails or surf the internet? Or do you really need Administrator privilege to compile your code? If you want to write secure code, login as a limited user and find out how you code works. Don't rely on testing afterwards.
Reality check:
- Although Windows Vista is supposed to provide better user experence for limited user account, frankly speaking, using windows 2000 / xp / 2003 as limited user account is not easy and intuitive as Mac OS X or Linux . Default user in windows xp is a Administrator!
- Being an Administrator for so long, you have indulged yourself by that power. After "eating your own dog food", it isn't really that fun to be a non-privilege user. You found yourself trapped in a "jungle" and can't breathe.
Since this is a blog about MSH, I am going to talk about some solutions in MSH.
1. -Credential Parameter
It is good to see that (although still in beta stage) monad designers kept "running at least privilege" in mind. For example, they plan to implemetation -Credential parameter for file/registry provider. There are some buildin cmdlet using -Credential parameter. For example those Item cmdlet:
Name : clear-item
Definition : clear-item [-Path] String[] [-Force] [-Filter String] [-Include St
ring[]] [-Exclude String[]] [-Credential MshCredential] [-Verbose]
[-Debug] [-ErrorAction ActionPreference] [-ErrorVariable String]
[-OutVariable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : copy-item
Definition : copy-item [-Path] String[] [[-Destination] String] [-Container] [-
Force] [-Filter String] [-Include String[]] [-Exclude String[]] [-
Recurse] [-PassThru] [-Credential MshCredential] [-Verbose] [-Debu
g] [-ErrorAction ActionPreference] [-ErrorVariable String] [-OutVa
riable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : get-item
Definition : get-item [-Path] String[] [-Filter String] [-Include String[]] [-E
xclude String[]] [-Force] [-Credential MshCredential] [-Verbose] [
-Debug] [-ErrorAction ActionPreference] [-ErrorVariable String] [-
OutVariable String] [-OutBuffer Int32]
Name : invoke-item
Definition : invoke-item [-Path] String[] [-Filter String] [-Include String[]]
[-Exclude String[]] [-Credential MshCredential] [-Verbose] [-Debug
] [-ErrorAction ActionPreference] [-ErrorVariable String] [-OutVar
iable String] [-OutBuffer Int32] [-WhatIf] [-Confirm]
Name : move-item
Definition : move-item [-Path] String[] [[-Destination] String] [-Force] [-Filt
er String] [-Include String[]] [-Exclude String[]] [-PassThru] [-C
redential MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionP
reference] [-ErrorVariable String] [-OutVariable String] [-OutBuff
er Int32] [-WhatIf] [-Confirm]
Name : new-item
Definition : new-item [-Path] String[] [-Type String] [-Value Object] [-Force]
[-Credential MshCredential] [-Verbose] [-Debug] [-ErrorAction Acti
onPreference] [-ErrorVariable String] [-OutVariable String] [-OutB
uffer Int32] [-WhatIf] [-Confirm]
Name : remove-item
Definition : remove-item [-Path] String[] [-Filter String] [-Include String[]]
[-Exclude String[]] [-Recurse] [-Force] [-Credential MshCredential
] [-Verbose] [-Debug] [-ErrorAction ActionPreference] [-ErrorVaria
ble String] [-OutVariable String] [-OutBuffer Int32] [-WhatIf] [-C
onfirm]
Name : rename-item
Definition : rename-item [-Path] String [-Name] String [-Force] [-PassThru] [-C
redential MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionP
reference] [-ErrorVariable String] [-OutVariable String] [-OutBuff
er Int32] [-WhatIf] [-Confirm]
Name : set-item
Definition : set-item [-Path] String[] [[-Value] Object] [-Force] [-PassThru] [
-Filter String] [-Include String[]] [-Exclude String[]] [-Credenti
al MshCredential] [-Verbose] [-Debug] [-ErrorAction ActionPreferen
ce] [-ErrorVariable String] [-OutVariable String] [-OutBuffer Int3
2] [-WhatIf] [-Confirm]
Unfortunately, if you try to use them now, you will get an error.
invoke-item : Dynamic parameters for the Cmdlet cannot be retrieved. Invocation of MakePath on the 'FileSystem' provider failed for path ''. Cannot call method. The provider does not support the use of credentials.
According to Abhishek Agrawal [MSFT] : "FileSystem and Registry provider do not supprt Credential parameter for V1. We plan to address this in V2. For now you will need to start an elevated MSH window using runas.exe (or Process.Start api) and carry out the required operations there. "
So you have to wait new version of monad which can utilize the -credential parameter for FileSystem and Registry provider.
2. System.Diagnostics.Process.Start() method
.NET framework 2.0 provide three new overloads of System.Diagnostics.Process.Start() method which (might use CreateProcessAsUser Win32 API to) start a process as different user:
public static Process Start ( ProcessStartInfo startInfo)
public static Process Start ( string fileName, string userName, SecureString password, string domain)
public static Process Start (string fileName, string arguments, string userName, SecureString password, string domain)
They take string type username and SecureString type password. (ProcessStartInfo.Password is a SecureString) SecureString is a new creature in .NET 2.0. How we can get an instance of SecureString? Using get-credential cmdlet we can easily and securely package username and password and then pass them to System.Diagnostics.Process.Start() method. See my previous blog entry for details.
Armed with this tool, we can do almost anything in MSH command line. For example, set file system access control rule, kill a process, modify registry or start a service.
Remember you need to type:
exitto stop this msh.exe process. You don't want to leave a shell with Administrator privilege to someone else, do you?
3. Have to use GUI?
Well, I have to admit that sometimes command line is just not good enough. For example, you want to disable wireless network connection. There is no way to use runas.exe or do shift-click tricks. Even if you started msh.exe as an Administrator, invoking ncpa.cpl will not give you "Network Connections" in control panel. The only choice is to use other command line tools like: devcon.exe (from microsoft) or network.exe (from Wingnut Software) If you know how to do this in MSH script, please tell me, please!
ps: I found this funny story. The author had same problem as mine.
But there is always a work around: With help of monad, we can easily get a GUI shell(explorer.exe) as Administrator.
1) using su.msh to get a msh prompt as Adminstrator
2) run following commands
stop-process -ProcessName explorerWelcome to Administrator's Explorer! Go to control panel and disable wireless network connection as you wish. But remember:
explorer.exe
a) Explorer.exe will reload itself quickly after been killed, so you better write those command in msh script.
b) After you finish your work, you need to return to your original explorer environment. You can't kill the explorer.exe process within task manager because you are a limited user (remember?). Instead, you can
stop-process -ProcessName explorerin the msh as Adminstrator
What? you have already closed that window? Run su.msh again!
then you either wait explorer.exe to reload itself or start explorer (Do this as a non-Administrator!) by
explorer.exe
Added on 26th, Feb 2006
You can change registry to start exporler.exe in seperate process (so there is no need to kill your original explorer.exe process):
First, run su.msh
Then modify registry:
set-property HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -property SeparateProcess -value 1 -force
Without reboot, we can now start new explorer.exe windows as Administrator:
explorer.exe
You can even use System.Diagnostics.Process.Start() method to start a explorer.exe process from your msh runing as limited user.
Added on 26th, Feb 2006
Have Fun!
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]
Tags: msh monad PowerShell