[SUCTF 2019]EasySQL WP 和我的扩展探索
原环境存在原码泄露,得到原码
可看出此题为sql注入
查看源码
<?php
session_start();
include_once "config.php";
$post = array();
$get = array();
global $MysqlLink;
//GetPara();
$MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
if(!$MysqlLink){
die("Mysql Connect Error!");
}
$selectDB = mysqli_select_db($MysqlLink,$dataName);
if(!$selectDB){
die("Choose Database Error!");
}
foreach ($_POST as $k=>$v){
if(!empty($v)&&is_string($v)){
$post[$k] = trim(addslashes($v));
}
}
foreach ($_GET as $k=>$v){
}
}
//die();
?>
<?php
if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile
|readfile|where|from|union|update|delete|if|sleep|extractvalue|
updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));
}
?>
其中过滤了
"prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\""
这么多关键字
其关键的sql语句是 $sql = "select ".$post['query']."||flag from Flag";
不太理解这条语句。我下载mysql做了实验。
select 数字 from table
得出一个行数和表行数一样的临时列,每行的列值是该数字
这里有几个相似的情况,做一个对比
select count(数字) from table
得到该表的行数
ps.SELECT COUNT(column_name) FROM table_name
函数返回指定列的值的数目(NULL 不计入)
select count(数字) from table
得出一个数,该数是表的行数乘sum括号里的值
ps. SELECT SUM(column_name) FROM table_name
函数返回数值列的总数(总额)
下面开始研究题目中的mysql语句 select *** || flag from table
可以看出这里的||作为逻辑或来使用,sql语句中原有的这个flag逻辑值为0,0或0=0;1或0=1;
这题的非预期解简单而实用 select *,任何数字||flag from table
还有一个非预期解 select flag,任何数字||flag from table
其相当于 select *,0 from table
可以得到所有列的值加上一列临时列0
测试中发现 select *** || *** from table
*处必须为数字或者存在的列名。
在测试中产生了一个新的问题:为什么 * ||flag 中flag的逻辑值默认为0.我试了一下表的另一个列名id
可看出id的逻辑默认值是1。
插入一新列,值为NULL,数据类型为VARCHAR。测试在||中的逻辑值
发现这一新列在||下的逻辑值也为0,不过不显示0,显示NULL。
在新列insert了一个值
继续测试 select *** || *** from table
,发现结果很有意思。
NULL似乎也是以逻辑或来显示的,只要有NULL便不会有0。只要有1全是1。
新建了非空,数据类型为int的一列
继续测试其在||下的逻辑值
其结果和之前两列一样,似乎和数据类型无关。此时key2下的值为0,0,0。
修改key2的值。并测试
发现值不为0时,||下逻辑值为1。
修改flag的值为非零数字,继续测试。
此时发现值为数字的那一列在||下逻辑值判断为1。粗略得出结论,mysql中的||是根据是否为非零数字来判定逻辑值的。且如果有NULL。判定为NULL。
如果在字符串中包含数字呢?再测试一次
基本可以得出结论,只要含有非0数字,在||运算中逻辑值就判定为1。
还有一种解法是改变||符号的作用,查询到:在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode 模式:pipes_as_concat 来实现oracle 的一些功能
payload: 1;set sql_mode=pipes_as_concat;select 1
构造语句 select 1;set sql_mode=pipes_as_concat;select 1 from table
此时成功
题外
关于Mysql的模式设置,可以替换为其他的模式,传统的,标准的,严格的,等等
我们常设置的 sql_mode 是 ANSI、STRICT_TRANS_TABLES、TRADITIONAL,ansi和traditional是上面的几种组合。
ANSI:更改语法和行为,使其更符合标准SQL
如REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE
TRADITIONAL:更像传统SQL数据库系统,该模式的简单描述是当在列中插入不正确的值时“给出错误而不是警告”。
如STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION
ORACLE:如 PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER