freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

实践分享-以多种伪造类漏洞分析(CSRF、SSRF等)伪造类漏洞的挖掘与防御逻辑
2024-08-15 16:01:12

伪造之于安全

信息安全的三大特性:机密性、完整性、可用性,而依据STRIDE模型,信息安全面临的八大威胁其中之一即为伪造。

伪造,顾名思义即为伪装、假装真实,在安全领域即为假装正常的用户(含cookie、token等验证机制)、假装正常的行为(含非法输入),即通过欺骗手段来模拟或伪装成另一个实体的行为,目的是绕过安全机制或获取不正当的访问权限。那么无论是注入类、劫持类漏洞,我私以为都可以归为伪造类漏洞的范畴,因为其本质都是伪造请求,无论主体是客户端还是服务端,通过恶意的方式伪造请求,达到恶意的目的。

伪造类漏洞种类具体如下:

这里我个人将其分成三大类,即客户端伪造、服务器端伪造、传输层伪造,其中IP、cookie、浏览器等都归属于客户端属性,DNS、服务器返回值等归属于服务端属性,协议、请求方式等归属传输层属性,具体如下:

1. IP地址伪造(IP Spoofing):攻击者伪造IP数据包的源IP地址,可以隐藏真实IP或发起中间人攻击。——客户端伪造(IP作为客户端的一个伪造特征)

2. 电子邮件伪造(Email Spoofing):通过修改电子邮件头部的发件人地址,攻击者可以伪装成可信的发件人,诱使接收者打开恶意链接或附件。——客户端伪造(发件人地址作为客户端的一个伪造特征)

3. DNS欺骗(DNS Spoofing):攻击者通过伪装DNS响应,将域名解析到攻击者控制的服务器,导致用户访问假冒网站或服务。——服务端伪造(DNS响应作为服务端伪造特征)

4. ARP欺骗(ARP Spoofing):在局域网中,攻击者通过发送伪造的ARP应答,使其他设备错误地将攻击者的MAC地址与某个IP地址关联,从而截获或篡改流量。——服务端伪造(ARP响应作为服务端伪造特征)

5. 点击劫持(Clickjacking):攻击者使用透明的覆盖层或iframe,诱使用户在不知情的情况下点击网页上的某个按钮或链接,执行恶意操作。——客户端伪造(前端JS作为客户端的一个伪造特征)

6. 跨站请求伪造(CSRF):攻击者利用用户的登录状态,通过第三方网站向用户已登录的网站发送请求,执行非用户意愿的操作,如资金转移、信息修改等。——服务端伪造

7. 跨站脚本攻击(XSS):分为反射型、存储型和DOM-based XSS。攻击者注入恶意脚本到页面中,当其他用户访问这些页面时,脚本在用户的浏览器中执行,可能盗取Cookie、会话信息等。——客户端伪造(前端JS作为客户端的一个伪造特征)

8. 服务器端请求伪造(SSRF):攻击者利用服务器应用程序的漏洞,让服务器发起对任意服务器的请求,可能用于访问内部系统、端口扫描或发起DDoS攻击。

9. 会话劫持(Session Hijacking):攻击者通过盗取或预测用户的会话标识(如Cookie或Session ID),接管用户的会话,获取用户的访问权限。——客户端伪造(用户会话标识作为客户端的一个伪造特征)

10. Wi-Fi伪造(Fake Wi-Fi Access Points):攻击者设置伪造的Wi-Fi接入点,诱导用户连接,然后进行数据截取、会话劫持或钓鱼攻击。

11. HTTP请求走私(HTTP Request Smuggling):攻击者利用HTTP请求在不同服务器软件解析上的差异,将多个请求伪装在一个请求中发送,可能导致安全漏洞或数据泄露。——传输层伪造

12. SSL/TLS证书伪造:攻击者创建伪造的SSL/TLS证书,用于中间人攻击,使加密通信被解密和篡改。——传输层伪造

12. 传参伪造:攻击者使用异常的请求传参,如Get转为Post,达到攻击效果——传输层伪造

伪造类漏洞攻击与防御拆解:

前面提到伪造类漏洞我个人拆解为客户端、传输层、服务端,三个主体的伪造,接下来我也将分别以这三个主体分别拆解伪造的风险与防御的逻辑(个人经验,风险可能会有遗漏,但是底层逻辑是相通的,如果有遗漏欢迎各位师傅评论区补充)

客户端:

风险:

IP、浏览器、APP(含cookie、token、会话标识、请求头等)等、用户输入(含API)

防御措施:

IP白名单、输入验证/过滤、cookie最佳安全实践(httponly、secure、samesite属性等)、最小权限、多因素认证

传输层:

风险:

中间人劫持、未加密的传输通道(HTTP)

防御措施:

HTTPS、HSTS、单向证书和双向证书校验(APP端)

服务器端:

风险:

服务端劫持(ARP欺骗、DNS欺骗)、请求伪造(SSRF)等

防御措施:

白名单、输入校验等

以CSRF分析客户端注入类漏洞与防御逻辑:

CSRF原理

举一个常规的银行转账的例子来简单说说csrf:

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

  1. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  2. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  3. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

那么这里攻击成功的本质就是:当用户已经通过了站点的认证,站点无法区分合法请求和伪造请求,导致攻击成功,其内核为:cookie伪造成功。而成功后的危害也有很多,这里我不详细说了,接下来直接分析防御措施

CSRF防御:

前面提到,CSRF的攻击本质是cookie伪造且通过了后端验证,那么防御的核心就是让cookie伪造不成功或无法通过后端验证,具体包括cookie最佳安全配置、令牌校验、请求头校验等

具体防御基线如下:

  • 使用框架内置的CSRF防护并确认属于开启状态。如spring security、springboot、django都配置有CSRF防护基线
  1. Spring Security:从Spring Security 4.x开始,默认启用CSRF保护。如果需要,可以通过配置禁用此功能。在客户端,需要在请求中包含CSRF Token。如果使用HTML表单,可以在表单中添加一个隐藏的input元素来包含Token值;如果使用JSON,则需要在HTTP请求头中添加Token值。
  2. Spring Boot:Spring Boot提供了内置的CSRF保护机制,并且默认启用。要配置Spring Security以使用CSRF保护,可以创建一个配置类并扩展WebSecurityConfigurerAdapter类。在配置类中,可以通过csrf().disable()来禁用CSRF保护,或者在表单中包含CSRF Token来启用保护。
  3. 其他框架:除了Spring框架,其他开发框架也可能提供了CSRF防护措施。例如,在Django框架中,默认开启了CSRF防护,开发者需要在表单中加入模板标签{% csrf_token %}来自动处理Token的生成与验证
  • 如果使用的框架中没有内置CSRF保护,为所有添加CSRF令牌,并在后端进行验证。
  • 对有状态软件使用同步令牌模式。如数据库管理系统、web服务器等。
  • 对无状态软件使用双提交cookies。如负载均衡、restful web服务。
  • 为会话cookies配置最佳安全实践,使用SameSite Cookie属性。
  • 为高度敏感的操作进行二次验证或多因素验证。
  • 如果可以的话使用自定义请求头。
  • 使用标准头验证来源。
  • 不要使用GET请求进行状态更改操作。

基于令牌的缓解措施

同步令牌模式是最推荐的缓解CSRF的方法之一,具体如下

使用内置或现有的CSRF实现进行CSRF保护

许多框架已经内置了同步令牌防御。在使用之前需确认内置令牌同步机制已开启。例如,.NET内置了保护,为CSRF易受攻击的资源添加了令牌。

同步令牌模式

CSRF令牌应在服务器端生成。它们可以每个用户会话生成一次,或每个请求生成一次。每个请求的令牌比每个会话的令牌更安全,因为攻击者利用被盗令牌的时间范围最小。但是可能会导致可用性问题。例如,“返回”按钮的浏览器功能可能会不可用,因为上一页可能包含不再有效的令牌。与此上一页的交互将在服务器上导致CSRF误报安全事件。在每个会话的令牌实现中,在初始生成令牌后,该值存储在会话中,并用于每个后续请求,直到会话过期。

当客户端发出请求时,服务器端需验证请求中令牌的存在和有效性,并且与用户会话中找到的令牌相比。如果请求中未找到令牌,或者提供的值与用户会话内的值不匹配,则应中止请求,终止用户会话,并将事件记录为潜在的CSRF攻击。

CSRF令牌应满足:

  • 每个用户会话唯一。
  • 保密性。
  • 不可预测(由安全方法生成的大随机值)。
  • CSRF令牌可以防止CSRF,因为没有令牌,攻击者无法向后台服务器创建有效请求。
  • CSRF令牌不应使用cookies传输。
  • CSRF令牌可以通过隐藏字段、头添加,并可与表单和AJAX调用一起使用。确保令牌不会在服务器日志或URL中泄露。GET请求中的CSRF令牌可能在多个位置泄露,例如浏览器历史记录、日志文件、记录HTTP请求第一行的网络设备,以及如果受保护的站点链接到外部站点,则在Referer头中可能会泄露CSRFtoken。

例如:

html

// 假设CSRF令牌存储在名为'XSRF-TOKEN'的cookie中

// 可以从服务器端的某个API获取它

constcsrfToken =getCookie('XSRF-TOKEN');

// 定义一个函数来发送POST请求并包含CSRF令牌

functionsendPostRequestWithCSRF(url, data) {

// 创建请求的headers对象,并添加CSRF令牌

constheaders =newHeaders({

'Content-Type':'application/json', // 根据需要设置content type

'X-CSRF-Token':csrfToken // 添加CSRF令牌

});

// 使用Fetch API发送POST请求

fetch(url, {

method:'POST', // 或者 'GET', 'PUT', 'DELETE', 等

headers:headers, // 设置请求头

body:JSON.stringify(data), // 将请求数据转换为JSON字符串

credentials:'same-origin', // 确保cookie被发送

})

.then(response =>{

if(!response.ok) {

thrownewError('Network response was not ok');

}

returnresponse.json(); // 解析JSON响应

})

.then(data =>{

console.log('Success:', data); // 处理响应数据

})

.catch((error) =>{

console.error('Error:', error); // 处理错误

});

}

// 调用函数发送请求

sendPostRequestWithCSRF('https://example.com/api/endpoint', { key:'value'});

// 辅助函数:从cookie中获取CSRF令牌

functiongetCookie(name) {

letcookieValue =null;

if(document.cookie &&document.cookie !=='') {

constcookies =document.cookie.split(';');

for(leti =0; i <cookies.length; i++) {

constcookie =cookies[i].trim();

if(cookie.substring(0, name.length +1) ===(name +'=')) {

cookieValue =decodeURIComponent(cookie.substring(name.length +1));

break;

}

}

}

returncookieValue;

}

通过JavaScript在自定义HTTP请求头中插入CSRF令牌是比在隐藏字段表单参数中添加令牌更安全的方法,因为它使用自定义请求头。

双提交Cookie

如果无法做到维护服务器端的CSRF令牌状态,另一种防御是使用双提交cookie技术。这种技术易于实现且无状态。其实质为使用两个不同的Cookie来增加CSRF攻击的难度。这种方法不是完全避免CSRF攻击,而是通过增加一层校验机制来降低风险。:

1. 传统的CSRF攻击

  • 在传统的CSRF攻击中,攻击者利用用户已认证的会话(通过Cookie或其他会话标识)来发起恶意请求。用户的浏览器会在每次请求到同一域时自动携带相应的Cookie。

2. 双Cookie机制

  • 双Cookie机制涉及使用两组不同的Cookie:一组用于维持会话状态(Session Cookie),另一组用于CSRF防护(CSRF-Token Cookie)。

3. Session Cookie

  • 这些Cookie用于传统的会话管理,存储用户的登录状态和会话信息。

4. CSRF-Token Cookie

  • 这些Cookie或存储在其他存储机制(如LocalStorage)中的Token用于CSRF防护。
  • 每次用户发起敏感操作(如转账、修改密码等)时,服务器要求请求中包含这个Token。

5. Token的生成和验证

  • 当用户登录后,服务器生成一个随机的Token,并将其发送给客户端存储在CSRF-Token Cookie或LocalStorage中。
  • 每当用户发起请求时,除了携带Session Cookie外,还需要携带CSRF-Token。

6. 服务器端验证

  • 服务器在处理请求时,会检查请求中是否包含有效的CSRF-Token。
  • 如果Token缺失或与服务器端存储的Token不匹配,服务器将拒绝该请求。

7. 防御原理

  • 由于CSRF-Token不是通过URL参数传递,攻击者很难通过第三方网站来自动获取这个Token。
  • 即使攻击者能够通过XSS等手段获取到Session Cookie,但没有对应的CSRF-Token,他们仍然无法发起成功的CSRF攻击。

子域可以向父域写入cookie,并且cookie可以设置为通过纯HTTP连接的域,所以需确定子域安全并且只接受HTTPS连接。

纵深防御:cookie配置最佳安全实践

SameSite Cookie属性

SameSite是一个cookie属性(与HTTPOnly、Secure等类似),旨在减轻CSRF攻击。这个属性帮助浏览器决定是否要将cookie随跨站请求一起发送。此属性值可以设置为Lax、Strict或None。

设置 SameSite 为 Strict 的 HTTP 响应头部

http

Set-Cookie: sessionid=1234567890abcdef; SameSite=Strict; Secure; HttpOnly

在这个例子中,服务器在响应中设置了名为 sessionid的 Cookie,并指定了以下属性:

  • SameSite=Strict:表明这个 Cookie 只在同一个域的请求中被发送,不允许跨站点请求携带该 Cookie。
  • Secure:表明这个 Cookie 只能通过 HTTPS 发送,增加了传输过程中的安全性。
  • HttpOnly:表明这个 Cookie 不能通过 JavaScript 访问,减少了 XSS 攻击的风险。

场景:

假设用户 Alice 访问了她信任的银行网站 bank.com并登录。她的浏览器收到了包含 SameSite=Strict的 Cookie。

  1. 正常访问
    1. Alice 直接访问 com,她的浏览器会发送请求到银行网站,并包含 sessionidCookie,因为这是一个同源请求。
  2. CSRF 攻击尝试
    1. 如果有攻击者试图通过 com网站上的恶意链接或表单来发起 CSRF 攻击,请求 bank.com的某个敏感操作(如转账)。
    2. 由于 SameSite=Strict的设置,Alice 的浏览器不会发送 sessionidCookie 到 com发起的请求,因为这是一个跨站点请求。
  3. 服务器接收请求
    1. 银行网站的服务器接收到请求,发现缺少了必要的 sessionidCookie,或者 Cookie 由于跨站点请求被浏览器阻止发送。
    2. 服务器识别出这个请求没有携带有效的会话标识,因此不会执行请求中的操作,从而防止了 CSRF 攻击。
  4. 用户安全
    1. 由于 SameSite=Strict的保护,Alice 的会话安全得到了增强,因为即使她不小心点击了恶意链接,她的会话信息也不会被用于 CSRF 攻击。

注:如果网站需要依赖第三方 Cookie(例如,用于广告或社交媒体插件),则 SameSite=Strict可能会干扰这些功能。在这种情况下,可以使用 SameSite=Lax或 SameSite=None

SameSite 配置为 Lax :

SameSite=Lax提供了一种较为宽松的同源策略,它允许Cookie在某些跨站点请求中被发送,但限制了顶级导航中的Cookie发送。

HTTP 响应头部示例:

http

Set-Cookie: sessionid=1234567890abcdef; SameSite=Lax; Secure; HttpOnly

在这个例子中,sessionidCookie 被设置了 SameSite=Lax属性,这意味着:

  • Cookie 会在相同站点的请求和从另一个站点开始的顶级导航请求中发送(例如,用户点击一个链接,从一个新标签或窗口打开网站)。
  • 但是,它不会被发送到从第三方站点发起的非顶级导航请求中(例如,通过iframe发起的请求)。

举例:

假设你正在浏览一个名为 example.com的网站,并且该网站使用了 SameSite=Lax属性来设置它的Cookie。下面是这个网站服务器可能发送的一个HTTP响应头部,其中包含了 Set-Cookie指令:

http复制

HTTP/1.1200OK

Content-Type: text/html; charset=UTF-8

Set-Cookie: session_token=abc123; Path=/; SameSite=Lax; Secure; HttpOnly

<!-- 其他响应头部和HTML内容 -->

在这个例子中,服务器设置了一个名为 session_token的Cookie,它的值是 abc123,并且具有以下属性:

  • Path=/:表明这个Cookie对于整个网站都是有效的。
  • SameSite=Lax:表明Cookie的行为遵循 "宽松" 模式,允许Cookie在相同站点的请求和某些情况下的跨站点请求中被发送。
  • Secure:表明这个Cookie只会在HTTPS连接中被发送,增加了安全性。
  • HttpOnly:表明这个Cookie不能通过JavaScript访问,有助于防止跨站脚本攻击(XSS)。

场景:

  1. 直接访问 com
    1. 用户在浏览器地址栏输入 com并访问网站,浏览器接收到上述响应头部,并存储 session_tokenCookie。
  2. 通过外部链接访问 com
    1. 用户点击来自另一个网站(如 external-site.com)的链接,链接指向 com的某个页面。由于这是一个顶级导航请求,并且 SameSite=Lax允许在这种情况下发送Cookie,所以 session_token会随请求一起发送到 example.com。
  3. com内部导航
    1. 用户在 com内部点击链接,从一个页面跳转到另一个页面。由于这同样是 example.com内的请求,无论是顶级导航还是非顶级导航,session_token也会被发送。
  4. com被iframe嵌入到另一个网站
    1. 如果 com的页面被另一个网站(如 another-site.com)通过iframe嵌入,并且尝试从iframe中发起请求到 example.com,session_token将不会随请求发送,因为 Lax模式不允许从iframe中的非顶级导航发送Cookie。

SameSite 配置为 None

SameSite=None允许Cookie在跨站点请求中被发送,但要求必须使用安全上下文(即通过HTTPS)。

HTTP 响应头部示例:

http

Set-Cookie: sessionid=1234567890abcdef; SameSite=None; Secure; HttpOnly

在这个例子中,sessionidCookie 被设置了 SameSite=None属性,这意味着:

  • Cookie 可以在任何类型的请求中被发送,无论是同源请求还是跨站点请求。
  • Secure属性确保了Cookie仅通过HTTPS发送,这为Cookie提供了传输层的加密保护。
  • HttpOnly属性确保了Cookie不能通过客户端脚本(如JavaScript)访问,这有助于防止跨站脚本攻击(XSS)。

场景:

假设一个单点登录(SSO)系统,用户可能需要从多个不同的子域或第三方服务进行身份验证。

  1. 用户登录:用户访问 example.com并登录,接收到一个设置了 SameSite=None的 Cookie。
  2. 跨站点访问:用户随后访问 example.com,这是一个需要身份验证的服务。由于 SameSite=None的设置,即使 service1.example.com是一个跨站点请求,用户的浏览器也会发送包含身份验证信息的 Cookie。
  3. 安全验证:example.com接收到请求,并检查 Secure和 HttpOnly属性以确保 Cookie 的安全性。服务验证了 Cookie 并允许用户访问,无需再次登录。
  4. 增强的用户体验:用户在不同的服务间导航时,享受到无缝的单点登录体验,同时保持了高安全标准。

使用 SameSite=None时,必须谨慎,因为它会允许跨站点请求携带Cookie,这可能增加安全风险。因此,结合使用 Secure和 HttpOnly属性是非常重要的,以确保即使在跨站点场景中也能保护用户会话的安全。

注:cookie的这个属性应该作为纵深防御的一部分落地。这个属性通过浏览器维持(但是有些浏览器不支持)。而不是进行samesite配置后就可以了,这个属性不应该替代CSRF令牌,它应该与令牌辅助进行纵深防御。

使用标准头验证来源

这个缓解措施有两个步骤,都依赖于检查HTTP请求头的值。

确定请求来源(源)。可以通过Origin或Referer头完成。

确定请求目标(目标)。

在服务器端验证它们是否匹配。如果匹配,我们接受请求作为合法的(意味着它是同一来源请求),如果不匹配,丢弃请求(意味着请求来自跨域)。对这些头的依赖来自于它们不能通过程序更改(使用XSS漏洞的JavaScript),因为它们属于禁止头列表,这意味着只有浏览器可以设置它们。

确定源(通过Origin/Referer头)

检查Origin头

如果Origin头存在,验证其值是否与目标起源匹配。与Referer不同,Origin头将在来自HTTPS URL的HTTP请求中存在。

检查Referer头

如果Origin头不存在,验证Referer头中的主机名是否与目标起源匹配。这种CSRF防御方法通常也用于未经身份验证的请求,例如在建立会话状态之前发出的请求。

在这两种情况下,确保目标起源检查相对安全一些。例如,如果站点是example.org,需确保example.org.attacker.com不会通过起源检查(即,通过目标后的/匹配,以确保您匹配整个起源)。

如果这些头都不存在,可以选择接受或阻止请求。但是建议阻止。或者可以记录所有这些情况,监控它们的用例/行为,确保没用误报后开始阻止请求。

确定目标

确定目标的方式有很多,第一种是简单地从请求中的URL获取目标(即其主机名和端口号)。然而,应用服务器经常位于一个或多个代理后面,原始URL与应用服务器实际接收到的URL不同。如果应用服务器直接由用户访问,那么使用URL中的目标起源就可以了。

如果应用服务器在代理后面,可以考虑以下几种方式。

1.配置应用程序获取其目标起源:在某个服务器配置条目中设置该值。这最直接,因为它在服务器端定义,因此是一个可信的值。但是如果应用程序部署在许多地方,例如开发、测试、QA、生产,可能还有多个生产实例,这个时候就很难维护。可以通过某种中央配置并提供实例来获取值(注意:确保中央配置存储安全,CSRF防御的主要部分取决于它。)

2.使用Host头值:如果希望应用程序自己找到其目标,而不需要为每个部署实例进行配置,可以使用Host系列头。Host头的目的是包含请求的目标起源。但是如果应用程序服务器位于代理后面,Host头的值很可能被代理更改为代理后面的URL的目标起源,这与原始URL不同。这个修改后的Host头起源将与原始的Origin或Referer头中的源起源不匹配。

3.使用X-Forwarded-Host头值:为了避免代理更改Host头的问题,还有另一个名为X-Forwarded-Host的头,其目的是包含代理接收到的原始Host头值。大多数代理会将原始Host头值传递在X-Forwarded-Host头中。因此,该头值可能是您需要与Origin或Referer头中的源起源进行比较的目标起源值。

当请求中存在来源时,这种缓解措施可以正常工作。尽管这些头大部分时间都包含在内,但也有一些用例它们没有被包含:

  • 在跨域302重定向后,Origin没有包含在重定向请求中,因为origin被认为是不应发送到其他起源的敏感信息。
  • 在一些隐私上下文中,Origin被设置为“null”。
  • Origin头包含在所有跨域请求中,但对于同源请求,在大多数浏览器中它只包含在POST/DELETE/PUT中。注意:还有许多开发人员使用GET请求进行状态更改操作。
  • Referer头也不例外。有许多用例Referer头也被省略了。负载均衡器、代理和嵌入式网络设备也因隐私原因在记录中剥离了Referer头。

使用__Host-前缀的Cookie

解决这个问题的另一个解决方案是使用带有CSRF令牌的Cookie前缀。如果cookie有__Host-前缀,例如Set-Cookie: __Host-token=RANDOM; path=/; Secure,那么这个cookie:

  • 不能从另一个子域(重)写入。
  • 必须有/路径。
  • 必须标记为Secure(即,不能通过未加密的HTTP发送)。

使用自定义请求头

添加CSRF令牌、双提交cookie和值、加密令牌,或涉及更改UI的其他防御通常可能较为复杂。适合AJAX或API端点的替代防御是使用自定义请求头。这种防御依赖于同源策略限制,即只有JavaScript可以用来添加自定义头,且只能在其原始域内。默认情况下,浏览器不允许JavaScript使用自定义头进行跨域请求。只需在所有服务器端AJAX端点验证此头和值的存在,以保护免受CSRF攻击。这种方法通常不需要更改UI,也不引入任何服务器端状态,这对于REST服务较为适配。这种技术适用于AJAX调用,但仍然需要增加纵深防御举措(如令牌)来保护<form>标签。此外,CORS配置也应该遵循最佳安全实践(因为来自其他域的请求的自定义头会触发预检CORS检查)。

基于用户交互的CSRF防御

以上技术不需要用户交互,但是如果涉及到非常敏感的操作,最好再进行二次验证:

  • 重新认证(密码或更强的方式)
  • 一次性令牌
  • 验证码

虽然这些防御方式很安全,但可能会对用户体验产生显著影响。因此,它们通常只用于非常敏感的操作(如密码更改、资金转移等)。

登录CSRF

大多数开发人员倾向于忽略登录表单上的CSRF漏洞,因为他们认为CSRF在登录表单上不适用,用户在这个阶段还没有认证,但CSRF漏洞仍然可能发生在用户未认证的登录表单上,只是风险较小。例如,如果攻击者使用CSRF在购物网站上使用攻击者的账户对受害者进行身份验证,受害者随后输入他们的信用卡信息,攻击者可能能够使用受害者存储的卡详情进行购买。这个时候可以通过创建会话前(用户认证之前的会话)并在登录表单中包含令牌来缓解登录CSRF。注:会话前不能在用户认证后转换为真实会话 - 会话应该被销毁,并且应该创建一个新的会话,以避免会话固定攻击。

Java防御框架:

这里我以JAVAEE Web过滤器防御CSRF攻击做个分析,在开发过程中如何落地:

1. 使用CSRF Token

  • 生成Token:在用户登录后,服务器生成一个随机的Token,并将其存储在Session或Cookie中。
  • 表单提交:在每个表单中添加一个隐藏字段,包含这个Token,每当表单提交时,Token也会一起提交。
  • 服务器验证:服务器接收到请求后,检查请求中的Token是否与Session或Cookie中的Token匹配,不匹配则拒绝请求。

2. SameSite Cookie属性

  • 设置Cookie的SameSite属性为Strict或Lax,以限制Cookie在跨站点请求中的发送。

3. 双重Cookie验证

  • 除了在表单中使用CSRF Token外,还可以在请求的URL或请求头中包含Token,并验证这些Token是否与服务器端存储的Token匹配。

4. 使用HTTPOnly Cookie

  • 设置Cookie的HttpOnly属性,使得Cookie不能通过客户端脚本访问,这可以防止XSS攻击获取Cookie值。

5. 验证请求的Referer头部

  • 检查HTTP请求的Referer头部,确保请求是从合法的页面发起的。但这种方法不是完全可靠,因为Referer头部可以被伪造或省略。

6. 使用Content Security Policy (CSP)

  • 通过CSP头部设置,限制资源加载和执行的源,减少XSS攻击的风险,从而间接降低CSRF攻击的可能性。

7. 过滤不安全的HTTP方法

  • 在服务器端验证所有敏感操作的HTTP方法,例如,只允许POST请求修改敏感数据。

8. 使用Web应用程序防火墙(WAF)

  • WAF可以帮助识别和阻止CSRF攻击模式。

9. 用户提示和界面设计

  • 提示用户不要点击不明链接,设计用户界面时敏感操作需设计的复杂些。

10. 利用Java EE安全上下文

Java EE提供了安全上下文管理,可以在用户请求时验证用户身份和权限。

11. 使用装饰者模式增强安全性

利用装饰者模式在不修改现有代码的情况下增加安全检查逻辑。

12. 定期的安全审计和测试

定期进行代码审查和安全测试,包括自动化的扫描工具和手动的渗透测试。

以下是通过JAVA EE Filter过滤器来实现CSRF防御的示例,实现默认安全,供开发的同事参考:

@WebFilter("/secure/*")

publicclassCSRFProtectionFilterimplementsFilter{

publicvoiddoFilter(ServletRequestrequest, ServletResponseresponse, FilterChainchain)

throwsIOException, ServletException{

HttpServletRequesthttpRequest =(HttpServletRequest) request;

HttpServletResponsehttpResponse =(HttpServletResponse) response;

// 检查请求是否包含有效的CSRF Token

StringrequestToken =httpRequest.getHeader("X-CSRF-Token");

StringsessionToken =(String) httpRequest.getSession().getAttribute("CSRF-Token");

if(requestToken ==null||!requestToken.equals(sessionToken)) {

httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF Token mismatch");

return;

}

chain.doFilter(request, response);

}

// init 和 destroy 方法...

}

通过这些策略和实践,Java EE应用程序可以有效地防御CSRF攻击,保护用户数据和操作的安全。

以SSRF分析服务端注入类漏洞与防御逻辑:

前面提到了客户端伪造,以CSRF举例,分析了客户端伪造的逻辑与防御的逻辑,接下来我再说一下服务器端伪造,以SSRF进行分析。其主要成因是服务端请求伪造,即服务器原本正常向别的服务器发起请求,但是由于发起请求被攻击者控制,而造成了请求伪造,其核心原因为:服务器应用程序接受用户输入作为请求的一部分,并且没有进行适当的验证或限制,一旦当请求域名受控了,那么可以做的事情就很多了,无论是down一个webshell还是内网扫描,都可以

特点:

  1. 服务器发起请求:与CSRF不同,SSRF是由服务器端发起的请求,而不是用户的浏览器。
  2. 攻击目标多样性:攻击者可以利用SSRF访问或操作内部系统、服务或数据库,甚至可以对外网的服务器进行攻击。
  3. 可携带有效凭证:如果请求是通过具有特定权限的用户会话发起的,那么请求可能会携带有效的认证信息。
  4. 难以被检测:由于请求是由服务器发出的,因此可能绕过一些客户端的安全措施。

SSRF漏洞的成因:——应用程序接受客户传参作为服务器请求参数且客户传参被攻击者控制

  • 应用程序接受用户输入作为URL参数,如API请求中的URL。
  • 应用程序没有对这些输入进行严格的验证或限制,允许访问任意URL。

SSRF 常见利用场景分析

注意:

  • SSRF 不限于 HTTP 协议。通常,第一个请求是 HTTP,但在应用程序本身执行第二个请求的情况下,它可能会使用不同的协议(例如 FTP、SMB、SMTP 等)和方案(例如 file://、phar://、gopher://、data://、dict:// 等)。

根据应用程序的功能和需求,SSRF 发生的情况有两种基本类型:

  1. 应用程序只能向已识别和受信任的应用程序发送请求:允许列表方法可用的情况——即可做白名单的情况
  2. 应用程序可以向任何外部 IP 地址或域名发送请求:不允许列表方法的情况——即不可做白名单的情况

因为这两种情况非常不同,后续我描述针对它们的防御措施。

案例 1 - 应用程序只能向已识别和受信任的应用程序发送请求

有时,应用程序需要向另一个应用程序(通常位于另一个网络区)执行请求以执行特定任务。根据业务案例,可能需要用户输入才能使功能正常工作。

示例

以一个 Web 应用程序为例,该应用程序接收并使用用户的个人信息(如他们的名、姓、出生日期等)在内部 HR 系统中创建个人资料。按设计,该 Web 应用程序需要使用 HR 系统理解的协议来处理该数据。用户无法直接访问 HR 系统,但是,如果负责接收用户信息的 Web 应用程序受到 SSRF 攻击,用户可以利用它来访问 HR 系统。用户利用 Web 应用程序作为代理访问 HR 系统。

防御措施

在应用程序和网络层面都可以采取几种保护措施。为了应用深度防御原则,这两个层面都将针对此类攻击进行加固。

应用程序层面

想到的第一级保护是输入验证。

基于这一点,接下来的问题是如何执行此输入验证?

根据不同的编程语言,解析器可能会被滥用。最好的方式是在使用输入验证时应用允许列表方法(白名单),因为大多数情况下,预期输入是已知的(攻击者的输入是未知的)。

发送到内部应用程序的请求将基于以下信息:

  • 包含业务数据的字符串。
  • IP 地址(V4 或 V6)。
  • 域名。
  • URL。
字符串

在 SSRF 的背景下,可以添加验证以确保输入字符串符合预期的业务/技术格式。

如果输入数据格式简单(例如令牌、邮政编码等),可以使用正则表达式确保从安全角度接收到的数据是有效的。否则,应使用字符串对象提供的库进行验证,因为复杂格式的正则表达式难以维护且容易出错。

假定用户输入与网络无关,并且包括用户的个人信息。

示例:

java

// 正则表达式验证具有简单格式的数据

if(Pattern.matches("[a-zA-Z0-9\\s\\-]{1,50}", userInput)){

// 继续处理,因为输入数据是有效的

}else{

// 停止处理并拒绝请求

}

IP 地址

在 SSRF 的背景下,可以执行以下两种验证:

  • 确保提供的数据是有效的 IP V4 或 V6 地址。
  • 确保提供的 IP 地址属于已识别和受信任应用程序的 IP 地址之一。

可以使用确保 IP 地址格式安全的库来应用第一层验证,基于使用的技术(这里建议使用库选项,以委托管理 IP 地址格式并利用经过实战测试的验证功能):

对所提出的库进行了验证,以确保它们不会执行任何 DNS 解析查询。

  • JAVA: Apache Commons Validator 库中的 isValid方法。
  • .NET: SDK 中的 CheckHostName方法。
  • JavaScript: is-valid-domain库。
  • Python: domain模块。

可以使用以下正则表达式(从这里获取):

^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$

举例:

ruby

domain_names =["owasp.org","owasp-test.org","doc-test.owasp.org","doc.owasp.org",

"<script>alert(1)</script>","<script>alert(1)</script>.owasp.org"]

domain_names.each{ |domain_name|

if( domain_name =~/^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)

puts "[i] #{domain_name}is VALID"

else

puts "[!] #{domain_name}is INVALID"

end

}

$ ruby test.rb

[i] owasp.org is VALID

[i] owasp-test.org is VALID

[i] doc-test.owasp.org is VALID

[i] doc.owasp.org is VALID

[!] <script>alert(1)</script>is INVALID

[!] <script>alert(1)</script>.owasp.org is INVALID

URL

不要从用户那里接受完整的 URL,因为 URL 难以验证并且可能会被绕过,可以只接受有效的 IP 地址或域名。

网络层面

网络层面安全的目标是防止 对任意应用程序进行调用,以路由维度和网络隔离维度进行网络层面阻断,即只有允许的路由通过,且结合防火墙进行阻断。

案例 2 - 应用程序可以向任何外部 IP 地址或域名发送请求

当用户可以控制指向外部资源的 URL,并且应用程序向此 URL 发出请求时(例如在 WebHooks 的情况下),就会发生这种情况。由于 IP/域名列表通常事先未知且在不断变化,因此无法在这里使用允许列表。

在这种情况下,外部指的是不属于内部网络的任何 IP,并且为互联网IP。

在应用程序层面阻止 URL 的挑战

基于上述应用程序的业务需求,允许列表方法不效果就不是特别好,这个时候就需要使用黑名单的思路。

应用程序层面过滤 URL 的难点:

主要难点在于应用程序必须能够在代码层面检测到提供的 IP(V4 + V6)不是官方私有网络范围的一部分,包括本地主机和 IPv4/IPv6 链路本地地址。但是并非每个 SDK 都提供了这种验证的内置功能,并且开发人员不一定能够针对SDK做二开,

对于域名也是如此:公司必须维护所有内部域名的列表,并提供集中服务,以允许应用程序验证提供的域名是否为内部域名。对于此验证,应用程序可以查询内部 DNS 解析器,但此内部 DNS 解析器不能解析外部域名。

防御措施

应用程序层面

与案例 1 一样,假定需要 IP 地址或域名来创建将发送到服务端请求。

案例 1 中介绍的输入数据的第一种验证将与这种情况相同,但对于第二种验证将有所不同。这里必须使用黑名单方法。

如何证明请求合法性:将接收请求的服务端必须生成一个随机令牌(例如:20 个字符的字母数字),预期由调用者通过参数传递(在正文中,参数名称也由应用程序本身定义,并且只允许字符集 [a-z]{1,10})以执行有效请求。接收端点必须仅接受 HTTP POST 请求。

验证流程(如果一个验证步骤失败,则拒绝该请求):

应用程序将接收目标应用的 IP 地址或域名,并使用本库/正则表达式对输入数据进行第一层验证。

使用以下黑名单方法对目标应用的 IP 地址或域名应用第二层验证:

对于 IP 地址:应用程序验证IP是否为公网IP。

对于域名:

  1. 应用程序将通过只解析内部域名的 DNS 解析器来验证它是否是公共的。是否为公共域名的返回值也不同。
  2. 应用程序将检索域名映射的所有 IP 地址,并验证是否为公网IP。应用程序将通过专用输入参数接收用于请求的协议,并将其值与允许的协议列表(HTTP 或 HTTPS)进行验证。
  3. 应用程序将通过专用输入参数接收传递给 目标服务器的令牌参数名称,并且只允许字符集 [a-z]{1,10}。
  4. 应用程序将通过专用输入参数接收令牌本身,并只允许字符集 [a-zA-Z0-9]{20}。
  5. 应用程序将接收并验证(从安全角度)执行有效调用所需的任何业务数据。
  6. 应用程序将仅使用经过验证的信息构建 HTTP POST 请求,并将其发送(同时在使用的 Web 客户端中禁用重定向支持)。

安全checklist

以上即为SSRF整体的防御思路,我同时做了个checklist,供各位安全开发的同时参考:

1. 输入验证对所有用户输入进行严格的格式和范围验证,确保输入不是潜在的恶意URL。

2. 使用白名单制定允许访问的域名或IP地址列表,拒绝所有非白名单内的请求。

3. 限制外部访问仅允许服务器端应用程序访问特定的、必要的外部系统或服务。

4. 权限控制确保应用程序的网络请求功能与用户的实际权限相匹配,避免过度授权。

5. 使用安全的协议强制使用HTTPS等安全协议进行外部通信,避免使用不安全的HTTP协议。

6. 监控和限制异常流量实施流量监控系统来检测和限制异常的网络请求模式,如突发的大量请求。

7. 限制HTTP方法仅允许应用程序使用安全的HTTP方法,如GET或POST,限制其他可能被滥用的方法。

8. 使用反向代理通过反向代理服务器来限制和监控对内部资源的访问,增加一层安全控制。

9. 错误处理确保错误消息不会泄露敏感信息,如服务器内部结构或状态。

10. 安全的外部服务集成当集成外部服务时,确保使用安全的连接和认证机制。

11. 使用防火墙和入侵检测系统等安全设备配置防火墙规则以限制对特定端口和IP地址的访问,使用入侵检测系统来监控可疑行为。

12. 实施CSP使用内容安全策略(Content Security Policy)来限制资源加载和执行的源。

14. 安全审计和测试定期进行代码审查、渗透测试和漏洞扫描,以发现和修复潜在的安全问题。

15. 利用云服务安全功能如果应用程序部署在云平台上,利用平台提供的安全组、网络ACLs等安全功能。

16. 限制解析外部输入对于需要从用户输入解析URL的场景,确保解析过程是安全的,避免路径遍历等漏洞。

17.  使用第三方库和框架使用经过验证的第三方库和框架来处理网络请求,这些库和框架通常已经内置了安全措施。

以上即为伪造类漏洞的拆解,欢迎各位师傅一起讨论,其实落地过程中还有个重要的难点,即如何帮助开发吸收所有的左移思路,欢迎各位师傅留言探讨

# SSRF # csrf # 企业安全 # 伪造 # 安全开发
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录