{"id":311,"date":"2021-08-07T13:27:07","date_gmt":"2021-08-07T13:27:07","guid":{"rendered":"https:\/\/johnlradford.io\/?p=311"},"modified":"2022-01-07T08:40:29","modified_gmt":"2022-01-07T08:40:29","slug":"my-homebrew-xsrf-demonstration","status":"publish","type":"post","link":"https:\/\/johnlradford.io\/blog\/2021\/08\/07\/my-homebrew-xsrf-demonstration\/","title":{"rendered":"My Homebrew XSRF Demonstration"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">In a nutshell\u2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cross Site Request Forgery (CSRF\/XSRF) is an attack where a malicious website forges request(s) to some legitimate website on behalf of an unsuspecting user.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, say Bob logs into his bank account to assess his finances. Bob&#8217;s bank&#8217;s website is vulnerable to XSRF and while logged in Bob happens to visit a sketchy anime site that is secretly targeting his bank. That sketchy site employs some sneaky JavaScript which secretly send requests to Bob&#8217;s banking site, on Bob&#8217;s behalf, without Bob knowing, transferring away his millions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">XSRF is an old school attack and as such safeguards have been invented to protect against it, namely the browser security model known as <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">CORS<\/a> and the larger security mechanism known as the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\" data-type=\"URL\" data-id=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\">Same Origin Policy (SOP)<\/a>. Nonetheless, with enough free time on a Sunday afternoon it is still possible to sort of pull off an XSRF attack (albeit against yourself).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This post attempts to demonstrate how an XSRF attack works in practice using the two example sites below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">In Practice<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Site 1: The Vulnerable Site<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"371\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1-1024x371.png\" alt=\"\" class=\"wp-image-312\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1-1024x371.png 1024w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1-300x109.png 300w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1-768x278.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1-1536x556.png 1536w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site1.png 1624w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Site 1: The Vulnerable Site<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see from the image, site 1 has some simple pages we can visit. If<br>we visit the &#8216;Get Cookie&#8217; page not much happens on our screen. However, if we<br>open up the chrome debugger and look at the HTTP Headers of the site&#8217;s response we&#8217;ll see the page has set a cookie.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"320\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/cookie_set-1024x320.png\" alt=\"\" class=\"wp-image-313\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/cookie_set-1024x320.png 1024w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/cookie_set-300x94.png 300w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/cookie_set-768x240.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/cookie_set.png 1198w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Cookie Set via HTTP Headers<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We can verify this by visiting, <code>https:\/\/site1\/check_cookie.php<\/code>. Sure enough,<br>when we visit the &#8216;Check Cookie&#8217; page the same random cookie value for &#8216;ID&#8217; is<br>displayed to us.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"270\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/check_cookie-1024x270.png\" alt=\"\" class=\"wp-image-314\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/check_cookie-1024x270.png 1024w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/check_cookie-300x79.png 300w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/check_cookie-768x203.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/check_cookie.png 1206w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Check Cookie<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And lastly on site 1 is the &#8216;Delete Cookie&#8217; page which does exactly what it<br>sounds like. We can think of &#8216;Get Cookie&#8217; and &#8216;Delete Cookie&#8217; as roughly<br>equivalent to login and logout, and &#8216;Check Cookie&#8217; as a stand-in for &#8216;do<br>something while logged into the site&#8217;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now let&#8217;s take a look at site 2 and pull off the XSRF attack against ourselves.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Site 2: The Attacker&#8217;s Site<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"379\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2-1024x379.png\" alt=\"\" class=\"wp-image-315\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2-1024x379.png 1024w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2-300x111.png 300w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2-768x284.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2-1536x568.png 1536w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/site2.png 1628w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Site 2: The Attacker&#8217;s Site<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As we can see, site 2 just has a link to a &#8216;Steal Cookie&#8217; page. And if we visit<br>the &#8216;Steal Cookie&#8217; we see a JavaScript alert box displaying the value of the<br>cookie given to us by site 1.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"378\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie-1024x378.png\" alt=\"\" class=\"wp-image-316\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie-1024x378.png 1024w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie-300x111.png 300w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie-768x284.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie-1536x567.png 1536w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2021\/08\/stolen_cookie.png 1636w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Stolen Cookie Alert<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">So how did that work? How did site 2 manage to get access to the cookie given<br>to us by site 1? We&#8217;ll lets look closer at the code that makes up sites 1 and<br>2 to get the answers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Into The Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s start by looking at the site 1 &#8216;Get Cookie&#8217; page.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>get_cookie.php<\/code><\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n$cookie_name = \"ID\";\n$cookie_value = rand();\n$session_set_cookie_params = (&#91;\n    'lifetime' =&gt; time() + (86400 * 30),\n    'path' =&gt; \"\/\",\n    'samesite' =&gt; 'None; Secure'\n]);\nsetcookie($cookie_name, $cookie_value, $session_set_cookie_params);\n?&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is part of site 1&#8217;s XSRF vulnerability. In particular, the way the cookie<br>is set. There&#8217;s something called the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Set-Cookie\/SameSite\">SameSite<\/a> attribute to the Set-Cookie HTTP Response Header. This flag on the HTTP &#8216;Set-Cookie&#8217; header controls the scope in which cookies can be used for a site.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the case of our vulnerable code above, the cookie is set with the SameSite<br>attribute &#8216;None&#8217;. This means our cookie will be sent to site 1 anytime our<br>browser makes a request to the site. It doesn&#8217;t matter if the request comes<br>from the same origin or a different origin, our cookie will be sent to site 1<br>all the same.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A quirk of setting SameSite to None is that you actually have to set it to<br><code>SameSite=None; Secure<\/code> because Chrome and other browsers have<br><a href=\"https:\/\/www.chromestatus.com\/feature\/5633521622188032\">deprecated<\/a> the insecure <code>SameSite=None<\/code>. This just means that you have to talk to site 1 via HTTPS instead of plain old unencrypted HTTP.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>&#8220;Cookies with SameSite=None must now also specify the Secure attribute (they<br>require a secure context\/HTTPS).&#8221;<\/p><\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Set-Cookie\/SameSite\">SameSite cookies<\/a> &#8211; developer.mozilla.org<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, all that alone is not enough to pull off our XSRF attack. Site 1 also<br>has some very specifically set &#8216;Access-Control-Allow-&#8216; headers and pretty<br>clearly reflects our cookie value back to us. Before we get there though let&#8217;s<br>look at the exploit code on site 2.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>xsrf_cookie.html<\/code><\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script&gt;\nvar xhr = new XMLHttpRequest();\nvar url = \"https:\/\/site1\/check_cookie.php\";\nxhr.open(\"GET\", url, true);\nxhr.withCredentials = true;\nxhr.send(null);\nxhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 &amp;&amp; xhr.status === 200) {\n        \/\/console.log(xhr.responseText);\n        alert(xhr.responseText);\n    }\n};\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The above code uses the JavaScript XMLHttpRequest object to make a request to site 1. JavaScript is executed in the browser, and so the GET request being made to <code>check_cookie.php<\/code> page comes from the victims browser. And because the &#8216;SameSite&#8217; attribute on our cookie was set to &#8216;none&#8217;, the cookie is sent along with the request whenever the above JavaScript is triggered.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing worth noting about the above code is that you have to use <code>xhr.withCredentials = true;<\/code> in order to tell the browser to send the cookie with the request to site 1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You may also have to allow third party cookies in your browser in order for<br>this all to work.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>How To: Chrome<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Chrome &gt; Settings &gt; Advanced Settings &gt; Privacy &gt; Content Settings &gt; Allow third-party cookies<\/code><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>How To: Firefox<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Tools &gt; Options &gt; Privacy &gt; Accept cookies from sites &gt; Accept third-party cookies<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But all that is still not enough to allow our XSRF attack. An important piece<br>of the puzzle remains. Let&#8217;s look back at the &#8216;Check Cookie&#8217; page on site 1.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>check_cookie.php<\/code><\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nheader(\"Access-Control-Allow-Origin: http:\/\/site2\");\nheader(\"Access-Control-Allow-Credentials: true\");\n\n$cookie_name = \"ID\";\n\nif(!isset($_COOKIE&#91;$cookie_name])) {\n  echo \"Cookie named '\" . $cookie_name . \"' is not set!\";\n} else {\n  echo \"Cookie '\" . $cookie_name . \"' is set!\\n\";\n  echo \"Value is: \" . $_COOKIE&#91;$cookie_name];\n}\n?&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two important HTTP headers are set at the top of the &#8216;Check Cookie&#8217; page. The first one, <code>Access-Control-Allow-Origin<\/code> specifies what origins are allowed to make requests to our page. Because we&#8217;re using <code>withCredentials = true<\/code> to send our cookie we have to explicitly specify the allowed origin. See <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\/Errors\/CORSNotSupportingCredentials\">this article<\/a> for more information as to why.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So nowadays due to <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">CORS<\/a>, site 1 has to allows this attack to happen to it from site 2. Or rather site 1  might just be a website that&#8217;s set up to allow cross origin requests from site 2. For example, maybe site 2 only exists on an intranet and therefore site 1 sort of &#8216;trusts&#8217; traffic from the internal origin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second header <code>Access-Control-Allow-Credentials<\/code> allows XMLHttpRequest.withCredentials. Since that is exactly what site 2 is doing, this header must be set on site 1. See t<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Access-Control-Allow-Credentials\">his documentation<\/a> for more info.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">In Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">XSRF attacks are a way for a malicious site to make cross origin requests to another site in order to extract information or take actions on an unsuspecting participant&#8217;s behalf. Its mostly blocked today by security mechanisms \/ practices that are built into browsers such as <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\" data-type=\"URL\" data-id=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">CORS<\/a>. However, there are real cases of this type of vulnerability. See the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cross-site_request_forgery#History\">History Section<\/a> of the XSRF Wikipedia article for examples. And even if it is mostly a dead vulnerability nowadays, it still provides good context for understanding the basic web security models that are in place today.<br><br>In the above example shows cross-<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Origin\" data-type=\"URL\" data-id=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Origin\">origin<\/a> XSRF. However, don&#8217;t confuse cross-origin with cross-site. XSRF attacks can also incorporate <a href=\"https:\/\/owasp.org\/www-community\/attacks\/xss\/\" data-type=\"URL\" data-id=\"https:\/\/owasp.org\/www-community\/attacks\/xss\/\">XSS<\/a> to embed malicious JavaScript into the same site, creating a same origin XSRF attack. This is actually a much easier attack to pull off as you can bypass all of the CORS stuff we delt with above. In that case you just have to contend with the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\" data-type=\"URL\" data-id=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\">Same Origin Policy (SOP)<\/a>. But thats a subject for another day. Thanks for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a nutshell\u2026 Cross Site Request Forgery (CSRF\/XSRF) is an attack where a malicious website forges request(s) to some legitimate website on behalf of an unsuspecting user. For example, say<span class=\"more-button\"><a href=\"https:\/\/johnlradford.io\/blog\/2021\/08\/07\/my-homebrew-xsrf-demonstration\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\">My Homebrew XSRF Demonstration<\/span><\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-311","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/comments?post=311"}],"version-history":[{"count":10,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/311\/revisions"}],"predecessor-version":[{"id":351,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/311\/revisions\/351"}],"wp:attachment":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/media?parent=311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/categories?post=311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/tags?post=311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}