DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • Guide To Selecting the Right GitOps Tool - Argo CD or Flux CD
  • Execution Type Models in Node.js
  • Batch Request Processing With API Gateway
  • Security Challenges for Microservice Applications in Multi-Cloud Environments
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Empowering Weak Primitives: File Truncation to Code Execution With Git

Empowering Weak Primitives: File Truncation to Code Execution With Git

In this article, SonarSource's R and D team discusses how they discovered a code vulnerability that allows you to truncate arbitrary files to execute arbitrary commands!

Thomas Chauchefoin user avatar by
Thomas Chauchefoin
·
May. 08, 23 · Tutorial
Like (1)
Save
Tweet
Share
2.15K Views

Join the DZone community and get the full member experience.

Join For Free

During recent security research, I came up with a fun "trick" that I later shared in a Capture the Flag challenge for the Hack.lu CTF and my Code Security Advent Calendar. I received good feedback and wanted to share the details with a broader audience. 

Let's say that you discovered a code vulnerability that allows you to truncate arbitrary files. It sounds like a pretty weak exploitation primitive, but if you are dealing with an application that involves operations on a Git repository under your control, you're in luck! 

The Vulnerable Snippet

For example, let's use the code snippet of Day 16 of this year's Code Security Advent Calendar. It implements a service that allows cloning an arbitrary Git repository and later running git blame on specific files and lines.

challenge.py

Python
 
def _git(cmd, args, cwd='/'): 
   proc = run(['git', cmd, *args], 
              stdout=PIPE, 
              stderr=DEVNULL, 
              cwd=cwd, 
              timeout=5) 
   return proc.stdout.decode().strip() 
@app.route('/blame', methods=['POST']) 
def blame(): 
   url = request.form.get('url', 
                          'https://github.com/package-url/purl-spec.git') 
   what = request.form.getlist('what[]') 
   with TemporaryDirectory() as local: 
       if not url.startswith(('https://', 'http://')): 
           return make_response('Invalid url!', 403) 
       _git('clone', ['--', url, local]) 
       res = [] 
       for i in what: 
           file, lines = i.split(':') 
           res.append(_git('blame', ['-L', lines, file], local)) 
       return make_response('\n'.join(res), 200)


This code suffers from an argument injection vulnerability when crafting the command line for git blame. Argument injections are widespread code vulnerabilities identified by my static analysis technology; you can find a scan report of the above snippet on SonarCloud:

SonarcloudExploiting argument injection vulnerabilities depends heavily on the features offered by the invoked binary.

For instance, if a hypothetic program supports the option --output=foo that writes the program output to the file foo, attackers who can inject this argument could create new files or overwrite existing ones. The attacker's goal is usually to gain the ability to execute arbitrary code on the server, and such primitives are very powerful but also quite rare.

Finding an Interesting Argument

Let's get back to the code snippet, where I can add new arguments to the git blame invocation. 

After looking at the manual of git-blame, I couldn't find any "interesting" option to execute arbitrary code. Most arguments alter the behavior of the blame process or the way it renders its output. Most importantly, the manual does not document the presence of the option --output, which is usually present on other git sub-commands. 

It is then surprising to see this behavior when running git blame --output=foo; notice the presence of a new file named foo:

Shell
 
$ git blame --output=foo
usage: git blame [<options>] [<rev-opts>] [<rev>] [--] <file> 
   <rev-opts> are documented in git-rev-list(1)
    --incremental         show blame entries as I find them, incrementally [...] $ ls -alh total 0 drwx------    4 thomas  staff   128B Dec 29 14:43 ./
drwx------@ 191 thomas  staff   6.0K Dec 29 14:43 ../
drwxr-xr-x    9 thomas  staff   288B Dec 29 14:43 .git/
-rw-r--r--    1 thomas  staff     0B Dec 29 14:43 foo


Although the command failed, an empty file named foo was created. If a file with the same name already exists, the destination file is truncated!

Shell
 
$ date > foo $ cat foo Thu Dec 29 15:42:56 CET 2022
$ git blame --output=foo
usage: git blame [<options>] [<rev-opts>] [<rev>] [--] <file>
    <rev-opts> are documented in git-rev-list(1) 
   --incremental         show blame entries as I find them, incrementally
[...] $ ls -alh total 0
drwx------    4 thomas  staff   128B Dec 29 14:43 ./
drwx------@ 191 thomas  staff   6.0K Dec 29 14:47 ../
drwxr-xr-x    9 thomas  staff   288B Dec 29 14:43 .git/
-rw-r--r--    1 thomas  staff     0B Dec 29 14:48 foo


This option provides attackers with an arbitrary file truncation primitive. The command git-blame supports --output because its implementation uses other sub-commands that do support --output: command-line arguments are parsed several times by these components.

Putting the Pieces Together

As I demonstrated in Securing Developer Tools: Git Integrations, control over the Git options of a local repository is dangerous: several configuration directives allow specifying external commands to change Git's behavior. For instance, core.fsmonitor can point to a third-party program to replace Git's built-in filesystem monitor. This process happens during most operations, including git blame. 

I could leverage this technique if I find a way to force Git operations to ignore the local repository and use one in my control instead. As you may have already guessed, the file truncation primitive was proven to be useful here. 

I can trick Git into loading a configuration from an unintended location by corrupting a critical file like .git/HEAD. In such cases, Git starts looking for repositories in the current folder, which the attacker fully controls as it is the work tree with all the files of the cloned remote repository.

Solving the Challenge

To solve the challenge, I created a Git repository with the following structure:

  • objects/, refs/, worktree/: empty folders to comply with the expected structure of a Git repository
  • HEAD: non-empty file to fake a valid reference
  • config: malicious configuration based on what I described in Securing Developer Tools: Git Integrations and Justin Steven's advisory. Most importantly, it should contain:
    • bare = false: don't mark the current directory as bare 
    • worktree = worktree: the working tree directory under which checked-out are files
    • fsmonitor =  $(id>/pwned)#: the custom filesystem monitor daemon to start at the next Git invocation; this is the attacker's payload

When the repository is imported for the first time, nothing happens because the local Git repository stored in .git is constructed during the clone operation: this repository is valid and ignores the bare repository I planted. 

Then, the argument injection is triggered to truncate .git/HEAD, corrupting the once-valid local repository. By invoking git blame a second time, git now uses the malicious bare repository and calls the custom filesystem monitor, effectively executing the attacker's payload. 

Closing Words

As I shared with the series of publications on vulnerabilities in the IT monitoring software Checkmk, seemingly minor vulnerabilities can hide a critical impact. SonarSource's Clean Code approach helps you identify these security liabilities before they are deployed to production. 

Git Vulnerability Repository (version control)

Published at DZone with permission of Thomas Chauchefoin. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Guide To Selecting the Right GitOps Tool - Argo CD or Flux CD
  • Execution Type Models in Node.js
  • Batch Request Processing With API Gateway
  • Security Challenges for Microservice Applications in Multi-Cloud Environments

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: