You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server存储过程执行后PHP变量混入无关文件内容问题

问题:SQL Server存储过程输出参数被PHP文件内容污染

向SQL Server存储过程按引用传递的PHP变量$FormType2,在存储过程执行后,其值混入了另一个PHP文件(Functions.php)的8000字符内容。该文件因包含常用函数被多数PHP文件引入,无法理解其内容为何会在SQL查询后被赋值给该变量。

存储过程会为输出参数@FormType赋值,同时将传入的XML数据插入临时表#NewValues并输出该表。预期执行后$FormType2应返回查询到的FormType值(如Termination),但实际却包含Functions.php从第25位开始的内容。若注释掉存储过程中的SELECT * FROM #NewValues语句,变量值则符合预期。


PHP参数代码

$xmlNewVals = new SimpleXMLElement("<?xml version='1.0'?><data></data>");
array_to_xml($NewVals, $xmlNewVals);

echo "
XML Data:<br>{$xmlNewVals->asXML()}<br>
"; //确认XML数据准确性

unset($FormType2);

$FormType2 = "-_-_-_-"; //初始化值

$stmtParams[] = array($PAFIndex, SQLSRV_PARAM_IN);
$stmtParams[] = array($TimeStamp, SQLSRV_PARAM_IN);
$stmtParams[] = array($xmlNewVals->asXML(), SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_XML);
$stmtParams[] = array($_SESSION['UserName'], SQLSRV_PARAM_IN);
$stmtParams[] = array($NewValReason, SQLSRV_PARAM_IN);

$stmtParams[] = array(&$FormType2, SQLSRV_PARAM_INOUT); //按引用传递变量

$stmt = "{call HR.PAFAlterChecks(?".str_repeat(",?", count($stmtParams)-1).")}";

echo "
Pre-Execute:<br>{$FormType2}<br>
"; //正确显示初始化值

$result = ExecSQL($stmt, $hasrows, $stmtParams);

echo "
Post-Execute:<br>{$FormType2}<br>
"; //包含Functions.php文件的8000字符内容

ExecSQL函数代码

function ExecSQL($SQL, &$hasRows = false, $Parameters = array(), $BypassError = false)
{
    global $presql, $postsql, $SQLconnection, $GenericErrorMessage;
    
    unset($Result, $Error);
    $Error = "";
    
    if(empty($Parameters)){ $SQL = $presql.$SQL.$postsql; }

    $Result = sqlsrv_query($SQLconnection, $SQL, $Parameters);

    if(empty($Result)){
        $Errors = sqlsrv_errors();
        foreach($Errors as $i => $err)
        {
            $Error .= $i.": ".$err['message']."  ";
        }
        $Error .= " ({ ".$SQL." }) ";
        
        echo "<br><br>
";
        print_r($Parameters); //仅测试用,生产环境已注释
        echo "<br><br>
";
    }
    else
    {
        do { $hasRows = sqlsrv_has_rows($Result); }
        while($hasRows === false && sqlsrv_next_result($Result));
    }
    
    if(!empty($Error) && empty($BypassError)){
        $bug = debug_backtrace(); 
        $res = Error(substr($bug[0]['file'], strrpos($bug[0]['file'], '\\')+1), $Error, $bug[0]['line']);
        die($GenericErrorMessage."
	".$Error); 
    }
    
    return $Result;
}

存储过程代码

USE [FormsDataTest]
GO

/****** Object:  StoredProcedure [HR].[PAFAlterChecks]    Script Date: 05/09/2024 1:09:58 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE  [HR].[PAFAlterChecks]
    -- 存储过程参数
    @PAFIndex INT,
    @TimeStamp DATETIME = '',
    @NewVal XML,
    @Verifier NVARCHAR(50) = '',
    @NewValReason NVARCHAR(MAX) = '',

    @FormType NVARCHAR(15) OUT

AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION

        SET @FormType = (SELECT [FormType] FROM HR.PersonnelAction WHERE [index] = @PAFIndex);

        DECLARE @xmlHandle INT

        EXEC sys.sp_xml_preparedocument @xmlHandle OUTPUT, @NewVal
        
        SELECT @PAFIndex AS [PAFIndex], * INTO #NewValues
        FROM OPENXML(@xmlHandle, '/data', 2) 
        WITH ([TerminationType] NVARCHAR(50), 
              [TerminationName] NVARCHAR(MAX), 
              [PolicyName] NVARCHAR(MAX),
              [OtherText] NVARCHAR(MAX)
        )

        EXEC sys.sp_xml_removedocument @xmlHandle

        SELECT * FROM #NewValues --验证XML输入是否正确

        DROP TABLE #NewValues

        COMMIT TRANSACTION
        
    END TRY

    BEGIN CATCH
        ROLLBACK TRANSACTION
    END CATCH
END
GO

解决方案

核心原因

存储过程返回了两个结果集:首先是输出参数@FormType的赋值逻辑,然后是SELECT * FROM #NewValues的查询结果。而ExecSQL函数仅遍历到第一个有行的结果集就停止,未处理完所有结果集,导致sqlsrv扩展内部资源指针混乱,进而污染了按引用传递的输出变量。

具体修复方案

方案1:遍历所有结果集(推荐)

修改ExecSQL函数,确保处理完存储过程返回的所有结果集,避免资源泄漏和变量污染:

else
{
    // 遍历所有结果集,直到没有更多结果
    do {
        $hasRows = sqlsrv_has_rows($Result);
        // 即使结果集没有行,也需要推进指针
    } while (sqlsrv_next_result($Result));
}

方案2:移除存储过程中的调试查询

如果SELECT * FROM #NewValues仅用于调试,可注释或删除该语句,确保存储过程只返回输出参数结果:

-- SELECT * FROM #NewValues -- 注释掉这条调试语句

方案3:显式指定输出参数的SQL类型

在定义输出参数时,明确指定SQL类型为NVARCHAR(15),避免类型不匹配导致的内存问题:

$stmtParams[] = array(&$FormType2, SQLSRV_PARAM_INOUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_NVARCHAR(15));

内容的提问来源于stack exchange,提问作者Jack Duece

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:52:10