代码注入
的典型代码就是文件包含(File Inclusion),在PHP中,用于文件包含的函数有4个:
- 1.require
- require_once
- include
- include_once
include
和require
区别主要是:
include
在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require
函数出现错误的时候,会直接报错并退出程序的执行。*_once()
表示如果一个文件包含过了则不再包含,以防止变量重定义等问题。
当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析
利用场景:
- 具有相关的文件包含函数
- 文件包含函数中存在动态变量,比如
include $file;
- 攻击者能够控制该变量,比如
$file = $_GET['file'];
分类
LFI(Local File Inclusion)
本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI。
RFI(Remote File Inclusion)
远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini
中进行配置
allow_url_fopen = On
allow_url_include = On
两个配置选项均需要为On,才能远程包含文件成功。
在php.ini中,allow_url_fopen
默认一直是On
,而allow_url_include
从php5.2
之后就默认为Off
。
文件包含利用
包含文件上传
|
|
我们可以先利用上传漏洞,上传我们要包含的文件在利用包含漏洞执行代码,然后利用包含漏洞指向其文件路径。
包含本地文件
|
|
包含木马图片
|
|
a.jpg
为我们上传的图片木马
包含session
session
文件路径已知,且其中内容部分可控。
php的session
文件的保存路径可以在phpinfo的session.save_path
看到。
session的文件名格式为sess_[phpsessid]
。而phpsessid
在发送的请求的cookie
字段中可以看到。
有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。
包含日志
需要知道服务器日志的存储路径,且日志文件可读。
很多时候,web服务器会将请求写入到日志文件中,比如说apache
。在用户发起请求时,会将请求写入access.log
,当发生错误时将错误写入error.log
。默认情况下,日志保存路径在 /var/log/apache2/
思路是去请求一个构造的异常URL,由于会记录日志,在包含日志的时候从而发挥作用。
比如我们去请求target.com/<?php phpinfo();?>
这个地址,将会在access.log
中添加一条记录:
|
|
SSH log
原理同包含日志,利用的是用SSH登陆时会在/var/log/auth.log
中记录是否成功:
|
|
包含临时文件
web上传文件,会创建临时文件。在linux
下使用/tmp
目录(或自定义),而在windows
下使用c:\winsdows\temp
目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux
下使用的随机函数有缺陷,而window
下只有65535中不同的文件名,所以这个方法是可行的。
包含其他服务文件
利用其它服务,ftp服务,数据库等,在运行过程中都会产生一些文件。
利用php伪协议
利用php伪协议,可以去读取一些内容:
- php://input
- php://filter
- phar://
- zip://
- data:URI schema
- ftp
php://input
需要allow_url_include
开启,比较难见。
|
|
直接将POST中的内容作为脚本运行,危害极大。
php://filter
php://filter
是PHP语言中特有的协议流,作用是作为一个“中间流”来处理其他流,在XXE攻击与防御中提到的一个伪协议
常见用法是利用其将内容编解码以绕过某些保护
|
|
phar://
phar
是一个文件归档的包,类似于Java
中的Jar
文件,方便了PHP
模块的迁移。php
中默认安装了这个模块。
在创建phar文件的时候要注意phar.readonly
这个参数要为off,否则phar文件不可写。
|
|
运行以上代码后会在当前目录下生成一个名为shell.phar
的文件,这个文件可以被include
,file_get_contents
等函数利用
|
即可被利用
有了phar文件,我们就能有一些思路了,比如上传的文件名遭到了限制,我们无法上传php的文件,但是却只能包含php文件的时候(包含文件后缀名被限制include '$file'.'.php'
),我们就可以通过上传phar文件,再利用php伪协议来包含。
zip://
构造zip包的方法同phar。
但使用zip协议,需要指定绝对路径,同时将#
编码为%23
,之后填上压缩包内的文件。
|
|
data:URI schema
我们可以将攻击代码转换为data:URL形式进行攻击,但是直接在URL连接中出现一些敏感字符,会导致被waf检测,所以我们需要给攻击代码进行base64编码。
|
|
/proc/self/environ
如果可以包含/proc/self/environ
,且返回类似下面的内容:
这种情况下我们会发现其中记录了UA
,所以可以构造包含:
|
|
来下载webshell到web目录下
利用技巧
上面的利用方式都是假设后台不对输入进行过滤或检测的情况,下面介绍一些绕过检测的技巧:
目录遍历
|
|
对于这种指定前缀的情况可以使用../../../xx/xxx/xxx
的方式进行目录遍历
编码绕过
所有注入都存在使用编码绕过的情况:
可以利用url
编码
|
|
二次编码:
|
|
服务器/容器的编码方式:
|
|
下面我们来分析下为什么这样可以。
我们看到每一个字符被编码成了2个字节,以/
为例,编码成了%C0 %AF
,将其写成2进制1100 0000
1010 1111
是不是很熟悉?没错,是UTF-8
编码。关于编码相关内容,可以看这里
2字节的UTF-8
编码格式应该是110x xxxx
10xx xxxx
我们将%C0 %AF
的xx
位抽取出来看其Unicode
值:0 0000 10 1111
= 0x2F
= 47 = /
但是又有个问题,47<128的, 按UTF-8
标准, ASCII范围(0-127)的Unicode
值应该是用1字节去表示,但我们发现,2个字节的UTF-8
通过构造也可以表示同样的内容,很正常的推测,是否统一个Unicode
码值可以有多个UTF-8
编码表示?(例如0-127的字符可以用1字节、2字节、3字节表示,只需要补0就可)
这就是问题所在: UTF-8 标准规定是不允许重复的(即两字节的UTF-8表示Unicode码值是U+0080-U+07FF) 如果不在这范围会报错, 但实际的库函数实现却不一定遵照标准,有时为了效率,不去检验所属范围,造成这个问题
指定后缀
|
|
# 利用URL结构
考虑指定后缀的情况,这时候就要去思考URL的格式了:
|
|
那么可以让我们利用的就是?
后面的查询和#
后面的fragment
除此之外,利用协议,比如phar、zip
还有一个技巧,不过需要在低版本的php下才能完成。
# 长度截断
php版本 < php 5.2.8
目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./
即可
|
|
#0字节截断
php版本 < php 5.3.4
|
|
%00 截断 PHP 内核是由 C 语言实现的,因此使用了 C 语言中的一些字符串处理函数。在连接字符串时,0 字节 (\x00) 将作为字符串的结束符。 截断后面的拼接
防御
- 做好文件的权限管理,禁止访问web目录之外的内容
- 关闭远程文件包含
- 避免由外界指定文件名与路径名
- 过滤危险字符 . /等
常见工具
- LFISuite
- fimap [kali 自带]