Added max_body_size for limiting the size of post form

pull/26/head
Sheng 2018-08-18 16:33:21 +08:00
parent d38453fd0b
commit d6de1340c4
6 changed files with 144 additions and 5 deletions

View File

@ -64,12 +64,13 @@ class Server(paramiko.ServerInterface):
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password): def check_auth_password(self, username, password):
print('Auth attempt with username: {!r} & password: {!r}'.format(username, password)) # noqa
if (username in ['robey', 'bar']) and (password == 'foo'): if (username in ['robey', 'bar']) and (password == 'foo'):
return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED return paramiko.AUTH_FAILED
def check_auth_publickey(self, username, key): def check_auth_publickey(self, username, key):
print('Auth attempt with key: ' + u(hexlify(key.get_fingerprint()))) print('Auth attempt with username: {!r} & key: {!r}'.format(username, u(hexlify(key.get_fingerprint())))) # noqa
if (username == 'robey') and (key == self.good_pub_key): if (username == 'robey') and (key == self.good_pub_key):
return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED return paramiko.AUTH_FAILED

View File

@ -1,15 +1,17 @@
import json import json
import webssh.handler as handler
import random import random
import threading import threading
import tornado.websocket import tornado.websocket
import tornado.gen import tornado.gen
import webssh.handler as handler
from tornado.testing import AsyncHTTPTestCase from tornado.testing import AsyncHTTPTestCase
from tornado.httpclient import HTTPError
from tornado.options import options from tornado.options import options
from webssh.main import make_app, make_handlers from webssh.main import make_app, make_handlers
from webssh.settings import get_app_settings from webssh.settings import get_app_settings, max_body_size
from tests.sshserver import run_ssh_server, banner from tests.sshserver import run_ssh_server, banner
from tests.utils import encode_multipart_formdata
handler.DELAY = 0.1 handler.DELAY = 0.1
@ -20,6 +22,12 @@ class TestApp(AsyncHTTPTestCase):
running = [True] running = [True]
sshserver_port = 2200 sshserver_port = 2200
body = u'hostname=127.0.0.1&port={}&username=robey&password=foo'.format(sshserver_port) # noqa body = u'hostname=127.0.0.1&port={}&username=robey&password=foo'.format(sshserver_port) # noqa
body_dict = {
'hostname': '127.0.0.1',
'port': str(sshserver_port),
'username': 'robey',
'password': ''
}
def get_app(self): def get_app(self):
loop = self.io_loop loop = self.io_loop
@ -44,6 +52,14 @@ class TestApp(AsyncHTTPTestCase):
cls.running.pop() cls.running.pop()
print('='*20) print('='*20)
def read_privatekey(self, filename):
return open(filename, 'rb').read().decode('utf-8')
def get_httpserver_options(self):
options = super(TestApp, self).get_httpserver_options()
options.update(max_body_size=max_body_size)
return options
def test_app_with_invalid_form(self): def test_app_with_invalid_form(self):
response = self.fetch('/') response = self.fetch('/')
self.assertEqual(response.code, 200) self.assertEqual(response.code, 200)
@ -103,6 +119,74 @@ class TestApp(AsyncHTTPTestCase):
self.assertIsNone(msg) self.assertIsNone(msg)
ws.close() ws.close()
@tornado.testing.gen_test
def test_app_auth_with_valid_pubkey_for_user_robey(self):
url = self.get_url('/')
client = self.get_http_client()
response = yield client.fetch(url)
self.assertEqual(response.code, 200)
privatekey = self.read_privatekey('tests/user_rsa_key')
files = [('privatekey', 'user_rsa_key', privatekey)]
content_type, body = encode_multipart_formdata(self.body_dict.items(),
files)
headers = {
"Content-Type": content_type, 'content-length': str(len(body))
}
response = yield client.fetch(url, method="POST", headers=headers,
body=body)
data = json.loads(response.body.decode('utf-8'))
self.assertIsNone(data['status'])
self.assertIsNotNone(data['id'])
self.assertIsNotNone(data['encoding'])
url = url.replace('http', 'ws')
ws_url = url + 'ws?id=' + data['id']
ws = yield tornado.websocket.websocket_connect(ws_url)
msg = yield ws.read_message()
self.assertEqual(msg.decode(data['encoding']), banner)
ws.close()
@tornado.testing.gen_test
def test_app_auth_with_invalid_pubkey_for_user_robey(self):
url = self.get_url('/')
client = self.get_http_client()
response = yield client.fetch(url)
self.assertEqual(response.code, 200)
privatekey = self.read_privatekey('tests/user_rsa_key')
privatekey = privatekey[:100] + u'bad' + privatekey[100:]
files = [('privatekey', 'user_rsa_key', privatekey)]
content_type, body = encode_multipart_formdata(self.body_dict.items(),
files)
headers = {
"Content-Type": content_type, 'content-length': str(len(body))
}
response = yield client.fetch(url, method="POST", headers=headers,
body=body)
data = json.loads(response.body.decode('utf-8'))
self.assertIsNotNone(data['status'])
self.assertIsNone(data['id'])
self.assertIsNone(data['encoding'])
@tornado.testing.gen_test
def test_app_post_form_with_large_body_size(self):
url = self.get_url('/')
client = self.get_http_client()
response = yield client.fetch(url)
self.assertEqual(response.code, 200)
privatekey = u'h' * (2 * max_body_size)
files = [('privatekey', 'user_rsa_key', privatekey)]
content_type, body = encode_multipart_formdata(self.body_dict.items(),
files)
headers = {
"Content-Type": content_type, 'content-length': str(len(body))
}
with self.assertRaises(HTTPError):
yield client.fetch(url, method="POST", headers=headers, body=body)
@tornado.testing.gen_test @tornado.testing.gen_test
def test_app_with_correct_credentials_user_robey(self): def test_app_with_correct_credentials_user_robey(self):
url = self.get_url('/') url = self.get_url('/')

15
tests/user_rsa_key Normal file
View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDI7iK3d8eWYZlYloat94c5VjtFY7c/0zuGl8C7uMnZ3t6i2G99
66hEW0nCFSZkOW5F0XKEVj+EUCHvo8koYC6wiohAqWQnEwIoOoh7GSAcB8gP/qaq
+adIl/Rvlby/mHakj+y05LBND6nFWHAn1y1gOFFKUXSJNRZPXSFy47gqzwIBIwKB
gQCbANjz7q/pCXZLp1Hz6tYHqOvlEmjK1iabB1oqafrMpJ0eibUX/u+FMHq6StR5
M5413BaDWHokPdEJUnabfWXXR3SMlBUKrck0eAer1O8m78yxu3OEdpRk+znVo4DL
guMeCdJB/qcF0kEsx+Q8HP42MZU1oCmk3PbfXNFwaHbWuwJBAOQ/ry/hLD7AqB8x
DmCM82A9E59ICNNlHOhxpJoh6nrNTPCsBAEu/SmqrL8mS6gmbRKUaya5Lx1pkxj2
s/kWOokCQQDhXCcYXjjWiIfxhl6Rlgkk1vmI0l6785XSJNv4P7pXjGmShXfIzroh
S8uWK3tL0GELY7+UAKDTUEVjjQdGxYSXAkEA3bo1JzKCwJ3lJZ1ebGuqmADRO6UP
40xH977aadfN1mEI6cusHmgpISl0nG5YH7BMsvaT+bs1FUH8m+hXDzoqOwJBAK3Z
X/za+KV/REya2z0b+GzgWhkXUGUa/owrEBdHGriQ47osclkUgPUdNqcLmaDilAF4
1Z4PHPrI5RJIONAx+JECQQC/fChqjBgFpk6iJ+BOdSexQpgfxH/u/457W10Y43HR
soS+8btbHqjQkowQ/2NTlUfWvqIlfxs6ZbFsIp/HrhZL
-----END RSA PRIVATE KEY-----

38
tests/utils.py Normal file
View File

@ -0,0 +1,38 @@
import mimetypes
from uuid import uuid4
def encode_multipart_formdata(fields, files):
"""
fields is a sequence of (name, value) elements for regular form fields.
files is a sequence of (name, filename, value) elements for data to be
uploaded as files.
Return (content_type, body) ready for httplib.HTTP instance
"""
boundary = uuid4().hex
CRLF = '\r\n'
L = []
for (key, value) in fields:
L.append('--' + boundary)
L.append('Content-Disposition: form-data; name="%s"' % key)
L.append('')
L.append(value)
for (key, filename, value) in files:
L.append('--' + boundary)
L.append(
'Content-Disposition: form-data; name="%s"; filename="%s"' % (
key, filename
)
)
L.append('Content-Type: %s' % get_content_type(filename))
L.append('')
L.append(value)
L.append('--' + boundary + '--')
L.append('')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % boundary
return content_type, body
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

View File

@ -5,7 +5,7 @@ import tornado.ioloop
from tornado.options import parse_command_line, options from tornado.options import parse_command_line, options
from webssh.handler import IndexHandler, WsockHandler from webssh.handler import IndexHandler, WsockHandler
from webssh.settings import (get_app_settings, get_host_keys_settings, from webssh.settings import (get_app_settings, get_host_keys_settings,
get_policy_setting) get_policy_setting, max_body_size)
def make_handlers(loop, options): def make_handlers(loop, options):
@ -29,7 +29,7 @@ def main():
parse_command_line() parse_command_line()
loop = tornado.ioloop.IOLoop.current() loop = tornado.ioloop.IOLoop.current()
app = make_app(make_handlers(loop, options), get_app_settings(options)) app = make_app(make_handlers(loop, options), get_app_settings(options))
app.listen(options.port, options.address) app.listen(options.port, options.address, max_body_size=max_body_size)
logging.info('Listening on {}:{}'.format(options.address, options.port)) logging.info('Listening on {}:{}'.format(options.address, options.port))
loop.start() loop.start()

View File

@ -29,6 +29,7 @@ define('version', type=bool, help='Show version information',
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
max_body_size = 1 * 1024 * 1024
def get_app_settings(options): def get_app_settings(options):