Onedev is vulnerable to RCE via local repo server push attack (OD-2224)
byc_404 opened 1 year ago

Summary

In Onedev's CI/CD jobs, job like checkout code and push to remote allow operations such as checkout and push commands based on the git repository.

Notably, when Onedev performs checkout operations, it leaves the git repository content in a temporary local directory. This means any content within the git repository will land in the local file system where the onedev server does.

This introduces a new attack surface called the server push attack. We know that git repositories have a hooks mechanism, divided into two types: client hooks and server hooks. Here, we focus on server hooks. For instance, during a git push operation, the server-side git repository receives data and triggers the pre-receive script under .git/hooks.

This creates an attack vector: If an attacker can create a git repository and have it land on the local file system, combined with workflows that push to the repository, the lack of validation for repository addresses on the server allows an attacker to exploit the file:// protocol to point to a malicious local repository. This triggers hooks scripts in the malicious repository, enabling command execution on the local machine.

Since git repositories can contain bare repo content, the following vulnerabilities arise in Onedev's scenario:

  • Lack of isolation between repository content during checkout/push and the server file system.
  • Lack of restrictions on allowed git protocols.
  • Lack of randomization for repository paths.

These issues lead to RCE vulnerabilities.

Steps to Reproduce

  1. Log in with code writer permissions for any project.

  2. Import a malicious git repository prepared by the attacker, https://gitlab.com/demo621918/testrepo/- . The exploit branch of this repository contains a subdirectory named evilgitdirectory, structured as a git bare repo. The hooks folder in this directory includes a pre-receive script that, when triggered, executes the command touch /tmp/pwned. Pasted image 20241220205533.png

Pasted image 20241220205030.png

  1. Switch to the exploit branch (important!) in this repository. Use the .onedev-buildspec.yml file to create the following jobs. This job represents a checkout operation on the malicious repository, followed by a step that executes the command sleep 30 in the container ubuntu:latest. Since the job does not complete, the temporary directory will persist instead of being deleted. The directory structure will be /opt/onedev/temp/server/onedev-build-{REPO_NUM}-{JOB_NUM}/workspace/.
version: 38
jobs:
- name: demo job
  steps:
  - !CheckoutStep
    name: mycheckout
    cloneCredential: !DefaultCredential {}
    withLfs: false
    withSubmodules: false
    condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
  - !CommandStep
    name: mysleep
    runInContainer: true
    image: ubuntu:latest
    interpreter: !DefaultInterpreter
      commands: |
        sleep 30
    useTTY: true
    condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
  retryCondition: never
  maxRetries: 3
  retryDelay: 30
  timeout: 3600
  1. Because of this, the file system path corresponding to the temp bare git repo during job execution can be determined, that is opt/onedev/temp/server/onedev-build-3-1/workspace/evilgitdirectory/. Create another job to push to this repository, triggering the hooks in the previous malicious repository. When creating this new job, calculate the accurate values for onedev-build-3-1, where 3 represents the project number in Onedev, and 1 is the build number of the checkout job. For instance, if the project is the third code repository and the first checkout job is being executed, use the following .onedev-buildspec.yml in another project or the current one:
version: 38
jobs:
- name: demo push
  steps:
  - !PushRepository
    name: demo-push
    remoteUrl: file:///opt/onedev/temp/server/onedev-build-3-1/workspace/evilgitdirectory/
    force: false
    condition: ALWAYS
  retryCondition: never
  maxRetries: 3
  retryDelay: 30
  timeout: 3600
  1. Finally, execute the job in the first repository, letting it reach the sleep 30 phase, ensuring the temporary directory persists. Then execute the second job to push to the first repository, triggering the server push and achieving RCE. Pasted image 20241220205240.pngPasted image 20241220205257.png

Pasted image 20241220205306.png

  • Robin Shen commented 1 year ago

    This is great insight! Thanks for your investigation and sharing the result with me. Really appreciated!

    OneDev clones the repository into local file system and mounts into container instead of cloning directly inside container in order not to require user supplied image (to build/test project etc) to contain git clone tools, which can be very cubersome.

    As long as OneDev server removes the possibility of pushing to file based repository on server file system (currently only exists in push repository step), vulnerabilities like this should no longer be possible.

    What do you think?

    PS: Curious how you've managed to find this vulnerability (and previous vulnerabilty for pull repository step), as these requires some internal knowledge of OneDev. There are other security researchers reporting issues before, but most of them are related to deserialization from untrusted source which is a common pattern.

  • Robin Shen commented 1 year ago

    Also this vulnerability should no longer be possible with previous vulnerability fix which verified http/https protocol of remote url when runs the job:

    https://code.onedev.io/onedev/server/~commits/bccaf576b5684bb8323c1b685c6610f7b3fca0e2

  • byc_404 commented 1 year ago

    Hi,

    First of all, I’m glad to assist OneDev in addressing this security issue. Let me respond to these questions in order:

    1. The fix you’ve implemented is indeed effective. The exploitation method requires multiple steps to work in conjunction, so if any one of these steps fails, the entire exploitation chain is ineffective. Therefore, restricting the repository protocol to HTTP/HTTPS should work fine.

    2. I am indeed a security researcher and am currently preparing to submit this idea to an industry conference. Since they require vulnerabilities to be disclosed to the vendor 90 days before the conference starts, I’m working hard to draft the issue :). As for how I discovered it, I found similar vulnerabilities in other products and wondered whether this exploitation technique could be extended to other systems. Unfortunately, OneDev has a similar implementation issue XD. I hope this helps in fixing the issue quickly.

    Additionally, since this technique hasn’t been publicly disclosed yet (if my conference submission is successful, it will be revealed next year), and because the current fix should already mitigate the vulnerability, I kindly ask you to keep this issue private for now. Thank you!

  • Robin Shen commented 1 year ago

    Additionally, since this technique hasn’t been publicly disclosed yet (if my conference submission is successful, it will be revealed next year), and because the current fix should already mitigate the vulnerability, I kindly ask you to keep this issue private for now. Thank you!

    Sure. I will keep it secret until you discloses this.

  • Robin Shen changed state to 'Closed' 1 year ago
    Previous Value Current Value
    Open
    Closed
  • Robin Shen commented 1 year ago

    Fixed in build OD-5797

  • byc_404 commented 4 months ago

    Hi, Since the vulnerability can now be disclosed, I’ve updated the issue status from confidential to open.

  • byc_404 changed confidential 4 months ago
    Previous Value Current Value
    true
    false
issue 1/1
Type
Security Vulnerability
Priority
Major
Assignee
Labels
No labels
Issue Votes (0)
Watchers (3)
Reference
OD-2224
Please wait...
Connection lost or session expired, reload to recover
Page is in error, reload to recover