New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle nested hashtables and arrays of hashtables #12

Closed
ChrisLGardner opened this Issue Oct 20, 2018 · 2 comments

Comments

Projects
None yet
2 participants
@ChrisLGardner
Owner

ChrisLGardner commented Oct 20, 2018

Currently we're splitting on a comma and then converting to hashtables (if it looks like one) but this won't work if you've got a nested array of hashtables.

So if we've got a string like this:

'DnsServer\\*','Get-*','ActiveDirectory\Set-*','Invoke-Cmdlet1', @{ Name = 'Invoke-Cmdlet2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }

Then it'll get converted into this:

Name                           Value
----                           -----
Name                           Invoke-Cmdlet2
Parameters                     {Name, ValidateSet}
 'Item2' }
Name                           Parameter2
ValidatePattern                L*

If we use the Convert-StringToArrayOfHashtable instead then we end up with:

Name                           Value
----                           -----
Name                           Invoke-Cmdlet2
Parameters                     {Name, ValidateSet}
Name                           Parameter1
ValidateSet                    Item1
 'Item2' }
Name                           Parameter2
ValidatePattern                L*

Neither option is good enough for what we want as it's broken the nested hashtables that are under Parameters.

We could make use of Invoke-Expression or [Scriptblock]::Create().Invoke() to easily turn them into the correct objects. But that's a horrible security issue since this resource will almost always be running as SYSTEM, and doing arbitrary code execution using a user provided string is a really bad idea and against exactly what the resource is trying to achieve.

We could try to detect if the string is going to try to do something malicious, possibly by regex'ing for $ and [ as a start (since that'll catch subexpressions and type declarations) but I'm sure there's more ways to abuse this.

Edit: We need to check for & (invocation operator) and . (dot sourcing operator) as well since those will let you execute arbitrary code.

@Agazoth

This comment has been minimized.

Agazoth commented Oct 21, 2018

It seems like you want the result to look something like this:

DnsServer\\*
Get-*
ActiveDirectory\Set-*
Invoke-Cmdlet1

Name                           Value
----                           -----
Name                           Invoke-Cmdlet2
Parameters                     {System.Collections.Hashtable, System.Collections.Hashtable}

You can get this if you parse the entire string as an ArrayLiteral first and then convert every element according to type. A function doing this could look like this:

function Convert-StringToStringsAndHashTable($hashtableAsString)
{
    if ($hashtableAsString -eq $null)
    {
        $hashtableAsString = '@{}'
    }
    $ast = [System.Management.Automation.Language.Parser]::ParseInput($hashtableAsString, [ref] $null, [ref] $null)
    $Data = $ast.Find( { $args[0] -is [System.Management.Automation.Language.ArrayLiteralAst] }, $false )
    foreach ($Element in $Data.Elements){
        if ($Element.StaticType.Name -eq 'String'){
            $Element.value
        }
        if ($Element.StaticType.Name -eq 'Hashtable'){
            [Hashtable]$Element.SafeGetValue()
        }
    }
}
@ChrisLGardner

This comment has been minimized.

Owner

ChrisLGardner commented Oct 22, 2018

Thanks @Agazoth that does almost everything I need and I know how to tweak it a bit to handle the last edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment