Skip to main content

Why attack paths matter more than lists of findings

· 4 min read
Vulkro
Security research

A security scan of a mature Salesforce org rarely comes back short. Hundreds of findings is normal: permissions that are broader than they need to be, Apex that runs without sharing, a session setting nobody has looked at since the org was created. Every one of them is technically true. Almost none of them tells you what to do on Monday morning.

The question a security team actually has is narrower: which of these lets someone reach data they should not?

How a finding differs from an attack path​

Take three findings you will find in many orgs:

  • A Customer Portal site is active and serves anonymous visitors as its guest user.
  • A permission set assigned to that guest user grants access to an Apex class.
  • That class runs without sharing and returns records by an id the caller supplies.

Read separately, each is a Medium-severity line in a long report. Read together, they are one sentence: anyone on the internet can read invoices for any account. The severity of the whole is not the severity of the worst part. It is the severity of what the parts add up to.

That is what an attack path is: the chain of facts that connects someone who can get in to something they can take.

UserGuest user

Anyone on the internet is this user.

The Customer Portal site is active and serves unauthenticated visitors as its guest user. There is no login between a stranger and everything this user can reach.

Read fromSite metadataLive org
Evidencesites/CustomerPortal.site-meta.xml
<CustomSite>  <active>true</active>  <guestProfile>Customer Portal Profile</guestProfile>  <siteType>ChatterNetwork</siteType></CustomSite>
CriticalProvenFix first

Break the path at the hop that carries it. Declare with sharing on InvoiceController and query WITH USER_MODE: one change in one class closes all five hops.

Why long lists hide the dangerous findings​

A findings list ranks each item on its own. That produces two predictable failures.

  1. The dangerous combination ranks low. None of its links is dramatic on its own, so it sits on page six.
  2. The dramatic finding nobody can reach ranks high. An over-privileged permission set that is assigned to no one looks alarming and changes nothing.

Teams that work a list from the top spend their week on the second kind and never reach the first.

How Vulkro builds the path​

Vulkro reads the org as one system rather than as separate checklists:

  • Who can get in. Sites and guest users, community and partner users, integration users, connected apps, and the people behind every profile and permission set.
  • What they are granted. Profiles, permission sets and groups, object and field access, sharing rules and org-wide defaults.
  • What that runs. Apex entry points (@AuraEnabled, @RestResource, @InvocableMethod, @RemoteAction), the LWC and Aura components that call them, Visualforce controllers and Flows.
  • What it reaches. The objects and fields behind each query and write, and how they are classified.

A finding becomes part of a path only when every hop is established. When a hop cannot be checked, Vulkro says so: the path is marked unproven or not checked rather than quietly promoted. Since vulkro-sf 0.20.0, findings are also checked against who actually holds each profile and permission set in the live org, so a permission nobody holds is no longer reported as a live risk.

Fix one hop to close the path​

The useful property of a path is that it usually has one cheap place to break it. In the example above there are five hops, and declaring with sharing on one class with a WITH USER_MODE query closes all of them. That is the fix to make first, and it is why Vulkro Cloud's overview leads with Fix first rather than with a count.

Where to see it​

  • In Vulkro for Salesforce, on your own machine: the attack surface and every path behind a finding.
  • In Vulkro Cloud for Salesforce, for every org on a schedule, with the paths that changed since the last scan.
  • In the VS Code extension, on the line of Apex that carries the path.

If you want to see the paths in your own org, start here.