SQL 注入
原理
SQL 注入的产生本质上是基于字符串的拼接原理从而产生的,通常数据库作为一个中间产品,需要我们将他和我们的开发代码结合起来使用,比如 Java
和 PHP
等语言。以 Java
为例,通常我们需要用 JDBC
来连接 DBMS, 也就是类似于 MySQL
的数据库管理系统,即,通过 Java
来执行 SQL 语句,这就意味着,我们通常需要将数据库的查询或者插入删除等等指令预先存储在代码中,在最简单的情况下,这些指令就是由字符串的格式被实现放在代码中。例如以下的Java
代码:
public boolean findAdmin(Admin admin){
String sql = "select count(*) from admin where usrname='"+admin.getUsername()+"'and password ='"+admin.getPassword()+"'"; // SQL 查询语句
try {
ResultSet res = this.conn.createStatement().executQuery(sql);
if(res.next()){
int i = res.getInt(1); // 取第一列的值
if (i>0){
return true;
}
}
} catch (Exception e){
e.printStackTrace();
}
return false;
}
这段代码就是一个管理员登录时的后端代码的判断语句操作,如果查询的值大于0, 则表示用户存在,查询成功,密码正确,最终判定登录成功,反之代表登录失败。尽管整个代码的逻辑没有任何问题,甚至看起来这个代码也不是仅仅一两句话,但是如果你仔细看就会发现,代表 SQL 代码指令的语句是以 String
类型存储的。就在代码段的第一句话中。
你在用户名和密码的输入框中输入的字符被存储到 Admin
类中,通过 admin.getUsername()
等方法获取,最终因为字符串的拼接作用被拼接成一个 String
,如果你填写的 user 为 user
, 密码为:pwd
,在第一个语句执行后你的变量 sql
就会变成如下的模样:
select count(*) from admin where username='user' and password='pwd'
那么,如果我们换一个方向呢?我们在 user 中填写的字段为'or 1=1-- '
,密码为空,也就是不做填写,那么这个变量 sql
就会长这样:
select count(*) from admin where username='' or 1=1-- and password='pwd'
注意在sql
中,单行注释的指令为--
;
也就是说,对 SQL 解释器来说,它看到的你的语句执行条件就是一个 or
关系的判断语句,且后面那一个恒定为真,那么这个查询语句一定能找到结果,最终在代码里一定会显示登录成功。我们登录成功,并不是因为这个我们的管理员的名字叫做 'or1=1--'
,而是输入的这个字段篡改了我们原先想要它执行的 SQL 语句,这种情况就称作 SQL 注入。
那么用维基百科的定义就是: 发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库資料服务器误认为是正常的SQL指令而执行,因此遭到破坏或是入侵 。
用途
SQL 注入可能存在于任何地方,在这种登录界面进行 SQL 注入的可能性事实上并不大,又或者说,注入后我们进入的某个账户里的信息并不足以让我们控制整个网站,注入可能会存在于任何地方,那么这个时候,也许我们可能可以尝试检查一下搜索框,如果存在 SQL 注入,则可以利用这个输入框获取信息。
利用 Union 来实现SQL 注入
SQL 的查询语法 Union 经常会被应用于获取数据库的其他信息,同时我们也会利用 Union 来猜测数据表各个列的数据类型,或是探测字段数。
Union
Union 通常被用来合并两个查询结果集的结果。这就意味着,前后两个查询的结果的列数必须相同,同时,查询的每个列的数据类型必须兼容。Union的使用可以参考:https://www.runoob.com/sql/sql-union.html
Union 探测字段数
试想一下一个存在 SQL 注入的搜索框,他对应的正常的 SQL 查询语句是:
select id, username, password, sex from user where id=1
虽然一个搜索框不可能是这样的查询语句,好吧,就姑且当作是这个把。以这个为例,我们可以得到他的查询语句的结果; 但是我们要知道,作为攻击者的我们,是不知道 select
的查询列数,就是说,我们不知道 select
后面跟着几个字段。Union 的作用就是试探出 select
后面跟着几个列,可以尝试输入 union select null
,那么完整的注入语句应该是:
select id, username, password, sex from user where id=1 union select null
如果列数不同,那么会收到一个报错提醒你的联合查询的结果列数并不相同,意味着我们知道这个表的列数不是1,那么可以接着试探:
select id, username, password, sex from user where id=1 union select null, null, null,... ,null
直到不再收到报错为止。
Union 查询敏感信息
例如在知道列数为4时,就可以尝试构造语句:
select id, ..., sex from user where id=1 union select 'x', null, null, null from sysobjects where xtype ='U'
sysobjects
是一个自带的系统表, xtype
是其中的一个列,'x'
在这里和 xtype
没有任何关联,他只是一个字符串,在这里他所起到的作用就是在第一列的查询结果的后面加一个 'x'
,这条语句实际上是在查询 xtype
的类型为 U
(代表用户表)的sysobjects
的信息,并在第一列的结果后面添加一个 'x'
,表示这一列的数据类型是一个字符串型。 可以以此调换 x 的位置,以此试探列的类型。
在知道某一列的类型同 'x' 相同时,就可以将字符 x 调换,例如替换成 version()
, 来显示版本号等等。
DBMS 自带函数的应用
DBMS 本身自带很多的系统函数足够可以便捷的查看信息,例如在 SQLServer
中可以使用语句:
select suser_name(); -- 返回用户的登录标识名;
select user_name(); -- 基于指定的标识号返回数据库用户名等等。
...
同样 MySQL
和 Oracal
同样有着其对应的系统函数,需要时进行查询即可。
延时注入
延时注入是盲注技术的一种,主要是基于页面返回给浏览器的时间不同来判断网页是否存在 SQL 注入;这种技术多数是运用在网页的容错技术做的很好的时候,无论你输入什么额外的参数,页面都将返回同样的内容,这时候就要通过页面返回的时间来判断,比如 sleep
函数。例如当你要渗透的网站使用的 DBMS 是 MySQL 时,你可以靠如下的方式来获取你的数据:
-- 以获取用户的登录名为例, 代码只显示注入的关键语句
and if(length(user()=0), sleep(3), 1) -- 获取用户名的长度,如果判断成功则3秒后返回
and if(hex(mid(user(), L, 1))=N,sleep(3),1) --循环比较,依次取出用户名字里的字符和ACSII码比较,如果相等则三秒后返回注入
SQL 注入的工具
在使用语句对单个 URL 进行测试时是比较容易的,但是如果要测试的 URL 非常多的时候,就需要借助工具进行使用了,常见的 SQL 注入工具有 SQLMap, Pangolin, Havij ,下面给出相应的可以参考学习的链接。
SQLMap: https://github.com/kvko/sqlmap-wiki-zhcn
Pangolin: https://developer.aliyun.com/article/393100
Havij:https://www.hackercoolmagazine.com/sql-injection-using-havij-step-by-step-guide/
防范
SQL 注入实际上完全是可以防范的,即使完全不知道所谓的 SQL注入,但是一个开发团队只要定义好自己的编程模板,就可以很大程度上避免这类问题的产生。例如在碰到 SQL 语句时完全采用 "PreparedStatement"类,且必须用参数绑定,这样就可以将安全问题转移到代码规范的问题上。
SQL注入包含着很多的内容,本篇笔记只是选取了一部分内容进行介绍和说明,但是还有一大部分的内容需要大家自行去查阅和研究,也可以通过靶场进一步的提升自己的实战能力。
Reference
https://zh.wikipedia.org/zh-cn/SQL注入
《Web 安全深度剖析》
标签:语句,查询,user,SQL,select,注入 From: https://www.cnblogs.com/tjdtec/p/18137178