Killing 🐦with 🐛🐛

Killing 🐦with 🐛🐛

A journey from subdomain #SELFXSS to site-wide #CSRF @Twitter. A private talk I delivered in 2016.

9b9863647e5085306b795717b03a430c?s=128

filedescriptor

August 25, 2016
Tweet

Transcript

  1. Killing with A journey from subdomain #SELFXSS to site-wide #CSRF

    @Twitter
  2. Whoami @filedescriptor XSS enthusiast Bug bounty hunter Pen-tester of Cure53

    2
  3. File Upload XSS ads.twitter.com 3

  4. Uploaded File: https://ton.twitter.com/i/ton/data/ta_data/3185435460/1471786489032.txt 4

  5. Nice. Where is the XSS? 5 Won’t execute Because the

    file is not interpreted as HTML
  6. File type • Content-Type (a.k.a. MIME type) identifies the file

    type • Browsers will determine how to interpret the file based on the Content-Type header • In this case, the file is served with text/plain • …which represents a plain text document 6
  7. Can browsers ignore Content-Type? Spoiler alert: yes 7

  8. MIME Sniffing • If the Content-Type for a file is

    unrecognised, browsers will try to fix it with MIME Sniffing • …which determines the file type by inspecting the content with different algorithms (e.g. Magic Number) • But text/plain is a perfectly recognisable MIME type, how do we activate MIME Sniffing in this case?
  9. MIME Sniffing & IE • Supposedly, the Content-Type header takes

    the highest precedence according to the specs • However, older versions of Internet Explorer will first guess the file type by performing MIME Sniffing • If a HTML sequence is found within the first
 256 bytes, then the file is parsed as HTML 9
  10. 10 Enabled by default

  11. Still won’t execute 11

  12. Anti-Sniffing • Turns out the header X-Content-Type-Options: nosniff is also

    served with the file • This header tells browsers not to perform MIME Sniffing • Now Internet Explorer plays by the rules 12
  13. Fuzzing file extension • The upload endpoint only accept certain

    file types • Namely, CSV file (.csv) and TXT file (.txt) • We could try different file extensions and hopefully the validation is based on a blacklist • The idea is that, even though we cannot upload HTML files, there are other file types that can execute JavaScript 13
  14. Fuzzing file extension (Cont.) Extension Results Content-Type .csv Accepted text/csv

    .html Rejected N/A .htm Rejected N/A .xml Rejected N/A .svg Rejected N/A .swf Rejected N/A .whatever Accepted No Content-Type 14
  15. The return of MIME Sniffing • Unrecognised extensions are accepted

    • No mapping for the corresponding MIME type • No Content-Type for the file • MIME Sniffing is then activated on browsers to determine the file type • X-Content-Type-Options: nosniff is ignored in this case 15
  16. 16

  17. 17

  18. 18

  19. 19

  20. Not so fast • ton.twitter.com hosts only static files, no

    damage can be done & • Files on ton.twitter.com can only be read by the uploader (Self-XSS) 20
  21. Ideas of making it harmful • Find a page on

    the main domain (twitter.com) which sets document.domain="twitter.com" • An old technique for sub/parent domain communication • ❌ No such page was found • Steal session id • The session cookie is set on *.twitter.com! • ❌ Cannot read due to httpOnly • Control CSRF token • 21
  22. Where the CSRF token is stored • Rails style cookie

    • _twitter_sess=BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGx lcjo6Rmxhc2g6OkZsYXNo250ASGFzaHsABjoKQHVzZWR7ADoPY3Jl YXRlZF9hdGwrCABm1thVAToMY3NyZl9p250AZCIlYjRhMzU1MjBiM 2U3MGQ1ZDlmZTliMTUxZDkwNjk2YWE6B2lkIiVhODVm250AYzY0Zj IxYTRiZDcwN2EyZTAxZDIwNGNjNDJlNA%253D%253D-- e7a90f98ff4d84ddf3340403841cdd8624f8ecce • Decoded:
 {“flashIC:'ActionController::Flash::FlashHash{:@used{ :created_atl +:csrf_id"%b4a35520b3e70d5d9fe9b151d90696aa:id"%a85fc 64f21a4bd707a2e01d204cc42e4 Base64 encoded serialised data Signature 22
  23. Observation • _twitter_sess is accessible to subdomains • This cookie

    is used to store all kinds of miscellaneous data, but not readable due to httpOnly • However, this cookie is not bound to the session (session ID is stored in a separate cookie) • Could we replace victim’s _twitter_sess with ours, and force victim to use our CSRF token? 23
  24. httpOnly in action no reading, no writing, ugh 24

  25. httpOnly in action no reading, no writing, ugh 25

  26. Cookie Tossing 26

  27. Cookie Tossing • Cookie key consists of the tuple (name,

    domain, path) • Subdomain can set cookies for parent domain and the effective path • foo is a different cookie from foo; path=/api. Their cookie flags are also separate • However, the Cookie header only contains the name and value for each of the cookies • Server will see two cookies with the same name 27
  28. 28

  29. Cookie Precedence • Specs don’t tell servers how to handle

    duplicated cookies • Usually the first occurrence is picked • But how do browsers arrange their order? 29
  30. –RFC 6265 “Cookies with longer paths are listed before cookies

    with shorter paths.” 30
  31. 31

  32. Side-wide CSRF • Most of the APIs on Twitter are

    under the path /i/ • e.g. https://twitter.com/i/tweet/create, https://twitter.com/i/ user/follow • Running the below code snippet on ton.twitter.com… • document.cookie = '_twitter_sess=ATTACKER_SESS; domain=.twitter.com; path=/i' • …overrides (shadows) victim's CSRF token! 32
  33. Not so fast • ton.twitter.com hosts only static files, no

    damage can be done ✅ • Files on ton.twitter.com can only be read by the uploader (Self-XSS) & 33
  34. Ideas of getting rid of the “self-” • Check if

    the upload endpoint is vulnerable to CSRF • ❌No dice • Social Engineering • Ask the victim to upload our crafted file • ❌Infeasible • Login & Logout CSRF • 34
  35. Attack flow 1. Logout victim’s session 2. Login to attacker’s

    account 3. Navigate victim to the XSS file 4. Override and fixate CSRF token 5. Logout attacker’s account 6. Next time victim logs in, attacker’s CSRF token will be used 35
  36. Can we do better? • Now the attack is feasible,

    but • …victim needs to re-login • Victim also needs to visit attacker’s website again to trigger the CSRF • Can we authorise a request without involving session? 36
  37. 37

  38. OAuth • An open protocol to allow secure authorisation to

    access protected resources • Twitter uses OAuth 1.0a, while the majority uses OAuth 2.0 • Instead of session, OAuth uses access_token for request authorisation • Example:
 GET /1.1/statuses/user_timeline.json HTTP/1.1
 Authorization: OAuth oauth_consumer_key="DC0sePOBbQ8bYdC8r4Smg",oauth_signature_ method="HMACSHA1",oauth_timestamp="14683423091",oauth_nonce ="9537061051",oauth_version="1.0",oauth_token="3185435460- FTrisxX5Wc7c4KZxEcUPApBIAkobAMHTYbVNU4k",oauth_signature="m VOi8NqfO8HzvKLpVv44LlN9eis%3D"
 Host: api.twitter.com OAuth parameter 38
  39. XSS ❤ OAuth • ton.twitter.com accepts OAuth requests • GET

    /i/ton/data/ta_data/3185435460/xss.whatever HTTP/1.1
 Authorization: OAuth oauth_consumer_key="DC0sePOBbQ8bYdC8r4Smg",oauth_signature_ method="HMACSHA1",oauth_timestamp="1468329288",oauth_nonce= "1930461031",oauth_version="1.0",oauth_token="3185435460- FTrisxX5Wc7c4KZxEcUPApBIAkobAMHTYbVNU4k",oauth_signature="l xJ7Of7imlvGnLQiHFqTO4EhLwA%3D"
 Host: ton.twitter.com • HTTP/1.1 200 OK
 […]
 
 <script>alert(1)</script> 39
  40. One step away • The file can now be accessed

    without a session • However, we need browsers to attach the OAuth parameter in the Authorisation header • There is no way to do it without violating SOP, at least for navigation • Dead end? Let’s check the spec… 40
  41. 41

  42. • GET /i/ton/data/ta_data/3185435460/xss.whatever HTTP/1.1
 Authorization: OAuth oauth_consumer_key="DC0sePOBbQ8bYdC8r4Smg",oauth_signatu re_method="HMACSHA1",oauth_timestamp="1468329288",oauth_ nonce="1930461031",oauth_version="1.0",oauth_token="3185 435460FTrisxX5Wc7c4KZxEcUPApBIAkobAMHTYbVNU4k",oauth_sig

    nature="lxJ7Of7imlvGnLQiHFqTO4EhLwA%3D"
 Host: ton.twitter.com • https://ton.twitter.com/i/ton/data/ta_data/3185435460/ xss.whatever? oauth_consumer_key=DC0sePOBbQ8bYdC8r4Smg&oauth_signature _method=HMACSHA1&oauth_timestamp=1468329288&oauth_nonce= 1930461031&oauth_version=1.0&oauth_token=3185435460FTris xX5Wc7c4KZxEcUPApBIAkobAMHTYbVNU4k&oauth_signature=lxJ7O f7imlvGnLQiHFqTO4EhLwA%3D 42
  43. 43

  44. 44

  45. Putting everything together 1. Upload a HTML file with an

    unknown extension 2. Attach OAuth parameter to the file URL 3. Make victim visit the file 4. The payload uses Cookie Tossing to override and fixate victim’s CSRF token 5. The payload performs actions on victim’s behalf 45
  46. What went wrong • File upload should validate the file

    type using a white-list filter • Static files should be served from a separate sandboxed domain • CSRF token should be bound to user’s session 46
  47. 47

  48. Mitigating Cookie Tossing & stuff • Drop duplicated cookies •

    GitHub’s approach • Cookie Prefixes • Currently only available on Chrome • If a cookie name begins with __Host-, the cookie cannot be accessed/modified from subdomains • Same-Site Cookies • Also available on Chrome only • Cookies with the SameSite flag will not be included the the request is issued cross- origin • The “ultimate” CSRF killer 48
  49. 49

  50. End Questions? Comments? Thanks a lot!