diff options
author | Prabhu Ramachandran | 2011-11-12 10:02:57 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2011-11-12 10:02:57 +0530 |
commit | fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8 (patch) | |
tree | 24b50d4bce7ac5faa5ec6ae98db041766340814b | |
parent | c69bc272c1c3ba02c3f244586b65254036e73de3 (diff) | |
download | online_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.tar.gz online_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.tar.bz2 online_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.zip |
ENH: Running remote code safely via XMLRPC.
Adding a python_server which executes code as nobody safely so users
cannot do too much damage.
-rw-r--r-- | README.txt | 17 | ||||
-rw-r--r-- | exam/views.py | 42 | ||||
-rw-r--r-- | exam/xmlrpc_clients.py | 5 | ||||
-rwxr-xr-x | python_server.py | 65 |
4 files changed, 87 insertions, 42 deletions
@@ -19,17 +19,24 @@ To install/deploy this app follow the steps below: Note that you can supply multiple xml files as arguments and all of those will be added to the database. - - 5. Now, run:: + + 5. First run the python server provided. This ensures that the code is + executed in a safe environment. Do this like so:: + + $ sudo python python_server.py + + Using sudo is necessary since the server is run as the user "nobody". + + 6. Now, run:: $ python manage.py runserver <desired_ip>:<desired_port> - 6. Go to http://server_ip:server_port/admin + 7. Go to http://server_ip:server_port/admin - 7. Login with your credentials and look at the questions and modify if + 8. Login with your credentials and look at the questions and modify if needed. - 7. Now ask users to login at: + 9. Now ask users to login at: http://server_ip:server_port/exam And you should be all set. diff --git a/exam/views.py b/exam/views.py index 7ee3c53..ed33c91 100644 --- a/exam/views.py +++ b/exam/views.py @@ -10,6 +10,7 @@ from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext from exam.models import Question, Quiz, Profile, Answer from exam.forms import UserRegisterForm, UserLoginForm +from exam.xmlrpc_clients import python_server def gen_key(no_of_chars): """Generate a random key of the number of characters.""" @@ -116,41 +117,6 @@ def question(request, q_id): return render_to_response('exam/question.html', context, context_instance=ci) -def test_python(answer, test_code): - """Tests given Python function with the test code supplied. - - Returns - ------- - - A tuple: (success, error message). - - """ - success = False - tb = None - try: - submitted = compile(answer, '<string>', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '<string>', mode='exec') - exec _tests in g - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - else: - success = True - err = 'Correct answer' - finally: - del tb - - return success, err - - def check(request, q_id): user = request.user question = get_object_or_404(Question, pk=q_id) @@ -162,8 +128,10 @@ def check(request, q_id): next_q = quiz.skip() return show_question(request, next_q) - # Otherwise we were asked to check. - success, err_msg = test_python(answer, question.test) + # Otherwise we were asked to check. We obtain the results via XML-RPC + # with the code executed safely in a separate process (the python_server.py) + # running as nobody. + success, err_msg = python_server.run_code(answer, question.test) # Add the answer submitted. new_answer = Answer(question=question, answer=answer.strip()) diff --git a/exam/xmlrpc_clients.py b/exam/xmlrpc_clients.py new file mode 100644 index 0000000..1bc5513 --- /dev/null +++ b/exam/xmlrpc_clients.py @@ -0,0 +1,5 @@ +from xmlrpclib import ServerProxy + +# Connect to the python server. +python_server = ServerProxy('http://localhost:8001') +
\ No newline at end of file diff --git a/python_server.py b/python_server.py new file mode 100755 index 0000000..debd73d --- /dev/null +++ b/python_server.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +"""This server runs an XMLRPC server that can be submitted code and tests +and returns the output. It *should* be run as root and will run as the user +'nobody' so as to minimize any damange by errant code. +""" +import sys +from SimpleXMLRPCServer import SimpleXMLRPCServer +import pwd +import os + + +# Set the effective uid +nobody = pwd.getpwnam('nobody') +os.seteuid(nobody.pw_uid) + + +def run_code(answer, test_code, in_dir=None): + """Tests given Python function (`answer`) with the `test_code` supplied. + If the optional `in_dir` keyword argument is supplied it changes the + directory to that directory (it does not change it back to the original when + done). + + Returns + ------- + + A tuple: (success, error message). + + """ + if in_dir is not None: + os.chdir(in_dir) + + success = False + tb = None + try: + submitted = compile(answer, '<string>', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '<string>', mode='exec') + exec _tests in g + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + else: + success = True + err = 'Correct answer' + finally: + del tb + + return success, err + + +def main(): + server = SimpleXMLRPCServer(("localhost", 8001)) + server.register_function(run_code) + server.serve_forever() + +if __name__ == '__main__': + main() +
\ No newline at end of file |