一、SQL注入概念与种类

    SQL注入,就是所谓的SQL Injection,是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过把SQL命令插入到表单窗体递交或输入域名或页面请求的查询字符串,从而成功获取想要的数据,最终达到欺骗服务器,实现无帐号登录,执行一些恶意的SQL命令,甚至篡改数据库。
    从具体而言,SQL注入可分为五大类,分别是:数字型注入、字符型注入、搜索型注入(like)、in型的注入、句语连接型注入。从应用来说,要特别注意IP、搜索、批量删除、从数据库转到数据库等地方的SQL注入。

二、SQL注入案例

1、SQL注入攻击的总体思路
寻找到SQL注入的位置;
判断服务器类型和后台数据库类型;
针对不通的服务器和数据库特点进行SQL注入攻击;

2、SQL注入攻击例子,本段引用了网上一贴()
假如某高校开发了一个网课系统,要求学生选课后完成学习,数据库中有一张表course,这张表存放着每个学生的选课信息及完成情况,具体设计如下:

 

数据如下:

 

本系统采用mysql做为数据库,使用Jdbc来进行数据库的相关操作。系统提供了一个功能查询该学生的课程完成情况,代码如下。

@RestController
public class Controller {
    
    @Autowired
    SqlInject sqlInject;
    
    @GetMapping("list")
    public List<Course> courseList(@RequestParam("studentId") String studentId){
        List<Course> orders = sqlInject.orderList(studentId);
        return orders;
    }
}
@Service
public class SqlInject {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List<Course> orderList(String studentId){
        String sql = "select id,course_id,student_id,status from course where student_id = "+ studentId;
        return jdbcTemplate.query(sql,new BeanPropertyRowMapper(Course.class));
    }
}

注意啦,下面是攻击演示
(1)正常情况下查询一个学生所选课程及完成情况只需要传入student_id,便可以查到相关数据。

 

根据响应结果,我们很快便能写出对应的sql,如下:

select id,course_id,student_id,status 
from course 
where student_id = 4

(2)如果我们想要获取这张表的所有数据,只需要保证上面这个sql的where条件恒真就可以了。

select id,course_id,student_id,status 
from course 
where student_id = 4 or 1 = 1 


请求接口的时候将studendId 设置为4 or 1 = 1,这样这条sql的where条件就恒真了。sql也就等同于下面这样

select id,course_id,student_id,status 
from course 

请求结果如下,我们拿到了这张表的所有数据

 

(3)查询mysql版本号,使用union拼接sql

union select 1,1,version(),1

 

(4)查询数据库名

union select 1,1,database(),1

 

(5)查询mysql当前用户的所有库

union select 1,1, (SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata) schemaName,1


看完上面这些演示后,你害怕了吗?你所有的数据配置都完全暴露出来了,除此之外,还可以完成很多操作,更新数据、删库、删表等等。

 

三、SQL注入防护措施

1、使用预编译或参数化,是最快捷有效的方法。
(1)java预编译

public List<Course> orderList(String studentId){
    String sql = "select id,course_id,student_id,status from course where student_id = ?";
    return jdbcTemplate.query(sql,new Object[]{studentId},new BeanPropertyRowMapper(Course.class));
}

(2)C#.NET参数化

/// <summary>
    /// 第二种登录验证操作(参数数组形式)
    /// </summary>
    /// <param name="_loginname"></param>
    /// <param name="_loginpwd"></param>
    private void UserLoginSafeMode(string _loginname, string _loginpwd)
    {
        DataSet ds = new DataSet();
        string sql = "select * from Users Where rg_LoginName=@rg_LoginName and rg_LoginPwd=@rg_LoginPwd"; //创建查询语句

        //创建参数数组
        OleDbParameter[] parameters ={
            new OleDbParameter("@rg_LoginName",OleDbType.VarChar),
            new OleDbParameter("@rg_LoginPwd",OleDbType.VarChar)
        };

        //参数数组赋值
        parameters[0].Value = _loginname;
        parameters[1].Value = _loginpwd;

        try
        {
            Open();
            OleDbCommand comm = new OleDbCommand(sql, ConnAcc);
            if (parameters != null)
            {
                //向comm中插入参数
                foreach (OleDbParameter p in parameters)
                {
                    comm.Parameters.Add(p);
                }
            }
            new OleDbDataAdapter(comm).Fill(ds, 0, 1, "Users");
        }
        catch (OleDbException exception)
        {
            ds = null;
            Label1.Text = exception.Message; //异常处理
            return;
        }
        finally
        {
            Free();
        }
        if (ds != null && ds.Tables[0].Rows.Count > 0) //如果数据集中有记录 代表输入的用户名密码组合正确
        {
            Label1.Text = "用户[" + tbx_name.Text + "]登录成功!"; //显示

            //int uid = int.Parse(ds.Tables[0].Rows[0]["rg_ID"].ToString());
            //保存UID等用户信息到Session或者cookies中
            //由于本案例重点是登录身份验证通过环节,因此此处逻辑可以不继续写
        }
        else
        {
            Label1.Text = "用户名或密码错误";
        }
    }


2、过滤参数中含有的一些数据库操作关键词
(1)java代码示例

@Component
public class SqlInjectionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req=(HttpServletRequest)servletRequest;
        HttpServletRequest res=(HttpServletRequest)servletResponse;
        //获得所有请求参数名
        Enumeration params = req.getParameterNames();
        String sql = "";
        while (params.hasMoreElements()) {
            // 得到参数名
            String name = params.nextElement().toString();
            // 得到参数对应值
            String[] value = req.getParameterValues(name);
            for (int i = 0; i < value.length; i++) {
                sql = sql + value[i];
            }
        }
        if (sqlValidate(sql)) {
            throw new IOException("您发送请求中的参数中含有非法字符");
        } else {
            chain.doFilter(servletRequest,servletResponse);
        }
    }

    /**
     * 关键词校验
     * @param str
     * @return
     */
    protected static boolean sqlValidate(String str) {
        // 统一转为小写
        str = str.toLowerCase();
        // 过滤掉的sql关键字,可以手动添加
        String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|" +
                "char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|" +
                "table|from|grant|use|group_concat|column_name|" +
                "information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
                "chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|%|#";
        String[] badStrs = badStr.split("\\|");
        for (int i = 0; i < badStrs.length; i++) {
            if (str.indexOf(badStrs[i]) >= 0) {
                return true;
            }
        }
        return false;
    }
}


(2)C#.NET代码示例

        /// <summary>
        /// 防止SQL注入
        /// </summary>
		public bool IsHasSQLInject(string str)
		{
			bool isHasSQLInject = false;//字符串中的关键字更新需要添加
			string inj_str = "'|;|and|exec|union|create|insert|select|delete|update|count|*|%|mid|master|truncate|char|declare|+|(|)|#";  //去除--,chr,openid中含有"--"/"chr"字符
			if(string.IsNullOrEmpty(str))
			{
				return isHasSQLInject; 
			}
			str = str.ToLower().Trim();
			string[] inj_str_array = inj_str.Split('|');
			foreach (string sql in inj_str_array)
			{
				if (str.IndexOf(sql) > -1)
				{
					isHasSQLInject = true;
					break;
				}
			}
			return isHasSQLInject; 
		}

3、判断参数类型,或判断参数长度,可以规避参数中的异常植入。

4、严格控制数据库权限,专用库用专用账号和密码,不要使用管理员账号,比如mysql的root账号、sql server的sa账号不要放在应用中使用。上一篇帖子详细讲解了sql server数据库添加专用账号和赋权的操作步骤。

5、避免直接响应一些sql异常信息,sql发生异常后,自定义异常进行响应。同时做好数据库操作日志,以便跟踪和发现问题。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐