wapiti-scanner/wapiti

RuntimeError in mod_xxe

devl00p opened this issue · 3 comments

Stumbled into this today:

[*] Launching module xxe
Traceback (most recent call last):
  File "wapitiCore/attack/mod_xxe.py", line 394, in finish
    mutated_request, __, __ = next(mutator.mutate(original_request, [used_payload]))
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "bin/wapiti", line 34, in <module>
    wapiti_asyncio_wrapper()
  File "wapitiCore/main/wapiti.py", line 395, in wapiti_asyncio_wrapper
    asyncio.run(wapiti_main())
  File "/home/devloop/.pyenv/versions/3.9.15/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/home/devloop/.pyenv/versions/3.9.15/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
    return future.result()
  File "wapitiCore/main/wapiti.py", line 382, in wapiti_main
    await wap.attack(global_stop_event)
  File "wapitiCore/controller/wapiti.py", line 536, in attack
    await attack_module.finish()
RuntimeError: coroutine raised StopIteration

It looks like the call to next returned nothing while it was expected.

The bug appeared while scanning a Wordpress install with the plugin "addonify-quick-view" but maybe it is possible to spot the bug just by looking at the Wapiti code.

Is it possible to enter in the else case in finish method where XXEUploadMutator is called even if there is no file_params ?

else:
mutator = XXEUploadMutator(
parameters=[parameter_name],
skip=self.options.get("skipped_parameters")
)
mutated_request, __, __ = next(mutator.mutate(original_request, [used_payload]))

In this specific case, mutate will yield nothing.
def mutate(self, request: Request, payloads: PayloadSource) -> Iterator[Tuple[Request, Parameter, PayloadInfo]]:
get_params = request.get_params
post_params = request.post_params
referer = request.referer
for i in range(len(request.file_params)):
new_params = request.file_params
param_name = new_params[i][0]

Indeed and XXEUploadMutator is used if the form have the enctype multipart/form-data which can happen even if no file upload field is present.

However if we crashed in finish it looks like something was triggered so an investigation would be helpful.

Let's keep the issue open till we find a way to reproduce

I've found a way to reproduce.
It seems happen when you have only one file_params in the request and you put the --skip option on that param.
Then, the for is passed and the yield is never triggered.

for i in range(len(request.file_params)):
new_params = request.file_params
param_name = new_params[i][0]
if self._skip_list and param_name in self._skip_list:
continue
if self._parameters and param_name not in self._parameters:
continue

image

Test done with out of band payloads only and this php file where the calendar input file is vulnerable :

<?php
if (isset($_FILES["calendar"])) {
    libxml_disable_entity_loader(false);
    $dom = new DOMDocument();
    move_uploaded_file($_FILES["calendar"]["tmp_name"], "/dev/shm/wapiti.xml");
    $calendar = file_get_contents("/dev/shm/wapiti.xml");
    $dom->loadXML($calendar, LIBXML_NOENT | LIBXML_DTDLOAD);
    $output = simplexml_import_dom($dom);
    echo "Data loaded.";
} else {
?>
    <form method="POST" enctype="multipart/form-data">
        Please send your xml calendar: <input type="file" name="calendar"/ >
        <input type="submit" value="Submit" />
    </form>
<?php
}
?>

Command : wapiti -u http://127.0.0.1/upload.php -m xxe --skip calendar