Skip to content

Instantly share code, notes, and snippets.

@stewartpark
Last active October 2, 2024 00:20
Show Gist options
  • Save stewartpark/1b079dc0481c6213def9 to your computer and use it in GitHub Desktop.
Save stewartpark/1b079dc0481c6213def9 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# This is an example Git server. http://blog.nerds.kr/post/106956608369/implementing-a-git-http-server-in-python
# Stewart Park <[email protected]>
from flask import Flask, make_response, request, abort
from StringIO import StringIO
from dulwich.pack import PackStreamReader
import subprocess, os.path
from flask.ext.httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
users = {
"john": "hello",
"susan": "bye"
}
@auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
@app.route('/<string:project_name>/info/refs')
@auth.login_required
def info_refs(project_name):
service = request.args.get('service')
if service[:4] != 'git-':
abort(500)
p = subprocess.Popen([service, '--stateless-rpc', '--advertise-refs', os.path.join('.', project_name)], stdout=subprocess.PIPE)
packet = '# service=%s\n' % service
length = len(packet) + 4
_hex = '0123456789abcdef'
prefix = ''
prefix += _hex[length >> 12 & 0xf]
prefix += _hex[length >> 8 & 0xf]
prefix += _hex[length >> 4 & 0xf]
prefix += _hex[length & 0xf]
data = prefix + packet + '0000'
data += p.stdout.read()
res = make_response(data)
res.headers['Expires'] = 'Fri, 01 Jan 1980 00:00:00 GMT'
res.headers['Pragma'] = 'no-cache'
res.headers['Cache-Control'] = 'no-cache, max-age=0, must-revalidate'
res.headers['Content-Type'] = 'application/x-%s-advertisement' % service
p.wait()
return res
@app.route('/<string:project_name>/git-receive-pack', methods=('POST',))
@auth.login_required
def git_receive_pack(project_name):
p = subprocess.Popen(['git-receive-pack', '--stateless-rpc', os.path.join('.', project_name)], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
data_in = request.data
pack_file = data_in[data_in.index('PACK'):]
objects = PackStreamReader(StringIO(pack_file).read)
for obj in objects.read_objects():
if obj.obj_type_num == 1: # Commit
print obj
p.stdin.write(data_in)
data_out = p.stdout.read()
res = make_response(data_out)
res.headers['Expires'] = 'Fri, 01 Jan 1980 00:00:00 GMT'
res.headers['Pragma'] = 'no-cache'
res.headers['Cache-Control'] = 'no-cache, max-age=0, must-revalidate'
res.headers['Content-Type'] = 'application/x-git-receive-pack-result'
p.wait()
return res
@app.route('/<string:project_name>/git-upload-pack', methods=('POST',))
@auth.login_required
def git_upload_pack(project_name):
p = subprocess.Popen(['git-upload-pack', '--stateless-rpc', os.path.join('.', project_name)], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(request.data)
data = p.stdout.read()
res = make_response(data)
res.headers['Expires'] = 'Fri, 01 Jan 1980 00:00:00 GMT'
res.headers['Pragma'] = 'no-cache'
res.headers['Cache-Control'] = 'no-cache, max-age=0, must-revalidate'
res.headers['Content-Type'] = 'application/x-git-upload-pack-result'
p.wait()
return res
if __name__ == '__main__':
app.run()
@mfm24
Copy link

mfm24 commented Oct 21, 2016

Nice, this is useful!

I think

    _hex = '0123456789abcdef'
    prefix = ''
    prefix += _hex[length >> 12 & 0xf]
    prefix += _hex[length >> 8  & 0xf]
    prefix += _hex[length >> 4 & 0xf]
    prefix += _hex[length & 0xf]

could be replaced with

    prefix = "{:04x}".format(length & 0xFFFF);

?

@rabbagliettiandrea
Copy link

Thank you so much :)

@stevepeak
Copy link

Thanks for this awesome post! I implemented it with Tornado here.

@yhojann-cl
Copy link

yhojann-cl commented Nov 28, 2020

Your code is vulnerable to recomte code execution:

    if service[:4] != 'git-': 
        abort(500)
    p = subprocess.Popen([service,

By example: GET /foo/info/refs?service=git-;rm%20-rf%20./*;exit;help HTTP/1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment