WithSecureLabs/drozer

scanner.provider.traversal returned exception error.

thesinghsec opened this issue · 4 comments

  • It was working well in previous version of Drozer, upon updating I got this error again and again.

dz> run scanner.provider.traversal -a com.mwr.example.sieve
Attempting to run shell module
Scanning com.mwr.example.sieve...
Exception occured: No files supported by provider at content://com.mwr.example.sieve.DBContentProvider/Passwords//../../../../../../../../../../../../../../../../etc/hosts

image

root@d7feae1123d5:/# drozer console connect --server host.docker.internal --debug
Selecting ebe9fcc0c47b28da (Google sdk_gphone64_x86_64 12)

            ..                    ..:.
           ..o..                  .r..
            ..a..  . ....... .  ..nd
              ro..idsnemesisand..pr
              .otectorandroidsneme.
           .,sisandprotectorandroids+.
         ..nemesisandprotectorandroidsn:.
        .emesisandprotectorandroidsnemes..
      ..isandp,..,rotecyayandro,..,idsnem.
      .isisandp..rotectorandroid..snemisis.
      ,andprotectorandroidsnemisisandprotec.
     .torandroidsnemesisandprotectorandroid.
     .snemisisandprotectorandroidsnemesisan:
     .dprotectorandroidsnemesisandprotector.

drozer Console (v3.0.1 debug mode)
dz> run scanner.provider.traversal --uri content://com.withsecure.example.sieve.provider.DBContentProvider
Attempting to run shell module
exception in module: ReflectionException: No files supported by provider at content://com.withsecure.example.sieve.provider.DBContentProvider/../../../../../../../../../../../../../../../../etc/hosts
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/drozer/console/session.py", line 402, in do_run
    module.run(argv[1:])
  File "/usr/local/lib/python3.10/dist-packages/drozer/modules/base.py", line 183, in run
    result = self.execute(arguments)
  File "/usr/local/lib/python3.10/dist-packages/drozer/modules/scanner/provider/traversal.py", line 26, in execute
    self.__test_uri(arguments.package_or_uri, vulnerable)
  File "/usr/local/lib/python3.10/dist-packages/drozer/modules/scanner/provider/traversal.py", line 53, in __test_uri
    data = self.contentResolver().read(uri + "/../../../../../../../../../../../../../../../../etc/hosts")
  File "/usr/local/lib/python3.10/dist-packages/drozer/modules/common/provider.py", line 127, in read
    fd = client.openFile(self.parseUri(uri), "r")
  File "/usr/local/lib/python3.10/dist-packages/pysolar/reflection/types/reflected_object.py", line 64, in _invoker
    result = self._reflector.invoke(self, method_name,
  File "/usr/local/lib/python3.10/dist-packages/pysolar/reflection/reflector.py", line 83, in invoke
    raise ReflectionException(response.reflection_response.errormessage)
pysolar.reflection.exceptions.ReflectionException: No files supported by provider at content://com.withsecure.example.sieve.provider.DBContentProvider/../../../../../../../../../../../../../../../../etc/hosts

yup legit. we'll look into it

So, this is a fun one.

At its core, the problem boils down to the logic of the __test_uri() function of the scanner.provider.traversal module. In drozer 2, this was implemented as follows:

    def __test_uri(self, uri, vulnerable):
        try:
            data = self.contentResolver().read(uri + "/../../../../../../../../../../../../../../../../etc/hosts")
        except ReflectionException as e:
            if e.message.find("java.io.FileNotFoundException") >= 0 or \
                e.message.find("java.lang.IllegalArgumentException") >= 0 or \
                e.message.find("java.lang.SecurityException") >= 0 or \
                e.message.find("No content provider") >= 0 or \
                e.message.find("RuntimeException"):
                data = ""
            else:
                raise
    
        if data != None and len(data) > 0:
            vulnerable.add(uri)

A cursory read of the try/except block makes it seem like an exception should be suppressed if it contains one of five strings, and raised otherwise. So, it got ported to drozer 3 as:

    def __test_uri(self, uri, vulnerable):
        try:
            data = self.contentResolver().read(uri + "/../../../../../../../../../../../../../../../../etc/hosts")
        except ReflectionException as e:
            if "java.io.FileNotFoundException" in str(e) or \
                "java.lang.IllegalArgumentException" in str(e) or \
                "java.lang.SecurityException" in str(e) or \
                "No content provider" in str(e) or \
                "RuntimeException" in str(e):
                data = ""
            else:
                raise
    
        if data != None and len(data) > 0:
            vulnerable.add(uri)

This is all sensible at a glance, but the drozer 2 logic was actually flawed due to a missing >= 0 - if the string RuntimeException was NOT found, string.find() would return -1, causing the entire if statement to evaluate as True. In practice, this means that it set data to "" if e.message contained one of the first four strings OR didn't start with RuntimeException. The only way for it to really go False would be to start with RuntimeException and not contain any of the other four strings. This probably always evaluated as True, and so the exception was likely never raised.

Now, drozer 3's logic attempts to implement the same intention, but without the same error. As a result it ends up with a statement that throws the exception much more often. My suspicion is that drozer 2 worked by accident.

The obvious solution here appears to be to remove the if statement entirely, and to gracefully handle all exceptions that come from the content provider. In the future, we may add handlers for scenarios like a non-existent provider (to give a meaningful message to the user), but for now this will make the module work as it did before.

Once pull request #427 is merged, please try building drozer 3 and see if this works for you. I strongly suspect it will.

We'll keep the issue open, because ideally we should add a few exception type checks here and there.

Keeping this open as a reminder to do further work on those exception checks

Okay, so, having done a bit more work on that, realistically speaking better error checking here is gonna be difficult. Different versions of Android throw different exceptions, and Catching 'Em All™ is likely not practical. Closing this for the time being - it currently works as well as drozer 2 ever did