CVE-2022-47945 (PHP, published 2022-12-23T21:15Z)
Description: value: ThinkPHP Framework before 6.0.14 allows local file inclusion via the lang parameter when the language pack feature is enabled (lang_switch_on=true). An unauthenticated and remote attacker can exploit this to execute arbitrary operating system commands, as demonstrated by including pearcmd.php.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/top-think/framework
Files changed:
src/think/Lang.php
src/think/middleware/LoadLangPack.php
Commit message: 删除废弃方法 优化多语言检测


Vulnerable code:
    /**
     * 自动侦测设置获取语言选择
     * @deprecated
     * @access public
     * @param Request $request
     * @return string
     */
    public function detect(Request $request): string
    {
        // 自动侦测设置获取语言选择
        $langSet = '';

        if ($request->get($this->config['detect_var'])) {
            // url中设置了语言变量
            $langSet = strtolower($request->get($this->config['detect_var']));
        } elseif ($request->header($this->config['header_var'])) {
            // Header中设置了语言变量
            $langSet = strtolower($request->header($this->config['header_var']));
        } elseif ($request->cookie($this->config['cookie_var'])) {
            // Cookie中设置了语言变量
            $langSet = strtolower($request->cookie($this->config['cookie_var']));
        } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
            // 自动侦测浏览器语言
            $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
            if ($match) {
                $langSet = strtolower($matches[1]);
                if (isset($this->config['accept_language'][$langSet])) {
                    $langSet = $this->config['accept_language'][$langSet];
                }
            }
        }

        if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
            // 合法的语言
            $this->range = $langSet;
        }

        return $this->range;
    }

    /**
     * 保存当前语言到Cookie
     * @deprecated
     * @access public
     * @param Cookie $cookie Cookie对象
     * @return void
     */
    public function saveToCookie(Cookie $cookie)
    {
        if ($this->config['use_cookie']) {
            $cookie->set($this->config['cookie_var'], $this->range);
        }
    }

            $langSet = strtolower($request->get($this->config['detect_var']));
            $langSet = strtolower($request->header($this->config['header_var']));
            $langSet = strtolower($request->cookie($this->config['cookie_var']));
            $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
            if ($match) {
                $langSet = strtolower($matches[1]);
                if (isset($this->config['accept_language'][$langSet])) {
                    $langSet = $this->config['accept_language'][$langSet];
                }
            $range = $langSet;
            $this->lang->setLangSet($range);
            $range = $this->lang->getLangSet();
        return $range;

Fixed code:
            $langSet = $request->get($this->config['detect_var']);
            $langSet = $request->header($this->config['header_var']);
            $langSet = $request->cookie($this->config['cookie_var']);
            $langSet = $request->server('HTTP_ACCEPT_LANGUAGE');
        }

        if (preg_match('/^([a-z\d\-]+)/i', $langSet, $matches)) {
            $langSet = strtolower($matches[1]);
            if (isset($this->config['accept_language'][$langSet])) {
                $langSet = $this->config['accept_language'][$langSet];
            $this->lang->setLangSet($langSet);
            $langSet = $this->lang->getLangSet();
        return $langSet;