Skip to content

Sinatra leaks sensitive information from any exception with an http_status attribute #1518

@garybernhardt

Description

@garybernhardt

This is fundamentally the same issue as #1204. However, I think I have a clearer explanation of what's happening, including an easily-reproduced example. I also think the bug is a serious security problem and want to make that clear.

First, Stripe's errors have an http_status attribute. This attribute represents the status code returned by Stripe's servers. For example, here you can see Stripe::InvalidRequestError taking an http_status argument, which is 400 when the error is actually raised.

  class InvalidRequestError < StripeError
    attr_accessor :param

    def initialize(message, param, http_status: nil, http_body: nil, json_body: nil,
                   http_headers: nil, code: nil)

In our application, we were seeing this error raised for correct reasons (i.e., we had an actual error). But Sinatra will treat any exception with an http_status attribute as a Sinatra-level error that should be translated into that HTTP status code.

    def handle_exception!(boom)
      [...]

      if boom.respond_to? :http_status
        status(boom.http_status)

Note that the Sinatra code above does not respond to any configuration option. As far as I can tell, this problem exists regardless of how Sinatra is configured.

By these two code snippets' powers combined:

  1. Stripe's servers are 400ing because we did something wrong.
  2. That 400 response exception's text contains sensitive Stripe identifiers, like Stripe token IDs.
  3. Sinatra sees the http_status attribute and incorrectly thinks that this exception is actually a Sinatra exception.
  4. Sinatra returns that status code and the response text to the user, including sensitive information from Stripe.

You can reproduce this yourself by installing the "stripe" and "sinatra" gems and running this single-file Sinatra app. Go to localhost:4567/error_test and you'll see the "sensitive information from stripe" string leaked to the end user.

#!/usr/bin/env ruby

require "sinatra"
require "stripe"

get "/error_test" do
  raise Stripe::InvalidRequestError.new(
    "sensitive information from stripe", 2, http_status: 400)
end

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions