文章目录
一、失效的访问控制
1、什么是访问控制
访问控制是一种策略,在这种策略的控制下,用户的操作不能逾越预设好的权限边界。而访问控制一旦失效通常会导致未认证信息泄露、内部数据篡改、数据删除和越权操作等后果。访问控制失效型问题通常有以下几种类型:
- 系统在实现过程中违背了“最小权限原则” 或 “默认拒绝原则”,在这种情况下用户可以获得一些特殊权限,而这些特殊权限原本只应该授权给特定的用户或角色;
- 通过修改 URL 地址、内部程序状态、HTML 页面,或者使用 Cyber 工具修改 API 请求的方式绕过访问控制;
- 通过提供唯一 ID 的方式预览或者修改其他账户信息及数据;
- 未经过访问控制地通过 POST、PUT 和 DELETE 方法访问 API;
- 通常意义上的提权,比如未登录状态下的用户操作,或者常规用户登录状态下的管理员操作;
- 元数据操纵,比如重放或者修改 JWT(JSON Web Token)访问控制令牌,或者通过操纵 Cookie 的方式进行提权;
- CORS 误配置,可以导致来自未认证源的 API 访问。
2、常见攻击场景
-
用户轻松的修改URL的参数内容,从而轻而易举的访问到admin页面。一下是两个假设URL,第一个是普通用户,第二个是管理员用户页面,如果普通用户访问第二个页面成功,那就说明访问控制策略存在问题。
https://example.com/app/getappInfo https://example.com/app/admin_getappInfo
-
由于实现过程中未对用户访问参数设置边界,导致了很多越权问题的发生,下方示例URL中,攻击者可以尝试修改 API 接口中的 order_id 参数,使其在程序接口上的输入合法,但是对于用户而言却是越权行为。
https://example.com/order/?order_id=2021102617429999
-
HTTP PUT 方法最早目的用于文件管理操作,可以对网站服务器中的文件实现更改删除的更新操作,该方法往往可以导致各种文件上传漏洞,造成严重的网站攻击事件,下方示例代码,在支持 PUT 方法的环境中,上传 Webshell 进行提权;在实际运用中,若必须启用该方法,则需要对该方法涉及文件资源做好严格的访问权限控制。
put /root/Desktop/shell.php
-
Web 应用将身份认证结果直接存储在 Cookie 中,并未施加额外的保护措施,下方示例中,攻击者通过web前端拦截cookie,实现对cookie的修改,从而达到未授权访问的效果:
Cookie: role=user --> Cookie: role=admin
-
有些开发者为了方便,直接在 Access-Control-Allow-Origin 中反射请求的 Origin 值。比如如下配置,意味着nginx相信任何网站,允许所有访问跨域读取器资源,造成隐私数据被窃取。
add_header "Access-Control-Allow-Origin" $http_origin; add_header “Access-Control-Allow-Credentials” “true”;
3、修复方案
-
优先开始设计访问控制体系
访问控制不仅是应用安全设计的一项主要事务,而且应当被设置在非常优先的位置,因为往往访问控制的设计在起步阶段是相对简单的,但是会很快随着功能点的增多快速复杂化。所以,如果你考虑使用成熟的软件框架来完成访问控制,一定要确保其能够满足你未来的应用定制化需求。
-
强制所有请求优先经过访问控制检查
可以这种方法开发一个访问控制检查层(Layer),然后确保所有请求都在某种程度上经过这个检查层。以 Java 的 filter 为例,许多自动化的请求处理机制都是能够帮助我们实现这种需求的技术形态。
-
默认拒绝
这是非常简单但是有效的策略,所谓默认拒绝是指,只要一个请求没有被指明是被允许的,那么它就是被拒绝的。
-
记录所有的访问控制类事件
所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。
二、路径穿越
1、什么是路径穿越
简单来说,你所构建的系统中有一个功能组件使用外部输入来构建文件名,而这个文件名会用来定位一个在受限目录的文件,如果文件名中既包含一些特殊元素,又没有进行合理的过滤处理,就会导致路径被解析到受限文件夹之外的目录。
扩展开讲一讲,很多系统内部的文件操作都希望被限制在特定目录中进行。通过使用…以及/符号,攻击者可以进行文件路径逃逸。其中最常见的符号组合是…/,这种符号组合在操作系统中会被解析为上级目录,这种漏洞被称为相对路径穿越。绝对路径穿越是另一种类型的路径穿越,比如/usr/local/bin就是典型的例子。
2、路径穿越的实例场景
实例1:
一种典型的社交网络应用代码,每个用户的配置文件都被存储在单独的文件中,所有文件被集中到一个目录里:
$dataPath = "/users/example/profiles";
$username = param("user");
$profilePath = $dataPath . "/" . $username;
// 可以发现并没有对用户传入的username参数进行验证
open( $fh, "<$profilePath") || ExitError("profile read error: $profilePath");
print "<ul>\n";
while(<$fh>) {
print "<li>$_</li>\n";
}
print "</ul>\n";
当用户尝试访问我们的的配置文件的时候,会组成如下路径:
/users/example/prfiles/hunter
但是这里并没有对我们的输入的访问文件名进行过滤,攻击者如果抓包修改访问文件问../../../../../../etc/passwd
这样的文件路径,完整路径如下:
/users/example/prfiles/../../../../../../etc/passwd ==》/etc/passwd
从而导致了路径穿越。
实例2:
下面这个代码在编写过程中考虑到输入的不安全性,采用了黑名单方式,过滤掉了输入中包含的../
字符,但是可见并没有抵用-g来进行全局参数匹配:
$username = GetUntrustedInput();
// 这里是老师写的注释
// 黑名单方式过滤
// 对username的过滤不严格
$username = ~ s/\.\.\///;
$filename = "/home/user/" . $username;ReadAndSendFile($filename);
所以被黑名单过滤了第一个../
之后,后续的../
还是会继续拼接到路径中造成路径穿越。
实例3:
如下代码,虽然采取了白名单,但是依然存在路径穿越的问题:
String path = getInputPath();
// 这里是老师写的注释
// 白名单方式过滤
// 对path的限制不够严格
if (path.startsWith("/safe_dir/"))
{
File f = new File(path);
f.delete()
}
当攻击者的攻击payload是/safe_dir/../../../../../../../etc/passwd
这种形式的时候,依然后造成路径穿越。
3、路径穿越的修复方案
- 在编码阶段进行实现:
- 假设所有的输入都是恶意的,使用“只接受已知的善意的”输入检查策略,也就是使用一些定义清晰且严格的参数格式;
- 输入都应该被解码为程序内部的处理格式,并且确保在应用系统没有被二次解码,防止攻击者通过编码或者二次编码进行绕过;
- 如果可能,为用户提供选项或者通过应用系统内部 ID 映射的方式进行对象访问,例如 ID 1 对应“info.txt”;
- 确保 Error Message 只包含最小必要信息,避免过于详细的信息展示,防止攻击者因此获取系统相关信息。
- 在架构设计阶段实现
- 确保所有客户端发生的安全检查,都在服务端完成第二次检查,这样做的目的是防止攻击者在客户端进行安全检查绕过;
- 使用成熟的库或者框架来使开发者更容易规避这种特定类型的风险。
- 在防御建设阶段
- 使用可以防御这种类型攻击的应用层防火墙,在某些特定情况下(比如应用系统漏洞无法修复)非常有效;
- 使用最小权限运行开发完毕的应用系统,如果可能,创建独立的受限账户用于应用系统运行;
- 沙箱环境运行开发完毕的应用系统,做好进程和系统之间的边界隔离
三、敏感信息泄露
1、什么是敏感数据泄露
简单来说,如果我们的应用系统向一个未得到访问授权的用户暴露了敏感信息,那么这就是一种敏感数据泄露风险。
敏感数据泄露即使在今天仍然是相当严重且普遍存在的一个风险点,主要原因是数据泄露并非一个纯粹的技术性问题,很多时候与业务流程、功能设计都息息相关。
单纯从漏洞危害程度来看,敏感数据泄露主要分为两种,一种是业务敏感数据泄露,另一种是技术敏感信息泄露。业务敏感数据的泄露危害性是巨大的,会直接影响到公司的品牌和业务运行;技术敏感信息泄露往往不能对应用系统安全性产生直接威胁,但配合其他漏洞的综合利用可以实现 1+1>2 的效果。
2、敏感数据泄露的产生场景
- 应用系统设计阶段逻辑问题;
- 异常处理输出不当;
- 应用部署时未关闭调试开关;
- 权限获取过多。
3、敏感数据泄露的防御方案
由于数据泄露风险的特殊性质,主要是很多时候风险来源是逻辑层面、设计层面或者权限层面的安全问题,导致传统的防御方案和扫描器等很难发挥作用,也正因如此导致其成为了排名第一的风险种类。
在开发及设计阶段,可以考虑引入威胁建模及审计工具,来协助我们发现在逻辑层面以及设计层面引入的安全风险。通过对系统功能以及业务流程的威胁建模,可以帮助我们消除许多常见的安全隐患。
四、权限配置不当
1、什么是权限配置不当
权限不合理简单来说,是不合理的权限赋予、权限处理以及权限管理过程。这里所说的权限,指的是终端角色的一种属性。
那么什么是终端角色呢?你可以理解为,用户就是一个终端角色。与权限相关的赋予、处理以及管理过程,我们主要通过权限管理来统一实现。
权限管理就是能够赋予终端执行某种特殊操作的权利,比如在某些运维场景下,运维人员能够获得系统维护的权限,这其中就包括重启服务器权限——我们都知道服务器重启可不是常规操作权限。
2、权限配置不当的漏洞场景
场景一:高权限运行服务
在安装和运行组件的过程中,某些程序组件的运行环境设置的权限过高,导致低权限应用通过服务调用关系可以完成提权操作。比如我们的web服务器,如果直接一root权限或者administrator权限运行,一旦web应用出现漏洞,被getshell之后,就会导致root权限被直接获取。这一类的问题大多数发生在运维层面。
场景二:降权异常
实例代码:
def makeNewUserDir(username):
...
try:
raisePrivileges()
os.mkdir('/home/' + username)
lowerPrivileges()
except OSError:
return False
...
上述代码包含了一次短暂提权,开发者在完成目标操作后立即进行了降权,但要注意的是 username 作为一个外部输入的参数,可能由于各种原因(输入不合法、安全过滤不严格等)导致 mkdir 函数报错进而抛出异常,一旦触发这种情况 lowerPrivileges 函数就无法得到执行,程序将持续以高权限状态运行,可能会为后续漏洞利用过程提供舒适的环境。
3、权限配置不当的修复方案
针对权限相关的安全问题,在三个不同的阶段,我们分别有不同的方式加以防御和检测。
- 在架构设计阶段,你可以使用“最小权限原则”来运行你的代码,如果可能的话,为你的任务代码创建一个独立的、拥有受限权限的账户。
- 在开发实现阶段,你需要对于高权限代码段要给予足够的关注,在输入检测层面要提供更严格的审核以及限制策略。
- 在系统配置阶段,对于复杂应用系统,你要确保配置文件得到良好的审计,配置文件往往会大幅度影响应用系统的权限级别。
五、CSRF漏洞
1、什么是CSRF漏洞
CSRF 的全名是 Cross-Site Request Forgery,中文名称是跨站点请求伪造,简单来说,就是让 Web 应用程序不能有效地分辨一个外部的请求,是否真正来自发起请求的用户,虽然这个请求可能是构造完整、并且输入合法的。
当一个 Web 应用在设计过程中没有充分考虑来自客户端请求的验证机制时,就可能会遇到 CSRF 问题。站在攻击者的视角来看,他可以通过一个 URL、图片加载或者 XMLHttpRequest 等方式,让用户触发一个自动化请求发送行为,这个请求在 Web Server 接受时会被认为是合法的。
2、CSRF漏洞的产生场景
如下代码,是让用户更新自己的信息:
<form action = "/url/profile.php" method = "post">
<input type = "text" name = "firstname" />
<input type = "text" name = "lastname" />
<br/>
<input type = "text" name = "email" />
<input type = "submit" name = "submit" value = "Update" />
</form>
其中profile.php包含如下代码:
// initial the seesion in order to validate sessions
session_start();
// if the session is registered a valid user the allow update
if ( !session_is_registered("username") )
{
echo "invalid session detected!";
// Redirect user to login page
...
exit;
}
// The user session is valid, so process the request
// and update the information
update_profile();
这里的 PHP 代码中是包含了一些保护措施的,结合我们前面几节课程学到的内容来看,它包含了用户身份的有效性认证,阻止了越权访问。但是上述代码并不能够有效地防止 CSRF 攻击,如果攻击者可以构建下面这段代码,并且将它托管到某个站点,那么当用户保持登录状态并且访问攻击代码页面时,就会触发攻击代码:
<script>
function attack()
{
form.email = "attacker@example.com"
form.submit();
}
<script>
<body onload = "attack()">
// ...
</body>
可以看到,上述攻击代码包含了用户在使用浏览器时不可见的内容,当攻击代码在浏览器中加载时,会触发 attack 函数。如果用户在访问受害网站时保持的登录状态,受害网站就会收到来自用户的请求,请求内容是将 E-mail 更新为攻击者的邮件地址.
通过上述典型的攻击代码,我们可以总结出几点 CSRF 攻击特征:
- 攻击一般发生在跨域场景下,主要原因是外域相较于被攻击目标通常安全级别更低,攻击者更容易控制;
- CSRF 在攻击过程中事实上并没有获取到用户的登录凭据,只是借用户之手发送了恶意的请求;
- 攻击者可以采用的方式有很多:图片 URL、超链接、表单提交等许多方式。案例实战
3、CSRF漏洞的防御方法
-
同源策略
该防御策略的产生主要为了针对 CSRF 攻击的第一个特征——跨域场景,它的设计思路主要是禁止外域(或者不受信任的域名)对 Web Server 发起请求。在 HTTP 协议中,有两个 Header 字段可以用来帮助我们判断来源域:Origin Header 和 Referer Header。这两个字段在浏览器发送请求时会自动携带,并且不能由前端修改。
-
Token
CSRF Token 如何实现呢?为每一个 form 表单生成唯一的 token,并且在 form 提交时验证 token,就是 CSRF Token 的实现思路,但是 token 需要保证不可预测。在代码实现上主要有 2 种思路。 第一种是在用户访问页面时,由服务器生成 Token,将生成的 Token 存放于 Session 中,一般 Token 生成时会通过加密算法实现,输入一般包括随机字符串、时间戳等. 第二种是通过JS遍历DOM树结构来插入token。
-
安全的接口设计
现在的防御方案,主要考虑的是如何防止跨域的 CSRF。因为攻击者无法获取到 Token,所以大家会普遍认为,本域发生的 CSRF 暂时是安全的。但是,如果 XSS 和 CSRF 问题同时在本域发生,由于 XSS 可以让攻击者获取 Token,CSRF 的防御就宣告失效。因此我们需要在 Web 应用设计和开发过程中,严格过滤用户的输入,确保用户不能够输入我们不希望出现的内容,这样可以同时规避掉 XSS 和 CSRF 安全风险。
-
双重cookie
在 Web 应用开发中新增 CSRF_Token 机制还是稍有些麻烦,那么我们该如何通过现有的组件,来实现 CSRF 防御方案呢?答案是双重 Cookie。 当用户访问 Web 网站时,Web 应用为用户随机生成一个新的 Cookie 值,当 Web 应用每次执行表单提交操作时都需要携带这个 Cookie 值;由于同源策略的保护,攻击者无法获取或者修改这个 Cookie 项,因此实现了 CSRF 的保护。