Invoke-SqlCmd - 将强制转换为整数以防止注入

Pet*_*ier 2 sql-server sql-injection powershell

我很懒惰,宁愿参数化一次(以获得名称的 object_id)并Invoke-SqlCmd用于其余工作。

给定一系列用于处理任意服务器对象的函数,下面的测试是否足以确认 CmdletBinding 输入参数Int32防止通过用户提供的方式注入$object_id

function foo {
    [CmdletBinding()]Param(
        [Int32]$object_id 
    )
    $query = "select type_desc from sys.objects where object_id = $object_id;"
    Invoke-Sqlcmd -ServerInstance "localhost" -Database "tempdb" -Query $query
}

foo 3
foo "0 union all select name from sys.syslogins where sid = 0x01"
foo $null
foo ([math]::Pow(2,31)+1)
foo @(1,2)
Run Code Online (Sandbox Code Playgroud)

Bac*_*its 5

你可以,但这样做有问题。

首先,它仍然不是一个好主意,因为该模式不可扩展。它仅适用于某些数据类型。一旦你有了一个字符串参数,你就回到了注入的地方。这也意味着您必须关心某些数据类型(字节数组、GUID、日期时间)如何转换为字符串。

您还必须注意,空值参数将被静默修改,0因为[int32]它是不可为空的类型。您必须指定[nullable[int32]]以防止这种行为。即便如此,由于转换为字符串的空值将导致空字符串,因此您会得到一个更具描述性的参数错误。

你更愿意调试这个:

Invoke-Sqlcmd:';' 附近的语法不正确。

或这个:

使用“1”参数调用“填充”的异常:“参数化查询 '(@object_id int)select type_desc from sys.objects where object_i' 需要参数 '@object_id',该参数未提供。”

最后,如果您要大量使用查询,系统可能更难重用查询计划。

一旦你习惯了它真的不难指定:

function foo {
    [CmdletBinding()]
    Param(
        $object_id 
    )

    $Query = 'select type_desc from sys.objects where object_id = @object_id;'
    $ConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f 'localhost', 'tempdb'

    $SqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $ConnectionString

    $SqlCommand = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $Query, $SqlConnection
    $SqlCommand.Parameters.Add('@object_id', [System.Data.SqlDbType]::Int).Value = $object_id

    $SqlDataAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList $SqlCommand

    $DataTable = New-Object -TypeName System.Data.DataTable -ArgumentList 'objects'

    [void]$SqlDataAdapter.Fill($DataTable)
    $SqlConnection.Dispose()

    , $DataTable
}
Run Code Online (Sandbox Code Playgroud)

[如果你只输出 DataRows 没问题,你可以去掉最后一行的逗号。逗号强制函数输出整个数据表。如果没有逗号,您将获得 DataRows 流。]

它看起来很复杂,但你可以一遍又一遍地重复使用这个模式。我基本上是从我自己的脚本中复制了上面的代码。

如果您关心的是代码部署,那么我认为这种方法仍然更好。上面的代码只需要 .Net Framework。事实上,该代码甚至可以在 PowerShell Core 6.0 上运行(当然,您必须调用(foo <value>).Rows以获得等效的输出,因为 Core 尚未学习如何在命令行中显示数据表)。UsingInvoke-Sqlcmd添加了对SqlServer正在安装的PowerShell 模块的依赖。当然,它是第一方模块,但默认情况下它仍然不随 Windows 一起提供。您肯定会失去其他人在没有 的情况下完成封装的一些好处Invoke-Sqlcmd,但仍然需要权衡。