
CVE-2021-28378 का विस्तृत प्रूफ-ऑफ-कॉन्सेप्ट और विश्लेषण, Gitea में एक संग्रहीत XSS भेद्यता जो git हुक के माध्यम से मनमाना कोड इंजेक्शन, विशेषाधिकार वृद्धि और दूरस्थ कोड निष्पादन को सक्षम बनाती है।
इस CVE के बारे में विवरण यहाँ देखें।
यह CVE सर्वर से प्राप्त सामग्री पर क्लाइंट-साइड पर स्ट्रिंग एस्केपिंग की कमी को लक्षित करता है। यह एक हमलावर को किसी issue या pull request पर टिप्पणी बनाकर आसानी से मनमाना कोड इंजेक्ट करने में सक्षम बनाता है। यह कमिट 7d7ab1eeae43d99fe329878ac9c8db5e45e2dee5 में पेश किया गया था, जनवरी 2020 के अंत तक, लेकिन squash के कारण हम ठीक से नहीं जान सकते कि इसे किसने किया ताकि समान बगों की जांच की जा सके।
सबसे पहले, आइए समझें कि यह कमजोरी कहाँ से आती है। इसके लिए, आइए वह कमिट देखें जो इस CVE को ठीक करता है।
पहले:
labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
html: `
<div>
<p><small>${issue.repository.full_name} on ${createdAt}</small></p>
<p><span class="${color}">${svg(octicon)}</span> <strong>${issue.title}</strong> #${index}</p>
<p>${body}</p>
${labels}
</div>
`
हम देख सकते हैं कि label.name, issue.repository.full_name, issue.title और body पर कोई html एस्केपिंग नहीं है, जो वे स्ट्रिंग्स हैं जिन्हें उपयोगकर्ता हेरफेर कर सकता है, और इसलिए, मनमाना कोड इंजेक्ट कर सकता है।
ध्यान दें कि issue.repository.full_name को वास्तव में कोड इंजेक्ट करने के लिए हेरफेर नहीं किया जा सकता क्योंकि इसे बहुत सारे नियमों का पालन करना होता है ([\w._-]+) जिन्हें हम इस CVE से बायपास नहीं कर सकते।
PR ठीक यही ठीक करता है उन्हें htmlEscape में डालकर।
बाद में:
labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${htmlEscape(label.name)}</div>`;
html: `
<div>
<p><small>${htmlEscape(issue.repository.full_name)} on ${createdAt}</small></p>
<p><span class="${color}">${svg(octicon)}</span> <strong>${htmlEscape(issue.title)}</strong> #${index}</p>
<p>${htmlEscape(body)}</p>
${labels}
</div>
`
आइए फ़ाइल web_src/js/features/contextpopup.js पर एक त्वरित नज़र डालें, ताकि पता लगाया जा सके कि CVE को कैसे ट्रिगर किया जाए।
...
const {AppSubUrl} = window.config;
export default function initContextPopups() {
const refIssues = $('.ref-issue');
if (!refIssues.length) return;
refIssues.each(function () {
const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse();
issuePopup(owner, repo, index, $(this));
});
}
function issuePopup(owner, repo, index, $element) {
$.get(`${AppSubUrl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => {
...
for (let i = 0; i < issue.labels.length; i++) {
...
labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
}
...
$element.popup({
variation: 'wide',
delay: {
show: 250
},
html: `
<div>
<p><small>${issue.repository.full_name} on ${createdAt}</small></p>
<p><span class="${color}">${svg(octicon, 16)}</span> <strong>${issue.title}</strong> #${index}</p>
<p>${body}</p>
${labels}
</div>
`
});
});
}
यहाँ हम देखते हैं कि ref-issue क्लास वाले प्रत्येक नोड के लिए, हम एक असुरक्षित पॉपअप जोड़ेंगे।
अब, हमें यह पता लगाने की आवश्यकता है कि ref-issue उपयोगकर्ता को कहाँ भेजा जाता है: आइए grep करें (और केवल दिलचस्प परिणाम रखें, यानी कोई परीक्षण फ़ाइलें या ऐसी ही नहीं)!
निम्नलिखित कमिट संख्या 1.12.4 टैग से मेल खाती है, जो आधिकारिक docker-compose.yml फ़ाइल का डिफ़ॉल्ट संस्करण है, जो इस CVE से प्रभावित है।
cd gitea
git checkout 8a51c48eb6367513e0518bcd412e64f6cbfe5e1a
grep -r ref-issue
modules/markup/html.go: replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue"))
modules/markup/html.go: replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue"))
modules/markup/html.go: link = createLink(com.Expand(ctx.metas["format"], ctx.metas), reftext, "ref-issue")
modules/markup/html.go: link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], path, ref.Issue), reftext, "ref-issue")
modules/markup/html.go: link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
modules/markup/sanitizer.go: sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue`)).OnElements("a")
अब हम जानते हैं कि ref-issue क्लास के साथ कोई स्थिर सामग्री नहीं है, लेकिन यह गतिशील रूप से उत्पन्न होती है।
अंतिम वाला दिलचस्प नहीं है क्योंकि यह केवल एक sanitizer नीति नियम है। बाकी दिलचस्प हैं, हम देखेंगे क्यों।
आइए modules/markup/html.go में गोता लगाएँ।
func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) {
if ctx.metas == nil {
return
}
m := getIssueFullPattern().FindStringSubmatchIndex(node.Data)
if m == nil {
return
}
link := node.Data[m[0]:m[1]]
id := "#" + node.Data[m[2]:m[3]]
// extract repo and org name from matched link like
// http://localhost:3000/gituser/myrepo/issues/1
linkParts := strings.Split(path.Clean(link), "/")
matchOrg := linkParts[len(linkParts)-4]
matchRepo := linkParts[len(linkParts)-3]
if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] {
// TODO if m[4]:m[5] is not nil, then link is to a comment,
// and we should indicate that in the text somehow
replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue"))
} else {
orgRepoID := matchOrg + "/" + matchRepo + id
replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue"))
}
}
हमें यह भी समझने की आवश्यकता होगी कि getIssueFullPattern क्या करता है।
func getIssueFullPattern() *regexp.Regexp {
if issueFullPattern == nil {
appURL := setting.AppURL
if len(appURL) > 0 && appURL[len(appURL)-1] != '/' {
appURL += "/"
}
issueFullPattern = regexp.MustCompile(appURL +
`\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#]\S+.(\S+)?)?\b`)
}
return issueFullPattern
}
आइए संक्षेप में समझें कि वहाँ क्या कार्य किया जाता है।
fullIssuePatternProcessor में, हमें पहले यह सुनिश्चित करना होगा कि ctx.metas शून्य नहीं है (हम मानते हैं कि यह सत्य है जबकि यह स्टैक ट्रेस में उच्च फ़ंक्शन/विधियों द्वारा बनाया गया है)।
फिर हम जाँचते हैं कि नोड डेटा http://mygitea.com/gituser/myrepo/issues/1 जैसे पथ से मेल खाता है या नहीं। यदि मिलान होता है, तो हम इसे एक लिंक से बदल देते हैं।
यदि लिंक उसी उपयोगकर्ता और रेपो के बारे में है, तो इसे #<issue_id> पैटर्न से बदलें, अन्यथा <user>/<repo>#<issue_id> से।
इन दोनों मामलों में, हमारे पास एक ref-issue क्लास है जो पॉपअप उत्पन्न करने के लिए जोड़ा जाता है।
अन्य grep-परिणाम भी कुछ ऐसा ही करते हैं।
अब हमारे पास संदर्भ है, जो http://mygitea.com/gituser/myrepo/issues/1 जैसे लिंक को एक छोटे लिंक से बदलने के बारे में है, हम अनुमान लगा सकते हैं कि ऐसा कार्य कहाँ किया जाता है: issues और PR में, जहाँ आप एक लेबल जोड़ सकते हैं और/या टिप्पणी पोस्ट कर सकते हैं।
इसके लिए, मान लें कि आपके पास लिखने की अनुमति वाला एक रिपॉजिटरी है।
अपने रिपॉजिटरी पर जाएँ, और मनमाना कोड (जैसे <script>alert('label.name')</script>) वाले नाम के साथ एक नया लेबल बनाएँ।
अपने रिपॉजिटरी issues पर जाएँ, और शीर्षक और बॉडी में मनमाना कोड के साथ एक नया issue बनाएँ, और इसे पिछला लेबल जोड़ें।
किसी भी रिपॉजिटरी पर एक नया issue बनाएँ, और उसकी बॉडी में अपने इंजेक्ट किए गए issue का लिंक जोड़ें जैसे http://mygitea.com/gituser/myrepo/issues/1।
यह उस लिंक से बदल दिया जाएगा जो हमने पहले देखा था, और जब कोई अपने माउस को लिंक पर ले जाएगा, तो मनमाना कोड चलेगा: label.name एम्बेड करते हुए एक पॉपअप प्रदर्शित होगा।
CVE-2021-28378 मनमाना कोड इंजेक्शन को सक्षम करने के लिए सिद्ध है। आइए कल्पना करें कि एक हमलावर क्या कर सकता है।