Hacking Apple - SQL Injection to Remote Code Execution

Hacking Apple - SQL Injection to Remote Code Execution

Introduction

In our last blog post, we delved into the inner workings of Lucee and took a look at the source code of Masa/Mura CMS, and the vastness of the potential attack surface struck us. It became evident that investing time in understanding the code could pay off. After dedicating a week to our exploration, we stumbled upon several entry points for exploitation, including a critical SQL injection flaw that we were able to exploit within Apple's Book Travel portal.

In this blog post, we aim to share our insights and experiences, detailing how we identified the vulnerability sink, linked it back to its source, and leveraged the SQL injection to achieve Remote Code Execution (RCE).

Finding the sink

From playing around with the Masa/Mura CMS, we understood our attack surface - mainly the attack surface accessible on Apple's environment. Our primary focus was on JSON API, as it exposes some methods that are accessible within Apple's environment. Any potentially vulnerable sink we find should have its source in JSON API.

We deliberated on optimising our approach to streamline our source code review process. We explored the availability of static analyzers or CFM parsers capable of traversing through code while disregarding sanitizers.

For instance, this is how a safe parameterised SQL query is written via tag-based CFM:

<cfquery>
select * from table where column=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.user_input#">
</cfquery>

And this is how an unsafe SQL query is written:

<cfquery>
select * from table where column=#arguments.user_input#
</cfquery>

It would be great if we could parse and traverse through the code and only print cfquery tags that have unsanitized input regardless of having the cfqueryparam tag inside or not. We came across https://github.com/foundeo/cfmlparser which could let us do this.

Here's how we targeted SQL injection sink detection:

  • Parse each CFM/CFC file.
  • Go through each statement, select the statement if it's a tag and its name is cfquery .
  • Strip all tags (like cfqueryparam) inside the code block of cfquery and if it still has arguments in the codeblock then the input is not parameterized and the query is susceptible to an SQL injection, given no other validation is in place.
  • Print this query.
<cfscript>
    targetDirectory = "../mura-cms/";
    files = DirectoryList(targetDirectory, true, "query");

    for (file in files) {
        if (FindNoCase(".cfc", file.name) or FindNoCase(".cfm", file.name)) {
            fname = file.directory & "/" & file.name;
            if (file.name != "dbUtility.cfc" && file.name != "configBean.cfc" && !FindNoCase("admin", file.directory) && !FindNoCase("dbUpdates", file.directory)) {
                filez = new cfmlparser.File(fname);
                statements = filez.getStatements();
                info = [];
                for (s in statements) {
                    if (s.isTag() && s.getName() == "cfquery" && FindNoCase("arguments", s.getStrippedInnerContent(true, true))) {
                        WriteOutput("Filename: <b>#fname#</b>");
                        WriteOutput("<br><br>" & s.getStrippedInnerContent(true, true));
                        WriteOutput("<br><br><br><br>");
                    }
                }
            }
        }
    }
</cfscript>

We started going through the result with a few things in mind, such as ignoring input like siteid because JSON API validates it in advance.

One of the queries that had two other inputs was this:

Tracing sink to source

Looking at the function which had this query concluded that there's only one exploitable argument, that is, ContentHistID. The argument columnid is numeric and siteid is validated by default.

<cffunction name="getObjects" output="false">
	<cfargument name="columnID" required="yes" type="numeric" >
	<cfargument name="ContentHistID" required="yes" type="string" >
	<cfargument name="siteID" required="yes" type="string" >

	<cfset var rsObjects=""/>

	<cfquery attributeCollection="#variables.configBean.getReadOnlyQRYAttrs(name='rsObjects')#">
		select tcontentobjects.object,tcontentobjects.name,tcontentobjects.objectid, tcontentobjects.orderno, tcontentobjects.params, tplugindisplayobjects.configuratorInit from tcontentobjects
		inner join tcontent On(
		tcontentobjects.contenthistid=tcontent.contenthistid
		and tcontentobjects.siteid=tcontent.siteid)
		left join tplugindisplayobjects on (tcontentobjects.object='plugin'
											and tcontentobjects.objectID=tplugindisplayobjects.objectID)
		where tcontent.siteid='#arguments.siteid#'
		and tcontent.contenthistid ='#arguments.contentHistID#'
		and tcontentobjects.columnid=#arguments.columnID#
		order by tcontentobjects.orderno
	</cfquery>

	<cfreturn rsObjects>

</cffunction>

The function getObjects was called within the dspObjects function in the core/mura/content/contentRendererUtility.cfc component.

The call stack was:

JSON API -> processAsyncObject -> object case: displayregion -> dspobjects() -> getobjects().

Triggering & Exploiting SQL injection

By default, Lucee escapes single quotes by adding a backslash before them when passed as input. This can be managed by using a backslash to escape one of the single quotes.

This should trigger the SQL injection:

/_api/json/v1/default/?method=processAsyncObject&object=displayregion&contenthistid=x%5c'

However, it didn't. Upon revisiting the source code, we identified a crucial condition in the dspObjects function. Before calling getObjects, an if condition must be satisfied: the isOnDisplay property must be set to true in the Mura servlet event handler. Initially, we assumed that any property on the event handler could be set simply by passing the property name as a parameter, along with its value. This assumption was based on our debugging session within the codebase.

Our attempts to set the isOnDisplay property in this manner were unsuccessful. It appears that somewhere in the code, this property is being overwritten.

After conducting some grep searches, we stumbled upon the standardSetIsOnDisplayHandler function call within the processAsyncObjects of the JSON API.

It appears that by simply passing the previewID parameter with any value, we can set the previewID property, which in turn will set the isOnDisplay property to true.

/_api/json/v1/default/?method=processAsyncObject&object=displayregion&contenthistid=x%5c'&previewID=x

And it worked:

Since this was an error-based SQL injection, we could exploit it quite easily to achieve Remote Code Execution (RCE). Locally, we successfully performed RCE by following these steps:

  1. Reset an Admin user's password.
  2. Obtain the reset token and user ID via SQL injection.
  3. Use the password reset endpoint with exfiltrated info.
  4. Utilize plugin installation to upload CFM files.

However, on Apple's environment, we encountered only an Unhandled Exception error without any query-related information, turning this into a blind SQL injection. Fortunately, the token and user ID are UUIDs, making it relatively straightforward to exfiltrate them. With a bit of scripting, we were able to accomplish this task.

We promptly submitted our report to Apple, including Proof of Concept (PoC) demonstrating logging into an account while theoretically providing them with RCE details.

Detection via Nuclei

This SQL injection vulnerability can be identified by utilizing the below Nuclei template:

id: CVE-2024-32640

info:
  name: Mura/Masa CMS - SQL Injection
  author: iamnoooob,rootxharsh,pdresearch
  severity: critical
  description: |
    The Mura/Masa CMS is vulnerable to SQL Injection.
  reference:
    - https://blog.projectdiscovery.io/mura-masa-cms-pre-auth-sql-injection/
    - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-32640
  impact: |
    Successful exploitation could lead to unauthorized access to sensitive data.
  remediation: |
    Apply the vendor-supplied patch or update to a secure version.
  metadata:
    verified: true
    max-request: 3
    vendor: masacms
    product: masacms
    shodan-query: 'Generator: Masa CMS'
  tags: cve,cve2022,sqli,cms,masa,masacms

http:
  - raw:
      - |
        POST /index.cfm/_api/json/v1/default/?method=processAsyncObject HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        object=displayregion&contenthistid=x\'&previewid=1

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 500'
          - 'contains(header, "application/json")'
          - 'contains_all(body, "Unhandled Exception")'
          - 'contains_all(header,"cfid","cftoken")'
        condition: and

We've also added template in nuclei-templates GitHub project.

Conclusion

In conclusion, our exploration of Masa/Mura CMS has been a rewarding journey, revealing critical vulnerabilities. The code review process begins by focusing on vulnerable SQL injection code patterns and then utilizing the CFM/CFC parser to search for specific patterns within the codebase, a similar approach to Semgrep. Once potential sinks were identified, we traced them back to the source, in this case, the JSON API of Mura/Masa CMS.

We responsibly disclosed these findings to Apple and the respective Masa and Mura CMS teams.

Apple's Response:

Apple responded and implemented a fix within 2 hours of the initial report, swiftly addressing the reported issue. As always, working with Apple has been a good collaboration.

Masa CMS:

Masa is an open-source fork of Mura CMS, they were quite transparent and released a new version of Masa CMS with fixes. The 7.4.6, 7.3.13 and 7.2.8 versions have the latest security patches including another critical pre-auth SQL injection which is assigned CVE (CVE-2024-32640).

Mura CMS:

Despite numerous attempts to reach out to the Mura team regarding these vulnerabilities, we received no response across multiple communication channels. With the 90-day standard deadline elapsed, we are now releasing this blog post detailing the reported vulnerability.


By leveraging Nuclei and actively engaging with the open-source community, or by becoming a part of the ProjectDiscovery Cloud Platform, companies can enhance their security measures, proactively address emerging threats, and establish a more secure digital landscape. Security represents a shared endeavor, and by collaborating, we can consistently adapt and confront the ever-evolving challenges posed by cyber threats.

Subscribe to our newsletter and stay updated.

Don't miss anything. Get all the latest posts delivered straight to your inbox. It's free!
Great! Check your inbox and click the link to confirm your subscription.
Error! Please enter a valid email address!
--