Slide 1

Slide 1 text

Killing with A journey from subdomain #SELFXSS to site-wide #CSRF @Twitter

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

File Upload XSS ads.twitter.com 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Nice. Where is the XSS? 5 Won’t execute Because the file is not interpreted as HTML

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Can browsers ignore Content-Type? Spoiler alert: yes 7

Slide 8

Slide 8 text

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?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

10 Enabled by default

Slide 11

Slide 11 text

Still won’t execute 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

19

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

httpOnly in action no reading, no writing, ugh 24

Slide 25

Slide 25 text

httpOnly in action no reading, no writing, ugh 25

Slide 26

Slide 26 text

Cookie Tossing 26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

–RFC 6265 “Cookies with longer paths are listed before cookies with shorter paths.” 30

Slide 31

Slide 31 text

31

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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
 […]
 
 alert(1) 39

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

41

Slide 42

Slide 42 text

• 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

Slide 43

Slide 43 text

43

Slide 44

Slide 44 text

44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

49

Slide 50

Slide 50 text

End Questions? Comments? Thanks a lot!