使用splat “…” 运算符将变量数组注入到PHP的bind_param()中有什么缺点吗?

huangapple go评论53阅读模式
英文:

Is there any disadvantage to using the splat "..." operator to inject an array of variables into bind_param() in PHP?

问题

I've been trying to streamline how I write my MySQLi code with PHP after learning more about SQL injection and safe coding with bind_param. I wrote a function that can estimate what the "resulting" SQL string generated by bind_param might look like. However, the function relies on the splat "..." operator to load all my variables for the estimate and into bind_param. It seems to work, but I want to know if I'm setting myself up for trouble if I deploy it throughout my site.

I borrowed the idea from this answer.

I call the function sqlBind:

function sqlBind($conn, $sql, $types = '', $aVars = [], $bTest = false, $sDescription = '')
{
    if($bTest) { // If test is set, generate what a non-injected SQL might look like
        $backtrace = debug_backtrace()[0];
        $sFile = $backtrace['file'];
        $iLine = (int) $backtrace['line'];
        $tsql = $sql; // not touching the original sql
        if ($types > '') {
            $tVars = [];
            for ($i = 0; $i < sizeof($aVars); $i++) { // Copy the variable values to another array
                if (is_null($aVars[$i])) {
                    $tVars[] = 'NULL';
                } else {
                    $tVars[] = (substr($types, $i, 1) === 's') ? "'$aVars[$i]'" : $aVars[$i];
                }
            }
            $tsql = sprintf(str_replace('?', '%s', $sql), ...$tVars); // I'm not really worried about this splat
        }
        $tsql = trim(preg_replace('/\s+/', ' ', $tsql));
        echo "<script>console.log('SQL: $tsql | $sFile, Line#$iLine' );</script>";
    }
    $stmt = $conn->prepare($sql);
    if($types > '')$stmt->bind_param($types, ...$aVars); // Using splat to inject from an array of many potential types
    $bWorked = $stmt->execute();
    if (!$bWorked) {
        if(!isset($iLine)) {
            $backtrace = debug_backtrace()[0];
            $sFile = $backtrace['file'];
            $iLine = (int) $backtrace['line'];
        }
        $sError = $stmt->error;
        echo "<script>console.log('SQL ERROR: $sError | $sFile, Line#$iLine' );</script>";
    }
    if ($bTest) {
        $sWorked = ($bWorked) ? 'Succeeded' : 'Failed';
        if ($sDescription > '')$sDescription .= ' ';
        echo "<script>console.log('$sDescription$sWorked | $sFile, Line#$iLine' );</script>";
    }
    if (strpos($sql, 'INSERT INTO') !== false) {
        return $stmt->insert_id;
    } else {
        return $stmt->get_result();
    }
}

I call it like this:

$sql = <<<SQL
    INSERT INTO tUShift (iUserKey, iDeskKey, dDate, fStart, fEnd, Comment)
    VALUES (?, ?, ?, ?, ?, ?);
SQL;
$iNew = sqlBind($conn, $sql, 'iisdds', [$uKey, $iDesk, $qdThisDay, $fStart, $fEnd, $sComment], true, 'Shift Insertion');

or

$sql = <<<SQL
    SELECT * FROM tHours
    WHERE iUserKey = ?
        AND dDate = ?
    ORDER BY dDate;
SQL;
$result = sqlBind($conn, $sql, 'is', [$uKey, $qdThisDay]);

It's designed to work even if you don't need to bind anything.

$sql = <<<SQL
    SELECT * FROM tHType
    ORDER BY sCode;
SQL;
$result = sqlBind($conn, $sql);

Also, any other comments to improve my sqlBind function would be greatly appreciated.

英文:

I've been trying to streamline how I write my MySQLi code with PHP after learning more about SQL injection and safe coding with bind_param. I wrote a function that can estimate what the "resulting" SQL string generated by bind_param might look like. However, the function relies on the splat "..." operator to load all my variables for the estimate and into bind_param. It seems to work, but I want to know if I'm setting myself up for trouble if I deploy it throughout my site.

I borrowed the idea from this answer.

I call the function sqlBind:

function sqlBind($conn, $sql, $types = &#39;&#39;, $aVars = [], $bTest = false, $sDescription = &#39;&#39;)
{
if($bTest) { // If test is set, generate what a non-injected SQL might look like
$backtrace = debug_backtrace()[0];
$sFile = $backtrace[&#39;file&#39;];
$iLine = (int) $backtrace[&#39;line&#39;];
$tsql = $sql; // not touching the original sql
if ($types &gt; &#39;&#39;) {
$tVars = [];
for ($i = 0; $i &lt; sizeof($aVars); $i++) { // Copy the variable values to another array
if (is_null($aVars[$i])) {
$tVars[] = &#39;NULL&#39;;
} else {
$tVars[] = (substr($types, $i, 1) === &#39;s&#39;) ? &quot;&#39;$aVars[$i]&#39;&quot; : $aVars[$i];
}
}
$tsql = sprintf(str_replace(&#39;?&#39;, &#39;%s&#39;, $sql), ...$tVars); // I&#39;m not really worried about this splat
}
$tsql = trim(preg_replace(&#39;/\s+/&#39;, &#39; &#39;, $tsql));
echo &quot;&lt;script&gt;console.log(&#39;SQL: $tsql | $sFile, Line#$iLine&#39; );&lt;/script&gt;&quot;;
}
$stmt = $conn-&gt;prepare($sql);
if($types &gt; &#39;&#39;)$stmt-&gt;bind_param($types, ...$aVars); // Using splat to inject from an array of many potential types
$bWorked = $stmt-&gt;execute();
if (!$bWorked) {
if(!isset($iLine)) {
$backtrace = debug_backtrace()[0];
$sFile = $backtrace[&#39;file&#39;];
$iLine = (int) $backtrace[&#39;line&#39;];
}
$sError = $stmt-&gt;error;
echo &quot;&lt;script&gt;console.log(&#39;SQL ERROR: $sError | $sFile, Line#$iLine&#39; );&lt;/script&gt;&quot;;
}
if ($bTest) {
$sWorked = ($bWorked) ? &#39;Succeeded&#39; : &#39;Failed&#39;;
if ($sDescription &gt; &#39;&#39;)$sDescription .= &#39; &#39;;
echo &quot;&lt;script&gt;console.log(&#39;$sDescription$sWorked | $sFile, Line#$iLine&#39; );&lt;/script&gt;&quot;;
}
if (strpos($sql, &#39;INSERT INTO&#39;) !== false) {
return $stmt-&gt;insert_id;
} else {
return $stmt-&gt;get_result();
}
}

I call it like this:

$sql = &lt;&lt;&lt;SQL
INSERT INTO tUShift (iUserKey, iDeskKey, dDate, fStart, fEnd, Comment)
VALUES (?, ?, ?, ?, ?, ?);
SQL;
$iNew = sqlBind($conn, $sql, &#39;iisdds&#39;, [$uKey, $iDesk, $qdThisDay, $fStart, $fEnd, $sComment], true, &#39;Shift Insertion&#39;);

or

$sql = &lt;&lt;&lt;SQL
SELECT * FROM tHours
WHERE iUserKey = ?
AND dDate = ?
ORDER BY dDate;
SQL;
$result = sqlBind($conn, $sql, &#39;is&#39;, [$uKey, $qdThisDay]);

It's designed to work even if you don't need to bind anything.

$sql = &lt;&lt;&lt;SQL
SELECT * FROM tHType
ORDER BY sCode;
SQL;
$result = sqlBind($conn, $sql);

Also, any other comments to improve my sqlBind function would be greatly appreciated.

答案1

得分: 2

整个mysqli扩展都有一个很大的缺点。 😜 在你能使用PDO的情况下尽量使用PDO。但如果你已经决定使用mysqli,并且希望继续使用它,那么我强烈建议使用$mysqli_stmt-&gt;execute($arrayOfParams)(自PHP 8.1可用)或使用$mysqli-&gt;execute_query($sql, $arrayOfParams)(自PHP 8.2可用)。

使用splat运算符与bind_param()一起是一个出乎意料的黑客,源于PHP数组内部的工作方式。它不会很快改变,因为那将是一个重大的破坏性变更。但这也不是它的真正预期用途。我还没有找到破解它的方法,但这并不意味着没有,也不意味着在未来引入新功能时不会有。至少在PHP 9之前使用它应该是安全的,但如果可以的话,像我上面提到的,使用更明智的东西会更好。

我必须谈一下你的参数替代,我假设这是为了调试目的。我希望这只是一个永远不会进入生产站点的临时措施。你不应该将这样的信息暴露给最终用户。如果你需要调试解决方案,可以使用其中一个流行的日志记录器或编写自己的日志记录器,但将这些信息存储在服务器上的安全日志文件中,不要放在JavaScript中。此外,所有这些复杂的代码几乎是没有用的,因为你可以使用普通的调试器,并在要调试的行上设置断点。没有必要替代参数、记录消息或将任何东西输出到JavaScript。我强烈建议在浪费更多时间之前停止编写你的调试解决方案。

英文:

The whole mysqli extension is one big disadvantage. 😜 Use PDO whenever you can. But if you already made a decision to use mysqli and you want to continue with it then I highly recommend using either $mysqli_stmt-&gt;execute($arrayOfParams) (available as of PHP 8.1) or using $mysqli-&gt;execute_query($sql, $arrayOfParams) (available as of PHP 8.2).

Using the splat operator with bind_param() is a surprising hack stemming from how PHP arrays work internally. It won't change anytime soon because that would be a major breaking change. But it's also not really the intended purpose of it. I have not found a way to break it yet, but it doesn't mean that there isn't one nor there won't be one in the future as new features are introduced. It should be safe to use at least until PHP 9, but if you can, use something more sensible as I mentioned above.

I have to say something about your parameter substitution, which I assume is for debugging purposes. I hope it's just a temporary measure that will never make it to a live site. You should never expose such information to the end user. If you need a debugging solution, use one of the popular loggers or write your own one, but store this information in a secure log file on the server. Do not put it in JavaScript. Additionally, all of this complex code is pretty much useless as you could just use a normal debugger and put a breakpoint on the line you want to debug. No need to substitute parameters, log messages or output anything to JavaScript. I highly recommend you stop writing your debugging solution before you waste more time on it.

huangapple
  • 本文由 发表于 2023年2月24日 01:57:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75548585.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定