一、编写安全的 Apex 控制器(01 Write Secure Apex Controllers)
本单元保护 Apex 控制器:理解共享规则为何至关重要、如何强制共享规则、以用户模式运行数据操作,以及防范 CRUD 和字段级安全违规。学完本单元你将能够:
- 描述共享规则在 Apex 中为何至关重要;
- 强制共享规则;
- 解释如何防范创建、读取、更新、删除(CRUD)和字段级安全(FLS)违规。
Apex 安全与共享(Apex Security and Sharing)
使用 Apex 时,代码安全至关重要。默认情况下,Apex 类以系统模式(system mode)执行,能读取和更新组织内的所有数据。因此你必须强制共享规则、设置对象和字段权限、防范 CRUD 和 FLS 违规。你需要判断哪些代码应以系统模式运行(对许多资源有访问权限),哪些应以用户模式运行(执行当前用户的权限、字段级安全和共享规则)。
强制共享规则(Enforcing Sharing Rules)
使用 Apex 的开发者必须确保不会无意暴露本应被用户权限、字段级安全或组织范围默认值隐藏的敏感数据。Apex 通常在系统上下文中运行——对象权限、字段级安全和共享规则对当前用户不生效,这确保代码不会因用户的隐藏字段或对象而运行失败。
但共享规则并非总被绕过——类必须用 without sharing 关键字声明才能确保不执行共享规则。有三个关键字要记住:
with sharing——必须执行共享规则;without sharing——不执行共享规则;inherited sharing——以调用它的类的共享模式运行。
唯一例外是 executeAnonymous 调用和 Apex 中的 Chatter——它们始终以当前用户的完整权限执行。
With Sharing
with sharing 关键字让你指定:对该类执行当前用户的共享规则。你必须显式选择这个关键字——类上空白共享子句意味着共享设置从调用者继承,可能执行也可能不执行共享。执行当前用户的共享规则会影响:
- SOQL/SOSL 查询:查询返回的行数可能比系统上下文更少;
- DML 操作:操作可能因当前用户权限不足而失败(例如用户指定了一个组织里存在、但当前用户无权访问的外键值)。
Without Sharing
声明类时用 without sharing 关键字,确保不执行当前用户的共享规则。例如,当一个类被声明为 with sharing 的类调用时,可以显式关闭共享规则执行。它最适合需要看到组织所有数据的系统级操作(如触发器和 Web 服务)。
Inherited Sharing
没有显式共享声明的 Apex 类,其共享规则执行是未知的——取决于调用者的共享执行,这是一种安全反模式,应尽量避免。设计可在运行时以 with sharing 或 without sharing 模式运行的类是高级技巧,很难与"意外遗漏共享声明"区分。显式的 inherited sharing 声明澄清了意图,避免省略声明带来的歧义和安全分析工具的误报。
使用 inherited sharing 能让你通过 AgentExchange 安全审查。带 inherited sharing 的类在作为 Aura 组件控制器、Visualforce 控制器、Apex REST 服务或任何 Apex 事务入口点时,以 with sharing 运行。注意:应用的是方法所定义类的共享设置,而非调用类的;此外,使用 Pricebook2 的 SOQL/SOSL 查询会忽略 with sharing 关键字,返回所有 Pricebook 记录。
强制对象和字段权限(Enforcing Object and Field Permissions)
Apex 中的数据操作(SOQL、DML、SOSL)默认以系统模式运行,通常对所有对象和字段拥有完整 CRUD 访问。要防止未授权访问,可以:以用户模式运行操作、给 SOQL 加 WITH SECURITY_ENFORCED、调用 CRUD/FLS 检查方法、用 stripInaccessible 移除隐藏字段。
用户模式操作(User Mode Operations)
2023 年春季(Spring '23),Apex 引入了新的访问级别,让开发者选择执行数据操作的模式:系统模式(默认)或用户模式。以用户模式执行数据操作,确保共享规则、CRUD 和 FLS 权限都被尊重和执行。用户模式操作是避免共享、CRUD 和 FLS 违规的推荐方式。
在用户模式下访问记录(Access Records in User Mode)
用 USER_MODE 关键字的 SOQL 查询,在用户模式下访问记录,确保共享规则、CRUD/FLS 和限制规则都被执行。其原理:查询期间暂时放下系统模式权限,只检索用户可访问的记录,查询执行完成后恢复系统模式。
在用户模式下插入记录(Insert Records in User Mode)
在用户模式下插入记录,确保仅当用户拥有创建新记录的权限、且对字段有编辑权限(FLS 检查)时,插入操作才执行。例如用户想创建一条 Amount 为 $500 的商机——只有用户拥有创建商机和对 Opportunity.Amount 字段的编辑权限时,插入才会继续。
在用户模式下更新记录(Update Records in User Mode)
在用户模式下更新记录,确保用户在记录变化前拥有所需权限——对对象的编辑权限、以及对更新字段的字段级编辑权限。违反权限的更新会被阻止,保护数据安全。
在用户模式下 SOSL(SOSL in User Mode)
SOSL 也能以用户模式运行——对 SOSL 搜索应用 user-mode 关键字,让结果尊重共享和 FLS。在 SOQL、DML 和 SOSL 中一致地使用用户模式,是避免共享、CRUD 和 FLS 违规的最干净方式。
使用 WITH SECURITY_ENFORCED
你可以把 WITH SECURITY_ENFORCED 子句整合进 Apex 代码的 SOQL SELECT 查询,自动验证字段级和对象级安全权限。它扩展到子查询和跨对象关系。放置位置:WHERE 子句之后(若有),或没有 WHERE 时 FROM 子句之后。
限制:多态字段关系遍历不支持(Owner、CreatedBy、LastModifiedBy 除外);带 ELSE 子句的 TYPEOF 表达式不受支持;AgentExchange 安全审查需用 API 版本 48.0+。如果引用的字段或对象用户无法访问,WITH SECURITY_ENFORCED 会抛出 System.QueryException。
使用 CRUD/FLS 检查方法
你也可以显式调用 Schema.DescribeSObjectResult 和 Schema.DescribeFieldResult 的 describe 结果方法,检查当前用户的访问权限级别。例如调用 isAccessible、isCreateable、isUpdateable 方法验证用户对 sObject 的读/建/改权限,isDeletable 检查删除权限——只有在用户拥有必要权限时才执行特定 DML 或查询。
isCreateable()
插入记录前,用 isCreateable() 检查登录用户是否同时拥有对象的创建权限和字段的编辑权限。例如用户需要创建一条 Amount 为 $500 的商机,代码应先检查用户对 Opportunity.Amount 有 isCreateable() 权限。
isAccessible()
从对象读取字段前,用 isAccessible() 验证登录用户是否有权限访问该字段。例如用户想访问商机的 Expected Revenue 字段,代码应先检查用户对 Opportunity.ExpectedRevenue 有 isAccessible() 权限。
isUpdateable()
更新记录前,用 isUpdateable() 检查登录用户是否拥有字段和对象的编辑权限。例如用户想把商机阶段标记为 Closed Won,代码应先检查用户对 Opportunity.StageName 有 isUpdateable() 权限。
isDeleteable()
执行删除操作前,用 isDeleteable() 强制"删除"访问限制。注意:与更新、创建、访问不同,删除只做 CRUD 检查——因为 SOQL 删除整条记录而非单个字段,所以只需检查用户对对象的 CRUD 访问。
使用 stripInaccessible()
stripInaccessible 方法强制字段级和对象级数据保护:从查询和子查询结果中剥离用户无法访问的字段和关系字段,在 DML 前移除不可访问的 sObject 字段以避免异常,并清洗从不受信来源反序列化得到的 sObject。访问检查基于当前用户在指定操作(创建/读取/更新/删除)上下文的字段级权限。
细节:ID 字段永不剥离;用 isSet 方法识别被移除的字段(如自定义字段 social_security_number__c 不可访问时 isSet 返回 false);不支持 AggregateResult sObject。
二、缓解 SOQL 注入(02 Mitigate SOQL Injection)
本单元防范 SOQL 注入:比较 SOQL 与 SQL、展示查询如何被利用,并讲解五种预防技术。学完本单元你将能够:
- 定义 SOQL 与 SQL 的区别;
- 解释 SOQL 漏洞;
- 学会预防 SOQL 注入攻击。
SOQL 与 SQL 的区别(SOQL Versus SQL)
在 Lightning 平台上用 SOQL 而非 SQL。SOQL 是专为 Salesforce 平台开发的定制版 SQL。SOQL 只用于查询数据库,不修改数据。SOQL 没有:
- INSERT、UPDATE、DELETE 语句(只有 SELECT);
- 命令执行;
- JOIN 语句(但可包含父/子相关对象,最多上下 5 层);
- UNION 操作符;
- 串联查询的能力。
SOQL 是否易受注入攻击?(Is SOQL Vulnerable to Injection?)
即使平台有这些限制,错误信任用户输入仍会导致信息泄露——即 SOQL 注入攻击。以 School District Management 开发组织为例:一个自定义页面让用户按 title 或 age 搜索人员,应用把用户提交的 textualTitle 参数直接拼进 SOQL 查询。攻击者可输入 %' and Performance_rating__c<2 and name like '%——关闭单引号并追加条件,产生语法有效的查询,从而访问本应受限的数据(如低绩效员工)。
静态查询与绑定变量(Static Query and Bind Variables)
预防 SOQL 注入的首选方法是使用带绑定变量的静态查询:把用户输入当作变量而非可执行查询元素。若用户输入 test' LIMIT 1,数据库会把它当作字面值搜索,攻击者无法"跳出"并控制查询。绑定变量只能用于:FIND 子句的搜索字符串、WHERE 子句的过滤字面量、WHERE 中 IN/NOT IN 的值列表、WITH DIVISION 子句的部门名、LIMIT 和 OFFSET 的数字值。
类型转换(Typecasting)
另一种预防策略是类型转换:把变量转换为整数或布尔值(适用时),错误输入就不被允许,再用 String.valueOf() 转回字符串插入查询(动态查询的 database.query() 只接受字符串)。例如年龄过滤器:输入 1 limit 1 会因不是整数而报错,注入被阻止。类型转换可预防许多非文本输入的 SOQL 注入。
转义单引号(Escaping Single Quotes)
String.escapeSingleQuotes() 用反斜杠转义字符串中的每个单引号,把攻击者输入限制在字符串边界内,使其无法作为查询控制字符。例如 title 搜索中,把 textualTitle 包在 String.escapeSingleQuotes() 里,之前的低绩效注入载荷就再也无法改变查询行为。注意:此方案只适用于带引号的字符串。
替换字符(Replacing Characters)
当 escapeSingleQuotes、类型转换和允许列表都不适用时,最后一个工具是字符替换(又称黑名单/blocklisting):从用户输入中移除"坏字符"。例如移除年龄过滤输入中的所有空格,注入载荷失去空格后就变得无害。注意:黑名单永远不如允许列表强——预测少数好输入远比预测所有可能的坏输入容易。把它作为特定场景的备用方案。
允许列表(Allowlisting)
在没有单引号、用户控制的是查询结构部分(如 SELECT 字段或 FROM 对象)时,用允许列表:创建一个用户可提供的所有"已知良好"值列表,用户输入其它任何内容都拒绝响应。这无需依赖单引号转义就能防注入。
三、缓解跨站请求伪造(03 Mitigate Cross-Site Request Forgery)
本单元防范 CSRF:了解跨站请求伪造攻击的样子,并用令牌、代码实践和 Salesforce 平台防护来预防。学完本单元你将能够:
- 定义跨站请求伪造(CSRF)漏洞;
- 识别 Lightning Platform 应用中的 CSRF 漏洞;
- 用代码级和组织级防护预防 CSRF 漏洞。
什么是 CSRF?(What Is CSRF?)
CSRF 是一种常见的 Web 应用漏洞:恶意应用让用户的客户端在用户当前已认证的受信站点上执行一个不想要的操作。以 School District 应用为例:Honor Roll 按钮向 /promote?UserId=<userid> 发 GET 请求,页面加载时读取 URL 参数值并自动把学生提升进荣誉榜。
如上图所示,管理员坐在屏幕前,荣誉榜名单在侧栏展示。
攻击场景:登录后你浏览另一个网站,点击一个跳转到 www.beststudents.com/promote?user_id=123 的超链接,这个恶意链接以你(已登录管理员)的身份执行请求,把一个学生提升进荣誉榜而你毫不知情。
如上图所示,CSRF 攻击通过把浏览器重定向到恶意站点而发生。
防止 CSRF 攻击(Prevent CSRF Attacks)
最常见的防御是 anti-CSRF 令牌:要求每个敏感请求都带一个随机、唯一、每次请求都变化的令牌。要成功防御,四件事必须成立:
- 所有改变状态的敏感请求都包含令牌;
- 令牌对请求或用户会话唯一;
- 令牌难以预测(长、用高级加密);
- 服务器验证令牌以确保请求来自预期用户。
如上图所示,要求特定的 UserID 令牌即可防止 CSRF 攻击。
四步都正确实现时,攻击者无法猜出令牌当前值,伪造请求失败,攻击被阻止。
用 Salesforce 平台防范 CSRF
Salesforce 为开发者提供了开箱即用的 CSRF 防护:对 Salesforce 资源的请求默认附带 CSRF 令牌,这些伪随机令牌防止超链接的复用和分发,保护特权账户免受意外状态更改请求。
此外,Lightning 应用开发者要关注代码结构:
- 避免改变状态的 HTTP GET 请求,需要状态更改时用 POST 或 PUT;
- 命中 API 端点时验证 origin 头;
- 用
setRequestHeader()给XMLHttpRequest添加自己的 anti-CSRF 令牌。
Lightning 页面加载事件上的 CSRF
另一个 CSRF 漏洞来源是:服务端 DML 操作作为页面加载事件(如 onInit 或 afterRender)的一部分自动执行。为降低页面加载 CSRF 风险,确保 DML 操作只在用户交互(点击按钮、选择选项等动作)时才执行,而非页面加载的副作用。
四、缓解服务器端请求伪造(04 Mitigate Server Side Request Forgery)
本单元防范 SSRF:了解服务器端请求伪造是什么、攻击者如何滥用服务端请求,以及阻止它的代码和组织级防护。学完本单元你将能够:
- 定义服务器端请求伪造(SSRF)漏洞;
- 识别 Lightning Platform 应用中的 SSRF 漏洞;
- 用代码级和组织级防护预防 SSRF 漏洞。
什么是服务器端请求伪造?(What Is SSRF?)
服务器端请求伪造(SSRF)是 Web 应用的安全漏洞:攻击者以服务器名义发起未授权的内部和外部请求。在 SSRF 攻击中,恶意应用诱骗服务器向内部/外部服务发起请求,可能导致未授权访问或数据泄露。
以 School District 应用为例:应用从 API 请求中的地址(如 studentApi=https://192.168.0.1/student)获取学生详情。攻击者观察到请求中的路径值可利用,把端点替换为云元数据服务的地址(如 169.254.169.254),从内部元数据端点窃取敏感的服务配置数据。
防止 SSRF 攻击(Preventing SSRF Attacks)
防止 SSRF 需要验证并限制请求范围。有效的防御是以下措施的组合:
- 验证和清洗输入;
- 实现允许列表;
- 使用 URL 解析库;
- 网络分段。
验证和清洗输入(Validate and Sanitize Inputs)
确保像 studentApi 这样的输入值被正确验证和清洗,防止恶意 URL 注入。经过正确验证的输入,无法把服务器指向意外的内部地址。
实现允许列表(Implement Allowlisting)
限制出站请求可去的目的地:强制 URL schema、端口、目的地允许列表,并禁用 HTTP 重定向。只允许向指定的、受信任的端点发请求。
使用 URL 解析库(Use URL Parsing Libraries)
发请求前用 URL 解析库解析并验证 URL,确保请求的 URL 符合预期模式、尽早捕获畸形或恶意 URL——在任何出站请求发出之前加一层验证。
网络分段(Network Segmentation)
实施网络分段,限制服务器访问内部资源的能力,缩小任何潜在 SSRF 攻击的影响范围——即使攻击得手,也局限于被分段的那部分网络。
Salesforce 平台对 SSRF 的防护
Salesforce 提供内置的 SSRF 防护:对 Salesforce 资源的请求包含防止 SSRF 漏洞利用的保护。Lightning 开发者可用以下最佳实践进一步降低风险:
- 避免 GET 请求,优先 POST 或 PUT;
- 与第三方 API 集成时验证 origin 头;
- 用
setRequestHeader()给XMLHttpRequest添加自定义 anti-SSRF 令牌。
避免 GET 请求(Avoid GET Requests)
优先使用 POST 或 PUT 而非 HTTP GET:GET 请求更容易被伪造和复用,通过 GET 改变状态或获取数据的调用可能泄露数据。用 POST 或 PUT 可最小化 SSRF 数据外泄的风险。
验证 Origin 头(Validate Origin Headers)
与第三方 API 集成时,验证 HTTP 请求的 origin 头,确保请求来自受信任来源,阻止看似来自别处的伪造请求。在服务端端点加 origin 检查。
实现反 SSRF 令牌(Implement Anti-SSRF Tokens)
在 Lightning 中用 setRequestHeader() 给 XMLHttpRequest 添加自定义 anti-SSRF 令牌,增加一层保护——攻击者更难向你的服务器构造有效请求。
Lightning 页面加载事件上的 SSRF
SSRF 也可能发生在页面加载事件(onInit 或 afterRender)期间由服务端 DML 操作触发。缓解:让 DML 操作只在用户交互(点击按钮、选择选项)时启动,避免页面加载本身驱动服务器请求。











































