[SQL Server]使用CLR存储过程替代xp_cmdshell

How to execute a DOS command when xp_cmdshell is disabled in SQL Server

By: Andy Novick -- 8/11/2010

Problem
For security reasons many sites disable the extended stored procedure xp_cmdshell, which is used to run DOS commands or executables.  When you really have to run a DOS command or an executable from a stored procedure how can you get around this limitation without a breakdown in security.

Solution
Disabling xp_cmdshell is pretty much a standard security practice and in SQL Server 2008 it's disabled by default. That's a good idea, because xp_cmdshell allows running a DOS command or executable with the privileges of the SQL Server database engine, creating a "privilege elevation" vulnerability.  Hackers have been known to execute such commands as "Format C:" using this security hole.

When SQL Server is running as an administrative user or under the system account anyone able to use xp_cmdshell can pretty much run any program or DOS command.  That's a good reason to run SQL Server as a domain user with limited privileges.  This limits any hacker to the privileges given to that user. That is still more than you want to let a hacker have access to, but it's better than administrative permissions.

With xp_cmdshell disabled, I tend to run into situations where SQL Server has to do something outside its own environment that just can't be done with T-SQL.  The example in this article runs the DOS attrib command, which changes attributes on files.  I use it to make certain input files read-only after they've been processed.  With xp_cmdshell available I'd execute a command like this:

exec xp_cmdshell 'attrib "c:/temp/foo.bar" +r'

The +r asks that the read-only attribute be turned on. 

To view the attributes of a file at a CMD prompt execute the command without any options like this:

c:/temp>attrib foo.bar
--the output would look like this
A R C:/temp/foo.bar

The A signifies the Archive attribute, R the Read-Only attribute.  There are also S for system and H for hidden attributes for each file. The absence of the letter shows that S and H are not set.

To execute the attrib command securely I created a custom SQLCLR stored procedure dedicated to the task.  I call my procedure file_attrib_dos_cmd and I built it with a Visual Studio 2010 SQLCLR project.  I start by creating the project and selecting a database to connect to with the "Add Database Reference" dialog.  I've covered creating stored procedures before in this article Writing to an operating system file using the SQL Server SQLCLR.  I then add the stored procedure to the project with the menu command "Project/Add Stored Procedure..." and give it the same name file_attrib_dos_cmd. To allow the execution of the Process.Start framework method the project must be marked as "UnSafe" in the Database tab of the project properties.  An unsafe SQLCLR procedure is no more "UnSafe" than an extended stored procedure and that's what file_attrib_dos_cmd replaces. 

Here is the code:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
using System.Diagnostics;
using System.Text;

public partial class StoredProcedures
{

    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void file_attrib_dos_cmd(SqlString DirectoryPath
                                          ,SqlString FileSpec
                                          ,SqlString arguments)
    {
        // Make sure the file exists
        string FullPath = Path.Combine(DirectoryPath.Value, FileSpec.Value);
        FileInfo fi = new FileInfo(FullPath);
        if (!fi.Exists)
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                cmd.CommandText = string.Format(
                         "raiserror('File ->{0}<- is not found.',16,1)",
                           FileSpec.Value);
                try { SqlContext.Pipe.ExecuteAndSend(cmd); }
                catch { return; }
            }
        }
        // ProcessStartInfo to run the DOS command attrib
        ProcessStartInfo pPStartInfo = new ProcessStartInfo("cmd.exe");
        pPStartInfo.WorkingDirectory = DirectoryPath.Value;
        pPStartInfo.UseShellExecute = true;
        // quote the file name incase it has spaces
        pPStartInfo.Arguments =string.Format(@" /C attrib "{0}""{1}",  
                                   FullPath, arguments.Value);
        // start a new process and wait for it to exit
        Process p = new Process();
        p.StartInfo = pPStartInfo;        
        p.Start();
        p.WaitForExit();
   
    }
};

The method checks to be sure that the file exists.  If it doesn't, it throws a SQL error in a way that works well in T-SQL.  It then builds a ProcessStartInfo structure supplying cmd.exe as the name of the command to run.  If we wanted to run an executable, it could be run directly, but cmd.exe is the program that implements DOS commands.  The arguments are formatted for the attrib command and the process is started and the procedure waits for the command to complete.  Executing the procedure doesn't produce any output.  Here's a typical command:

exec dbo.file_attrib_dos_cmd 'c:/temp/', 'foo.bar', '+r';
GO 
Command(s) completed successfully.

A more complex alternative would have been to write a SQLCLR procedure that made the attribute changes directly.  However, that would have been more complex and this method is easily extended to other commands as well as to running executable programs.

Next Steps

  • Disable xp_cmdshell if at all possible
  • Use SQLCLR procedures that are very specific to your need to accomplish
  • Alos, use a unique name for the CLR procedure instead of a generic name like "xp_cmdshell2"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
wire [ROWBUF_IDX_W-1:0] sbuf_cnt_r; wire [ROWBUF_IDX_W-1:0] sbuf_cnt_nxt; wire sbuf_cnt_clr; wire sbuf_cnt_incr; wire sbuf_cnt_ena; wire sbuf_cnt_last; wire sbuf_icb_cmd_hsked; wire sbuf_icb_rsp_hsked; wire nice_rsp_valid_sbuf; wire nice_icb_cmd_valid_sbuf; wire nice_icb_cmd_hsked; assign sbuf_icb_cmd_hsked = (state_is_sbuf | (state_is_idle & custom3_sbuf)) & nice_icb_cmd_hsked; assign sbuf_icb_rsp_hsked = state_is_sbuf & nice_icb_rsp_hsked; assign sbuf_icb_rsp_hsked_last = sbuf_icb_rsp_hsked & sbuf_cnt_last; assign sbuf_cnt_last = (sbuf_cnt_r == clonum); //assign sbuf_cnt_clr = custom3_sbuf & nice_req_hsked; assign sbuf_cnt_clr = sbuf_icb_rsp_hsked_last; assign sbuf_cnt_incr = sbuf_icb_rsp_hsked & ~sbuf_cnt_last; assign sbuf_cnt_ena = sbuf_cnt_clr | sbuf_cnt_incr; assign sbuf_cnt_nxt = ({ROWBUF_IDX_W{sbuf_cnt_clr }} & {ROWBUF_IDX_W{1'b0}}) | ({ROWBUF_IDX_W{sbuf_cnt_incr}} & (sbuf_cnt_r + 1'b1) ) ; sirv_gnrl_dfflr #(ROWBUF_IDX_W) sbuf_cnt_dfflr (sbuf_cnt_ena, sbuf_cnt_nxt, sbuf_cnt_r, nice_clk, nice_rst_n); // nice_rsp_valid wait for nice_icb_rsp_valid in SBUF assign nice_rsp_valid_sbuf = state_is_sbuf & sbuf_cnt_last & nice_icb_rsp_valid; wire [ROWBUF_IDX_W-1:0] sbuf_cmd_cnt_r; wire [ROWBUF_IDX_W-1:0] sbuf_cmd_cnt_nxt; wire sbuf_cmd_cnt_clr; wire sbuf_cmd_cnt_incr; wire sbuf_cmd_cnt_ena; wire sbuf_cmd_cnt_last; assign sbuf_cmd_cnt_last = (sbuf_cmd_cnt_r == clonum); assign sbuf_cmd_cnt_clr = sbuf_icb_rsp_hsked_last; assign sbuf_cmd_cnt_incr = sbuf_icb_cmd_hsked & ~sbuf_cmd_cnt_last; assign sbuf_cmd_cnt_ena = sbuf_cmd_cnt_clr | sbuf_cmd_cnt_incr; assign sbuf_cmd_cnt_nxt = ({ROWBUF_IDX_W{sbuf_cmd_cnt_clr }} & {ROWBUF_IDX_W{1'b0}}) | ({ROWBUF_IDX_W{sbuf_cmd_cnt_incr}} & (sbuf_cmd_cnt_r + 1'b1) ) ; sirv_gnrl_dfflr #(ROWBUF_IDX_W) sbuf_cmd_cnt_dfflr (sbuf_cmd_cnt_ena, sbuf_cmd_cnt_nxt, sbuf_cmd_cnt_r, nice_clk, nice_rst_n); // nice_icb_cmd_valid sets when sbuf_cmd_cnt_r is not full in SBUF assign nice_icb_cmd_valid_sbuf = (state_is_sbuf & (sbuf_cmd_cnt_r <= clonum) & (sbuf_cnt_r != clonum));,详细解释一下这段代码
06-02
这段代码是一个 Verilog 文件中的部分代码。它定义了一些逻辑电路中使用的信号,这些信号用于控制和监视电路的状态。这段代码的功能是实现一个带有计数器的缓冲区,用于存储和处理来自外部接口的数据。 具体来说,这段代码定义了以下信号: - sbuf_cnt_r:缓冲区中当前存储的数据数量,使用 ROWBUF_IDX_W 位的无符号整数表示。 - sbuf_cnt_nxt:下一个时钟周期中 sbuf_cnt_r 的值,同样是 ROWBUF_IDX_W 位的无符号整数。 - sbuf_cnt_clr:用于清空计数器的信号,当它被置为 1 时,sbuf_cnt_r 被重置为 0。 - sbuf_cnt_incr:用于增加计数器的信号,当它被置为 1 时,sbuf_cnt_r 的值加 1。 - sbuf_cnt_ena:用于使能计数器的信号,当它被置为 1 时,计数器根据 sbuf_cnt_clr 和 sbuf_cnt_incr 的状态进行重置或增加。 - sbuf_cnt_last:用于指示缓冲区是否已满的信号,当 sbuf_cnt_r 的值等于 clonum 时,它被置为 1。 - sbuf_icb_cmd_hsked:用于控制向外部接口发送数据请求的信号,当它被置为 1 时,表示可以发送数据请求。 - sbuf_icb_rsp_hsked:用于控制从外部接口接收数据响应的信号,当它被置为 1 时,表示可以接收数据响应。 - nice_rsp_valid_sbuf:用于指示是否可以从缓冲区中读取数据的信号,当缓冲区已满且接收到数据响应时,它被置为 1。 - nice_icb_cmd_valid_sbuf:用于指示是否可以向缓冲区中写入数据的信号,当缓冲区未满且可以发送数据请求时,它被置为 1。 - sbuf_cmd_cnt_r:用于记录缓冲区中当前存储的数据请求数量,同样是 ROWBUF_IDX_W 位的无符号整数。 - sbuf_cmd_cnt_nxt:下一个时钟周期中 sbuf_cmd_cnt_r 的值,同样是 ROWBUF_IDX_W 位的无符号整数。 - sbuf_cmd_cnt_clr:用于清空数据请求计数器的信号,当它被置为 1 时,sbuf_cmd_cnt_r 被重置为 0。 - sbuf_cmd_cnt_incr:用于增加数据请求计数器的信号,当它被置为 1 时,sbuf_cmd_cnt_r 的值加 1。 - sbuf_cmd_cnt_ena:用于使能数据请求计数器的信号,当它被置为 1 时,数据请求计数器根据 sbuf_cmd_cnt_clr 和 sbuf_cmd_cnt_incr 的状态进行重置或增加。 - sbuf_cmd_cnt_last:用于指示数据请求缓冲区是否已满的信号,当 sbuf_cmd_cnt_r 的值等于 clonum 时,它被置为 1。 这段代码还使用了 Verilog 中的一些逻辑运算符和模块实例化语句,用于实现上述信号的计算和更新。其中,sirv_gnrl_dfflr 是一个模块实例,用于实现一个数据触发器,用于存储和更新计数器的值。这个模块接受 sbuf_cnt_ena 或 sbuf_cmd_cnt_ena 作为时钟输入,sbuf_cnt_nxt 或 sbuf_cmd_cnt_nxt 作为数据输入,sbuf_cnt_r 或 sbuf_cmd_cnt_r 作为数据输出。这个模块还接受 nice_clk 和 nice_rst_n 作为时钟和复位信号,用于控制触发器的工作状态。 总体来说,这段代码实现了一个用于处理数据请求和响应的缓冲区,通过计数器来控制缓冲区的状态和数据的访问。这个缓冲区可以在逻辑电路中被复用,并可以根据具体的应用场景进行定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值