输入单引号直接报错,说明有注入点
直接order by二分法得出主查询字段数为7
直接union注入,测出回显点为2,3,4,5
查数据库信息:
测出数据库名为bwapp,用户为root,版本为5.5.53
查询表名:
查询字段名:
查字段值:
没有难度。
尝试多种方法都不行,看了一眼源码:
发现是用addslashes()进行转义的,尝试了下宽字节绕过,也不行。
看了下原来数据库采用的是urf8编码,本来想改成gbk复现一下,结果改不成。
character_set_system:数据库系统使用的编码格式,这个值一直是utf8,不需要设置,它是为存储系统元数据的编码格式。
所以暂时想不到什么绕过方法。
使用了mysql_real_escape_string()函数将特殊字符转义
受影响的字符:
暂时无法注入。
是一个选择表单的格式,但这也不妨碍注入。直接在url中注入
输入单引号报错,说明存在注入点。
判断是数字型
order by测出主查询字段数为7
直接union注入
找到了回显点。
查询数据库名 bwapp
查询表名
可以用limit来限制回显个数,也可以用group_concat()一次性查出效率更高
查询字段名
查询数据
虽然用了addslashes()函数进行了转义,但因为是数字型的不需要单引号闭合,所以没什么用,方法和上面一样。
采用了PDO技术,做了参数化和预处理,将sql语句和用户输入分离,有效预防了sql注入。
由于是post型注入,所以直接burp抓包:
输入单引号直接报错
order by 测到8报错,所以确定主查询为7个字段
测出回显点位2,3,4,5。剩下的就和上面一样了,暴数据库名、表名、字段名、字段值
用了addslashes()函数进行了转义’ " \ null,所以绕过比较困难。
但并不是说,只要加了这个函数就可以防御所有情况,这篇文章给了三种情况:
1、参数没有加引号的,直接绕过
2、宽字节注入(两种情况1.数据库字符集为gbk,2.代码中用了转换字符编码的函数)
3、addslashes()过滤后进行了url解码,直接url双重编码绕过。
总之比较灵活,还是要具体情况具体对待。
但是对这一关而言,绕过还是比较难的。
用了mysql_real_escape_string()函数进行了转义,绕过比较困难。
这一关与SQL Injection(GET/Select)只是请求方式上的差异。
可以看到通过AjAX异步请求发送到sqli_10-2.php,然后其他的和前面一样。
这一关和前面的区别就是多了一道验证码,正确输入验证码进入后,直接注入,手法和之前一样。
使用万能密码直接登入(需要提前知道用户名)
在用户名的位置进行注入测试:
用order by测出主查询字段数为4
进行union注入:
如图所示,获得回显点为2和4
获取数据库名和用户名:
获取表名:
获取字段名:
获取字段值:
最后将密码利用md5在线解密网站进行解密。
打完收工。
medium级别使用addslashes()进行转义
high级别使用mysql_real_escape_string()函数将特殊字符转义
进行注入测试,发现存在sql注入:
order by测出主查询字段数为9,但是无法进一步union注入。
看一眼源码:
发现只有密码通过后面的验证之后才能回显。
而这一段payload回显password值为3,显然无法通过判断。
所以要想进一步注入,就要知道bee账号对应的密码的散列值,这要在实战中直接就拉闸了,顶多能进账户,想脱库很难。
为了演示,假设我们已经知道bee账号对应的散列值,重新更改payload:
如图所示,这样的回显应该可以通过检测了。
试一下:
果然如此,和猜想一致,不过要注意的是password字段的值要加引号,不然会报错。
然后进一步暴数据库名:
接着暴表名、字段名、字段值。
与上一关不同之处在于,上一关在回显之前并没有验证密码,如图所示:
先随便输一个:333
发现直接将输入的语句回显到了页面上,于是判断后台应该是先将用户输入的内容存入数据库,然后查询回显。
语句大概是这样: insert into blog values(username, now(), ‘$content’)
所以可以尝试使用报错注入:
insert into blog values(username, now(), ''or updatexml(1,concat(0x7e,database()),2) or ‘’)
如图所示,成功爆出数据库名。
接着暴表名:
暴字段名:
暴字段值:
如图所示,成功爆出用户名和字段的md5值,但是回显的字符串长度有限制,已经被截断了。
于是可以使用substr()函数来截取后一段:
以此类推,将所有的密码截取完拼接起来。
小结:
注意报错注入一定要用concat()函数,否则无法正常回显。
还有一种思路是构造闭合,在注入字段的后面字段中做文章。
条件:注入的那个字段,不是insert语句中的最后一个字段。
例如原句为:
insert into blog (login, content, date) values(username, ‘$content’, now())
理想效果:
insert into blog (login,content, date) values(username, ‘hello baby’, database())#’)
这是我们先验证注入字段的位置:
如图所示报错:Column count doesn’t match value count at row 1
说明注入的字段并不是insert语句中的最后一个,满足条件。
于是加一个字段,继续测试:
这一次没有报错,成功插入!
于是可以在后面的那个字段上做文章:
查询数据库名:
暴表名:
暴字段名:
暴字段值:
打完收工!
从页面回显的内容来看,猜测网站会将数据包中的host和user-agent字段存入数据库中,然后回显到页面。
所以注入点可能在user-agent字段,然后后端的语句应该是insert
所以报错注入来一波:
如图所示,成功回显数据库名,然后再依次暴表名、字段名、字段值,姿势与上面类似这里不再赘述。
点击any bugs然后抓包:
发现请求包的数据是一个xml格式。
于是尝试更改bee为单引号:
结果报错。
分析页面的功能,发现是重置秘密的,所以可能是update语句
于是报错注入来一波:
如图所示,成功爆出数据库名。后续脱库步骤与上面相仿。
//sqli_8-1.php
<script type="text/javascript">
function ResetSecret()
{
var xmlHttp;
// Code for IE7+, Firefox, Chrome, Opera, Safari
if(window.XMLHttpRequest)
{
xmlHttp = new XMLHttpRequest();
}
// Code for IE6, IE5
else
{
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlHttp.open("POST","sqli_8-2.php",true);
xmlHttp.setRequestHeader("Content-type","text/xml; charset=UTF-8");
xmlHttp.send("login"])){echo $_SESSION["login"];}?> Any bugs? "); //利用Ajax技术将xml文件发送到sqli_8-2.php
}
script>
//sqli_8-2.php
$body = file_get_contents("php://input"); //访问请求的原始数据的只读流
// If the security level is not MEDIUM or HIGH
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2")
{
ini_set("display_errors",1);
$xml = simplexml_load_string($body); // 函数转换形式良好的 XML 字符串为 SimpleXMLElement 对象
// Debugging
// print_r($xml);
$login = $xml->login;
$secret = $xml->secret;
if($login && $login != "" && $secret)
{
$sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'";
// Debugging
// echo $sql;
$recordset = $link->query($sql);
if(!$recordset)
{
die("Connect Error: " . $link->error);
}
$message = $login . "'s secret has been reset!";
}
从上面的代码可以看出,sqli_8-1.php利用Ajax技术将xml数据发送到sqli_8-2.php后,进行xml解析,然后没有进行任何过滤直接将解析的数据拼接到sql语句中执行了,所以造成sql注入漏洞。修复方法:在拼接sql语句之前,将参数进行过滤或转义,或者使用PDO技术进行预处理和参数化。
相关函数:
file_get_contents(“php://input”) //访问请求的原始数据的只读流 详细
simplexml_load_string() //函数转换形式良好的 XML 字符串为 SimpleXMLElement 对象
输入单引号直接报错:
经过多次尝试,无论输入什么都只会回显The movie exists in our database!和The movie does not exist in our database!两种情况。如下图:
于是判断是布尔盲注。
//猜解数据库名
先猜解数据库名长度
测出数据库名的第一个字母ascii为96(b)
以此类推一共猜解5次测出数据库名为bwapp;
//猜解表名
先猜解数据库中表的数量
二分法测出表的数量为5
猜解第一个表的长度:
二分法猜解出表的长度为4
猜解第一个表的第一个字母
二分法测出为98(b)
然后通过更改substr的参数更换字母,逐个猜解出表名的每个字母;然后再更改limit的参数更换表名,重复上述过程猜解出每张表的表名。最后猜解出表名依次为blog,heroes,movies,users,visitors。
//猜解字段名,字段值与上述过程相仿。
总结猜解三步走:
1、猜解数量
例:and (select count(table_name) from information_schema.tables where table_schema=database())>6
2、猜解长度
例:and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)>5
3、猜解值
例:and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>110
以上三步不断迭代方可制胜
如图所示,根据回显延迟的大小就可判断。只需将布尔注入的语句与if函数和sleep函数相结合便可完成注入。
扔进sqlmap中一会就完事。
关于addslashes()函数绕过的三种方式
报错注入时一定要与concat()函数相结合,不然无法正常回显
报错注入的两种思路
盲注时三步走:猜解数量、猜解长度、猜解值