Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Esoteric Web Application Vulnerabilities

Esoteric Web Application Vulnerabilities

A summary of the strangest vulnerabilities I've found during last year

andresriancho

April 13, 2017
Tweet

More Decks by andresriancho

Other Decks in Programming

Transcript

  1. Esoteric Web Application
    Vulnerabilities
    OWASP LATAM Tour

    View Slide

  2. /me
    ● Application security expert (web|API)
    ● Developer (Python!)
    ● Open Source evangelist
    ● w3af project leader
    ● Independent consultant

    View Slide

  3. Intro: SQL injection is dead
    I don't care

    View Slide

  4. ORM killed the pentest star
    ● All modern web development frameworks
    provide abstractions to interact with (no) SQL
    databases. Developers don’t write raw SQL
    queries anymore.
    ● SQL injections are rare nowadays, this requires
    us testers to dig deeper into the application to
    find high risk vulnerabilities.

    View Slide

  5. MVC, templates and default HTML encode killed XSS
    ● Most modern web development frameworks use a model view controller
    architecture, which uses templates to render the HTML shown to users.
    ● Templating engines, such as Jinja2, HTML encode the context data by
    default.
    ● Developers need to write more code to make the template vulnerable to
    Cross-Site Scripting, which leads to less vulnerabilities.

    {% for user in user_list %}
    {{ user.username }}
    {% endfor %}

    View Slide

  6. View Slide

  7. “Aggressive Input Decoding”.to_s
    Esoteric #1

    View Slide

  8. Aggressive input decoding
    Ruby on Rails, Sinatra and other (ruby) web frameworks perform aggressive input
    decoding:
    http://www.phrack.org/papers/attacking_ruby_on_rails.html
    post '/hello' do
    name = params[:name]
    render_response 200, name
    POST /hello HTTP/1.1
    Host: example.com
    Content-Type: application/x-www-form-urlencoded
    name=andres
    POST /hello HTTP/1.1
    Host: example.com
    Content-Type: application/json
    {"name": "andres"}

    View Slide

  9. Decode to a Ruby Hash
    POST /hello HTTP/1.1
    Host: example.com
    Content-Type: application/json
    {"name": {"foo": 1}}
    In all previous cases the type of the name variable was a String, but we can force
    it to be a Hash:

    View Slide

  10. noSQL ODM introduction
    When MongoId ODM (Object Document Mapper) and similar frameworks are in
    use developers can write code similar to:
    Which will query the Mongo database and return the first registration flow where
    the user_id and confirmation_token match.
    post '/registration/complete' do
    registration = Registration.where({
    user_id: params[:user_id],
    confirmation_token: params[:token]
    }).first
    ...
    POST /registration/complete HTTP/1.1
    Host: vulnerable.com
    Content-Type: application/json
    {"token": "dee1...bb8e", "user_id": 3578}

    View Slide

  11. noSQL ODM complex queries
    Developers can write “complex” ODM queries using Ruby Hashes as parameters:
    user = Users.where({user_id: params[:user_id],
    country: {"$ne": "Argentina"}}).first
    users = Users.where({user_id: {"$in": [123, 456, 789]}})

    View Slide

  12. Decode to Hash leads to noSQL injection
    It’s possible to bypass the token validation!
    post '/registration/complete' do
    registration = Registration.where({
    user_id: params[:user_id],
    confirmation_token: params[:token]
    }).first
    ...
    POST /registration/complete HTTP/1.1
    Host: vulnerable.com
    Content-Type: application/json
    {"token": {"$ne": "nomatch"}, "user_id": 3578}

    View Slide

  13. “User controlled input”.to_s
    Fixing this vulnerability is quick and easy:
    Most developers will forget to add the .to_s and it’s easy to miss in a source code
    review. Recommend Sinatra param or similar.
    get '/registration/complete' do
    @registration = Registration.where({
    user_id: params[:user_id].to_s,
    confirmation_token: params[:token].to_s
    }).first
    ...

    View Slide

  14. Esoteric #2
    Host header injection

    View Slide

  15. Call me to verify my identity #1
    The application requires users to provide a cellphone to verify their identity. A
    phone call is initiated by the application using a service like Twilio, the call audio
    contains a verification code which needs to be input into the application to verify
    phone ownership.
    HTTP request
    Verify my phone +1 (541) 754-3010

    View Slide

  16. Call me to verify my identity #2
    Call +1 (541) 754-3010
    Send code 357896 in audio
    HTTP request
    Please call +1 (541) 754-3010
    Audio for the call is available at
    https://vulnerable.com/audio/
    HTTP request
    https://vulnerable.com/audio/

    View Slide

  17. Call me to verify my identity #3
    HTTP request
    Code is 357896
    HTTP response
    Welcome admin!

    View Slide

  18. Bypass phone verification
    Hacker wants to bypass phone verification, ideas:
    ○ Hack admin’s smartphone
    ○ Hack vulnerable.com
    ○ Create a raw cellphone tower and sniff admin’s phone call
    ○ Hack Twilio
    Hacking vulnerable.com seems to be the easiest path to follow. But… what do
    we need?

    View Slide

  19. UUID4
    Version 4 UUIDs use a scheme relying only on random numbers, thus the audio
    URLs can’t be brute forced:
    https://vulnerable.com/audio/f47ac10b-58cc-4372-a567-0e02b2c3d479

    View Slide

  20. Zoom into HTTP request to Twilio
    HTTP request
    Please call +1 (541) 754-3010
    Audio for the call is available at
    https://vulnerable.com/audio/
    POST /call/new HTTP/1.1
    Host: api.twilio.com
    Content-Type: application/json
    X-Authentication-Api-Key: 2bc67a5...
    {"phone_number": "+1 (541) 754-3010"},
    "audio_callback": "https://vulnerable.com/f47ac10b-5..."}

    View Slide

  21. Insecure Twilio API call
    HTTP request
    Please call +1 (541) 754-3010
    Audio for the call is available at
    https://vulnerable.com/audio/
    import requests
    def start_call(phone, callback_url):
    requests.post('https://api.twilio.com/call',
    data={'phone_number': phone,
    'audio_callback': callback_url})

    audio_id = generate_audio(request.user_id)
    callback_url = 'https://%s/%s' % (request.host, audio_id)
    start_call(request['phone'], callback_url)

    View Slide

  22. Change Host header to exploit
    HTTP request
    Verify my phone +1 (541) 754-3010
    POST /verify-my-phone HTTP/1.1
    Host: vulnerable.com
    Content-Type: application/json
    {"phone_number": "+1 (541) 754-3010"}}
    POST /verify-my-phone HTTP/1.1
    Host: evil.com
    Content-Type: application/json
    {"phone_number": "+1 (541) 754-3010"}}

    View Slide

  23. Exploit results in modified callback_url
    HTTP request
    Please call +1 (541) 754-3010
    Audio for the call is available at
    https://evil.com/audio/
    HTTP request
    https://evil.com/audio/
    HTTP request
    https://vulnerable.com/audio/

    View Slide

  24. MUST-HAVE: Strict validation for
    Host header
    ● Make sure that your nginx, apache, and web frameworks validate the host
    header before any further code is run.
    ● Django has strict host header validation built in using ALLOWED_HOSTS
    configuration setting.

    View Slide

  25. Esoteric #3
    null, nil and NULL

    View Slide

  26. Password reset
    ● Password resets are very sensitive and, in some cases, insecure. The most
    wanted vulnerability is to be able to reset the password for a user for which
    we don’t have the password reset token.
    ● Usually password resets are implemented as follows:
    ○ User starts a new password reset flow
    ○ An email is sent by the application containing a randomly generated token
    ○ The token is used to prove that the user has access to the email address and the password
    is reset.

    View Slide

  27. Implementation details
    class AddPasswordResetTokenToUser < ActiveRecord::Migration
    def change
    add_column :users, :pwd_reset_token, :string, default: nil
    end
    end
    post '/start-password-reset' do:
    user = Users.where({"email": params["email"]}).first
    token = generate_random_token()
    user.pwd_reset_token = token
    user.save!
    send_email(user.email, token)
    post '/complete-password-reset' do:
    user = Users.where({"pwd_reset_token": params["token"]}).first
    user.password = params["new_password"]
    user.pwd_reset_token = nil
    user.save!

    View Slide

  28. Token defaults to NULL in the database
    POST /complete-password-reset HTTP/1.1
    Host: vulnerable.com
    Content-Type: application/json
    {"token": null, "new_password": "l3tm31n"}
    ● Each time a new user is created his pwd_reset_token field is set to NULL in
    the database.
    ● When the user starts a new password reset flow a randomly generated
    token is assigned to pwd_reset_token
    ● What if...

    View Slide

  29. Safe defaults and strict type validation
    post '/complete-password-reset' do:
    user = Users.where({"pwd_reset_token": params["token"].to_s}).first
    user.password = params["new_password"]
    user.pwd_reset_token = nil
    user.save!
    class AddPasswordResetTokenToUser < ActiveRecord::Migration
    def change
    add_column :users, :pwd_reset_token, :string,
    default: generate_random_token()
    end
    end

    View Slide

  30. Esoteric #4
    Paypal double spend

    View Slide

  31. Paypal’s Instant Payment Notification
    ● I love payment gateways! See my previous talk on this subject.
    ● Paypal uses IPN to notify a site that a new payment has been processed and
    further action, such as increasing the user funds in the application, should be
    performed.
    ● The developer sets the IPN URL in the merchant account settings at Paypal:
    https://www.example.com/paypal-handler

    View Slide

  32. View Slide

  33. Zoom into Paypal’s IPN HTTP request
    POST /paypal-handler HTTP/1.1
    Host: www.example.com
    Content-Type: application/x-www-form-urlencoded
    mc_gross=19.95&protection_eligibility=Eligible&address_status=confirmed&p
    ayer_id=LPLWNMTBWMFAY&tax=0.00&address_street=1+Main+St&payment_date=20%3
    A12%3A59+Jan+13%2C+2009+PST&payment_status=Completed&charset=windows-1252
    &address_zip=95131&first_name=Test&mc_fee=0.88&address_country_code=US&ad
    dress_name=Test+User&notify_version=2.6&custom=665588975&payer_status=ver
    ified&address_country=United+States&address_city=San+Jose&quantity=1&veri
    fy_sign=AtkOfCXbDm2hu0ZELryHFjY-Vb7PAUvS6nMXgysbElEn9v-1XcmSoGtf&payer_em
    ail=gpmac_1231902590_per%40paypal.com&txn_id=61E67681CH3238416&payment_ty
    pe=instant&last_name=User&address_state=CA&receiver_email=gpmac_123190268
    6_biz%40paypal.com&payment_fee=0.88&receiver_id=S8XGHLYDW9T3S&txn_type=ex
    press_checkout&item_name=&mc_currency=USD&item_number=&residence_country=
    US&handling_amount=0.00&transaction_subject=&payment_gross=19.95&shipping
    =0.00

    View Slide

  34. Zoom into Paypal’s IPN HTTP request
    There are a few important parameters that we need to understand:
    ● mc_gross=19.95 is the amount paid by the user
    ● custom=665588975 is the user’s ID at the merchant application, which is
    sent to Paypal when the user clicks the “Pay with Paypal” button in the
    merchant’s site
    ● receiver_email=gpmac_1231902686_biz%40paypal.com is the merchant’s
    email address
    ● payment_status=Completed is the payment status

    View Slide

  35. Why does the merchant verify the IPN data?

    View Slide

  36. Insecure IPN handler
    import requests
    PAYPAL_URL = 'https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate'
    def handle_paypal_ipn(params):
    # params contains all parameters sent by Paypal
    response = requests.post(PAYPAL_URL, data=params).text
    if response == 'VERIFIED':
    # The payment is valid at Paypal, mark the cart instance as paid
    cart = Cart.get_by_id(params['custom'])
    cart.record_user_payment(params['mc_gross'])
    cart.user.send_thanks_email
    else:
    return 'Error'

    View Slide

  37. Insecure IPN handlers
    No receiver email check

    View Slide

  38. Insecure IPN handlers
    No receiver email check

    View Slide

  39. ● Attacker needs to perform a special Paypal payment using a target specific
    custom_id parameter which will associate the spoofed payment with his
    account.
    ● The payment is made from the attacker’s credit card to his paypal account.
    Money is still under his control, but the attacker will lose Paypal’s
    commission for each transaction.
    ● Many example IPN implementations in github.com are vulnerable. I
    wonder how many were used to create applications which are currently live
    in production?

    View Slide

  40. Secure IPN handler
    import requests
    PAYPAL_URL = 'https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate'
    MERCHANT_PAYPAL_USER = '[email protected]'
    def handle_paypal_ipn(params):
    if params['receiver_email'] == MERCHANT_PAYPAL_USER:
    return 'Error'
    # params contains all parameters sent by Paypal
    response = requests.post(PAYPAL_URL, data=params).text
    if response == 'VERIFIED':
    # The payment is valid at Paypal, mark the cart instance as paid
    cart = Cart.get_by_id(params['custom'])
    cart.record_user_payment(params['mc_gross'])
    cart.user.send_thanks_email
    else:
    return 'Error'

    View Slide

  41. Is this Paypal’s fault?
    ● Are all payment gateways vulnerable?
    ● MercadoPago implemented a different communication protocol for their
    IPN. Their protocol is much better than Paypal’s since it doesn’t rely on the
    developer’s IPN handler implementation to provide security.
    ● MercadoPago sends a GET request with the purchase ID to the IPN URL,
    then the developer needs to perform a GET request to
    https://api.mercadopago.com/ in order to retrieve the transaction details.
    This request is authenticated, and any attempts to access transactions from
    other merchants is denied.

    View Slide

  42. Esoteric #5
    MessageVerifier Marshal RCE

    View Slide

  43. ActiveSupport::MessageVerifier Marshal RCE
    ● ActiveSupport::MessageVerifier uses Ruby’s Marshal to serialize arbitrary
    information, which is then signed using a developer provided secret. A
    verified message looks like:
    ● The message can be decoded:
    BAhJIhphb...XNlYy5jb20GOgZFVA==--8bacd5cb3e72ed7c457aae1875a61d668438b616
    1.9.3-p551 :006 >
    Base64.decode64('BAhJIhphbmRyZXNAYm9uc2FpLXNlYy5jb20GOgZFVA==')
    => "\x04\bI\"\[email protected]\x06:\x06ET"
    1.9.3-p551 :007 >
    Marshal.load(Base64.decode64('BAhJIhphbmRyZXNAYm9uc2FpLXNlYy5jb20GOgZFVA=='))
    => "[email protected]"
    1.9.3-p551 :008 >

    View Slide

  44. ActiveMessages are signed
    ● When the application receives the signed message, it will take the base64
    encoded data and calculate HMAC SHA1 for it using using the developer
    controlled secret.
    ● The calculated signature must match the one provided with the message:
    ● Once the signature is verified the data is base64 decoded and Unmarshaled.
    BAhJIh...--8bacd5cb3e72ed7c457aae1875a61d668438b616

    View Slide

  45. Guessable signing secret leads to RCE
    Ruby’s documentation clearly states that unmarshaling arbitrary data is insecure
    and will lead to arbitrary code execution. ActiveSupport::MessageVerifier is
    protected against this vulnerability by a developer controlled secret. Poorly
    chosen secrets allow:
    Brute-force attack to discover the secret
    Specially crafted gadget/object is created, serialized and encoded.
    Secret is used to sign gadget
    Signed message is sent to the application, where it will be unmarshalled
    and remote code execution is achieved

    View Slide

  46. Secure ActiveSupport::MessageVerifier usage
    ● Choose randomly generated, long, secrets to sign your messages.
    ● Use a different serialization method:
    @verifier = ActiveSupport::MessageVerifier.new(long_secret, serializer: json)

    View Slide

  47. Outro:
    That’s why I don’t care

    View Slide

  48. View Slide

  49. Vulnerabilities are always there
    ● You’re smarter than your tools. Let the automation do the grunt work and
    focus your time on source code review, application logic flaws, issues
    specific to the target application, etc.
    ● You’re smarter than your client. Convince them that with the source code
    you’ll be able to identify more vulnerabilities and provide greater ROI.
    ● You’re smarter (well, actually more trained in security, vulnerabilities and
    risks) than most developers. They will make mistakes, no matter how good
    they are.

    View Slide

  50. Thanks!

    View Slide

  51. For hire
    Does your company need these services?
    ● Application Penetration Test
    ● Secure Coding Training for Developers
    ● Source Code Review
    ● Cloud Security Assessment
    Let's get in touch, I can help your company deliver secure
    web applications.

    View Slide