RubyOnRails: SQL injection causes ActionController::BadRequest which cannot be handled

Viewed 182

I am getting SQL-injection url requests such as: ?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=12345.php&vars[1][1]=

which is causing a:

ActionController::BadRequest (Invalid query parameters: expected Hash (got Array) for param `1'):

in my home#index. I believe the error is caught in a middleware or routing as I cannot catch the error in the home#index controller (or application controller either).

This is my development log response to the url:

ActionController::BadRequest (Invalid query parameters: expected Hash (got Array) for param `1'):
  rack (1.6.11) lib/rack/utils.rb:162:in `normalize_params'
  rack (1.6.11) lib/rack/utils.rb:163:in `normalize_params'
  rack (1.6.11) lib/rack/utils.rb:122:in `block in parse_nested_query'
  rack (1.6.11) lib/rack/utils.rb:119:in `each'
  rack (1.6.11) lib/rack/utils.rb:119:in `parse_nested_query'
  rack (1.6.11) lib/rack/request.rb:371:in `parse_query'
  actionpack (4.2.10) lib/action_dispatch/http/request.rb:339:in `parse_query'
  rack (1.6.11) lib/rack/request.rb:191:in `GET'
  actionpack (4.2.10) lib/action_dispatch/http/request.rb:300:in `GET'
  actionpack (4.2.10) lib/action_dispatch/http/parameters.rb:14:in `parameters'
  actionpack (4.2.10) lib/action_dispatch/http/filter_parameters.rb:37:in `filtered_parameters'
  actionpack (4.2.10) lib/action_controller/metal/instrumentation.rb:22:in `process_action'
  actionpack (4.2.10) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
  activerecord (4.2.10) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (4.2.10) lib/abstract_controller/base.rb:137:in `process'
  actionview (4.2.10) lib/action_view/rendering.rb:30:in `process'
  actionpack (4.2.10) lib/action_controller/metal.rb:196:in `dispatch'
  actionpack (4.2.10) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
  actionpack (4.2.10) lib/action_controller/metal.rb:237:in `block in action'
  actionpack (4.2.10) lib/action_dispatch/routing/route_set.rb:74:in `dispatch'
  actionpack (4.2.10) lib/action_dispatch/routing/route_set.rb:43:in `serve'
  actionpack (4.2.10) lib/action_dispatch/journey/router.rb:43:in `block in serve'
  actionpack (4.2.10) lib/action_dispatch/journey/router.rb:30:in `each'
  actionpack (4.2.10) lib/action_dispatch/journey/router.rb:30:in `serve'
  actionpack (4.2.10) lib/action_dispatch/routing/route_set.rb:817:in `call'
  bullet (6.0.2) lib/bullet/rack.rb:12:in `call'
  rack (1.6.11) lib/rack/etag.rb:24:in `call'
  rack (1.6.11) lib/rack/conditionalget.rb:25:in `call'
  rack (1.6.11) lib/rack/head.rb:13:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/flash.rb:260:in `call'
  rack (1.6.11) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.6.11) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.2.10) lib/active_record/query_cache.rb:36:in `call'
  airbrake (9.4.3) lib/airbrake/rack/middleware.rb:32:in `call!'
  airbrake (9.4.3) lib/airbrake/rack/middleware.rb:21:in `call'
  activerecord (4.2.10) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
  activerecord (4.2.10) lib/active_record/migration.rb:377:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.2.10) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
  activesupport (4.2.10) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
  activesupport (4.2.10) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.10) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  web-console (2.3.0) lib/web_console/middleware.rb:28:in `block in call'
  web-console (2.3.0) lib/web_console/middleware.rb:18:in `catch'
  web-console (2.3.0) lib/web_console/middleware.rb:18:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  railties (4.2.10) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.2.10) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.2.10) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.2.10) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.2.10) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.2.10) lib/rails/rack/logger.rb:20:in `call'
  request_store (1.4.1) lib/request_store/middleware.rb:19:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.6.11) lib/rack/methodoverride.rb:22:in `call'
  rack (1.6.11) lib/rack/runtime.rb:18:in `call'
  activesupport (4.2.10) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
  rack (1.6.11) lib/rack/lock.rb:17:in `call'
  actionpack (4.2.10) lib/action_dispatch/middleware/static.rb:120:in `call'
  rack (1.6.11) lib/rack/sendfile.rb:113:in `call'
  utf8-cleaner (0.2.5) lib/utf8-cleaner/middleware.rb:21:in `call'
  railties (4.2.10) lib/rails/engine.rb:518:in `call'
  railties (4.2.10) lib/rails/application.rb:165:in `call'
  rack (1.6.11) lib/rack/content_length.rb:15:in `call'
  thin (1.7.2) lib/thin/connection.rb:86:in `block in pre_process'
  thin (1.7.2) lib/thin/connection.rb:84:in `catch'
  thin (1.7.2) lib/thin/connection.rb:84:in `pre_process'
  thin (1.7.2) lib/thin/connection.rb:53:in `process'
  thin (1.7.2) lib/thin/connection.rb:39:in `receive_data'
  eventmachine (1.2.7) lib/eventmachine.rb:195:in `run_machine'
  eventmachine (1.2.7) lib/eventmachine.rb:195:in `run'
  thin (1.7.2) lib/thin/backends/base.rb:73:in `start'
  thin (1.7.2) lib/thin/server.rb:162:in `start'
  rack (1.6.11) lib/rack/handler/thin.rb:19:in `run'
  rack (1.6.11) lib/rack/server.rb:287:in `start'
  railties (4.2.10) lib/rails/commands/server.rb:80:in `start'
  railties (4.2.10) lib/rails/commands/commands_tasks.rb:80:in `block in server'
  railties (4.2.10) lib/rails/commands/commands_tasks.rb:75:in `tap'
  railties (4.2.10) lib/rails/commands/commands_tasks.rb:75:in `server'
  railties (4.2.10) lib/rails/commands/commands_tasks.rb:39:in `run_command!'
  railties (4.2.10) lib/rails/commands.rb:17:in `<top (required)>'
  bin/rails:9:in `require'
  bin/rails:9:in `<top (required)>'
  spring (2.1.0) lib/spring/client/rails.rb:28:in `load'
  spring (2.1.0) lib/spring/client/rails.rb:28:in `call'
  spring (2.1.0) lib/spring/client/command.rb:7:in `call'
  spring (2.1.0) lib/spring/client.rb:30:in `run'
  spring (2.1.0) bin/spring:49:in `<top (required)>'
  spring (2.1.0) lib/spring/binstub.rb:11:in `load'
  spring (2.1.0) lib/spring/binstub.rb:11:in `<top (required)>'
  bin/spring:13:in `require'
  bin/spring:13:in `<top (required)>'
  bin/rails:3:in `load'
  bin/rails:3:in `<main>'

How can I make sure these spam/sql inject-requests are being handled so they don't cause exceptions?

2 Answers

In Your Application Controller:

rescue_from ActionController::BadRequest, with: :bad_request


def bad_request(exception)
  render status: 400, json: {:error => exception.message}.to_json
end

I came across this same issue this evening. The error is being thrown before ApplicationController begins to handle the request so catching it in ApplicationController won't work because it's too late.

What you need to do is insert a piece of middleware that catches ActionController::BadRequest before the ActionDispatch::ParamsParser middleware which is throwing the error.

Example which catches the error, adds it to the log and then returns a plain text 400 page.

# app/middleware/catch_batch_request_error_mw.rb

class CatchBadRequestErrorMw
  def initialize(app)
    @app = app
  end

  def call(env)
    begin
      @app.call(env)
    rescue ActionController::BadRequest => e
      Rails.logger.error("CatchBadRequestErrorMw: #{e.message}")
      return [
        400,
        {
          'Content-Type': 'text/plain',
        },
        ['Bad Request']
      ]
    end
  end
end

And then in Application.rb

config.middleware.insert_before(ActionDispatch::ParamsParser, CatchBadRequestErrorMw)

I should note that the examples here are from a Rails 4.2 app so if you have any issues I'd start there.

Related