之前一直在想一个问题,怎么在不使用eval
函数的前提下执行php代码,因为不管是一句话还是其他的骚操作好像都要通过eval
函数来执行PHP代码(如果有其他方式,请指出吧,我表示就知道一个),现在的一句话代码也是如此,那么如何绕过这个东西来执行代码?用PHP也有几年了,今天突然灵光一闪,我们来看下面的代码:
1
| include 'http://www.test.com/xx.php'
|
如上代码可以通过包含远程HTTP文件来执行PHP文件,也就是执行远程代码,我们在玩一句话的时候基本上都不使用这些东西来操纵,如果我们设置了环境变了
不过也提供了一些思路,现在们来了解下这串代码所包含的含义,先看下我如今的PHP配置项
1 2 3 4 5 6 7 8 9 10 11
|
allow_url_fopen =Off
allow_url_include = Off
|
在默认的情况下,好像可以通过include
来包含远程文件,现在我们把这两个都关了,远程文件打开和远程文件包含都禁止使用,也就是说,上面第一串代码也就算是失效了
PHP的包装器
在上面的配置文件中,我们看到了一个概念 Fopen Wrapper
, 在官方的参考文档中的位置:http://php.net/manual/en/class.streamwrapper.php 在这里中我们可以看到一个标准的包装器的介绍,那么,我们实现一个包装器来执行代码怎么样??
开发任务
实现一个包装器,使其出现如下代码的时候输出 'hello, dxkite'
1
| include 'hello://dxkite'
|
HelloStream 实现
对于一个流来说,按照官方的文档,必须实现如下的类方法:
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
| streamWrapper { public resource $context ; __construct ( void ) __destruct ( void ) public bool dir_closedir ( void ) public bool dir_opendir ( string $path , int $options ) public string dir_readdir ( void ) public bool dir_rewinddir ( void ) public bool mkdir ( string $path , int $mode , int $options ) public bool rename ( string $path_from , string $path_to ) public bool rmdir ( string $path , int $options ) public resource stream_cast ( int $cast_as ) public void stream_close ( void ) public bool stream_eof ( void ) public bool stream_flush ( void ) public bool stream_lock ( int $operation ) public bool stream_metadata ( string $path , int $option , mixed $value ) public bool stream_open ( string $path , string $mode , int $options , string &$opened_path ) public string stream_read ( int $count ) public bool stream_seek ( int $offset , int $whence = SEEK_SET ) public bool stream_set_option ( int $option , int $arg1 , int $arg2 ) public array stream_stat ( void ) public int stream_tell ( void ) public bool stream_truncate ( int $new_size ) public int stream_write ( string $data ) public bool unlink ( string $path ) public array url_stat ( string $path , int $flags ) }
|
在刚开始测试的时候,发现只需要实现用到的方法即可,不需要实现所有的功能,实现如下接口的功能即可:
1 2 3 4 5 6 7 8 9
| interface IncludeStreamWrapper { function stream_open($path, $mode, $options, &$opened_path); function stream_read($count); function stream_tell(); function stream_eof(); function stream_seek($offset, $whence); function stream_stat(); }
|
按照这个写法,我们实现一个我们自己的 HelloStream
类:
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
| class HelloStream { protected $position; protected $code;
public function stream_open($path, $mode, $options, &$opened_path) { $url = parse_url($path); $name = $url["host"]; $this->code = "<?php echo 'hello, {$name}';"; $this->position = 0; return true; }
public function stream_read($count) { $ret = substr($this->code, $this->position, $count); $this->position += strlen($ret); return $ret; }
public function stream_tell() { return $this->position; }
public function stream_eof() { return $this->position >= strlen($this->code); }
public function stream_seek($offset, $whence) { switch ($whence) { case SEEK_SET: if ($offset < strlen($this->code) && $offset >= 0) { $this->position = $offset; return true; } else { return false; } break;
case SEEK_CUR: if ($offset >= 0) { $this->position += $offset; return true; } else { return false; } break; case SEEK_END: if (strlen($this->code) + $offset >= 0) { $this->position = strlen($this->code) + $offset; return true; } else { return false; } break;
default: return false; } } public function stream_stat() { return []; } }
|
注册 HelloStream
通过函数 stream_wrapper_register
注册包装器
1 2 3 4
| require_once __DIR__ .'/HelloStream.php';
stream_wrapper_register('hello', HelloStream::class); include 'hello://dxkite';
|
运行这串代码我们可以看到如下的结果
1 2
| PS D:\WorkSpace\PHP\Stream> php test.php hello, dxkite
|