0

I've written a pair of apps; one that issues powershell scripts to clients (the server) and one that executes powershell scripts passed to it (the client).

I've added functions to detect if a particular script requires elevation. In the event that a script requires elevation, the server app prompts the user for their credentials. The password is converted to a secure string and saved in a SQL database, as is the username along with the script.

The client app grabs the script and, if elevation is required, grabs the username and secure string then tries to build a credential object from it.

The functionality is working fine for non-elevated scripts, but elevated scripts are not working. Its not erroring or throwing an exception (finally) but the scripts are not executed.

The first part of this process reads the data from SQL into a datatable and then i loop through the rows of that datatable. Once I've got a row that contains a script that needs running, I build the script.

Here's how I'm building and executing the powershell in VB...

    If this_routine_elevation_required = 1 Then
        Dim scriptbody As String = row("scriptbody").ToString
        Dim elevated_user_un As String = row("elevated_user").ToString
        Dim elevated_user_ss As String = row("securestring").ToString
        credential_script = "$securepassword = '" & elevated_user_ss & "' | ConvertTo-SecureString; $username='" & elevated_user_un & "';$credential = New-Object System.Management.Automation.PsCredential($username, $securepassword)"
        action_response = RunPowershellScript(credential_script & "; " & scriptbody)

    End If

and here is the function that executes the powershell (using 'Imports System.Management.Automation)...

 Private Function RunPowershellScript(ByVal scriptText As String) As String

    ' create Powershell runspace 
    Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()
    MyRunSpace.Open()
    Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()
    MyPipeline.Commands.AddScript(scriptText)
    Dim results As Collection(Of PSObject) = MyPipeline.Invoke()
    MyRunSpace.Close()
    Dim MyStringBuilder As New StringBuilder()
    For Each obj As PSObject In results
        MyStringBuilder.AppendLine(obj.ToString())
    Next
    Return MyStringBuilder.ToString()

    End Function

I've thrown up a messagebox of the script before its passed to the RunPowershellScript function so i could make sure nothing was malformed or to ensure i wasnt doing anything stupid (i've manipulated the secure string for the purposes of this image)...

messagebox - powershellscript

The example here is just a test to see if the executor could stop the W32Time service, something that would normally require elevation. It does not work. I get an empty response back from the RunPowershellScript function and the service continues to run.

It occured to me that I'm getting a securestring from the database and then converting that securestring to a securestring, so perhaps its not ending up with the correct valid password in $credential, but from what i understand I have to provide a securestring for the password parameter of PsCredential, and without -ConvertTo-SecureString it would consider the password to just be a string. I tried this and it threw an exception about the password being null.

Can anyone help point me in the right direction?

Many thanks in advance.

John
  • 755
  • 1
  • 18
  • 46
  • As Example 1 on https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-6 shows, the way you are converting the securestrings seems valid. Could you show the full contents of the `scriptbody` variable? – mhu Jun 11 '19 at 11:40
  • 2
    Is the script running locally on the target or from the server? Credential objects are specific to the computer AND user account which creates them – Scepticalist Jun 11 '19 at 11:52
  • The script runs on a different machine than on the one where they were input and converted to secure string. So is it likely that its simply an 'access denied' type scenario as it won't accept the credential passed to it? if so, is there a different way to achieve this goal? – John Jun 11 '19 at 11:55
  • scriptbody = "Stop-Service -name w32time" in this instance – John Jun 11 '19 at 11:56
  • So the credential isn't actually used? Even so, Scepticalist has a valid point. – mhu Jun 11 '19 at 12:44
  • not used on the machine where it's created, but passed to a remote machine and used there... but alas, Scepticalist is bang on the money. Its simply not valid on the remote machine. @Scepticalist - If you add an answer, I'll mark it as correct. Thanks for your input all. – John Jun 11 '19 at 13:53

1 Answers1

1

Is the script running locally on the target or from the server?

Credential objects are specific to the computer AND user account which creates them, so they are not transferable and can only be used on the computer which creates them.

Scepticalist
  • 3,737
  • 1
  • 13
  • 30