通过Salesforce,我们可以配置或开发出功能强大的网络应用。与此同时,无论作为管理员还是开发者,我们都要面对数据安全的问题。
常见的数据安全隐患有:
- SQL注入
- 跨站脚本攻击
- 跨站请求伪造
- 点击劫持
- 重定向攻击
本文将阐述在Salesforce中对于上述隐患的基本防护措施。
SOQL注入
SQL注入是一种常见的攻击方式。Salesforce中使用了类似于SQL的数据库查询语言SOQL,同样存在注入攻击的风险。
实例
假设系统提供给用户的功能是查询所有金额少于某个数值的记录,用户可以输入数值进行查询。
当用户输入数值之后,系统会用类似于下面的SOQL进行查询。
SELECT Id, Name, Amount
FROM Certain_Object__c
WHERE Amount < 1000
其中的1000就是直接来源于用户的输入。
那么当用户输入“1000 LIMIT 1”的时候,查询语句将变为:
SELECT Id, Name, Amount
FROM Certain_Object__c
WHERE Amount < 1000 LIMIT 1
此时,系统总是只能返回一个结果,功能被破坏了。
防护方法
Salesforce提供了多种方法来过滤用户的输入,避免SOQL注入攻击的情况出现。它们多用于Apex代码中。
静态查询和参数绑定
如果在代码中预先设定好了SOQL的语句,并且将之与用户输入拼接形成最终的查询语句,使用静态查询和参数绑定的方法可以避免注入攻击。
比如:
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + userInputString + '\'';
result = Database.execute(query);
这段查询代码信任了用户的输入,从而有了被注入攻击的风险。
比如用户输入是:“test' LIMIT 1”,“test”后面的单引号就将查询语句里的WHERE部分结束,而将LIMIT部分加入了查询。注入攻击的结果就是每次返回的记录只有一条。
使用静态查询和参数绑定,改写上面的代码:
result = [SELECT Id, Name FROM Account WHERE Name = :userInputString];
这段代码将用户的输入作为参数绑定到查询语句中。当用户输入包含恶意代码时,它并不会拼接到前面的查询中,而是作为用户输入的一部分进行正常查询。
当用户输入是“test' LIMIT 1”时,系统会将其看作一个整体,单引号不会将WHERE部分结束,后面的LIMIT的部分也不会被执行了。
数据类型转换
Salesforce提供了几种数据类型转换函数,可以安全的将用户输入转换为相应的数据类型。
比如:
result = string.valueOf(userInputString)
可以将用户输入userInputString变为一个字符串,以便放入查询语句中使用。
过滤单引号
Salesforce提供了一个函数将字符串中的单引号全部过滤。
result = string.escapeSingleQuotes(userInputString)
这个函数的返回结果是将用户输入userInputString中的所有单引号前面都加上转义符“\”,保证查询语句的安全。
跨站脚本攻击(XSS)
跨站脚本攻击全称Cross-site scripting。攻击者将恶意代码注入网页,当用户浏览网站时,恶意代码就会自动执行。攻击成功后,攻击者将可能获得更高的权限、可以查看session、cookie或其他私密内容。
XSS的恶意代码多数是由于网页上对于用户的输入不加检查而导致。比如攻击者将恶意代码输入表单,在未经检查的情况下被保存到数据库中,等待其他页面读取它,然后执行。
XSS的基本防护措施
XSS的基本防护措施分为两方面:
- 输入过滤:一般的编程语言都有相应的函数来过滤用户的输入,将可能存在的恶意代码转化为普通字符串,从而使其在被读取的时候无法执行
- 输出编码:输出编码的目的是防止已经存在于系统中的恶意代码无法被执行。当网页需要输出内容时,已经存在的恶意代码会经过编码转化为普通的字符串,从而无法执行
Salesforce中XSS的防护措施
为了避免将用户的某些输入错误的过滤掉,Salesforce默认对于用户的输入不会进行输入过滤,而在做输出时,总是会执行输出编码用来避免XSS攻击。
HTML自动编码机制
Visualforce中“apex”命名空间下的标签在执行输出时,会将输出的字符串自动进行HTML编码。比如:
<apex:outputText value="{!$CurrentPage.parameters.name}" />
如果调用这个元素的URL中加入了带有“<”和“>”的“script”标签(可能造成JavaScript代码注入),那么Salesforce页面的输出则是:
<script>
而其HTML源码则是:
<script>
在这种情况下,HTML的标签起止符“<”和“>”被替换成了“<”和“>”,从而避免了注入的JavaScript代码的执行。
apex:outputText相关知识
在Visualforce页面中,“apex:outputText”标签可以将其中的内容使用一个“span”标签包括起来,并且对HTML编码进行自动转换。这样就可以对XSS进行防护。
另外,<apex:outputText>中也可以接受参数。比如:
<apex:outputText value="This is {0} text with {1}">
<apex:param value="the" />
<apex:param value="parameters" />
</apex:outputText>
使用的规则是:
- 在“apex:outputText”标签中的value属性中使用大括号和数字来对参数进行占位
- 使用“apex:param”标签来定义参数的值,多个参数按照顺序替换value属性中的占位符
上面的代码内容当显示在页面中时,其后台的HTML源码是:
<span>This is the text with parameters.</span>
关于“apex:outputText”更详细的讲解可以参考官方文档
关闭HTML自动编码机制
有时页面中需要输出原始的HTML内容,那么在“apex:outputText”元素中将“escape”属性设置为“false”即可。
<apex:outputText escape="false">
{!$CurrentPage.parameters.htmlStringToOutput}
</apex:outputText>
当然,此时必须有其他对于XSS的防护措施来确保输出的原始HTML内容是安全的。
非HTML内容的防护
Salesforce对于非HTML内容并没有默认的编码机制。比如在JavaScript代码中使用用户输入的数据时,该数据不会被编码。
<script>
var x = '{!$CurrentPage.parameters.stringFromUser}';
</script>
在上面的代码中,用户输入的字符串“stringFromUser”不会被自动编码,从而形成了一个安全漏洞。
Visualforce提供的编码函数
对于非HTML的内容,Visualforce提供了一些编码函数来帮助开发者防止XSS攻击,主要包括:
- JSENCODE()
- HTMLENCODE()
- JSINHTMLENCODE()
JSENCODE()
JSENCODE()函数主要用于JavaScript代码中。它的主要功能是在特殊字符的前面加上转义符“\”。
<script>
var x = '{!JSENCODE($CurrentPage.parameters.stringFromUser)}';
</script>
在上面的代码中,用户输入的字符串“stringFromUser”会被自动编码,避免了恶意代码的自动执行。
假设用户输入的字符串“stringFromUser”是:
dummy';alert("dummy text");
如果不用JSENCODE(),alert函数会被自动执行。
HTMLENCODE()
HTMLENCODE()主要用于将HTML内容编码,用于当Salesforce自动的HTML编码机制被“escape”属性关掉时。
<apex:outputText escape="false">
{!HTMLENCODE($CurrentPage.parameters.htmlStringToOutput)}
</apex:outputText>
在上面的代码中,字符串“htmlStringToOutput”是有可能造成攻击的,需要使用HTMLENCODE()来避免。
JSINHTMLENCODE()
HTMLENCODE()和JSENCODE()可以组合使用,HTMLENCODE(JSENCODE())。这等价于另一个函数:JSINHTMLENCODE()。
当需要同时对HTML和JavaScript编码时,可以使用任意一种方式。
Apex中的编码
Salesforce并不鼓励在Apex代码中对数据内容进行编码,因为数据数据的输入输出应该交由前端来处理,Apex代码并不需要关心数据内容本身。
跨站请求伪造
跨站请求伪造(Cross-site request forgery),简称CSRF或XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
摘自维基百科:
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
举个例子:
假如一个银行网站转账请求的URL地址是:(URL-1)
https://www.example.com/transfer?username=alice&amount=1000&to=bob
那么攻击者可以在网上放置这样伪造的URL地址:(URL-2)
https://www.example.com/transfer?username=alice&amount=1000&to=charlie
当一个用户登录了银行网站,其用户名是alice,在尚未登出时访问了攻击者的站点,点击了伪造的URL链接(URL-2),那么其银行账户会自动转账1000给用户名为charlie的用户。
基本防护方法
CSRF的一般防护方法是在URL的参数中加入一个口令(token),口令的值是很长的字符串,并且随着每次请求随机生成。服务器在接受请求时也会验证口令。这样的话攻击者就无法猜到准确的URL,也就无法利用伪造的URL地址来攻击了。
Salesforce中的设置功能自带了关于CSRF攻击的防护设置。在设置界面的“会话设置”连接中,可以进入“会话设置”的界面。在其中可以设置多种安全保护措施。在“跨站请求伪造 (CSRF) 保护”一栏,可以设置是否启用CSRF保护。
在Apex和Visualforce中防护跨站请求伪造
在Visualforce页面中,有一种情况是Salesforce中CSRF防护设置无法保护的,比如下面的Visualforce页面(以下称作“页面1”):
<apex:page controller="ExampleController" action="{!initFunction}">
<!-- ... -->
</apex:page>
与之对应的Apex类:
public class ExampleController {
public void initFunction() {
String id = ApexPages.currentPage().getParameters().get('UserId');
// 使用UserId发送请求
}
}
页面1的功能是直接读取URL中的UserId参数,然后利用该参数发送请求。当在另一个Visualforce页面(以下称作“页面2”)中使用“outputLink”链接到页面1时:
<apex:outputLink value="/apex/Example?UserId={!person.Id}">
Click
</apex:outputLink>
因为函数“initFunction()”是定义在页面1的“apex:page”标签的“action”属性中的,所以该函数会在页面1载入浏览器之前就执行,所以它会跳过在设置界面中设置的CSRF防护措施。
要让CSRF防护措施执行,需要将页面2中的“outputLink”链接变为“commandLink”,即:
<apex:commandLink value="Click" action="{!alternativeInitFunction}">
<apex:param name="uId" value="{!person.Id}" assignTo="{!curUserId}" />
</apex:commandLink>
将页面1中的“initFunction”的功能挪到“commandLink”定义的“alternativeInitFunction()”函数中,并传入相应的参数。然后将页面1中的“apex:page”标签中的“action”属性去掉。
这样做的结果是:本来在页面1中刚开始就要执行的功能和请求被放在了页面2的“commandLink”链接中,而后者因为页面2肯定已经被载入了,所以在执行功能和发送请求时,会调用Salesforce中设置的CSRF防护措施。
这两种流程总结对比一下:
- 使用outputLink:先重定向到新的页面,在页面载入之前执行outputLink的action属性中定义的函数,在函数中会发送请求,发送的请求无法使用CSRF防护措施,函数执行完毕后才载入页面
- 使用commandLink:先用Ajax异步执行commandLink的action属性中定义的函数,此时因为当前页面时已经载入了,所以可以使用CSRF防护措施,然后通过链接重定向到新的页面并载入
点击劫持
维基百科上对“点击劫持”是这样定义的:
点击劫持(clickjacking)是一种在网页中将恶意代码等隐藏在看似无害的内容(如按钮)之下,并诱使用户点击的手段。
举个例子:在网页上,攻击者将一个包含恶意链接或其他恶意内容的iframe元素设置透明度为0,并显示在网页正常内容的上方。由于iframe的透明度为0,用户无法看到其中的恶意内容,只能看到网页上的正常内容。当用户在看似正常的内容上面点击时,实际上点击的是包含恶意内容的iframe元素,从而被攻击者利用,遭受损失。
在Salesforce的设置页面中,进入“会话设置”的界面。在其中可以设置是否启用“Clickjack 保护”。
重定向攻击
在浏览网站时,用户需要页面重定向功能。重定向功能往往体现在URL的参数中,比如
https://www.example.com?returnURL=https://www.exampleWebsite.com
当用户使用此URL时,页面会自动重定向到“https://www.exampleWebsite.com”中。
当攻击者将重定向的链接改为了恶意网页,则该URL便成为了网络攻击的工具。
Salesforce中的页面重定向功能
在Salesforce中,提供了标准的页面重定向参数:
- startURL:当页面被载入时重定向到指定的URL
- retURL:当用户点击“后退”按钮时重定向到指定的URL
- saveURL:当用户点击“保存”按钮时重定向到指定的URL
- cancelURL:当用户点击“取消”按钮时重定向到指定的URL
当这些参数的值可以被攻击者利用时,比如参数的值从用户输入中得到,那么Salesforce的网页就存在被攻击的风险。
重定向攻击防护
在Salesforce中,可以采取以下几种方法进行重定向攻击防护:
对重定向进行硬编码(Hardcode)
通过对重定向进行硬编码,可以完全防止用户更改重定向链接。但是这会损失灵活性。
只重定向到本地
另一种方法是强迫所有重定向链接只能重定向到同一个域名下,比如只能重定向到“xxx.salesforce.com”下,这样的话可以保证无论用户如何更改重定向链接,被重定向的链接总是处于开发者的控制下。
假设在一个Visualforce页面中包含一个“urlToRedirect”参数,它来源于用户的输入,然后该页面会重定向到“urlToRedirect”。那么我们可以用以下代码强制用户的输入重定向到当前的域名下:
PageReference finalPage;
// 得到用户的输入
String urlToRedirect = ApexPages.currentPage().getParameters().get('urlToRedirect');
// 清除用户输入开头的“/”符号
if(urlToRedirect.startsWith('/') {
urlToRedirect = urlToRedirect.replaceFirst('/', '');
})
// 将页面的重定向设定到当前域名下,保证用户转到的页面是当前网站下的某一个页面(或不存在的页面)
finalPage = new PageReference('/' + urlToRedirect);
finalPage.setRedirect(true);
设定域名白名单
如果开发的过程中的确需要重定向到外部网站,则可以在代码中手动建立一个字符串列表,作为可信的域名白名单。当用户的输入包含白名单中的域名时,允许页面重定向到外部网站。
Salesforce中没有预设此类功能或函数,需要开发者自己实现。
总结
以上介绍了在Salesforce中对于常见网络攻击的防护措施,并侧重于在Apex和Visualforce中的实现。
随着技术的发展,这些防护措施可能会过时。所以,追踪数据安全的最新动态、养成时刻考虑数据安全的习惯更加重要。
如果想对数据安全(不光是Salesforce)进行更多的学习和了解,在此推荐OWASP组织的网站。