読者です 読者をやめる 読者になる 読者になる

odeの開発メモ日記

プログラマーやってます。今までの道のり ポケコンbasic→C(DirectX5 ネットやろうぜ)→Perl(ちょろっと)→Java→C#→Ruby→Android(Java)

willcomの高速化サービスでIP spoofing attackエラー

rails

問題

rails2.1のサイト(リバースプロクシ使用)をwillcomの高速化サービスのプロクシ(フロントプロクシ)を使うと
IP spoofing attack?!
というエラーになってしまいました。
(恐らくこのエラーを出すようになったのは2.1からっぽい。)

理由

ソースをおっていくとHTTPのリクエストヘッダーに
HTTP_CLIENT_IPとHTTP_X_FORWARDED_FORの
両方がある場合でかつIPがどちらも違う場合に起きるみたい。
(どちらもプロクシ経由時の要求元IPを返すヘッダー。リバプロ経由だとREMOTE_ADDRがリバプロのIPになるためこちらを参照する必要がある)
それで一体どっちのIPを使えばいいんだ、このインチキ野郎とエラーを出しているらしい。

恐らくHTTP_CLIENT_IPがフロントプロクシの要求元IPで
HTTP_X_FORWARDED_FORがリバースプロクシの要求元IPになっていると思われる。


HTTP_X_FORWARDED_FORの仕様として調べた限り

  • 複数のプロクシを経由するとカンマ区切りで複数くる
  • ただしリバースプロクシ(mod_proxy)を経由するとフロントプロクシで付与したHTTP_X_FORWARDED_FORは消される。

 (そのようなドキュメントは見つからなかったのですがうちのリバプロではこんな挙動でした。
  消えない動作の場合もあるみたいなのですが、何故消えるのかは不明。ver?)


そしてwillcomの仕様としては下記のどちらかと思われる

  • HTTP_CLIENT_IPとHTTP_X_FORWARDED_FORの両方を送ってきている。(元のHTTP_X_FORWARDED_FORはリバプロで削除)
  • HTTP_CLIENT_IPのみ送ってきている。(HTTP_X_FORWARDED_FORはリバースプロクシが付与)


このためHTTP_CLIENT_IPとHTTP_X_FORWARDED_FORが違くなると思われます。


同様の挙動をするプロクシを経由した場合でもこの問題が起きるでしょう。


HTTP_CLIENT_IPなんていうどこで改ざんされるかわからない信用できない値は使わなくてよいと思うのだけど
HTTP_CLIENT_IPを返すリバースプロクシがあった場合への対応かな?
そうだとすればモード切替があったほうがいいと思う。
でもってデフォルトはメジャーなHTTP_X_FORWARDED_FORみたいな。

解決策

  • apacheレベルでClient-IPヘッダーを削除する。
    • 下記の設定を追加?(試してはないです)
    • RequestHeader unset Client-IP
  • railsにモンキーパッチをあてる。
    • config/enviroment.rb
module ActionController
  class AbstractRequest
    def remote_ip
      remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)

      unless remote_addr_list.blank?
        not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
        return not_trusted_addrs.first unless not_trusted_addrs.empty?
      end
      remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')

      # HTTP_CLIENT_IPを返すリバースプロキシの場合はtrueにする。railsっぽいconf化したいけどわからない。。
      if false
        if @env.include? 'HTTP_CLIENT_IP'
          return @env['HTTP_CLIENT_IP']
        else
          raise ActionControllerError.new("no header. HTTP_CLIENT_IP.")
        end
      end

      if remote_ips
        while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
          remote_ips.pop
        end

        return remote_ips.last.strip
      end

      @env['REMOTE_ADDR']
    end
  end
end

remote_ipメソッドの挙動

ちなみにこのエラーを出しているremote_ipメソッドを眺めてて思ったのが
HTTP_CLIENT_IPやHTTP_X_FORWARDED_FORを
書き換えればIP偽装できんでねって疑問。

結論からいくと問題なさそうです。

最初にREMOTE_ADDRがローカルIPかどうかのチェックをし、
グローバルIPならそのまま返します。

リバースプロクシ経由の場合はREMOTE_ADDRにリバースプロクシのローカルIPが入るので
HTTP_X_FORWARDED_FORか、HTTP_CLIENT_IPを使います。

HTTP_X_FORWARDED_FORは複数になる可能性があるので
最後のIPがリバースプロクシに繋いできたIPになるため最後のIPを使用。
ただし多段のリバースプロクシの可能性もあるためお尻のローカルIPのものを削除してから。

元ソース
actionpack-2.1.2\lib\action_controller\request.rb

    # Which IP addresses are "trusted proxies" that can be stripped from
    # the right-hand-side of X-Forwarded-For
    TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i

    # Determine originating IP address.  REMOTE_ADDR is the standard
    # but will fail if the user is behind a proxy.  HTTP_CLIENT_IP and/or
    # HTTP_X_FORWARDED_FOR are set by proxies so check for these if
    # REMOTE_ADDR is a proxy.  HTTP_X_FORWARDED_FOR may be a comma-
    # delimited list in the case of multiple chained proxies; the last
    # address which is not trusted is the originating IP.
    def remote_ip
      remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)

      unless remote_addr_list.blank?
        not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
        return not_trusted_addrs.first unless not_trusted_addrs.empty?
      end
      remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')

      if @env.include? 'HTTP_CLIENT_IP'
        if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
          # We don't know which came from the proxy, and which from the user
          raise ActionControllerError.new(<<EOM)
IP spoofing attack?!
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
EOM
        end

        return @env['HTTP_CLIENT_IP']
      end

      if remote_ips
        while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
          remote_ips.pop
        end

        return remote_ips.last.strip
      end

      @env['REMOTE_ADDR']
    end

参考HP

IP spoofing attack関連の情報(英語でよくわかってないです)
http://iprog.com/posting/2008/08/rails_500_error_ip_spoofing_attack
http://rails.lighthouseapp.com/projects/8994/tickets/322

プロクシのヘッダについて
http://www.nurs.or.jp/~sug/homep/proxy/proxy7.htm

実行環境

rails(2.1.2)
apache(2.2.10)