KB961040 - Microsoft安全公告:SQL Server中的漏洞可能允许远程执行代码

应用对象
Microsoft SQL Server 2005 Service Pack 2 Microsoft SQL Server 2005 Standard Edition Microsoft SQL Server 2005 Enterprise Edition Microsoft SQL Server 2005 Express Edition Microsoft SQL Server 2005 Express Edition with Advanced Services Microsoft SQL Server 2005 Enterprise X64 Edition Microsoft SQL Server 2005 Standard X64 Edition

简介

Microsoft发布了有关Microsoft SQL Server中可能允许远程执行代码的漏洞的安全公告。 安全公告包含其他与安全相关的信息。 若要查看安全公告,请访问以下Microsoft网站:

http://www.microsoft.com/technet/security/advisory/961040.mspx本文包含一个 VB 脚本,可用于将解决方法应用于本地计算机上所有正在运行的 SQL Server 实例。

可用于应用解决方法的 VB 脚本示例

可以使用此 VB 脚本拒绝对本地计算机上运行的所有受影响SQL Server版本的sp_replwritetovarbin扩展存储过程的公共角色执行权限。

Microsoft 的编程示例仅用于说明,不做任何明示或暗示的保证。 这包括但不限于特定用途的适销性或适用性的隐含保证。 本文假定你熟悉所演示的编程语言以及用于创建和调试过程的工具。 Microsoft 支持工程师可以帮助解释特定过程的功能。 但他们不会修改这些示例以提供附加功能或构造满足你的特定要求的过程。

 将此代码复制到文本文件,使用 .vbs 文件扩展名保存文件,然后使用 CScript.exe 运行脚本文件。 该脚本循环访问本地计算机上正在运行的 SQL Server 实例,并在受影响的版本上应用解决方法。 你必须是每个 SQL Server 实例的 sysadmin 角色的成员才能应用解决方法。 如果 Windows 帐户不是运行 SQL Server 的所有受影响的服务器上的 sysadmin 角色的成员,则可能必须从多个帐户运行此脚本。 在 Windows Server 2008 和 Windows Vista 上,如果使用的 Windows 管理员帐户是 sysadmin 角色的成员,则必须从“提升的”命令提示符运行此脚本。

'*************************************************************************************
'Description: This script iterates through all the running instances of SQL Server 
'            and denies execute permission on sp_replwritetovarbin to public on all
'            the affected versions.
'            THIS IS PROVIDED AS A WORKAROUND AND SHOULD NOT BE USED IN THE EVENT THAT 
'            A SECURITY UPDATE IS PROVIDED AND INSTALLED.
'*************************************************************************************

OPTION EXPLICIT
ON ERROR RESUME NEXT

' Constant values
CONST EXIT_SUCCESS       = 0
CONST EXIT_FAILURE       = 1
CONST EXIT_NOINSTANCES   = -1
CONST DEFAULTNAMESPACE   = "root\default"
CONST STDREGPROV         = "stdregprov"
CONST HKEY_LOCAL_MACHINE = &H80000002
CONST REG_MULTI_SZ       = 7
CONST REG_SZ             = 1
CONST adCmdText          = 1

Call VBMain()

Function VBMain()
    Err.Clear
    ON ERROR RESUME NEXT

    Dim sInstances(), strInstance, i, TotalCount
    VBMain = EXIT_SUCCESS
    If GetInstances(sInstances, TotalCount) = FALSE Then
        WScript.Quit EXIT_FAILURE
    End If

    If IsEmptyNull(sInstances) Then 
        WScript.Echo "INFO: No instances are present."
        VBMain = EXIT_NOINSTANCES
        Exit Function
    End If

    For i = 0 To TotalCount-1
        strInstance = sInstances(i,0)
        GetFullInstance strInstance, sInstances(i,1)
        If ApplyFix(sInstances(i,0), strInstance) = FALSE Then
            WScript.Echo "ERROR: Could not apply the workaround on " + sInstances(i,0) + "." + vbCRLF
            VBMain = EXIT_FAILURE
        End If
    Next

    WScript.Echo "INFO: Completed processing all the running SQL instances."
End Function

Function GetInstances(ByRef sInstances, ByRef TotalCount)
    Err.Clear
    ON ERROR RESUME NEXT

    Dim sInstances1, sInstances2, i
    Dim instCount1, instCount2
    GetInstances = FALSE

    If NOT GetRegValue ("", HKEY_LOCAL_MACHINE, "Software\Microsoft\Microsoft SQL Server", "InstalledInstances", sInstances1, REG_MULTI_SZ, TRUE) Then
        WScript.Echo "ERROR:Failed to read SQL instances installed on the machine."
        Exit Function
    End If

    sInstances2 = NULL
    If IsOs64Bit() = TRUE Then
        If NOT GetRegValue ("", HKEY_LOCAL_MACHINE, "Software\Microsoft\Microsoft SQL Server", "InstalledInstances", sInstances2, REG_MULTI_SZ, FALSE) Then
            WScript.Echo "ERROR:Failed to read SQL instances installed on the machine."
            Exit Function
        End If
    End If

    If IsEmptyNull(sInstances1) AND IsEmptyNull(sInstances2) Then 
        WScript.Echo "INFO: No instances present."
        WScript.Quit EXIT_SUCCESS
    End If

    instCount1 = 0
    instCount2 = 0 
    TotalCount = 0
    If IsEmptyNull(sInstances1) = FALSE Then
        instCount1 = UBound(sInstances1) + 1
        TotalCount = instCount1
    End If

    If IsEmptyNull(sInstances2) = FALSE Then
        instCount2 = UBound(sInstances2) + 1
        TotalCount = TotalCount + instCount2
    End If

    ReDim PRESERVE sInstances(TotalCount,1)
    if instCount1 > 0 Then
        For i = 0 To UBound(sInstances1)
            sInstances(i,0) = sInstances1(i)
            sInstances(i,1) = True
        Next
    End If
    If instCount2 >0 Then
        For i = 0 To UBound(sInstances2)
            sInstances(i+instCount1,0) = sInstances2(i)
            sInstances(i+instCount1,1) = FALSE
        Next
    End If
    GetInstances = TRUE
End Function

Function ApplyFix(ByVal strInstance, ByVal strServerName)
    Err.Clear
    ON ERROR RESUME NEXT

    Dim objConn, objCmd, objCmd1, objRS, objRS1
    Dim strCommand, strConn
    Dim strBuildVersion, strProductLevel, bApplyFix

    ' Initialize return value
    ApplyFix = FALSE

    strConn = "Provider=sqloledb;Initial Catalog=master;Integrated Security=SSPI;Data Source=" + strServerName + ";"
    ' Error checking is intentionally left to keep the code short
    Set objConn = CreateObject("ADODB.Connection")
    Set objCmd = CreateObject("ADODB.Command")
    Set objCmd1 = CreateObject("ADODB.Command")

    ' Open a Connection to the master Database
    objConn.Open strConn 
    If ErrorOccurred("Error: Could not connect to " + strInstance) Then
        Set objConn = Nothing
        Exit Function
    End If

    ' Validate the version before applying the fix
    strCommand = "select SERVERPROPERTY('ProductVersion') as version, SERVERPROPERTY('productlevel') as productlevel"
    objCmd.ActiveConnection = objConn
    objCmd.CommandType = adCmdText
    objCmd.CommandText = strCommand

    Set objRS = objCmd.Execute()
    If ErrorOccurred("ERROR: Could not execute """ + strCommand + """ on " + strInstance) = TRUE Then
        objConn.Close()
        Set objConn = Nothing
        ApplyFix = FALSE
        Exit Function   
    End If

    strBuildVersion = objRS("version")
    strProductLevel = UCase(objRS("productlevel"))

    bApplyFix = FALSE
    ' Apply the workaround only for SQL 2000 and SQL 2005 (RTM, SP1 and SP2) versions
    If (CInt(Mid(strBuildVersion,1,1)) = 8) Then
        bApplyFix = TRUE
    ElseIf CInt(Mid(strBuildVersion,1,1)) = 9 AND (StrComp(strProductLevel,"RTM") = 0 OR StrComp(strProductLevel,"SP1") = 0 OR StrComp(strProductLevel,"SP2") = 0) Then
        bApplyFix = TRUE
    End If 

    If bApplyFix = TRUE Then
        strCommand = "deny execute on sp_replwritetovarbin to public"
        objCmd1.ActiveConnection = objConn
        objCmd1.CommandType = adCmdText
        objCmd1.CommandText = strCommand
        Set objRS1 = objCmd1.Execute()
        If ErrorOccurred("ERROR: Could not execute """ + strCommand + """ on " + strInstance) = FALSE Then
            WScript.Echo "INFO: Successfully applied the workaround on " + strInstance + " (" + strBuildVersion + ")." + vbCRLF
            ApplyFix = TRUE
        End If
    Else
        WScript.Echo "INFO: Skipping collecting information for " + strInstance + " (" + strBuildVersion + ") as this instance is not vulnerable." + vbCRLF
        ApplyFix = TRUE
    End If

    objConn.Close()
    Set objConn = Nothing
    Set objCmd = Nothing
    Set objCmd1 = Nothing
    Set objRS = Nothing
    Set objRS1 = Nothing
End Function

Private Function GetRegValue (ByVal strMachineName, ByVal hMainKey, ByVal strPath, ByVal strValueName, ByRef strValue, ByVal iValueType, ByVal b32bit)
    Err.Clear
    ON ERROR RESUME NEXT

    Dim objLocator, objServices, objRegistry, objCtx
    Dim sMultiStrings, lRc
    GetRegValue = TRUE

    'Connect to WMI and get an object to STDREGPROV class.
    Set objCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
    If b32bit = TRUE Then
        objCtx.Add "__ProviderArchitecture", 32
    Else
        objCtx.Add "__ProviderArchitecture", 64
    End If
    objCtx.Add "__RequiredArchitecture", TRUE
    set objLocator = createobject("wbemscripting.swbemlocator")
    set objServices = objLocator.connectserver(strMachineName,DEFAULTNAMESPACE, "", "",,,,objCtx)
    set objRegistry = objServices.get(STDREGPROV)
    If ErrorOccurred ("ERROR: Could not connect to WMI namespace " + DEFAULTNAMESPACE) Then
        GetRegValue = FALSE
        Exit Function
    End If 

    lRc = 0
    Select Case iValueType
        ' We only care about REG_MULTI_SZ
        Case REG_MULTI_SZ
            strValue = ""
            lRC = objRegistry.GetMultiStringValue(hMainKey, strPath, strValueName, sMultiStrings)
            strValue = sMultiStrings
        Case REG_SZ
            strValue = ""
            lRC = objRegistry.GetStringValue(hMainKey, strPath, strValueName, strValue)
        Case Else
            GetRegValue = FALSE
    End Select

    If lRc = 2 Or lRc = 3 Then
        GetRegValue = TRUE
        strValue = ""
    ElseIf Err.Number OR lRc <> 0 Then
        GetRegValue = FALSE
    End If

    Set objLocator = Nothing
    Set objServices = Nothing
    Set objRegistry = Nothing
End Function

Function IsEmptyNull(sCheck)
    IsEmptyNull = FALSE
    If IsObject(sCheck) Then Exit Function
    If IsArray(sCheck) Then Exit Function
    If VarType(sCheck) = vbEmpty Then IsEmptyNull = TRUE : Exit Function
    If VarType(sCheck) = vbNull Then IsEmptyNull = TRUE : Exit Function
    If sCheck = "" Then IsEmptyNull = TRUE
End Function

Private Function ErrorOccurred (ByVal strIn)
    If Err.Number <> 0 Then
        WScript.Echo strIn
        WScript.Echo "ERROR: 0x" & Err.Number & " - " & Err.Description
        Err.Clear
        ErrorOccurred = TRUE
    Else
        ErrorOccurred = FALSE
    End If
End Function

Function IsOs64Bit()
    Err.Clear
    ON ERROR RESUME NEXT

    Dim objProc
    Set objProc = GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'")
    If objProc.Architecture = 0 Then
        IsOs64Bit = FALSE
    Else
        IsOs64Bit = TRUE
    End If
End Function

Function GetFullInstance (ByRef strInstanceName, ByVal b32bit)
    Err.Clear
    ON ERROR RESUME NEXT 

    Dim objServices, objClusters, objCluster
    Dim strMacName, isEmpty
    Dim strKey, strInstID

    GetFullInstance = TRUE

    If strComp(UCase(strInstanceName), "MICROSOFT##SSEE", 1) = 0 Then
        strInstanceName = "np:\\.\pipe\mssql$microsoft##ssee\sql\query"
        Exit Function
    End if

    strMacName = ""
    Set objServices = GetObject("winmgmts:root\cimv2")

    ' Query Cluster service
    Set objClusters = objServices.ExecQuery ("select * from win32_service where Name='ClusSvc' AND Started = TRUE")
    isEmpty = TRUE
    If Err.Number = 0 Then
        For each objCluster in objClusters
            isEmpty = FALSE
        Next
    End If

    Set objServices = Nothing
    Set objClusters = Nothing

    If isEmpty = TRUE Then
        strInstanceName = BuildInstanceName (".", strInstanceName)
        Exit Function
    End If

    ' If we reach here that means the machine is a clustered node.
    ' So lets query registry to determine whether the SQL instance is clustered or not.
    ' For SQL 2000 query the following value
    ' HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<InstanceName>\Cluster
    ' ClusterName
    strKey = "SOFTWARE\Microsoft\Microsoft SQL Server\" + strInstanceName + "\Cluster"
    GetRegValue "", HKEY_LOCAL_MACHINE, strKey, "ClusterName", strMacName, REG_SZ, b32bit

    If StrComp(strMacName, "") <> 0 Then
        strInstanceName = BuildInstanceName (strMacName, strInstanceName)
        Exit Function
    End If

    strKey = "SOFTWARE\Microsoft\" + strInstanceName + "\Cluster"
    GetRegValue "", HKEY_LOCAL_MACHINE, strKey, "ClusterName", strMacName, REG_SZ, b32bit

    If StrComp(strMacName, "") <> 0 Then
        strInstanceName = BuildInstanceName (strMacName, strInstanceName)
        Exit Function
    End If

    ' Lets try querying the registry value for 2005/2008 instances
    'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL
    ' RegValue = InstanceName
    strInstID = ""
    strKey = "SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"
    GetRegValue "", HKEY_LOCAL_MACHINE, strKey, strInstanceName, strInstID, REG_SZ, b32bit

    If StrComp(strInstID, "") = 0 Then
        ' If this key doesnt exist, then return back as a SQL 2000 local instance
        strInstanceName = BuildInstanceName (".", strInstanceName)
        Exit Function
    End If

    strKey = "SOFTWARE\Microsoft\Microsoft SQL Server\" + strInstID + "\Cluster"
    GetRegValue "", HKEY_LOCAL_MACHINE, strKey, "ClusterName", strMacName, REG_SZ, b32bit

    If StrComp(strMacName, "") = 0 Then
        strMacName = "."
    End If

    strInstanceName = BuildInstanceName (strMacName, strInstanceName)
End Function

Function BuildInstanceName (ByVal strMachineName, ByVal strInstanceName)
    Dim strPrefix

    strPrefix = ""
    If StrComp(strMachineName, ".") = 0 Then
        strPrefix = "lpc:"
    End If

    If strComp(UCase(strInstanceName), "MSSQLSERVER", 1) = 0 Then
        BuildInstanceName = strPrefix + strMachineName
    Else
        BuildInstanceName = strPrefix + strMachineName + "\" + strInstanceName
    End if
End Function

有关 CScript.exe 的详细信息,请访问以下Microsoft网站:

http://technet.microsoft.com/en-us/library/bb490887.aspx 注意 建议不要使用此脚本(如果已提供安全更新,并且已安装它)。

运行此脚本时可能发生的已知问题

问题 1

运行脚本时,会收到以下错误消息:

注意

错误:无法对 <instancename 执行“拒绝执行sp_replwritetovarbin到公共”>
 错误:0x-2147217900 - 找不到对象“sp_replwritetovarbin”,因为它不存在或您没有权限。
错误:无法对 <instancename> 应用解决方法。

原因 1

如果你没有应用更改所需的权限,则会收到此错误消息。 此错误消息指示你能够成功登录到实例“<instancename>”。

此错误消息通常出现在“内置\用户”组默认登录到数据库的SQL Server Express中。 但是,此组不是 sysadmin 角色的成员。

如果删除了sp_replwritetovarbin过程,也可能会出现此错误消息。 这是第三方报告的建议。 建议不要删除存储过程。 相反,建议应用此解决方法。

解决方案 1

确保连接到的帐户是该数据库实例上的 sysadmin 角色的成员。 如果帐户不是成员,请将要连接的用户添加为 sysadmin 角色,或使用另一个用户帐户。 对于 SQL Server 2005 及更早版本,默认情况下,“内置\管理员”组是 sysadmin 角色的成员。 在 Windows Vista 或 Windows Server 2008 上运行此脚本时,请确保从“提升的”命令提示符运行该脚本。

问题 2

如果在 SQL Server 2005 中运行此脚本,则会收到以下错误消息:

注意

错误:无法连接到 <instancename>
错误:0x-2147217843 - 用户“user>”<登录失败。
错误:无法对 <instancename> 应用解决方法。

原因 2

如果无法连接到实例“instancename>”<,即使此实例存在,也会收到此错误消息。

连接到 Windows 内部数据库 或 Microsoft SQL Server 2000 Desktop Edition (Windows) 实例时,通常会出现此错误消息。 通常,没有用户帐户具有这些数据库的登录名。

解决方案 2

确保用于运行脚本的帐户在数据库上具有登录名,该登录名是 sysadmin 角色的成员。

建议不要将单个用户添加到 Windows 内部数据库 和 Microsoft SQL Server 2000 Desktop Edition (Windows) 数据库。 如果执行此操作,则添加的用户可能会干扰这些数据库的正常操作。 在这种情况下,请确保从作为 sysadmin 角色成员的帐户进行连接。 默认情况下,Windows 中的“内置\管理员”组在 SQL Server 2005 和早期版本中通常是 sysadmin 角色的成员。 在 Windows Vista 或 Windows Server 2008 上运行此脚本时,请确保从“提升的”命令提示符运行该脚本。

问题 3

你可能会注意到名为 MICROSOFT##SSEE 的数据库实例。 但是,未安装此数据库。

原因 3

此数据库是Windows 内部数据库,也称为“SQL Server Embedded Edition”,有时也称为“Windows 内部数据库”或“Microsoft SQL Server 2000 桌面版 (Windows) ”。它随Microsoft中的某些产品一起安装,包括 SharePoint Services。

解决方法 3

解决方法脚本旨在通过Windows 内部数据库运行。 你无需执行任何操作。

某些应用程序在卸载时不会删除Windows 内部数据库。
  有关如何删除Windows 内部数据库的详细信息,请单击下面的文章编号以查看Microsoft知识库中的文章:

920277 Windows 内部数据库未在“添加或删除程序”工具中列出,并且从计算机中删除 Windows SharePoint Services 3.0 时不会删除

问题 4

运行脚本时,会收到以下错误消息:

注意

错误:无法连接到 .\<instancename>
错误:0x-2147467259 - [DBNETLIB][ConnectionOpen (Connect () ) .]不存在SQL Server或访问被拒绝

原因 4

如果满足以下条件,则会收到此错误消息:

  • x64 位操作系统上安装了 32 位版本的 SQL Server 2000。
  • 计算机上安装了 64 位版本的 SQL Server 2005 或 SQL Server 2008。

当脚本使用 64 位版本的 dbmslpcn.dll 文件时,会出现此错误消息。 此版本无法与 SQL Server 2000 的 WoW 实例通信。

解决方法 4

使用 %WINDOWS%\SysWOW64 文件夹中的 32 位版本的 cscript.exe 文件启动脚本。 这将加载可以检测 WoW 实例的 32 位版本的 dbmslpcn.dll 文件。

参考资料

有关如何标识SQL Server版本的详细信息,请单击下面的文章编号以查看Microsoft知识库中的文章:

321185如何标识SQL Server版本

详细信息

下表列出了本文的重要技术修订。 本文中的修订号和上次审阅日期可能表示表中未包括的对本文的次要编辑修订或结构修订。

日期 修订
2008 年 12 月 31 日 包括一个更新的脚本,用于检测SQL Server故障转移聚类分析实例。
2008 年 12 月 30 日 包括一个更新的脚本,用于检测在 64 位版本的 Windows 上运行的 32 位版本的SQL Server。