一、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发生异常后,自定义异常进行响应。同时做好数据库操作日志,以便跟踪和发现问题。
|