GestSup v3.1.15 multiple vulnerabilities
Disclosure timeline
Developers were contacted the 30/12/2021
Developers acknowledged receiving the vulnerabilities and asking to wait for two months so users may have the time to update
Disclosure on this blog was made the 06/03/2022
Remote Code Execution during installation
GestSup v3.1.15, when /install/
is available, allows an attacker to append arbitrary code in connect.php
(contains database connection credentials) due to the lack of sanitization.
Extract of /install/index.php
from line 35 to 43
$_POST['server']=htmlspecialchars($_POST['server'], ENT_QUOTES, 'UTF-8');
$_POST['port']=htmlspecialchars($_POST['port'], ENT_QUOTES, 'UTF-8');
$_POST['dbname']=htmlspecialchars($_POST['dbname'], ENT_QUOTES, 'UTF-8');
$_POST['user']=htmlspecialchars($_POST['user'], ENT_QUOTES, 'UTF-8');
$_POST['password']=str_replace("';","",$_POST['password']);
$_POST['password']=str_replace('";','',$_POST['password']);
$_POST['password']=str_replace('system(','',$_POST['password']);
$_POST['password']=str_replace('$_GET','',$_POST['password']);
$_POST['password']=str_replace(';//','',$_POST['password']);
As we can see, the $_POST['password']
is not properly sanitized as it just consists of stripping several characters as well as the system(
, $_GET
, ;//
strings in order to prevent code execution.
An attacker can bypass these checks by using another function than system()
to execute code like passthru()
and by passing commands using HTTP POST
requests.
MySQL stacked query SQL injection
GestSup v3.1.15 is vulnerable to MySQL stacked query SQL injection. The /admin/lists/display.php
does not sanitize the table
GET
parameter which results in SQL injection.
In the /admin/list.php
file on lines 51 and 52, we can see that the $db_table
is set and that HTML tags and single quotes are stripped from it which is not sufficient to prevent SQL injection.
$db_table=strip_tags($db->quote($_GET['table']));
$db_table=str_replace("'","`",$db_table);
This parameter is then passed to /admin/lists/display.php
when it get loaded and is not processed before being added to the SQL query an be executed.
Extract of /admin/lists/display.php
from line 204 to 229:
//define order
if($_GET['table']=='tassets_model'){$order='ORDER BY tassets_model.type,tassets_model.manufacturer ';}
elseif($_GET['table']=='tcategory'){$order='ORDER BY number,service,name';}
elseif($_GET['table']=='tsubcat'){$order='ORDER BY cat,name';}
elseif($_GET['table']=='tcriticality'){$order='ORDER BY service,number';}
elseif($_GET['table']=='tstates'){$order='ORDER BY number';}
elseif($_GET['table']=='tpriority'){$order='ORDER BY number';}
elseif($_GET['table']=='tassets_state'){$order='ORDER BY `order`';}
elseif($_GET['table']=='tassets_network'){$order='ORDER BY `network`';}
elseif($_GET['table']=='ttime'){$order='ORDER BY min';}
else {$order='ORDER BY name';}
if($_GET['hide_disabled_values']){$disable=" AND disable='0' ";} else {$disable='';}
if($rright['dashboard_service_only']!=0 && $rparameters['user_limit_service']==1 && $_SESSION['profile_id']!=4){
$where_service_list=str_replace('tincidents.u_service','service',$where_service);
if($_GET['table']=='tsubcat') {
$query="SELECT tsubcat.id,tsubcat.cat,tsubcat.name FROM `tsubcat`,`tcategory` WHERE tsubcat.cat=tcategory.id $where_service_list $disable ORDER BY tsubcat.name";
} else {
$query="SELECT * FROM $db_table WHERE 1=1 $where_service_list $disable $order";
}
} else {$query="SELECT * FROM $db_table WHERE id!=0 $disable $order";}
//build each line
if($rparameters['debug']) {echo 'QRY : '.$query;}
$query = $db->query($query);
In this piece of code, the value of the table
GET
parameter is used to define the ORDER BY
part of the SQL statement. lastly, we can see that if we do not meet conditions of this portion of code, our parameter will be added in the final else
in the following query :
SELECT * FROM $db_table WHERE id!=0 $disable $order
And Boolean-based blind SQL injection
GestSup v3.1.15 is vulnerable to a boolean-based blind SQL injection. The dashboard.php
does not sanitize the order
get parameter which results in SQL injection.
The order
parameter is set. It is stripped from HTML tags and single quotes which does not seem to be sufficient to prevent SQL injection.
Extract of dashboard.php
lines 167 and 168 :
$db_order=strip_tags($db->quote($_GET['order']));
$db_order=str_replace("'","",$db_order);
Extract of /dashboard.php
from line 536 to 550
if(!$reload) //avoid double query for reload parameters in url optimization for large database
{
$masterquery = $db->query("
SELECT SQL_CALC_FOUND_ROWS $select
FROM $from
$join
WHERE $where
ORDER BY $db_order $db_way
LIMIT $db_cursor,
$rparameters[maxline]
");
} else {$masterquery='';}
$query=$db->query("SELECT FOUND_ROWS();");
$resultcount=$query->fetch();
$query->closeCursor();
As we can see, the parameter is set, then is directly used after HTML & single strippring.
After enabling the debug mode, we try to confirm that the order
parameter is vulnerable.
Since the result of this query is not directly displayed in the page, we found that using a boolean-based blind payload would be the best way to confirm the vulnerability.
True statement: 1+AND+6501=(SELECT+(CASE+WHEN+(6501=6501)+THEN+6501+ELSE+(SELECT+9081+UNION+SELECT+9677)+END))--+-
False statement: 1+AND+6501=(SELECT+(CASE+WHEN+(6501=9999)+THEN+6501+ELSE+(SELECT+9081+UNION+SELECT+9677)+END))--+-
We can clearly a difference in the response size as the table is not displayed when we inject a statement that is evaluated to false.
Multiple Stored XSS
During our research, we identified several stored XSS in GestSup v 3.1.15. The /core/ticket.php
does not properly sanitize the text
and text2
GET
parameters which results in the ability of storing HTML code in the database which is then displayed in /tickets.php
page. This occurs during the edition of a ticket.
Extract of /core/tickets.php
line 336 through 344:
//escape special char and secure string before database insert
$_POST['description']=$_POST['text'];
$_POST['resolution']=$_POST['text2'];
//remove <br> generate by IE browser
$_POST['description']=str_replace('<br><br><br>','',$_POST['description']);
$_POST['resolution']=str_replace('<br><br><br>','',$_POST['resolution']);
if($_POST['description']=='<br>'){$_POST['description']='';}
if($_POST['resolution']=='<br>'){$_POST['resolution']='';}
We can see that the sanitization here consists of removing <br>
. We can then see from line 571 to 606 that a query is built using our initial text
parameter which is now named $_POST['description']
.
$qry->execute(array(
'user' => $_POST['user'],
'observer1' => $_POST['observer1'],
'observer2' => $_POST['observer2'],
'observer3' => $_POST['observer3'],
'type' => $_POST['type'],
'type_answer' => $_POST['type_answer'],
'u_group' => $u_group,
'u_service' => $u_service,
'u_agency' => $_POST['u_agency'],
'sender_service' => $_POST['sender_service'],
'technician' => $_POST['technician'],
't_group' => $t_group,
'title' => $_POST['title'],
'description' => $_POST['description'],
'date_create' => $_POST['date_create'],
'date_modif' => $_POST['date_create'],
'date_hope' => $_POST['date_hope'],
'date_res' => $_POST['date_res'],
'priority' => $_POST['priority'],
'criticality' => $_POST['criticality'],
'billable' => $_POST['billable'],
'state' => $_POST['state'],
'user_validation' => $_POST['user_validation'],
'user_validation_date' => $_POST['user_validation_date'],
'creator' => $_SESSION['user_id'],
'time' => $_POST['time'],
'time_hope' => $_POST['time_hope'],
'category' => $_POST['category'],
'subcat' => $_POST['subcat'],
'techread' => $techread,
'techread_date' => $techread_date,
'userread' => $userread,
'place' => $_POST['ticket_places'],
'asset_id' => $_POST['asset_id']
));
On lines 746 to 758, we can see that the initial value of text
2 which is now named $_POST['resolution']
is inserted in the database.
//check your own ticket for update thread right
if($row['author']==$_SESSION['user_id'])
{
if($rright['ticket_thread_edit']) {
$qry=$db->prepare("UPDATE `tthreads` SET `text`=:text WHERE `id`=:id");
$qry->execute(array('text' => $_POST['resolution'],'id' => $_GET['threadedit']));
}
} else {
if($rright['ticket_thread_edit_all']) {
$qry=$db->prepare("UPDATE `tthreads` SET `text`=:text WHERE `id`=:id");
$qry->execute(array('text' => $_POST['resolution'],'id' => $_GET['threadedit']));
}
}