Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New, license violation-free version of WPScan #16336

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

paralax
Copy link
Contributor

@paralax paralax commented Jan 2, 2018

  • built a list of WP plugins myself, no external sources, included
  • built an updater to keep it updated
  • use the new file in the updated crawl/wpscan plugin

no more license violations, however no vulnerability information

for line in html.splitlines():
if line.strip().startswith("<li><a href="):
f.write(re.sub("<[^<]+>", "", line).strip().rstrip('/') + '\n')
print('done')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some kind of safety check here? If the HTML failed to download, or the file format changed, the user will still get "done" in the output and no indication that the update failed.

Maybe count the number of plugins extracted and write something like this to the console?

Found and processed 5498 plugins.
Done!



URL = 'http://plugins.svn.wordpress.org/'
PLUGIN_FILE = 'w3af/plugins/crawl/wpscan/plugins.txt'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write a unittest that makes sure that the plugins file has been updated in the last month.

@andresriancho
Copy link
Owner

Thanks for the PR!

I'm very happy that you found a way to be nice to the wpscan guys and have the same features we had before 👍

An important thing to change is the name ;-) We don't want to use wpscan since it might confuse users, the wpscan team might have registered the name, etc.

Also, if you could write a unittest using the mock responses like in https://github.com/andresriancho/w3af/blob/fe6de41c5bd539a6597fc571a0d83852b8c7defd/w3af/plugins/tests/crawl/test_find_backdoors.py that would be amazing.

You can use nosetests w3af/plugins/tests/crawl/test_wpscan.py to run the test suite.

Copy link
Owner

@andresriancho andresriancho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really love to see this plugin in production!

@paralax what can I do to make this happen?

if not self._exec:
raise RunOnce()
else:
domain_path = fuzzable_request.get_url().get_domain_path()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin tries to find wordpress plugins in all site paths at least one time.

The plugin list has ~70k entries. This means that if the site has 10 paths, this plugin will generate 700k HTTP requests, which is something we can't do (at least not as default).

What do you think about implementing something like

# Check if there is a wordpress installation in this directory
domain_path = fuzzable_request.get_url().get_domain_path()
wp_unique_url = domain_path.url_join('wp-login.php')
response = self._uri_opener.GET(wp_unique_url, cache=True)
# If wp_unique_url is not 404, wordpress = true
if not is_404(response):
self._enum_users(fuzzable_request)
? What this does is check if the wp-login.php file exists in the path, if it does it will perform all the checks it wants, otherwise it just ignores the path.

If something like this is implemented, I would still keep the self._already_tested in order to prevent checking if wp-login.php exists in the path more than once.

Also, if we do it like this, we could remove this code:

        if not self._exec:
            raise RunOnce()

And everything related with it. We want to check if there site has more than one wordpress installation (in different paths), so raising RunOnce doesn't make sense.

:param fuzzable_request: A fuzzable_request instance that contains
(among other things) the URL to test.
"""
self._plugin_list = open(os.path.join(self.BASE_PATH, 'plugins.txt'), 'r').readlines()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several problems here:

  • The plugin list is kept in memory even when not used
  • The plugin list is read every time crawl is called.

Move this to the place where you use it (_dir_name_generator) and use xreadlines() to prevent the whole file from being in-memory

try:
dir_url = base_path.url_join(directory_name + '/')
except ValueError, ve:
msg = 'The "%s" line at "%s" generated an ' \
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens with the chinese / russian names in plugins.txt?

:return: None, data is stored in self.output_queue
"""
try:
http_response = self._uri_opener.GET(dir_url, cache=False)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does an HTTP GET to target.com/wp-content/plugins/{plugin-name}/, correct?

What if the plugin is installed, but "directory indexing" is off? What is shown in the output when a request like this one is sent? Maybe the plugins.txt file should contain not only the plugin name but also a file in the plugin? A readme.txt or something?

dir_url = base_path.url_join(directory_name + rand_alnum(5) + '/')
invalid_http_response = self._uri_opener.GET(dir_url,
cache=False)
if is_404(invalid_http_response):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a stupid comment, but after coding a lot in python I got used to:

if not is_404(invalid_http_response):
    return

That way, the rest of the code that goes below is indented at the same level of the if statement and you don't have "problems" with the 80 column 'restriction' imposed by PEP-8.


return """
This plugin finds WordPress plugins.
While it is not possible to fingerprint the plugin version automatically,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Challenge: Since all wordpress plugins seem to be in http://plugins.svn.wordpress.org/ and we have the different releases (tags: http://plugins.svn.wordpress.org/1000eb/tags/) it should be possible to not only identify that a plugin is present but also identify its version?

For example:

  • In the initial commit of plugin http://plugins.svn.wordpress.org/1000eb/trunk/, the file screenshot-1.png had md5sum X
  • In release 2.0 the same file has md5sum Y
  • In release 3.0 the file was removed and file screenshot-2.png was added

Using that information it should be possible to fingerprint the version, right?

I'm not saying that this should be implemented in order for the PR to be approved, but it would be a nice thing to have. After that we just need a list of vulnerable wordpress plugins and we can cross-reference them to give the user very valuable results.

@paralax
Copy link
Contributor Author

paralax commented Mar 3, 2019 via email

@andresriancho
Copy link
Owner

No problem, completely understandable 👍

Will add this to my TODO list

@andresriancho andresriancho self-assigned this Mar 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants