[tor-commits] [ooni-probe/master] Fix security vulnerability that could have been an issue in the future.

art at torproject.org art at torproject.org
Sun Oct 7 19:15:29 UTC 2012


commit 1371d0de76d1d003d9c38c8e68a786f1fac2514d
Author: Arturo Filastò <arturo at filasto.net>
Date:   Sun Oct 7 19:13:07 2012 +0000

    Fix security vulnerability that could have been an issue in the future.
    Basically the problem was that we were not using yaml.safe_dump to write
    reports to the log. What this means is that when we were to go back to parse
    the reports we could have been loading python objects that may contain
    arbitrary code. We could not out of the box use safe_dump because we were also
    dumping certain objects in some test and safe_dump does not serialize complex
    numbers.
    * We have now a custom representer that serializes complex numbers
    * We have our own custom dumper that does the type checking and can be expanded
      to support other objects we think are safe to read.
    
    * Bug fixing in other various parts of the code.
---
 nettests/experimental/domclass_collector.py |    8 ++--
 ooni/nettest.py                             |   11 ++++--
 ooni/oonicli.py                             |    2 +-
 ooni/reporter.py                            |   45 ++++++++++++++++++++++++++-
 ooni/runner.py                              |   18 ++++++++--
 ooni/templates/httpt.py                     |    2 +-
 ooni/utils/date.py                          |    8 +++++
 7 files changed, 79 insertions(+), 15 deletions(-)

diff --git a/nettests/experimental/domclass_collector.py b/nettests/experimental/domclass_collector.py
index 9b2c8d8..c1866f2 100644
--- a/nettests/experimental/domclass_collector.py
+++ b/nettests/experimental/domclass_collector.py
@@ -17,8 +17,9 @@ class DOMClassCollector(httpt.HTTPTest):
     author = "Arturo Filastò"
     version = 0.1
 
-    inputs = ['http://news.google.com/', 'http://wikileaks.org/']
-    #inputFile = ['f', 'file', None, 'The list of urls to build a domclass for']
+    followRedirects = True
+
+    inputFile = ['file', 'f', None, 'The list of urls to build a domclass for']
 
     def test_collect(self):
         if self.input:
@@ -29,5 +30,4 @@ class DOMClassCollector(httpt.HTTPTest):
 
     def processResponseBody(self, body):
         eigenvalues = domclass.compute_eigenvalues_from_DOM(content=body)
-        self.report['eigenvalues'] = eigenvalues
-
+        self.report['eigenvalues'] = eigenvalues.tolist()
diff --git a/ooni/nettest.py b/ooni/nettest.py
index 835a5a0..7f9562a 100644
--- a/ooni/nettest.py
+++ b/ooni/nettest.py
@@ -1,5 +1,6 @@
 import itertools
-from twisted.python import log
+
+from twisted.python import log, usage
 from twisted.trial import unittest, itrial
 from twisted.internet import defer
 
@@ -84,13 +85,11 @@ class TestCase(unittest.TestCase):
         writing.
         """
         if result.reporterFactory.firstrun:
-            print "Running both!!"
             d1 = result.reporterFactory.writeHeader()
             d2 = unittest.TestCase.deferSetUp(self, ignored, result)
             dl = defer.DeferredList([d1, d2])
             return dl
         else:
-            print "Only one :P"
             return unittest.TestCase.deferSetUp(self, ignored, result)
 
     def inputProcessor(self, fp):
@@ -99,9 +98,13 @@ class TestCase(unittest.TestCase):
         fp.close()
 
     def getOptions(self):
-        if self.inputFile:
+        if type(self.inputFile) is str:
             fp = open(self.inputFile)
             self.inputs = self.inputProcessor(fp)
+        elif not self.inputs[0]:
+            pass
+        else:
+            raise usage.UsageError("No input file specified!")
         # XXX perhaps we may want to name and version to be inside of a
         # different object that is not called options.
         return {'inputs': self.inputs,
diff --git a/ooni/oonicli.py b/ooni/oonicli.py
index 63edbe3..af93af9 100644
--- a/ooni/oonicli.py
+++ b/ooni/oonicli.py
@@ -46,7 +46,7 @@ class Options(usage.Options, app.ReactorSelectionMixin):
                 ]
 
     optParameters = [
-        ["reportfile", "o", "report.yaml", "report file name"],
+        ["reportfile", "o", None, "report file name"],
         ["logfile", "l", "test.log", "log file name"],
         ['temp-directory', None, '_ooni_temp',
          'Path to use as working directory for tests.']
diff --git a/ooni/reporter.py b/ooni/reporter.py
index b335738..47b8158 100644
--- a/ooni/reporter.py
+++ b/ooni/reporter.py
@@ -2,6 +2,12 @@ import time
 import sys
 import yaml
 import itertools
+import copy_reg
+
+from yaml.representer import *
+from yaml.emitter import *
+from yaml.serializer import *
+from yaml.resolver import *
 
 from datetime import datetime
 from twisted.python.util import OrderedDict, untilConcludes
@@ -20,6 +26,43 @@ except:
 
 pyunit =  __import__('unittest')
 
+class OSafeRepresenter(SafeRepresenter):
+    def represent_complex(self, data):
+        if data.imag == 0.0:
+            data = u'%r' % data.real
+        elif data.real == 0.0:
+            data = u'%rj' % data.imag
+        elif data.imag > 0:
+            data = u'%r+%rj' % (data.real, data.imag)
+        else:
+            data = u'%r%rj' % (data.real, data.imag)
+        return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data)
+
+OSafeRepresenter.add_representer(complex,
+                                 OSafeRepresenter.represent_complex)
+
+class OSafeDumper(Emitter, Serializer, OSafeRepresenter, Resolver):
+
+    def __init__(self, stream,
+            default_style=None, default_flow_style=None,
+            canonical=None, indent=None, width=None,
+            allow_unicode=None, line_break=None,
+            encoding=None, explicit_start=None, explicit_end=None,
+            version=None, tags=None):
+        Emitter.__init__(self, stream, canonical=canonical,
+                indent=indent, width=width,
+                allow_unicode=allow_unicode, line_break=line_break)
+        Serializer.__init__(self, encoding=encoding,
+                explicit_start=explicit_start, explicit_end=explicit_end,
+                version=version, tags=tags)
+        OSafeRepresenter.__init__(self, default_style=default_style,
+                default_flow_style=default_flow_style)
+        Resolver.__init__(self)
+
+
+def safe_dump(data, stream=None, **kw):
+    return yaml.dump_all([data], stream, Dumper=OSafeDumper, **kw)
+
 class OReporter(pyunit.TestResult):
     """
     This is an extension of the unittest TestResult. It adds support for
@@ -56,7 +99,7 @@ class OReporter(pyunit.TestResult):
         self._write('\n')
 
     def writeYamlLine(self, line):
-        to_write = yaml.dump([line])
+        to_write = safe_dump([line])
         self._write(to_write)
 
 
diff --git a/ooni/runner.py b/ooni/runner.py
index ebeb4c5..0b9503a 100644
--- a/ooni/runner.py
+++ b/ooni/runner.py
@@ -16,7 +16,7 @@ from ooni.reporter import ReporterFactory
 from ooni.inputunit import InputUnitFactory
 from ooni.nettest import InputTestSuite
 from ooni import nettest
-from ooni.utils import log, geodata
+from ooni.utils import log, geodata, date
 from ooni.plugoo import tests as oonitests
 
 def isTestCase(thing):
@@ -92,7 +92,10 @@ def adaptLegacyTest(obj, config):
     return LegacyOONITest
 
 def processTest(obj, config):
-    if obj.optParameters:
+    if obj.optParameters or obj.inputFile:
+        if not obj.optParameters:
+            obj.optParameters = []
+
         class Options(usage.Options):
             optParameters = obj.optParameters
 
@@ -100,6 +103,7 @@ def processTest(obj, config):
         if inputFile:
             Options.optParameters.append(inputFile)
 
+        print Options.optParameters
         options = Options()
         options.parseOptions(config['subArgs'])
 
@@ -107,6 +111,12 @@ def processTest(obj, config):
 
         if inputFile:
             obj.inputFile = options[inputFile[0]]
+        try:
+            tmp_obj = obj()
+            tmp_obj.getOptions()
+        except usage.UsageError:
+            options.opt_help()
+
 
     return obj
 
@@ -157,7 +167,6 @@ def loadTestsAndOptions(classes):
             options.append(opts)
         except AttributeError:
             options.append([])
-
         tests = reflect.prefixedMethodNames(klass, methodPrefix)
         if tests:
             cases = makeTestCases(klass, tests, methodPrefix)
@@ -182,7 +191,8 @@ class ORunner(object):
         try:
             reportFile = open(config['reportfile'], 'a+')
         except:
-            reportFile = open('report.yaml', 'a+')
+            filename = 'report_'+date.timestamp()+'.yaml'
+            reportFile = open(filename, 'a+')
         self.reporterFactory = ReporterFactory(reportFile,
                                     testSuite=self.baseSuite(self.cases))
 
diff --git a/ooni/templates/httpt.py b/ooni/templates/httpt.py
index 963189a..6e3163b 100644
--- a/ooni/templates/httpt.py
+++ b/ooni/templates/httpt.py
@@ -128,7 +128,7 @@ class HTTPTest(TestCase):
         return d
 
     def _cbResponse(self, response):
-        self.response['headers'] = response.headers
+        self.response['headers'] = list(response.headers.getAllRawHeaders())
         self.response['code'] = response.code
         self.response['length'] = response.length
         self.response['version'] = response.length
diff --git a/ooni/utils/date.py b/ooni/utils/date.py
index dcd8e36..25250a6 100644
--- a/ooni/utils/date.py
+++ b/ooni/utils/date.py
@@ -20,3 +20,11 @@ def pretty_date():
     pretty = cur_time.strftime(d_format)
     return pretty
 
+def timestamp():
+    cur_time = datetime.utcnow()
+    d_format = "%d_%B_%Y_%H-%M-%S"
+    pretty = cur_time.strftime(d_format)
+    return pretty
+
+
+



More information about the tor-commits mailing list