About
Blog
Donate
Gaming
Network
Projects

Translation

This post is about vulnerabilities I found in Japanese OSS softwares.

I've written the same content in other languages so the translator is not required for this content.

Please check my company's blog for more detailed information.

  • Japanese: https://flattsecurity.hatenablog.com/entry/2020/10/26/150047
  • English: https://flattsecurity.hatenablog.com/entry/2020/11/02/124807


Flatt Security Inc.

Flatt Security Inc. provides security assessment services. We are willing to have offers from overseas.

If you have any question, please contact us by https://flatt.tech/en/. Thank you in advance for reading this article.

Note: ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๋‚ด์šฉ์€ ์ „๋ถ€ ์ง€์› ์œผ๋‹ˆ ์–‘ํ•ด๋ฐ”๋ž๋‹ˆ๋‹ค.

์„œ๋ก 

์•ˆ๋…•ํ•˜์„ธ์š”. ์ฃผ์‹ํšŒ์‚ฌ Flatt Security์˜ stypr (@stereotype32) ์ž…๋‹ˆ๋‹ค.

์•ž์„œ ๊ณต๊ฐœํ–ˆ๋˜ ๊ธฐ์‚ฌ (https://flattsecurity.hatenablog.com/entry/2020/08/04/120000)์—์„œ ์–ธ๊ธ‰ํ•œ ๋Œ€๋กœ, ์ €๋Š” ํ˜„์žฌ Flatt Security์—์„œ 0day hunting๊ณผ ๊ธฐ์ˆ ์—ฐ๊ตฌ๋ฅผ ์ค‘์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณธ ๊ธฐ์‚ฌ์—์„œ๋Š” ์ œ๊ฐ€ 5์›”์— ์ž…์‚ฌ ํ›„ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌํ•œ 0day๋“ค์— ๋Œ€ํ•œ ๊ธฐ์ˆ ์ ์ธ ์„ค๋ช…์„ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์—ฌํƒœ๊นŒ์ง€ ์ฐพ์€ ์ทจ์•ฝ์ ์ด ๊ฝค ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ๋ณธ ๊ธ€์ด ๋งค์šฐ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ˜„์žฌ๊นŒ์ง€ ํ•ด๊ฒฐ๋œ ์ทจ์•ฝ์ ๋“ค ์ค‘ ๊ฐœ์ธ์ ์œผ๋กœ ํฅ๋ฏธ๋กญ๊ฒŒ ์ƒ๊ฐํ–ˆ๋˜ ์ทจ์•ฝ์ ๋“ค์„ ์œ„์ฃผ๋กœ ๊ธฐ์ˆ ์ ์ธ ๋ถ€๋ถ„์„ ์ค‘์ ์œผ๋กœ ์„ค๋ช…ํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค.

0day hunting ์™ธ์—๋„ ๋‹ค๋ฅธ ์žฌ๋ฏธ๋‚œ ์—…๋ฌด๋“ค๋„ ๋ณ‘ํ–‰ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, 0day hunting์— ์‹ค์ œ๋กœ ์†Œ๋น„ํ•œ ์‹œ๊ฐ„์€ ํ•œ ์ œํ’ˆ๋‹น ์ตœ์†Œ 1์ผ ~ ์ตœ๋Œ€ 1 ์ฃผ ๋‚ด์˜ ๊ธฐ๊ฐ„์œผ๋กœ ์ƒ์ •ํ•˜๊ณ  ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ž…์‚ฌ ํ›„ ์ง€๊ธˆ๊นŒ์ง€ ์ด๋ฏธ ๋งŽ์€ ์ทจ์•ฝ์ ์„ ์ฐพ์•˜๊ณ , ์•„์ง ํŒจ์น˜๋˜์ง€ ์•Š์€ ์ทจ์•ฝ์ ๋„ ์žˆ๊ณ , ๋ชจ๋“  ์ทจ์•ฝ์ ์„ ํ•˜๋‚˜์”ฉ ์†Œ๊ฐœํ•˜๋Š” ๊ฒƒ์€ ์กฐ๊ธˆ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์ดํ›„์— ์ถ”๊ฐ€์ ์ธ ๊ธ€์„ ์ž‘์„ฑํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

์šฐ์„  ์ตœ์ดˆ ์—ฐ๊ตฌ ํƒ€๊ฒŸ์€ ์ผ๋ณธ ๋‚ด์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค ์ œํ’ˆ๋“ค์„ ์œ„์ฃผ๋กœ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ณธ์—์„œ ๊ฐœ๋ฐœ๋œ ์˜คํ”ˆ์†Œ์Šค ์ œํ’ˆ์ด ๊ต‰์žฅํžˆ ๋งŽ๋‹ค๋Š” ์ ๊ณผ ์ œ๊ฐ€ ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์Šค์Šค๋กœ ํŒ๋‹จํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ณธ ๊ธ€์—์„œ๋Š” ํ˜„์žฌ๊นŒ์ง€ ๋ณด๊ณ ํ•œ ์ทจ์•ฝ์ ์˜ ์ผ๋ถ€๋ฅผ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณด๊ณ ์‹œ ์‹ค์ œ๋กœ ์•…์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์ฆ๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์ฆ๋ช… ์ฝ”๋“œ(์ดํ•˜ PoC)๋ฅผ ์ž‘์„ฑํ•˜์˜€์œผ๋‚˜, ๊ฐ ์ทจ์•ฝ์ ์˜ ์œ„ํ—˜์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ PoC ์ฝ”๋“œ๋Š” ๋”ฐ๋กœ ๊ฐœ์ œํ•˜์ง€ ์•Š๊ณ  ์ทจ์•ฝ์ ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” PoC ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์—ˆ์„ ๋•Œ๋ฅผ ๋…นํ™”ํ•œ ์˜์ƒ์œผ๋กœ ๋Œ€์ฒดํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ๋„ˆ๊ทธ๋Ÿฝ๊ฒŒ ์ดํ•ดํ•ด ์ฃผ์…จ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์ ๋“ค์„ ์ œ๋ณดํ•˜๋Š”๋ฐ ํ˜‘๋ ฅํ•ด์ฃผ๊ณ  ์•Œ๋งž๊ฒŒ ๋Œ€์‘ํ•ด์ฃผ์‹  ๊ฐ ์ œํ’ˆ์˜ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค๊ณผ JPCERT/CC ๋‹ด๋‹น์ž ๋ถ„๋“ค, ๊ทธ๋ฆฌ๊ณ  ์ œ๋กœ๋ฐ์ด๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๊ณผ์ •์—์„œ ๋งŽ์€ ๋„์›€์„ ์ฃผ์‹  ์‚ฌ๋‚ด ์—ฌ๋Ÿฌ๋ถ„๊ป˜ ๊ฐ์‚ฌ์˜ ๋ง์”€์„ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

BaserCMS

BaserCMS๋ž€

https://basercms.net/

BaserCMS๋Š” ์ž์œ ๋กญ๊ฒŒ ์›น์‚ฌ์ดํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ณธ ์˜คํ”ˆ์†Œ์Šค CMS ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. CakePHP์—์„œ ๊ตฌํ˜„๋œ CMS์ด๋ฉฐ, ์ˆ˜๋…„๊ฐ„ ๊พธ์ค€ํžˆ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ปจํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ํ•˜๊ณ  ์žˆ์–ด ๋งŽ์€ ์‚ฌ์ดํŠธ์—์„œ BaserCMS๋ฅผ ์‚ฌ์šฉ ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค.

(CVE-2020-15159) Cross-site Scripting(XSS) and Remote Code Execution (RCE)

์ „์ œ ์กฐ๊ฑด

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ „์ œ์กฐ๊ฑด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • XSS ๋ฐ RCE๋ฅผ ์„ฑ๊ณต์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ทจ์•ฝ์  ๋‚ด์šฉ

์šฐ์„  Cross-Site Scripting(XSS)์— ๋Œ€ํ•ด ๋จผ์ € ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ XSS ์ทจ์•ฝ์ ์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๊ฒ ์ง€๋งŒ, ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ์ œ๋Œ€๋กœ sanitize๋ฅผ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋‹ˆ sanitize๋ฅผ ์ œ๋Œ€๋กœ ์•ˆํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ํ˜•ํƒœ๋กœ ์ ‘๊ทผํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, app/webroot/theme/admin-third/ThemeFiles/admin/index.php:48 ๋ฅผ ๋ณด์‹œ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด currentPath์— ๋Œ€ํ•ด echo๋ฅผ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

<div class="em-box bca-current-box"><?php echo __d('baser', '็พๅœจใฎไฝ็ฝฎ') ?>๏ผš<?php echo $currentPath ?>...

currentPath๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์„ ์ถ”์ ํ•ด๋ณด๋ฉด lib/Baser/Controller/ThemeFilesController.php:171 ์—์„œ ๊ฐ€์ง€๊ณ ์˜ค๋Š”๋ฐ, ์ž…๋ ฅ๋ฐ›์€ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ํŠน๋ณ„ํžˆ sanitize๋ฅผ ํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— XSS๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

public function admin_index() {
...
        $args = $this->_parseArgs(func_get_args());
        extract($args);
...
        $currentPath = str_replace(ROOT, '', $fullpath);
        $this->subMenuElements = ['theme_files'];
        $this->set('themeFiles', $themeFiles);
        $this->set('currentPath', $currentPath);
        $this->set('fullpath', $fullpath);
...
        $this->help = 'theme_files_index';
...
}
...

๊ณ„์†ํ•ด์„œ Remote Code Execution ์ทจ์•ฝ์ ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ PHP ์ œํ’ˆ์˜ ๊ฒฝ์šฐ, PHPํŒŒ์ผ์„ ์ž„์˜์ ์œผ๋กœ ์—…๋กœ๋“œ๋ฅผ ํ•˜๊ณ  ์—…๋กœ๋“œ๋œ PHPํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์žˆ์œผ๋ฉด, ๊ณต๊ฒฉ์ž๊ฐ€ ์›ํ•˜๋Š” ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— PHP ํŒŒ์ผ์„ ์ž„์˜์ ์œผ๋กœ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

lib/Baser/Plugin/Uploader/Controller/UploaderFilesController.php:291 ๋ฅผ ๋ณด์‹œ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

    public function admin_ajax_upload() {
...
        $this->layout = 'ajax';
        Configure::write('debug',0);
...
        $user = $this->BcAuth->user();
        if(!empty($user['id'])) {
            $this->request->data['UploaderFile']['user_id'] = $user['id'];
        }
        $this->request->data['UploaderFile']['file']['name'] = str_replace(['/', '&', '?', '=', '#', ':'], '', h($this->request->data['UploaderFile']['file']['name']));
        $this->request->data['UploaderFile']['name'] = $this->request->data['UploaderFile']['file'];
        $this->request->data['UploaderFile']['alt'] = $this->request->data['UploaderFile']['name']['name'];
        $this->UploaderFile->create($this->request->data);
...
        if($this->UploaderFile->save()) {
            echo true;
        }
...
...
    }

์—ฌ๊ธฐ์„œ ํŒŒ์ผ ๋ช…์— ๋Œ€ํ•œ ๊ฒ€์ฆ์„ ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ํ™•์žฅ์ž์— ๋Œ€ํ•œ ๊ฒ€์ฆ์€ ํ•˜๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋”์šฑ ๋” ์„ธ๋ถ€์ ์ธ ํ™•์ธ์„ ํ•˜๊ธฐ ์œ„ํ•ด $this->UploaderFile ์„ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class UploaderFile extends AppModel {
...
    public function __construct($id = false, $table = null, $ds = null) {
...
        if(!BcUtil::isAdminUser()) {
            $this->validate['name'] = [
                'fileExt' => [
                    'rule' => ['fileExt', Configure::read('Uploader.allowedExt')],
                    'message' => __d('baser', '่จฑๅฏใ•ใ‚Œใฆใ„ใชใ„ใƒ•ใ‚กใ‚คใƒซๅฝขๅผใงใ™ใ€‚')
                ]
            ];
        }
        parent::__construct($id, $table, $ds);
...
    }
}

ํ™•์‹คํžˆ ํ™•์žฅ์ž ๊ฒ€์ฆ์„ ํ•˜๊ณ  ์žˆ์ง€๋งŒ, Admin ๊ณ„์ •์ด ์•„๋‹๋•Œ๋งŒ ์ž‘๋™ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๊ด€๋ฆฌ์ž์—๊ฒŒ XSS๋ฅผ ์„ฑ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ ํŒŒ์ผ ๊ฒ€์ฆ์€ ๋ฌด์‹œํ•œ์ฑ„ ์—…๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

PoC ๋ฐ ์ฐธ๊ณ 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ FormData ํด๋ž˜์Šค์—์„œ Blob Object ๋ฅผ ์ด์šฉํ•˜๋ฉด ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด XSS๋ฅผ ํ†ตํ•ด ์ž„์˜์ ์ธ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ์ž„์˜์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์—…๋กœ๋“œ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

...
    filename = Math.floor(Math.random() * Math.floor(13371337)) + 'exploit.php';
...
    var blob = new Blob(["[email protected]<pre><?php $_GET[cmd]($_GET[arg]); ?></pre>"]);
    var fd = new FormData();
    fd.append('data[_Token][key]', token);
    fd.append('data[UploaderFile][file]', blob, filename);
    // Upload XHR Request
...

ํ•ด๋‹น ๊ณต๊ฒฉ์ด ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ PoC๋Š” ๋‹ค์Œ GIF๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

https://i.imgur.com/J3ZnpZg.gif

๋Œ€์‘

๊ฐœ๋ฐœ์ž๋ถ„๊ป˜์„œ ๋น ๋ฅด๊ฒŒ ๋Œ€์‘ํ•˜์…จ๊ณ , CVE ํ”„๋กœ์„ธ์Šค๋„ ๋งˆ์นœ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.

https://basercms.net/security/20200827

EC-Cube

EC-Cube๋ž€

https://www.ec-cube.net/

EC-Cube๋Š” ์ผ๋ณธ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” EC ์‚ฌ์ดํŠธ ์˜คํ”ˆ์†Œ์Šค์ž…๋‹ˆ๋‹ค.

Symfony๋กœ ๊ตฌํ˜„๋œ ์ œํ’ˆ์ด๋ฉฐ , ์ด ์ œํ’ˆ ๋˜ํ•œ ๊พธ์ค€ํžˆ ๊ฐœ๋ฐœ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€๊ฒŸ์„ ์ •ํ•˜๋˜ ๋„์ค‘ udon์”จ๊ฐ€ ๋‹ค์Œ ํƒ€๊ฒŸ์œผ๋กœ ์ถ”์ฒœํ•ด์ฃผ์…”์„œ ์งง์€ ๊ธฐ๊ฐ„์ด์ง€๋งŒ ๊ฒ€ํ† ํ•ด๋ณด๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Unauthenticated/Authenticated Remote Code Execution (RCE)

์ „์ œ ์กฐ๊ฑด

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ „์ œ์กฐ๊ฑด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • Unauthenticated ํ™˜๊ฒฝ์ด APP_DEBUG=1 ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. * ์ผ๋ฐ˜์ ์ธ Docker๋กœ ์„ค์น˜๋ฅผ ํ†ตํ•ด ์žฌํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

  • Authenticated์˜ ๊ฒฝ์šฐ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.* ์ด๋Š” XSS์™€ ๊ฐ™์€ ์ทจ์•ฝ์ ์„ ํ†ตํ•ด์„œ ์ถ”๊ฐ€์ ์œผ๋กœ ๊ณต๊ฒฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์  ๋‚ด์šฉ

Unauthenticated์˜ ๊ฒฝ์šฐ๋ฅผ ๋จผ์ € ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋จผ์„œ APP_DEBUG=1์ด๋ผ๋Š” ์ „์ œ ์กฐ๊ฑด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค๋งŒ, ๊ธฐ๋ณธ์ ์œผ๋กœ EC-Cube์˜ ๊ณต์‹ ์„ค์น˜ ๊ฐ€์ด๋“œ๋ฅผ ํ†ตํ•ด Docker ํ˜•ํƒœ๋กœ ์„ค์น˜ํ•˜๊ฒŒ ๋˜๋ฉด DEBUG ๋ชจ๋“œ๋Š” ์ž๋™์œผ๋กœ ํ™œ์„ฑํ™” ๋ฉ๋‹ˆ๋‹ค.

$ # https://doc4.ec-cube.net/quickstart_install#3dockerใ‚’ไฝฟ็”จใ—ใฆใ‚คใƒณใ‚นใƒˆใƒผใƒซใ™ใ‚‹
$ git clone https://github.com/ec-cube/ec-cube
...

$ cd ec-cube; git checkout c1dbe4267e1a3f353522835a22793aa278f42ef3 # ์ทจ์•ฝ์  ์ œ๋ณด ์‹œ์ 
Note: switching to 'c1dbe4267e1a3f353522835a22793aa278f42ef3'.
...

$ docker build -t eccube4-php-apache .
Sending build context to Docker daemon  23.16MB
Step 1/21 : FROM php:7.3-apache-stretch
...

$ docker run --name ec-cube -p "8080:80" -p "4430:443" eccube4-php-apache
...

^C
$ docker start ec-cube
ec-cube
$ docker exec -it ec-cube bash
[email protected]:/var/www/html# cat .env | grep DEBUG
APP_DEBUG=1

์šฐ์„  ์œ„์™€ ๊ฐ™์ด ์ž‘๋™ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์œผ๋ฉด, http://[๋ณธ์ธ_IP]:8080/ ๋กœ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

์œ„ ์Šคํฌ๋ฆฐ์ƒท์„ ์ž˜ ๋ณด์‹œ๋ฉด ์šฐ์ธก ํ•˜๋‹จ์— sf ๋ผ๋Š” ๋กœ๊ณ ๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค. ๋ˆŒ๋Ÿฌ์„œ sf๊ฐ€ ์ƒ๊ธด ๋งˆํฌ๋ฅผ ํ•œ๋ฒˆ ๋” ๋ˆ„๋ฅด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค.. ๊ฐ€๋” sf ์ด๋ฏธ์ง€๊ฐ€ ์•ˆ๋‚˜์˜ค๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์œผ๋‹ˆ ๋ฐ”๋กœ /_profiler/๋กœ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

์œ„๋Š” Symfony Profiler๋ผ๊ณ  ํ•˜๋Š” ๊ธฐ๋Šฅ์ธ๋ฐ, ๊ด€๋ จ ์ž๋ฃŒ๊ฐ€ ์ธํ„ฐ๋„ท์—์„œ ์ž˜ ์•ˆ๋‚˜์˜ค๋Š” ํŽธ์ž…๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋‹จ์ˆœํžˆ ์š”์•ฝํ•ด์„œ ์„ค๋ช…ํ•˜์ž๋ฉด Symfony๋กœ ๊ฐœ๋ฐœํ•˜๋Š” ์ œํ’ˆ์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„๋•Œ ๋””๋ฒ„๊น…์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด ๊ธฐ๋Šฅ์€ ๋””๋ฒ„๊ทธ ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™” ๋˜์—ˆ์„๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Symfony ์ž์ฒด๋Š” ๊ฝค ์•ˆ์ „ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ด์ง€๋งŒ, ์ด ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™” ๋ ์‹œ ๋ณด์•ˆ์ƒ์œผ๋กœ ์ •๋ง ์ทจ์•ฝํ•ด์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Profiler๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด Profile Search๋ผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ, ์ œ๊ฐ€ ์†ก์‹ ํ•œ ์š”์ฒญ์„ ์ „๋ถ€ ์—ด๋žŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Token์„ ๋ˆŒ๋Ÿฌ๋ณด๋ฉด ๋‹ค์Œ ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๊ฐ™์€ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๋ฅผ ์ž์„ธํžˆ ๋ณด๋ฉด POST ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๊ด€๋ฆฌ์ž๋ฐ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ์ •๋ณด๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์œ„ ์ทจ์•ฝ์ ์„ ํ†ตํ•ด ๊ด€๋ฆฌ์ž ๊ณ„์ •์œผ๋กœ ์ •์ƒ์ ์œผ๋กœ ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  Remote Code Execution์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๊ด€๋ฆฌ์ž๋กœ ๋กœ๊ทธ์ธ์„ ํ•ด์„œ ์‚ฌ์ดํŠธ๋ฅผ ๋‘˜๋Ÿฌ๋‹ค๋ณด๋ฉด, ํŒŒ์ผ ๊ด€๋ฆฌ๋ผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์•ž์„œ BaserCMS์—์„œ ์„ค๋ช…ํ•œ ๊ฒฝ์šฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. PHP ๊ธฐ๋ฐ˜ ์ œํ’ˆ์—์„œ๋Š” PHP ํ™•์žฅ์ž์™€ ๊ฐ™์€ PHPํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์„ฑ๊ณตํ•œ ๋‹ค์Œ, PHP ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์•Œ์•„๋‚ด์–ด ์ ‘์†ํ•˜๋ฉด ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

src/Eccube/Controller/Admin/Content/FileController.php:52๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ์–ด๋А์ •๋„ ๋ณด์•ˆ ์กฐ์น˜๋Š” ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ($jaiNowDir ๋“ฑ์„ ํ™•์ธํ•˜๋ฉด ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) ์‹ค์ œ๋กœ ํ™•์žฅ์ž์— ๋Œ€ํ•œ ํ™•์ธ์€ ์—†์ด ๊ทธ๋Œ€๋กœ ํŒŒ์ผ์„ ์ด๋™ํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.

    public function index(Request $request)
    {
        $form = $this->formFactory->createBuilder(FormType::class)
            ->add('file', FileType::class, [
                'multiple' => true,
                'attr' => [
                    'multiple' => 'multiple'
                ],
            ])
            ->add('create_file', TextType::class)
            ->getForm();

        // user_data_dir
        $userDataDir = $this->getUserDataDir();
        $topDir = $this->normalizePath($userDataDir);
...
        $htmlDir = $this->normalizePath($this->getUserDataDir().'/../');
...
        $nowDir = $this->checkDir($this->getUserDataDir($request->get('tree_select_file')), $this->getUserDataDir())
            ? $this->normalizePath($this->getUserDataDir($request->get('tree_select_file')))
            : $topDir;
...
        $nowDirList = json_encode(explode('/', trim(str_replace($htmlDir, '', $nowDir), '/')));
        $jailNowDir = $this->getJailDir($nowDir);
        $isTopDir = ($topDir === $jailNowDir);
        $parentDir = substr($nowDir, 0, strrpos($nowDir, '/'));
...
        if ('POST' === $request->getMethod()) {
            switch ($request->get('mode')) {
...
                case 'upload':
                    $this->upload($request);
                    break;
...
            }
        }
...
    }
...
    public function upload(Request $request)
    {
        $form = $this->formFactory->createBuilder(FormType::class)
            ->add('file', FileType::class, [
                'multiple' => true,
                'constraints' => [
                    new Assert\NotBlank([
                        'message' => 'admin.common.file_select_empty',
                    ]),
                ],
            ])
            ->add('create_file', TextType::class)
            ->getForm();
        $form->handleRequest($request);
...
        if (!$form->isValid()) {
            foreach ($form->getErrors(true) as $error) {
                $this->errors[] = ['message' => $error->getMessage()];
            }

            return;
        }
...
        $data = $form->getData();
        $topDir = $this->getUserDataDir();
        $nowDir = $this->getUserDataDir($request->get('now_dir'));
...
        foreach ($data['file'] as $file) {
            $filename = $this->convertStrToServer($file->getClientOriginalName());
            try {
                $file->move($nowDir, $filename);
                $successCount ++;
...
    private function getUserDataDir($nowDir = null)
    {
        return rtrim($this->getParameter('kernel.project_dir').'/html/user_data'.$nowDir, '/');
    }

์‹ ๊ธฐํ•˜๊ฒŒ๋„ ์–ด๋А์ •๋„ ๋ณด์•ˆ์ด๋ผ๋Š” ๊ฒƒ์„ ํ•˜๊ธฐ ์œ„ํ•ด .htaccess ํŒŒ์ผ๋„ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ composer, docker, ์ค‘์š” ํŒŒ์ผ๋“ค๋งŒ ํ™•์ธํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ทธ์น˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

<FilesMatch "^composer|^COPYING|^\.env|^\.maintenance|^Procfile|^app\.json|^gulpfile\.js|^package\.json|^package-lock\.json|web\.config|^Dockerfile|\.(ini|lock|dist|git|sh|bak|swp|env|twig|yml|yaml|dockerignore)$">
   order allow,deny
   deny from all
</FilesMatch>

์ด๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์—์„œ PHP ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์—ฌ http://[host]/user_data/[ํŒŒ์ผ๋ช…].php๋กœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PoC

์•„๋ž˜์™€ ๊ฐ™์€ ๋ฃจํ‹ด์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด PoC๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.

  1. Symfony Profiler๋ฅผ ํ†ตํ•ด admin ํด๋”์˜ directory, ๊ด€๋ฆฌ์ž์˜ ID, PW๋ฅผ ๊ฐ€๋กœ์ฑ•๋‹ˆ๋‹ค.

    • ๊ทธ๋ ‡์ง€ ์•„๋‹ˆํ•œ ๊ฒฝ์šฐ ๊ธฐ๋ณธ์ ์œผ๋กœ admin์˜ ์•„์ด๋””๋Š” admin/password ์ž…๋‹ˆ๋‹ค.
  2. ๊ฐ€๋กœ์ฑˆ ๊ณ„์ •์ •๋ณด๋ฅผ ํ†ตํ•ด admin์œผ๋กœ ๋กœ๊ทธ์ธ ํ•œ ๋‹ค์Œ, PHP ํŒŒ์ผ์„ ์—…๋กœ๋“œ ํ•ฉ๋‹ˆ๋‹ค.

  3. html/user_data/์— ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์„ ์ง์ ‘ URL๋กœ ๋ถˆ๋Ÿฌ์˜ค๋ฉด PHP ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

ํ•ด๋‹น ๊ณต๊ฒฉ์ด ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ PoC๋Š” ๋‹ค์Œ ์˜์ƒ์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

https://www.youtube.com/watch?v=w6otERfcAww

๋Œ€์‘ ๋ฐ ์ฃผ๋ณ€ ํ™˜๊ธฐ

์‹ค์ œ๋กœ ํ•ด๋‹น ์ทจ์•ฝ์ ์— ๋Œ€ํ•ด ๊ฐœ๋ฐœ์ž์™€ ์˜ค๋žœ๊ธฐ๊ฐ„ ์˜๊ฒฌ์„ ๋‚˜๋ˆ„์—ˆ๊ณ , ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ ์ฒ˜์— ๋ฌธ์˜ํ•˜๊ณ  ํšŒ์˜๋ฅผ ํ•œ ๊ฒฐ๊ณผ ๊ฐœ๋ฐœ์ž๋Š” ์ทจ์•ฝ์ ์œผ๋กœ ์ธ์ •ํ•˜์ง€ ์•Š๊ธฐ๋กœ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. PHP ํŒŒ์ผ์ด ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์—์„œ ์—…๋กœ๋“œ ๋˜๋Š” ์ ์€ EC-Cube์˜ ์ž์ฒด ์‚ฌ์–‘

  2. ์ดˆ๊ธฐ์„ค์ •์ด ๋””๋ฒ„๊ทธ ๋ชจ๋“œ๊ฐ€ ๋˜๋Š” ์ ์€ ์ œํ’ˆํŒ์—์„œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ทจ์•ฝ์ ์ด ์•„๋‹ˆ๋ผ๋Š” ํŒ๋‹จ

์ด๋ฏธ ๋Œ€์‘ ์กฐ์น˜๋ฅผ ์œ„ํ•ด ํ™˜๊ฒฝ์„ค์ •์— ๊ด€ํ•œ ๊ฐ€์ด๋“œ ๊ณต์œ , ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ, ๋ณด์•ˆ ๊ด€๋ จ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ณต์œ  ๋ฐ ๊พธ์ค€ํ•œ ์ฃผ์œ„ ํ™˜๊ธฐ๋ฅผ ํ•˜๊ณ  ์žˆ์Œhttps://www.ec-cube.net/products/detail.php?product_id=2040https://doc4.ec-cube.net/environmental_setting

์‹ค์ œ๋กœ ์ธํ„ฐ๋„ท ์ƒ์— ํ•ด๋‹น ์ทจ์•ฝ์ ์„ ํ†ตํ•ด ํ”ผํ•ด๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ดํŠธ๊ฐ€ ๋‹ค์ˆ˜ ์กด์žฌํ•˜๊ณ  ์žˆ๊ณ (์ด๋ฏธ ํ”ผํ•ด๋ฅผ ๋ฐ›์€ ์‚ฌ์ดํŠธ๋„ ์žˆ๊ฒ ์ง€๋งŒ), ์•ž์„œ ์†Œ๊ฐœํ•œ BaserCMS์˜ ์ทจ์•ฝ์ ์˜ ๊ฒฝ์šฐ ์œ„์™€ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ PHP ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ์•ˆ๋˜๋„๋ก ์ˆ˜์ •ํ•˜๋Š” ์‹์˜ ๋Œ€์‘์„ ์ทจํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ต‰์žฅํžˆ ๋‹นํ™ฉ์Šค๋Ÿฌ์› ๊ณ  ๋‚ฉ๋“ํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋“ค๋„ ๋ณด์•ˆ ์กฐ์น˜๋ฅผ ์œ„ํ•ด .htaccess ์ž‘์„ฑ๋“ฑ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŽ์€ ๋…ธ๋ ฅ์„ ํ–ˆ์Œ์—๋„ ๋ง์ž…๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ EC-Cube ๊ฐœ๋ฐœ์‚ฌ๊ฐ€ ์ฃผ๋ณ€ํ™˜๊ธฐ ๋ฐ ์•Œ๋งž์€ ๋Œ€์‘์„ ๊พธ์ค€ํžˆ ํ•ด๋‚˜๊ฐ€๊ธฐ๋กœ ํ•˜์˜€๊ณ , ์ œ๋ณดํ•œ ์ทจ์•ฝ์ ์€ PoC๋ฅผ ์ œ์™ธํ•œ ์–ด๋А ์ •๋„์˜ ์ •๋ณด๋ฅผ ๊ณต๊ฐœํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์กฐ๊ฑด ํ•˜์— Close ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ๋กœ์จ๋Š” ํ•ด๋‹น ์ทจ์•ฝ์ ์„ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด localhost๋ฅผ ์ œ์™ธํ•œ ์–ด๋А ํ™˜๊ฒฝ์—์„œ๋„ debug๊ฐ€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š๋„๋ก ์—ฌ๋Ÿฌ๋ฒˆ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ• ๋ฟ์ž…๋‹ˆ๋‹ค.

์ œ๋ณด์—์„œ ์˜ค๊ฐ„ ๋‚ด์šฉ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋Š” ์ œ ๊ฐœ์ธ ๋ธ”๋กœ๊ทธ์—์„œ ์ž‘์„ฑํ–ˆ์œผ๋‹ˆ ๊ถ๊ธˆํ•˜์‹  ๋ถ„๋“ค์€ ์ด์ชฝ์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

https://blog.harold.kim/2020/09/unauthenticated-authenticated-remote-code-execution-in-ec-cube

SoyCMS

SoyCMS๋ž€?

  • https://saitodev.co/soycms/
  • http://www.soycms.org/

SOY CMS๋Š” ๋ธ”๋กœ๊ทธ์™€ ์ธํ„ฐ๋„ท ์‡ผํ•‘๋ชฐ์„ ๊ตฌ์ถ• ํ•  ์ˆ˜์žˆ๋Š” ์ž์œ ๋„๊ฐ€ ๋†’์€ CMS (์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ ์‹œ์Šคํ…œ)์ž…๋‹ˆ๋‹ค. ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด๋กœ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธํ„ฐ๋„ท์„ ๊ฒ€์ƒ‰ํ•˜๋˜ ๋„์ค‘ ์šฐ์—ฐํžˆ ๋ฐœ๊ฒฌํ•œ ์ œํ’ˆ์ด์ง€๋งŒ, ๋ญ”๊ฐ€ ์ž‘๋ช… ์„ผ์Šค๋„ ๊ทธ๋ ‡๊ณ  ์•„์ง๊นŒ์ง€ ๊พธ์ค€ํžˆ ๊ฐœ๋ฐœ๋˜๊ณ  ์žˆ๋Š” ์ œํ’ˆ์ด๋ผ ํŒ๋‹จํ•˜์—ฌ ๋„์ „ํ•ด๋ณด๊ธฐ๋กœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

(CVE-2020-15183) Cross-site Scripting (XSS) leading to Remote Code Execution (RCE)

์ „์ œ์กฐ๊ฑด

์ „์ œ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ธ ์ƒํƒœ์—์„œ ์ž‘๋™

์ทจ์•ฝ์  ๋‚ด์šฉ

์šฐ์„  Cross-Site Scripting์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์šฐ์„  ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๋‹ค ๋ณด๋ฉด cms/app/webapp/inquiry/admin.php์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

class SOYInquiryApplication{
...
    function init(){
        $level = CMSApplication::getAppAuthLevel();
...
        CMSApplication::main(array($this,"main"));
...
    }
...
    function main(){
...
        $arguments = CMSApplication::getArguments();
...
        foreach($arguments as $key => $value){
            if(is_numeric($value)){
                $flag = true;
            }

            if($flag){
                $args[] = $value;
            }else{
                $classPath[] = $value;
            }
        }
        $path = implode(".",$classPath);
        $classPath = $path;
...
        if(preg_match('/^Help/',$classPath)){
            CMSApplication::setActiveTab(4);
        }

        if(!SOY2HTMLFactory::pageExists($classPath)){
            return $classPath;
        }
...
}

$app = new SOYInquiryApplication();
$app->init();

page๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด $classPath๋ฅผ ๋ฐ”๋กœ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ์ด ๋ณด์—ฌ ๋ฌด์–ธ๊ฐ€์˜ ์ˆ˜์ƒํ•จ์„ ๋А๋ผ๊ณ  ์ด๊ฒƒ์ €๊ฒƒ ์‹œ๋„ํ•ด๋ณธ ๊ฒฐ๊ณผ XSS๊ฐ€ ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Root Cause๋ฅผ ์ฐพ์•„๋ณด๊ธฐ ์œ„ํ•ด ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ์—์„œ ์ถœ๋ ฅํ•˜๊ธฐ ๊นŒ์ง€์˜ ๋ฃจํ‹ด์„ ์ˆœ์„œ๋Œ€๋กœ trace ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋  ๋•Œ ์ฒ˜์Œ์—๋Š” app/index.php ์—์„œ CMSApplication::run() ์œผ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

<?php
define("CMS_APPLICATION_ROOT_DIR", dirname(__FILE__) . "/");
define("CMS_COMMON", dirname(dirname(__FILE__)) . "/common/");

include_once(dirname(__FILE__)."/webapp/base/config.php");

try{
    //ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎๅฎŸ่กŒ
    CMSApplication::run();

    //่กจ็คบ
    CMSApplication::display();

}catch(Exception $e){
    $exception = $e;
    include_once(CMS_COMMON . "error/admin.php");       
}

์ดํ›„์—, CMSApplication ๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

class CMSApplication {
...
        public static function run(){
                $self = CMSApplication::getInstance();
                $self->root = SOY2PageController::createRelativeLink("./");
....
                //pathinfoใ‹ใ‚‰ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณIDใ‚’ๅ–ๅพ—
                $pathinfo = (isset($_SERVER["PATH_INFO"])) ? $_SERVER["PATH_INFO"] : "";
...
                $paths = array_values(array_diff(explode("/",$pathinfo),array("")));
                if(count($paths)<1){
                        SOY2PageController::redirect("../admin/");
                        exit;
                }
                $self->applicationId = $paths[0];
                $self->arguments = array_slice($paths,1);
...
                $cacheDir = dirname(dirname(dirname(__FILE__)))."/cache/".$self->applicationId."/";
...
                //ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎ่ชญใฟ่พผใฟ
                include_once($base . $self->applicationId . "/admin.php");
...
                $self->application = call_user_func($self->appMain);
         }
        public static function display(){
                $self = CMSApplication::getInstance();
                include_once(dirname(__FILE__) . "/" . $self->mode . ".php");
        }
        public static function main($func){
                $obj = CMSApplication::getInstance();
                $obj->appMain = $func;
        }
...

        public static function display(){
                $self = CMSApplication::getInstance();
                include_once(dirname(__FILE__) . "/" . $self->mode . ".php");
        }

...
}

$classPath๊ฐ€ ๋„๋‹ฌํ•˜๋Š” ์ง€์ ๊นŒ์ง€์˜ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ณผ์ •์„ ์ˆœ์„œ๋Œ€๋กœ ๋‚˜์—ดํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ตœ์ดˆ ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋  ๋•Œ๋Š” app/index.php์—์„œ ์‹œ์ž‘ํ•˜๋ฉฐ CMSApplication::run(); ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. run() ํ•จ์ˆ˜๊ฐ€ ์‹œ์ž‘ํ•˜๋Š” ์‹œ์ ์—์„œ๋ถ€ํ„ฐ ์ค‘์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋‚˜์—ดํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. $pathinfo = $_SERVER["PATH_INFO"] ๋Š” ํ˜„์žฌ ์‹คํ–‰ํ•˜๋Š” ํŒŒ์ผ์˜ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.์˜ˆ๋ฅผ ๋“ค์–ด http://hoge.com/index.php/inquiry/blah ๋ฉด $pathinfo๋Š” /1/2 ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

  2. $pathinfo ๋ฅผ ํ†ตํ•ด $self->arguments , $self->applicationId ๋“ฑ์˜ ๊ฐ’์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

  3. ์ž…๋ ฅ๋ฐ›์€ $self->applicationId๋ฅผ ํ†ตํ•ด $base . "inquiry/admin.php" ๊ฐ€ include ๋ฉ๋‹ˆ๋‹ค.

    • SOYInquiryApplication์˜ ์ฝ”๋“œ์˜ ์ตœํ•˜๋‹จ์„ ๋ณด๋ฉด $app = new SOYInquiryApplication(); $app->init(); ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

    • $app->init() ์ด ์‹คํ–‰๋˜๋Š” ๊ณผ์ •์„ ๋ณด๋‹ค๋ณด๋ฉด CMSApplication::main(array($this,"main")); ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

      • CMSApplication::main(array(SoyInquiryApplication,โ€mainโ€)); ์„ ์‹คํ–‰ํ•˜๋ฉด $obj->appMain = Array(SoyInquiryApplication, โ€mainโ€); ์ด ๋ฉ๋‹ˆ๋‹ค.
    • ์ดํ›„ ๋งˆ์ง€๋ง‰์— $self->application = call_user_func($self->appMain); ๋ฅผ ํ†ตํ•ดSoyInquiryApplication->main()์„ ํ˜ธ์ถœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

      • class๋Š” $arguments = CMSApplication::getArguments(); ๋ฅผ ๊ฐ€์ง€๊ณ  $classPath๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

      • ํ•˜์ง€๋งŒ ์•„๊นŒ ์–ธ๊ธ‰ํ•œ๋Œ€๋กœ ์กด์žฌํ•˜์ง€ ์•Š๋Š” page์ธ ๊ฒฝ์šฐ return $classPath๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๋ฐ”๋กœ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ ์ตœ์ข…์ ์œผ๋กœ๋Š” $self->application = $classPath ๊ฐ€ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ์˜ ๋ฌธ์ œ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ์œ„์—์„œ ์ง„ํ–‰ํ•˜๋˜ ์ผ๋ จ์˜ ๊ณผ์ •๋“ค ์ค‘์— $pathinfo์— ๋Œ€ํ•œ ๋ฌธ์ž์—ด ๊ฒ€์ฆ์ด ํ•œ๊ฐœ๋„ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

  2. $pathinfo๋กœ ์ƒ์„ฑ๋œ $classPath ๊ฐ€ ์ž˜๋ชป๋œ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ํ•ด๋‹น ๊ฐ’์„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

  3. ๊ฒฐ๊ตญ $self->application = $classPath ์—์„œ $classPath๋Š” $pathinfo ์— ์ž…๋ ฅํ–ˆ๋˜ ๊ฐ’ ๊ทธ๋Œ€๋กœ assign๋ฉ๋‹ˆ๋‹ค.

์ด์ œ CMSApplication::run() ๋‹ค์Œ์œผ๋กœ ์‹คํ–‰๋˜๋Š” CMSApplication::display(); ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด include_once(dirname(FILE) . "/" . $self->mode . ".php"); ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ $self->mode ๋Š” ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์ž…๋‹ˆ๋‹ค. ์ด ํ…œํ”Œ๋ฆฟ์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ž ๊น ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

...
<div id="tabs" class="content-wrapper">
    <?php CMSApplication::printTabs(); ?>
</div>

<div id="content" class="content-wrapper last"><?php CMSApplication::printApplication(); ?></div>
...

์—ฌ๊ธฐ์„œ CMSApplication::printApplication() ์„ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

        public static function printApplication(){
                $self = CMSApplication::getInstance();
                echo $self->application;
        }

๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ์•„๊นŒ ์ „์— ์–ธ๊ธ‰ํ•œ $self->application = $classPath ์ด๋ฏ€๋กœ, ์•…์˜์ ์ธ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๊ฒŒ ๋˜๋ฉด ๊ทธ๋Œ€๋กœ HTML ํƒœ๊ทธ๊ฐ€ ์ž…๋ ฅ๋˜๊ฒŒ ๋˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด Javascript๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณ„์†ํ•ด์„œ Remote Code Execution์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น ์ทจ์•ฝ์ ์—โ€œleading to RCEโ€ ๋ผ๊ณ  ์–ธ๊ธ‰ํ•˜์˜€๋Š”๋ฐ, ์ด๋Š” ์•„์ง ํŒจ์น˜๋˜์ง€ ์•Š์€ ๊ธฐ์กด ์ด์Šˆ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ณต๊ฒฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Issue #5: getshell https://github.com/inunosinsi/soycms/issues/5

๊ด€๋ฆฌ์ž ๊ถŒํ•œ์—์„œ ์ž„์˜๋กœ index.php ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ ์ด ๋ฐœ๊ฒฌ๋˜์—ˆ๋Š”๋ฐ, ์ด ๋ฌธ์ œ์ ์ด ์•„์ง ํŒจ์น˜๋˜์ง€ ์•Š์€๋“ฏ ํ•˜์—ฌ ์ด ์ทจ์•ฝ์ ์„ ์ด์šฉํ•˜์—ฌ RCE๋ฅผ ์„ฑ๊ณต์‹œํ‚ฌ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์‘

๋‹จ๋…์œผ๋กœ ๊ฐœ๋ฐœ์„ ๋‹ด๋‹นํ•˜๊ณ  ๊ณ„์‹ ๊ฒƒ ๊ฐ™์•„ ์ง์ ‘ PR์„ ์š”์ฒญํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

RCE์˜ ๊ฒฝ์šฐ Referer ๊ฒ€์‚ฌ์™€ CSRF ํ† ํฐ๋“ค์„ ํ™œ์šฉํ•˜์—ฌ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

(CVE-2020-15188) Remote Code Execution (RCE)

์ „์ œ์กฐ๊ฑด

์ „์ œ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ธ ์ƒํƒœ์—์„œ ์ž‘๋™

  2. ๊ด€๋ฆฌ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ, XSS๋ฅผ ์ด์šฉํ•œ ๊ณต๊ฒฉ. ์•ž์„œ ์†Œ๊ฐœํ•œ CVE-2020-15183๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์  ๋‚ด์šฉ

์•ž์„œ ์–ธ๊ธ‰ํ•˜์˜€๋˜ XSS๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ RCE ๊ฐ€์ ฏ์ž…๋‹ˆ๋‹ค.

๋ฌด์–ธ๊ฐ€ ๋Œ€๋‹จํ•œ ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ, elFinder์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ ์ด ์‹ค์ œ๋กœ ๋ฐœ๊ฒฌ๋˜์—ˆ๋‹ค๋Š” ์ ์ด ํฅ๋ฏธ๋กœ์› ์Šต๋‹ˆ๋‹ค.

SoyCMS์— ์žˆ๋Š”soycms/js/elfinder/php/connector.php:143-159๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค

$opts = array(
    // 'debug' => true,
    'roots' => array(
        // Items volume
        array(
            'driver'        => 'LocalFileSystem',           // driver for accessing file system (REQUIRED)
            'path'          => $path,                       // path to files (REQUIRED)
            'URL'           => $url,                        // URL to files (REQUIRED)
            //'trashHash'     => 't1_Lw',                     // elFinder's hash of trash folder
            'winHashFix'    => DIRECTORY_SEPARATOR !== '/', // to make hash same to Linux one on windows too
            'uploadDeny'    => array('all'),                // All Mimetypes not allowed to upload
            'uploadAllow'   => array('image', 'text/plain', 'text/css', 'application/zip', 'application/epub+zip','application/pdf'),// Mimetype `image` and `text/plain` allowed to upload
            'uploadOrder'   => array('deny', 'allow'),      // allowed Mimetype `image` and `text/plain` only
            'accessControl' => 'access'                     // disable and hide dot starting files (OPTIONAL)
        ),
    )
);

์œ„์™€ ๊ฐ™์ด mimetype ์— ๋Œ€ํ•œ ํ™•์ธ๋งŒ ํ•˜๊ณ  ์‹ค์ œ๋กœ ํ™•์žฅ์ž๋ฅผ ํ™•์ธํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ณต๊ฒฉ์ž๋Š” ์ž„์˜ ํŒŒ์ผ์„ ์—…๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•ด elFinder์—์„œ ์ด์Šˆ๊ฐ€ ์—ฌ๋Ÿฌ๋ฒˆ ์ œ๊ธฐ๋˜์—ˆ์—ˆ๋Š”๋ฐ, ๊ฒฐ๊ตญ ์œ ์ผํ•œ ํ•ด๊ฒฐ์ฑ…์€ $opts์— attributes๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์œ„์™€ ๊ฐ™์€ ํ˜„์ƒ์„ ์—†์• ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

            'uploadOrder'   => array('deny', 'allow'),      // allowed Mimetype `image` and `text/plain` only
            'accessControl' => 'access',                     // disable and hide dot starting files (OPTIONAL)
            'attributes' => array(
                                //ใƒ•ใƒญใƒณใƒˆใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผ
                                array(
                                        'pattern' => '/\\.php(\\.old(\\.[0-9][0-9])?)?$/',
                                        'read' => false,
                                        'write' => false,
                                        'locked' => true,
                                        'hidden' => true,
                                ),
                        )
        ),

PoC

์œ„ ๊ณต๊ฒฉ์„ ์œ„ํ•ด BaserCMS์—์„œ ์ด์šฉํ–ˆ์—ˆ๋˜ Blob Object๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ณต๊ฒฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๊ณต๊ฒฉ์ด ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ PoC๋Š” ๋‹ค์Œ ์˜์ƒ์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

https://www.youtube.com/watch?v=FWIDFNXmr9g

๋Œ€์‘

๋‹จ๋…์œผ๋กœ ๊ฐœ๋ฐœ์„ ๋‹ด๋‹นํ•˜๊ณ  ๊ณ„์‹ ๊ฒƒ ๊ฐ™์•„ ์ง์ ‘ PR์„ ์š”์ฒญํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

(CVE-2020-15189) Cross-site Request Forgery (CSRF) leading to RCE

์ „์ œ์กฐ๊ฑด

์ „์ œ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ธ ์ƒํƒœ์—์„œ ์ž‘๋™

์ทจ์•ฝ์  ๋‚ด์šฉ

์šฐ์„  ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ ์ฐพ์•„๋ณด๋‹ค ๋ณด๋ฉดcms/app/webapp/inquiry/pages/Template/EditPage.class.php ์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    function doPost(){
        
        $target = $this->target;
        $dir = SOY2::RootDir() . "template/";
        if(!file_exists($dir . $target) || !is_writable($dir.$target)){
            CMSApplication::jump("Template");
            exit;
        }
        
        $path = $dir . $target;
        
        //bk
        $content = file_get_contents($path);
        file_put_contents($path . "_" . date("YmdHis"),$content);
        
        $content = $_POST["content"];
        file_put_contents($path,$content);
        
        CMSApplication::jump("Template");
        exit;
    }
function __construct() {
        
        $target = @$_GET["target"];
        $this->target = $target;
        $dir = SOY2::RootDir() . "template/";
        if(!file_exists($dir . $target) || !is_writable($dir.$target)){
            CMSApplication::jump("Template");
            exit;
        }
        
        parent::__construct();
        
        $path = $dir . $target;
        
        $content = file_get_contents($path);
        
        $this->createAdd("target","HTMLLabel",array(
            "text" => $target
        ));
        
        $this->createAdd("content","HTMLTextArea",array(
            "name" => "content",
            "value" => $content
        ));
    }

์œ„ ์ฝ”๋“œ์˜ doPast ๋ฉ”์˜๋“œ์—์„œ๋Š” CSRF ํ† ํฐ์„ ๊ฒ€์ฆํ•˜์ง€๋„ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ํŒŒ์ผ์„ ์ž…๋ ฅํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€ URL์—์„œ ๋ณธ์ธ ์‚ฌ์ดํŠธ๋กœ ์ ‘๊ทผํ•˜๋ฉด CSRF๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์— ์•…์˜์ ์ธ ํŒŒ์ผ์„ ๊ฐ•์ œ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ ์™ธ์— ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ์˜ ํŒŒ์ผ๋“ค์„ ๋ฆฌ์ŠคํŒ…ํ•˜๊ณ  includeํ•˜๋Š” ์ทจ์•ฝ์ ์ด __construct์— ์žˆ์—ˆ์œผ๋‚˜, ์ด๋Š” ๋”ฐ๋กœ CVE๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ ํŒจ์น˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

PoC

ํ•ด๋‹น ๊ณต๊ฒฉ์ด ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ PoC๋Š” ๋‹ค์Œ ์˜์ƒ์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

https://www.youtube.com/watch?v=ffvKH3gwyRE

๋Œ€์‘

๋‹จ๋…์œผ๋กœ ๊ฐœ๋ฐœ์„ ๋‹ด๋‹นํ•˜๊ณ  ๊ณ„์‹ ๊ฒƒ ๊ฐ™์•„ ์ง์ ‘ PR์„ ์š”์ฒญํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

(CVE-2020-15182) Unauthenticated Remote Code Execution (RCE)

์ „์ œ์กฐ๊ฑด

์ „์ œ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๊ธฐ๋ณธ ๋‚ด์žฅ ํ”Œ๋Ÿฌ๊ทธ์ธ์ธ Soy Inquiry๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ Soy CMS๋ฅผ ์‚ฌ์šฉ์ค‘์ธ ์‚ฌ์ดํŠธ์—์„œ Soy Inquiry๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์‹ค์ œ๋กœ ์ด ์ทจ์•ฝ์ ์„ ํ†ตํ•ด ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ PoC๋ฅผ ํ†ตํ•ด ์ฆ๋ช…ํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์˜ํ–ฅ๋ ฅ์€ ๋ณด๊ณ ํ•œ 4๊ฐœ์˜ ์ทจ์•ฝ์  ์ค‘ ๊ฐ€์žฅ ๋†’์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์  ๋‚ด์šฉ

์šฐ์„  ๊ธฐ๋ณธ ํ”Œ๋Ÿฌ๊ทธ์ธ Soy Inquiry๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์—ฌ, cms/app/webapp/inquiry/page.php๋ฅผ ๋ถ„์„ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.

        if(isset($_POST["form_value"]) && isset($_POST["form_hash"])){
            $value = base64_decode($_POST["form_value"]);

            //ไธๆญฃใชๆ›ธใๆ›ใˆใงใชใ„ๅ ดๅˆใฎใฟ
            if(md5($value) == $_POST["form_hash"]){
                $_POST["data"] = unserialize($value);
            }
        }

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๋‘๊ฐ€์ง€์˜ ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. md5($value) == $_POST["form_hash"]

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ไธๆญฃใชๆ›ธใๆ›ใˆใงใชใ„ๅ ดๅˆใฎใฟ ์„ ์œ„ํ•ด md5($value) == $_POST["form_hash"] ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์œผ๋‚˜, ์‹ค์ œ๋กœ๋Š” md5๋Š” ์•”ํ˜ธํ™”๊ฐ€ ์•„๋‹Œ ๊ทธ์ € ๋‹จ๋ฐฉํ–ฅ ํ•ด์‰ฌ์ด๋ฉฐ $_POST["form_value"]์™€ $_POST["form_hash"]๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋Ÿฐ ๋ณดํ˜ธ๊ฐ€ ๋˜์–ด์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  1. $_POST["data"] = unserialize($value);

๊ฐ€์žฅ ์œ„ํ—˜ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. PHP์˜ ๊ณต์‹ ๋ฌธ์„œ ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Do not pass untrusted user input to unserialize() regardless of the options value of allowed_classes. Unserialization can result in code being loaded and executed due to object instantiation and autoloading, and a malicious user may be able to exploit this. Use a safe, standard data interchange format such as JSON (via json_decode() and json_encode()) if you need to pass serialized data to the user. If you need to unserialize externally-stored serialized data, consider using hash_hmac() for data validation. Make sure data is not modified by anyone but you.

๋ง ๊ทธ๋Œ€๋กœ, unserialize() ๋Š” ์ ˆ๋Œ€ ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ์— ์žˆ์–ด์„œ๋Š” ์•ˆ๋˜๋Š” ์œ„ํ—˜ํ•œ ์ฝ”๋“œ๋“ค ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ธํ„ฐ๋„ท์„ ๊ฒ€์ƒ‰ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด unserialize() ์— ๋Œ€ํ•œ ์œ„ํ—˜์„ฑ ๋ฐ ๋Œ€์ฑ…์„ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • https://www.hack.vet/entry/20170328/1490712989

  • https://blog.tokumaru.org/2015/07/phpunserialize.html

  • https://blog.ohgaki.net/how-to-use-php-serialize-unserialize-with-security

์‹ค์ œ๋กœ unserialize()๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์˜ Soy Inquiry๋Š” ํ•œ ๋ฌธ์„œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์ •๋ง ๋งŽ์€ class๋“ค์„ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๊ณ , ๊ทธ ์ค‘ ์œ„ํ—˜ํ•ด๋ณด์ด๋Š” class๋ฅผ ์ž˜ ํ™œ์šฉํ•˜์—ฌ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

PoC ๋ฐ ์ฐธ๊ณ 

์ €์˜ ๊ฒฝ์šฐ PoC๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ์œ ์‚ฌํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์˜ PHP์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ž„์˜์˜ PHP ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ณด๋‹ค ๋”์šฑ ์งง์€ ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋„์ „ํ•ด๋ณด๊ณ  ์‹ถ์œผ์‹  ๋ถ„์€ ํ•œ๋ฒˆ ๋„์ „ํ•ด๋ณด์„ธ์š”.

<?php

class ...ClassHoge2 {
...
}

class ...ClassHoge1 {
...
}

$exploit = serialize(Array(
    "column_1" => ...ClassHoge1,
    "column_2" => "[email protected]",
    "column_3" => "c",
    "column_4" => "e",
    "column_5" => "e",
    "column_6" => "f",
    "hash" => "1337",
    "captcha" => "1337",
));

$form_hash = md5($exploit);
$form_value = base64_encode($exploit);

echo "form_hash = '$form_hash'\n";
echo "form_value = '$form_value'\n";

?>

unserialize() exploit์— ๊ด€ํ•œ ํŒ์„ ๋“œ๋ฆฌ์ž๋ฉด, unserialize๋ฅผ ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ด๊ฒŒ exploitํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์‹ค์ œ๋กœ unserialize() ๋ช…๋ น์–ด๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ๋ฐ”๋กœ ์ „์— var_dump(get_declard_classes());์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋”์šฑ ๋” ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๊ณต๊ฒฉ์ด ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ PoC๋Š” ๋‹ค์Œ ์˜์ƒ์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

https://www.youtube.com/watch?v=zAE4Swjc-GU

๋Œ€์‘

๋‹จ๋…์œผ๋กœ ๊ฐœ๋ฐœ์„ ๋‹ด๋‹นํ•˜๊ณ  ๊ณ„์‹ ๊ฒƒ ๊ฐ™์•„ ์ง์ ‘ PR์„ ์š”์ฒญํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ธ€์„ ๋งˆ์น˜๋ฉฐ

๋„ˆ๋ฌด ๊ธ€์ด ๊ธธ์–ด์กŒ๋„ค์š”.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” 4๊ฐœ์˜ ์ œํ’ˆ์—์„œ ์ด 6๊ฐ€์ง€์˜ ์ทจ์•ฝ์ ์„ ์†Œ๊ฐœํ–ˆ์ง€๋งŒ, ์•„์ง ์ทจ์•ฝ์ ์ด ํŒจ์น˜๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์™„๋ฒฝํžˆ ํŒจ์น˜๊ฐ€ ์ง„ํ–‰๋˜์ง€ ์•Š์€ ์ทจ์•ฝ์ ์ด 25๊ฑด ์ด์ƒ ๋” ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ด€๋ จ ๋‚ด์šฉ์€ ์ถ”ํ›„ ํŒจ์น˜๊ฐ€ ์–ด๋А์ •๋„ ๋๋‚˜๊ฒŒ ๋˜๋ฉด ๋‚˜์ค‘์— ๋ธ”๋กœ๊ทธ์— ๊ฒŒ์‹œํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

ย  ย  ย  ย  ย ย 

a286e487f0a2455b
b7917adecfbc3b2c