Skip to content

Back to the catalog

Salesforce Pentest: Apex Predator
reco.aiwriteup

Salesforce Pentest: Apex Predator

A deep technical guide from Reco on pentesting Salesforce Experience Cloud "Aura" sites, covering how to enumerate guest-user-accessible sObjects via HostConfigController and SelectableListDataProviderController, and how to discover and invoke custom server-side Apex controller methods exposed to guest/authenticated users. It introduces novel techniques for extracting Apex method parameters from compiled Lightning Web Component JS (via the lri/moduleDefs structures), using ComponentController.getComponent and siteforce:routerInitializer to enumerate every site route/page and its Apex-backed components beyond what standard tools like AuraInspector find, plus an appendix of undocumented native controllers including an Aura-exposed GraphQL execution endpoint (RecordUiController.executeGraphQL). It closes with defensive guidance on monitoring AuraRequest events via Salesforce Shield to detect abuse.

TL;DR Detailed technique for enumerating and exploiting custom Apex controllers and hidden pages on Salesforce Experience Cloud (Aura) sites, including a GraphQL bypass, during pentests/bug bounty.
Document

What it is

Reco's operational playbook for pentesting Salesforce Experience Cloud Aura sites. The summary lists the controllers and the general approach; below are the concrete procedural details that make the techniques reproducible — the LWC parameter-extraction algorithm, the request tweaks needed to enumerate every page, and the exact detection query.

How it works — the parts the summary omits

Aura request basics. Calls go to /aura or /s/sfsites/aura (POST or GET). Three params matter: message (the actions list), aura.context, and aura.token — for guest requests the token is the literal string "null". Only apps like siteforce:communityApp support guest actions.

Extracting LWC Apex parameters (no pa array). Legacy Lightning components declare params in a structured pa array; LWCs do not, so you trace them through compiled JS in context.moduleDefs:

  1. In the lri object, find keys valued "apexMethod" — the key is the method in dot-notation (HC_ContextNavController.getContextNavData → descriptor apex://HC_ContextNavController/ACTION$getContextNavData).
  2. In the co code string, find the method's import string @salesforce/apex/Class.method in the imports array and note its positional index — that index maps to the corresponding factory-function argument (6th import → 6th arg o).
  3. Salesforce wraps args in a helper (l); find c = l(o), then search for c.default( — the object literal passed to it is the parameter map (siteLabel, language, showProducts, productsPosition).
  4. Unsure of a type? Pass a deliberately invalid value (e.g. a list of lists); the error names the expected type: Value provided is invalid for action parameter 'siteLabel' of type 'String'.

Full page enumeration (beyond AuraInspector). Tools that only parse app.js/bootstrap.js/appcore.js return a small fraction of Apex methods. Instead:

  • Call ComponentController.getComponent with markup://siteforce:routerInitializer to get every route.
  • Re-run route enumeration under three cookie contexts — no cookies, renderCtx from the home page, renderCtx from the login page — because the route set varies by session context; some sites only reveal the full method list one way.
  • To pull a specific page's components, call getComponent with markup://siteforce:pageLoader using the route's id, view_uuid, and themeLayoutType. Critical: in aura.context, change "uad": true to "false" or this fails. Read results from the response's context (componentDefs + moduleDefs), not the action's returnValue.

This works even on sites that force-redirect to login — the "hidden" pages are still reachable directly, and Reco reports finding vulns in them at major companies.

Appendix controller specifics:

  • RecordGvpController.getRecord takes a dot-separated recordDescriptor (recordId.recordTypeId.layoutType...fields.mode...); relationship fields encode the dot as ;2 (CreatedBy;2Name). Result lands in context, not actions.
  • ScopedResultsDataProviderController.getLookupItems does keyword search across an object (scope, term) — useful when getItems times out or to hunt terms like "Salary"/"Token" without knowing the field; additionalFields pulls fields beyond the default layout.

What to do (defense)

Review guest-user sharing rules and guest-user profile permissions first, then audit Apex classes using Without Sharing. Monitoring requires a Salesforce Shield subscription (data lives only in Shield-exclusive event log files). Alert on guest-user GraphQL use and bursts of getItems; do not alert on getComponent (too common). Query:

SELECT Id, LogDate, Interval, LogFile, LogFileLength 
FROM EventLogFile 
WHERE EventType in ('AuraRequest')

Caveats

Reco frames this as authorized-testing-only: DoS is always out of scope (Salesforce-authorized only), no infra/RCE/desync attacks, and the appendix controllers are "documented publicly by researchers" rather than novel here.

Technologies named here

  • Salesforce Experience Cloud
  • Burp Suite
  • Caido
  • Salesforce Aura Framework
  • OWASP Zap
  • Salesforce LWR framework
  • AuraInspector
  • Legacy Lightning Components
  • Lightning Web Components
  • Salesforce Shield
  • Salesforce Guest User profile
  • Salesforce
  • GraphQL

Techniques named here

  • Aura message/action enumeration
  • Custom Apex controller invocation
  • Legacy Lightning Component Apex method extraction
  • LWC Apex method extraction from compiled JS
  • Full site enumeration via ComponentController.getComponent
  • Session-context route enumeration
  • Page component retrieval via id/view_uuid/themeLayoutType
  • Direct record retrieval via RecordGvpController.getRecord