GitBucket Remote Code Execution | Video | Lucideus

Introduction
GitBucket is Git platform powered by Scala with easy installation, high extensibility & GitHub API compatibility. It is hosted on GitHub io pages. By default, GitBucket runs on port 8080.

GitBucket version 4.3.21 suffers from remote code execution. Abusing weak secret token and passing an insecure parameter to File function, we can get a shell access to the remote pc.

Lab Setup
Target Machine: Windows 7 Ultimate
Also tested on Windows 8.
Attacker Machine: Kali Linux 2017.3


Vulnerability
Inside GitLfsTransferServlet class getLfsFilePath output is passed directly to Java new File function.

 override protected def doPut(req: HttpServletRequest, res: HttpServletResponse): Unit =
 {
    for 
     {
      (owner, repository, oid) <- getPathInfo(req, res) if checkToken(req, oid)
    } 

    yield 

     {
      val file = new File(FileUtil.getLfsFilePath(owner, repository, oid))
      FileUtils.forceMkdir(file.getParentFile)
      using(req.getInputStream, new FileOutputStream(file)) { (in, out) =>
        IOUtils.copy(in, out)
      }
      res.setStatus(HttpStatus.SC_OK)
    }
  }


Oid value is obtained using getPathInfo function.

private def getPathInfo(req: HttpServletRequest, res: HttpServletResponse): Option[(String, String, String)] = {

    req.getRequestURI.substring(1).split("/").reverse match {

      case Array(oid, repository, owner, _*) => Some((owner, repository, oid))

      case _                                 => None

    }

  }

URL address is split using / and then last 3 parts are treated as: owner, repository and oid. But there is one problem here. On Windows it’s possible to change directories using \ (backslash), i.e. : cd c:\temp\some_dir.

In this table there is requested URL address and owner, repository and oid value:

URL: gitbucket/KACPER/EXPLOITS/STH

owner = KACPER

repository = EXPLOITS

oid = STH

URL: gitbucket/KACPER/EXPLOITS/../STH

owner = EXPLOITS

repository = ..

oid = STH

URL: gitbucket/KACPER/EXPLOITS/../../STH

owner = ..

repository = ..

oid = STH

Normally, it’s possible to do path traversal attack using ../. Because getPathInfo splits string using / character, oid value will never have /. Situation is little different with \:

URL: gitbucket/KACPER/EXPLOITS/STH\..\

owner = KACPER

repository = EXPLOITS

oid = STH\..\

URL: gitbucket/KACPER/EXPLOITS/..\..\STH

owner = KACPER

repository = EXPLOITS

oid = ..\..\STH

Now oid can contain ..\ string. That’s the reason why this exploit works only when GitBucket is running on Windows. oid value is important because it’s used inside

getLfsFilePath:

def getLfsFilePath(owner: String, repository: String, oid: String): String =     Directory.getLfsDir(owner, repository) + "/" + oid

There isn’t any validation here:

new File(FileUtil.getLfsFilePath("owner", "repository", "..\..\STH"))

Each lfs request requires proper Authorization header. It’s checked inside the checkToken function:

private def checkToken(req: HttpServletRequest, oid: String): Boolean = {

    val token = req.getHeader("Authorization")

    if (token != null) {

      val Array(expireAt, targetOid) = StringUtil.decodeBlowfish(token).split(" ")

      oid == targetOid && expireAt.toLong > System.currentTimeMillis

    } else {

      false

    }

  }

Token consists of two parts.

First is expiration date so the server knows if the request is valid at the specific point in time.
Second is oid of the file which is sent. Because of that, it’s not possible to send two files in two different locations using the same token value.

The token is encoded using Blowfish and base64:

def decodeBlowfish(value: String): String = {

    val spec = new javax.crypto.spec.SecretKeySpec(BlowfishKey.getBytes(), "Blowfish")

    val cipher = javax.crypto.Cipher.getInstance("Blowfish")

    cipher.init(javax.crypto.Cipher.DECRYPT_MODE, spec)

    new String(cipher.doFinal(Base64.getDecoder.decode(value)), "UTF-8")

  }

For decryption, BlowfishKey variable is used.

private lazy val BlowfishKey = {

    // last 4 numbers in current timestamp

    val time = System.currentTimeMillis.toString

    time.substring(time.length - 4)

  }

It’s only 4 digits in length. Because of that, there are only 10,000 combinations to check. From 0000 to 9999. It’s the great example of brute force attack.

Here is the script to perform the attack: Download

Usage: python filename.py baseUrl payload

baseUrl is the URL of GitbucketServer. Powershell alphanumeric payload is generated using setoolkit. This string is copied in place of payload.

To get a remote connection, we have used Metasploit multi/handler listener.

                                                                               POC






Post a Comment

0 Comments