mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-15 14:16:08 -06:00
fix: some concurrency/race condition issues (related to #148)
This commit is contained in:
parent
d8fac51e64
commit
221d17d470
4 changed files with 207 additions and 186 deletions
240
burpui/agent.py
240
burpui/agent.py
|
|
@ -8,6 +8,8 @@ import json
|
|||
import logging
|
||||
import traceback
|
||||
|
||||
from gevent.coros import RLock
|
||||
from gevent.pool import Pool
|
||||
from gevent.server import StreamServer
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from .exceptions import BUIserverException
|
||||
|
|
@ -25,6 +27,8 @@ G_PASSWORD = u'password'
|
|||
|
||||
DISCLOSURE = 5
|
||||
|
||||
lock = RLock()
|
||||
|
||||
|
||||
class BurpHandler(BUIbackend):
|
||||
# These functions MUST be implemented because we inherit an abstract class.
|
||||
|
|
@ -128,10 +132,11 @@ class BUIAgent(BUIbackend, BUIlogging):
|
|||
self.password = self.conf.safe_get('password')
|
||||
|
||||
self.cli = BurpHandler(self.vers, self.logger, self.conf)
|
||||
pool = Pool(10000)
|
||||
if not self.ssl:
|
||||
self.server = StreamServer((self.bind, self.port), self.handle)
|
||||
self.server = StreamServer((self.bind, self.port), self.handle, spawn=pool)
|
||||
else:
|
||||
self.server = StreamServer((self.bind, self.port), self.handle, keyfile=self.sslkey, certfile=self.sslcert)
|
||||
self.server = StreamServer((self.bind, self.port), self.handle, keyfile=self.sslkey, certfile=self.sslcert, spawn=pool)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
|
|
@ -141,129 +146,130 @@ class BUIAgent(BUIbackend, BUIlogging):
|
|||
|
||||
def handle(self, request, address):
|
||||
"""self.request is the client connection"""
|
||||
try:
|
||||
self.request = request
|
||||
err = None
|
||||
res = ''
|
||||
lengthbuf = self.request.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
data = self.recvall(length)
|
||||
self._logger('info', 'recv: {}'.format(data))
|
||||
txt = data.decode('UTF-8')
|
||||
if txt == 'RE':
|
||||
return
|
||||
j = json.loads(txt)
|
||||
if j['password'] != self.password:
|
||||
self._logger('warning', '-----> Wrong Password <-----')
|
||||
self.request.sendall(b'KO')
|
||||
return
|
||||
with lock:
|
||||
try:
|
||||
if j['func'] == 'proxy_parser':
|
||||
parser = self.cli.get_parser()
|
||||
if j['args']:
|
||||
res = json.dumps(getattr(parser, j['method'])(**j['args']))
|
||||
else:
|
||||
res = json.dumps(getattr(parser, j['method'])())
|
||||
elif j['func'] == 'restore_files':
|
||||
res, err = getattr(self.cli, j['func'])(**j['args'])
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', 'Restoration failed')
|
||||
return
|
||||
elif j['func'] == 'get_file':
|
||||
path = j['path']
|
||||
path = os.path.normpath(path)
|
||||
err = None
|
||||
if not path.startswith('/'):
|
||||
err = 'The path must be absolute! ({})'.format(path)
|
||||
if not path.startswith(self.cli.tmpdir):
|
||||
err = 'You are not allowed to access this path: ' \
|
||||
'({})'.format(path)
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', err)
|
||||
return
|
||||
size = os.path.getsize(path)
|
||||
self.request.sendall(b'OK')
|
||||
self.request.sendall(struct.pack('!Q', size))
|
||||
with open(path, 'rb') as f:
|
||||
buf = f.read(1024)
|
||||
while buf:
|
||||
self._logger('info', 'sending {} Bytes'.format(len(buf)))
|
||||
self.request.sendall(buf)
|
||||
self.request = request
|
||||
err = None
|
||||
res = ''
|
||||
lengthbuf = self.request.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
data = self.recvall(length)
|
||||
self._logger('info', 'recv: {}'.format(data))
|
||||
txt = data.decode('UTF-8')
|
||||
if txt == 'RE':
|
||||
return
|
||||
j = json.loads(txt)
|
||||
if j['password'] != self.password:
|
||||
self._logger('warning', '-----> Wrong Password <-----')
|
||||
self.request.sendall(b'KO')
|
||||
return
|
||||
try:
|
||||
if j['func'] == 'proxy_parser':
|
||||
parser = self.cli.get_parser()
|
||||
if j['args']:
|
||||
res = json.dumps(getattr(parser, j['method'])(**j['args']))
|
||||
else:
|
||||
res = json.dumps(getattr(parser, j['method'])())
|
||||
elif j['func'] == 'restore_files':
|
||||
res, err = getattr(self.cli, j['func'])(**j['args'])
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', 'Restoration failed')
|
||||
return
|
||||
elif j['func'] == 'get_file':
|
||||
path = j['path']
|
||||
path = os.path.normpath(path)
|
||||
err = None
|
||||
if not path.startswith('/'):
|
||||
err = 'The path must be absolute! ({})'.format(path)
|
||||
if not path.startswith(self.cli.tmpdir):
|
||||
err = 'You are not allowed to access this path: ' \
|
||||
'({})'.format(path)
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', err)
|
||||
return
|
||||
size = os.path.getsize(path)
|
||||
self.request.sendall(b'OK')
|
||||
self.request.sendall(struct.pack('!Q', size))
|
||||
with open(path, 'rb') as f:
|
||||
buf = f.read(1024)
|
||||
os.unlink(path)
|
||||
lengthbuf = self.request.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
data = self.recvall(length)
|
||||
txt = data.decode('UTF-8')
|
||||
if txt == 'RE':
|
||||
return
|
||||
elif j['func'] == 'del_file':
|
||||
path = j['path']
|
||||
path = os.path.normpath(path)
|
||||
err = None
|
||||
if not path.startswith('/'):
|
||||
err = 'The path must be absolute! ({})'.format(path)
|
||||
if not path.startswith(self.cli.tmpdir):
|
||||
err = 'You are not allowed to access this path: ' \
|
||||
'({})'.format(path)
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', err)
|
||||
return
|
||||
res = json.dumps(False)
|
||||
if os.path.isfile(path):
|
||||
while buf:
|
||||
self._logger('info', 'sending {} Bytes'.format(len(buf)))
|
||||
self.request.sendall(buf)
|
||||
buf = f.read(1024)
|
||||
os.unlink(path)
|
||||
res = json.dumps(True)
|
||||
else:
|
||||
if j['args']:
|
||||
if 'pickled' in j and j['pickled']:
|
||||
# de-serialize arguments if needed
|
||||
import hmac
|
||||
import hashlib
|
||||
from base64 import b64decode
|
||||
pickles = j['args']
|
||||
key = u'{}{}'.format(self.password, j['func'])
|
||||
key = key.encode(encoding='utf-8')
|
||||
bytes_pickles = pickles.encode(encoding='utf-8')
|
||||
digest = hmac.new(key, bytes_pickles, hashlib.sha1).hexdigest()
|
||||
if digest != j['digest']:
|
||||
raise BUIserverException('Integrity check failed')
|
||||
j['args'] = pickle.loads(b64decode(pickles))
|
||||
res = json.dumps(getattr(self.cli, j['func'])(**j['args']))
|
||||
lengthbuf = self.request.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
data = self.recvall(length)
|
||||
txt = data.decode('UTF-8')
|
||||
if txt == 'RE':
|
||||
return
|
||||
elif j['func'] == 'del_file':
|
||||
path = j['path']
|
||||
path = os.path.normpath(path)
|
||||
err = None
|
||||
if not path.startswith('/'):
|
||||
err = 'The path must be absolute! ({})'.format(path)
|
||||
if not path.startswith(self.cli.tmpdir):
|
||||
err = 'You are not allowed to access this path: ' \
|
||||
'({})'.format(path)
|
||||
if err:
|
||||
self.request.sendall(b'ER')
|
||||
self.request.sendall(struct.pack('!Q', len(err)))
|
||||
self.request.sendall(err.encode('UTF-8'))
|
||||
self._logger('error', err)
|
||||
return
|
||||
res = json.dumps(False)
|
||||
if os.path.isfile(path):
|
||||
os.unlink(path)
|
||||
res = json.dumps(True)
|
||||
else:
|
||||
res = json.dumps(getattr(self.cli, j['func'])())
|
||||
self._logger('info', 'result: {}'.format(res))
|
||||
self.request.sendall(b'OK')
|
||||
# should not happen
|
||||
except Exception as e:
|
||||
raise BUIserverException(str(e))
|
||||
except BUIserverException as e:
|
||||
self.request.sendall(b'ER')
|
||||
res = str(e)
|
||||
self._logger('error', 'Forwarding Exception: {}'.format(res))
|
||||
if j['args']:
|
||||
if 'pickled' in j and j['pickled']:
|
||||
# de-serialize arguments if needed
|
||||
import hmac
|
||||
import hashlib
|
||||
from base64 import b64decode
|
||||
pickles = j['args']
|
||||
key = u'{}{}'.format(self.password, j['func'])
|
||||
key = key.encode(encoding='utf-8')
|
||||
bytes_pickles = pickles.encode(encoding='utf-8')
|
||||
digest = hmac.new(key, bytes_pickles, hashlib.sha1).hexdigest()
|
||||
if digest != j['digest']:
|
||||
raise BUIserverException('Integrity check failed')
|
||||
j['args'] = pickle.loads(b64decode(pickles))
|
||||
res = json.dumps(getattr(self.cli, j['func'])(**j['args']))
|
||||
else:
|
||||
res = json.dumps(getattr(self.cli, j['func'])())
|
||||
self._logger('info', 'result: {}'.format(res))
|
||||
self.request.sendall(b'OK')
|
||||
# should not happen
|
||||
except Exception as e:
|
||||
raise BUIserverException(str(e))
|
||||
except BUIserverException as e:
|
||||
self.request.sendall(b'ER')
|
||||
res = str(e)
|
||||
self._logger('error', 'Forwarding Exception: {}'.format(res))
|
||||
self.request.sendall(struct.pack('!Q', len(res)))
|
||||
self.request.sendall(res.encode('UTF-8'))
|
||||
return
|
||||
self.request.sendall(struct.pack('!Q', len(res)))
|
||||
self.request.sendall(res.encode('UTF-8'))
|
||||
return
|
||||
self.request.sendall(struct.pack('!Q', len(res)))
|
||||
self.request.sendall(res.encode('UTF-8'))
|
||||
except AttributeError as e:
|
||||
self._logger('warning', '{}\nWrong method => {}'.format(traceback.format_exc(), str(e)))
|
||||
self.request.sendall(b'KO')
|
||||
except Exception as e:
|
||||
self._logger('error', '!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
finally:
|
||||
try:
|
||||
self.request.close()
|
||||
except AttributeError as e:
|
||||
self._logger('warning', '{}\nWrong method => {}'.format(traceback.format_exc(), str(e)))
|
||||
self.request.sendall(b'KO')
|
||||
except Exception as e:
|
||||
self._logger('error', '!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
finally:
|
||||
try:
|
||||
self.request.close()
|
||||
except Exception as e:
|
||||
self._logger('error', '!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
|
||||
def recvall(self, length=1024):
|
||||
buf = b''
|
||||
|
|
|
|||
|
|
@ -15,6 +15,18 @@ from ...exceptions import BUIserverException
|
|||
from ..._compat import pickle
|
||||
from ...utils import implement
|
||||
|
||||
try:
|
||||
from gevent.coros import RLock
|
||||
lock = RLock()
|
||||
except ImportError:
|
||||
class DummyLock():
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
|
||||
lock = DummyLock()
|
||||
|
||||
INTERFACE_METHODS = BUIbackend.__abstractmethods__
|
||||
PARSER_INTERFACE_METHODS = BUIparser.__abstractmethods__
|
||||
|
|
@ -377,80 +389,81 @@ class NClient(BUIbackend):
|
|||
|
||||
def do_command(self, data=None, restarted=False):
|
||||
"""Send a command to the remote agent"""
|
||||
res = '[]'
|
||||
err = None
|
||||
if not data:
|
||||
raise BUIserverException('Missing data')
|
||||
try:
|
||||
data['password'] = self.password
|
||||
# manage long running operations
|
||||
if data['func'] in ['restore_files', 'get_file', 'del_file']:
|
||||
self.connected = False
|
||||
self.conn(notimeout=True)
|
||||
else:
|
||||
self.conn()
|
||||
if not self.connected:
|
||||
raise BUIserverException('Failed to connect to agent')
|
||||
raw = json.dumps(data)
|
||||
length = len(raw)
|
||||
self.sock.sendall(struct.pack('!Q', length))
|
||||
self.sock.sendall(raw.encode('UTF-8'))
|
||||
self.logger.debug("Sending: {}".format(raw))
|
||||
tmp = self.sock.recv(2).decode('UTF-8')
|
||||
self.logger.debug("recv: '{}'".format(tmp))
|
||||
if 'ER' == tmp:
|
||||
with lock:
|
||||
res = '[]'
|
||||
err = None
|
||||
if not data:
|
||||
raise BUIserverException('Missing data')
|
||||
try:
|
||||
data['password'] = self.password
|
||||
# manage long running operations
|
||||
if data['func'] in ['restore_files', 'get_file', 'del_file']:
|
||||
self.connected = False
|
||||
self.conn(notimeout=True)
|
||||
else:
|
||||
self.conn()
|
||||
if not self.connected:
|
||||
raise BUIserverException('Failed to connect to agent')
|
||||
raw = json.dumps(data)
|
||||
length = len(raw)
|
||||
self.sock.sendall(struct.pack('!Q', length))
|
||||
self.sock.sendall(raw.encode('UTF-8'))
|
||||
self.logger.debug("Sending: {}".format(raw))
|
||||
tmp = self.sock.recv(2).decode('UTF-8')
|
||||
self.logger.debug("recv: '{}'".format(tmp))
|
||||
if 'ER' == tmp:
|
||||
lengthbuf = self.sock.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
err = self.recvall(length).decode('UTF-8')
|
||||
raise BUIserverException(err)
|
||||
if 'OK' != tmp:
|
||||
self.logger.debug('Ooops, unsuccessful!')
|
||||
return res
|
||||
self.logger.debug("Data sent successfully")
|
||||
if data['func'] == 'get_file':
|
||||
self.connected = False
|
||||
return self.sock
|
||||
lengthbuf = self.sock.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
err = self.recvall(length).decode('UTF-8')
|
||||
raise BUIserverException(err)
|
||||
if 'OK' != tmp:
|
||||
self.logger.debug('Ooops, unsuccessful!')
|
||||
return res
|
||||
self.logger.debug("Data sent successfully")
|
||||
if data['func'] == 'get_file':
|
||||
self.connected = False
|
||||
return self.sock
|
||||
lengthbuf = self.sock.recv(8)
|
||||
length, = struct.unpack('!Q', lengthbuf)
|
||||
res = self.recvall(length).decode('UTF-8')
|
||||
except IOError as e:
|
||||
if not restarted and e.errno == errno.EPIPE:
|
||||
self.connected = False
|
||||
self.logger.warning('Broken pipe, restarting the request')
|
||||
return self.do_command(data, True)
|
||||
elif e.errno == errno.ECONNRESET:
|
||||
self.connected = False
|
||||
self.logger.error('!!! {} !!!\nPlease check your SSL configuration on both sides!'.format(str(e)))
|
||||
else:
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
raise e
|
||||
except socket.timeout as e:
|
||||
if self.app.gunicorn and not restarted:
|
||||
self.connected = False
|
||||
self.logger.warning('Socket timed-out, restarting the request')
|
||||
return self.do_command(data, True)
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
raise e
|
||||
# catch all
|
||||
except Exception as e:
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
if data['func'] == 'restore_files':
|
||||
err = str(e)
|
||||
elif isinstance(e, BUIserverException):
|
||||
res = self.recvall(length).decode('UTF-8')
|
||||
except IOError as e:
|
||||
if not restarted and e.errno == errno.EPIPE:
|
||||
self.connected = False
|
||||
self.logger.warning('Broken pipe, restarting the request')
|
||||
return self.do_command(data, True)
|
||||
elif e.errno == errno.ECONNRESET:
|
||||
self.connected = False
|
||||
self.logger.error('!!! {} !!!\nPlease check your SSL configuration on both sides!'.format(str(e)))
|
||||
else:
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
raise e
|
||||
else:
|
||||
raise BUIserverException(str(e))
|
||||
finally:
|
||||
if self.connected:
|
||||
self.sock.close()
|
||||
self.connected = False
|
||||
except socket.timeout as e:
|
||||
if self.app.gunicorn and not restarted:
|
||||
self.connected = False
|
||||
self.logger.warning('Socket timed-out, restarting the request')
|
||||
return self.do_command(data, True)
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
raise e
|
||||
# catch all
|
||||
except Exception as e:
|
||||
self.logger.error('!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
|
||||
if data['func'] == 'restore_files':
|
||||
err = str(e)
|
||||
elif isinstance(e, BUIserverException):
|
||||
raise e
|
||||
else:
|
||||
raise BUIserverException(str(e))
|
||||
finally:
|
||||
if self.connected:
|
||||
self.sock.close()
|
||||
self.connected = False
|
||||
|
||||
if data['func'] == 'restore_files':
|
||||
if err:
|
||||
res = None
|
||||
return res, err
|
||||
if data['func'] == 'restore_files':
|
||||
if err:
|
||||
res = None
|
||||
return res, err
|
||||
|
||||
return res
|
||||
return res
|
||||
|
||||
def recvall(self, length=1024):
|
||||
"""Read the answer of the agent"""
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ EOF
|
|||
cat ${CONFIG_DIR}/burp/burp.conf >/tmp/burp.conf
|
||||
chown burpui: /tmp/burp.conf
|
||||
|
||||
sed -i "s/^max_status_children.*$/max_status_children = 10000/" /etc/burp/burp-server.conf
|
||||
echo "restore_client = agent" >>/etc/burp/burp-server.conf
|
||||
|
||||
cp ${CONFIG_DIR}/burp-ui/buiagent.cfg /etc/burp/buiagent.cfg
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ chown burpui: /tmp/burp.conf
|
|||
|
||||
cat ${CONFIG_DIR}/burp/CA.cnf >/etc/burp/CA.cnf
|
||||
|
||||
sed -i "s/^max_status_children.*$/max_status_children = 10000/" /etc/burp/burp-server.conf
|
||||
echo "restore_client = agent" >>/etc/burp/burp-server.conf
|
||||
echo "monitor_browse_cache = 1" >>/etc/burp/burp-server.conf
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue