Check isAdmin value + Docker Testing + README update

pull/25/head
Swissky 2018-07-29 18:58:46 +02:00
parent f53723a54a
commit 06bb761c87
12 changed files with 2157 additions and 4583 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
TODO.md
TODO/ TODO/
*.pyc *.pyc
engine/*.pyc engine/*.pyc

View File

@ -1,9 +1,9 @@
# Wordpresscan # Wordpresscan
A simple Wordpress scanner written in python based on the work of WPScan (Ruby version) A simple Wordpress scanner written in python based on the work of WPScan (Ruby version), some features are inspired by WPSeku.
## Disclaimer ## Disclaimer
``` ```
The author of this github is not responsible for misuse or for any damage that you may cause! The authors of this github are not responsible for misuse or for any damage that you may cause!
You agree that you use this software at your own risk. You agree that you use this software at your own risk.
``` ```
@ -17,14 +17,15 @@ cd Wordpresscan
``` ```
Virtualenv Virtualenv
``` ```bash
virtualenv .venv -p /usr/bin/python2.7 virtualenv .venv -p /usr/bin/python2.7
source .venv/bin/activate source .venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
``` ```
Example 1 : Basic update and scan of a wordpress ## Examples
``` ### Example 1 : Basic update and scan of a wordpress
```python
python main.py -u "http://localhost/wordpress" --update --random-agent python main.py -u "http://localhost/wordpress" --update --random-agent
-u : Url of the WordPress -u : Url of the WordPress
@ -33,13 +34,13 @@ python main.py -u "http://localhost/wordpress" --update --random-agent
--random-agent : Use a random user-agent for this session --random-agent : Use a random user-agent for this session
``` ```
Example 2 : Basic bruteforce (option --brute, option --nocheck) ### Example 2 : Basic bruteforce (option --brute, option --nocheck)
* bruteforce customs usernames * bruteforce customs usernames
``` ```python
python main.py -u "http://127.0.0.1/wordpress/" --brute --usernames "admin,guest" --passwords-list fuzz/wordlist.lst python main.py -u "http://127.0.0.1/wordpress/" --brute --usernames "admin,guest" --passwords-list fuzz/wordlist.lst
``` ```
* bruteforce with usernames list * bruteforce with usernames list
``` ```python
python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list fuzz/wordlist.lst python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list fuzz/wordlist.lst
``` ```
* bruteforce detected users * bruteforce detected users
@ -48,7 +49,7 @@ python main.py -u "http://127.0.0.1/wordpress/" --brute --passwords-list fuzz/wo
``` ```
``` ```python
╭─ 👻 swissky@crashlab: ~/Github/Wordpresscan master* ╭─ 👻 swissky@crashlab: ~/Github/Wordpresscan master*
╰─$ python main.py -u "http://127.0.0.1/wordpress/" --brute --users-list fuzz/wordlist.lst --passwords-list 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
_______________________________________________________________ _______________________________________________________________
@ -74,8 +75,8 @@ _______________________________________________________________
Bruteforcing - ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Bruteforcing - ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
``` ```
Example 3 : Thinking is overrated, this is aggressive, mostly not advised! ### Example 3 : Thinking is overrated, this is aggressive, mostly not advised!
``` ```python
python main.py -u "http://127.0.0.1/wordpress/" --fuzz python main.py -u "http://127.0.0.1/wordpress/" --fuzz
[i] Enumerating components from aggressive fuzzing ... [i] Enumerating components from aggressive fuzzing ...
@ -91,6 +92,11 @@ python main.py -u "http://127.0.0.1/wordpress/" --fuzz
## Output example from a test environment ## Output example from a test environment
![alt tag](https://github.com/swisskyrepo/Wordpresscan/blob/master/screens/Version%204.4.7.png?raw=true) ![alt tag](https://github.com/swisskyrepo/Wordpresscan/blob/master/screens/Version%204.4.7.png?raw=true)
## Deploy a test environment
```bash
docker-compose -f wordpress_compose.yml up -d
```
To enable `wp-json` api you need to change "Permalink" to anything but "simple" in the settings.
## Credits and Contributors ## Credits and Contributors
* Original idea and script from [WPScan Team](https://wpscan.org/) * Original idea and script from [WPScan Team](https://wpscan.org/)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,14 @@ class Brute_Engine:
users_to_brute = usernames.split(',') users_to_brute = usernames.split(',')
for user in users_to_brute: for user in users_to_brute:
user = user.replace(' ', '') user = user.replace(' ', '')
print notice("Bruteforcing " + user) print(notice("Bruteforcing " + user))
self.bruteforcing_pass(wordpress, user, passwords_list) self.bruteforcing_pass(wordpress, user, passwords_list)
# Bruteforce with usernames list # Bruteforce with usernames list
elif users_list: elif users_list:
for file_list in [users_list, passwords_list]: for file_list in [users_list, passwords_list]:
if not os.path.isfile(file_list): if not os.path.isfile(file_list):
print critical("Can't found %s file" % file_list) print(critical("Can't found %s file" % file_list))
exit() exit()
# launch users & passwords bruteforce # launch users & passwords bruteforce
self.bruteforcing_user(wordpress, users_list, passwords_list) self.bruteforcing_user(wordpress, users_list, passwords_list)
@ -34,10 +34,10 @@ class Brute_Engine:
else: else:
if len(wordpress.users) != 0: if len(wordpress.users) != 0:
if not os.path.isfile(passwords_list): if not os.path.isfile(passwords_list):
print critical("Can't found %s file" % passwords_list) print(critical("Can't found %s file" % passwords_list))
exit() exit()
print notice("Bruteforcing detected users: ") print(notice("Bruteforcing detected users: "))
for user in wordpress.users: for user in wordpress.users:
print info("User found "+ user['slug']) print info("User found "+ user['slug'])
self.bruteforcing_pass(wordpress, user['slug'], passwords_list) self.bruteforcing_pass(wordpress, user['slug'], passwords_list)
@ -48,7 +48,7 @@ class Brute_Engine:
description : description :
""" """
def bruteforcing_user(self, wordpress, users_list, passwords_list): def bruteforcing_user(self, wordpress, users_list, passwords_list):
print notice("Bruteforcing all users") print(notice("Bruteforcing all users"))
with open(users_list) as data_file: with open(users_list) as data_file:
data = data_file.readlines() data = data_file.readlines()
@ -70,12 +70,12 @@ class Brute_Engine:
try: try:
html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text
except: except:
print critical('ConnectionError in thread, retry...') print(critical('ConnectionError in thread, retry...'))
continue continue
break break
# valid login -> the submited user is printed by WP # valid login -> the submited user is printed by WP
if '<div id="login_error">' in html and '<strong>%s</strong>' % user in html: if '<div id="login_error">' in html and '<strong>%s</strong>' % user in html:
print info("User found "+ user) print(info("User found "+ user))
users_found.append(user) users_found.append(user)
@ -84,7 +84,7 @@ class Brute_Engine:
description : description :
""" """
def bruteforcing_pass(self, wordpress, user, passwords_list): def bruteforcing_pass(self, wordpress, user, passwords_list):
print info("Starting passwords bruteforce for " + user) print(info("Starting passwords bruteforce for " + user))
with open(passwords_list) as data_file: with open(passwords_list) as data_file:
data = data_file.readlines() data = data_file.readlines()
@ -108,9 +108,20 @@ class Brute_Engine:
try: try:
html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text html = requests.post(wordpress.url + "wp-login.php", data=data, verify=False).text
except: except:
print critical('ConnectionError in thread, retry...') print(critical('ConnectionError in thread, retry...'))
continue continue
break break
if not '<div id="login_error">' in html: if not '<div id="login_error">' in html:
print warning("Password found for {} : {}{}".format(user,pwd, ' '*100)) print(warning("Password found for {} : {}{}".format(user,pwd, ' '*100)))
found[0] = True found[0] = True
self.xmlrpc_check_admin(user, pwd)
def xmlrpc_check_admin(self, username, password):
post = "<methodCall><methodName>wp.getUsersBlogs</methodName><params><param><value><string>" + username + "</string></value></param><param><value><string>" + password + "</string></value></param></params></methodCall>"
req = requests.post("http://127.0.0.1:8000/xmlrpc.php", data=post)
regex = re.compile("isAdmin.*boolean.(\d)")
match = regex.findall(req.text)
if int(match[0]):
print(critical("User is an admin !"))

View File

@ -18,7 +18,7 @@ def notice(msg):
return "\n\033[1m[i] " + msg + "\033[0m" return "\n\033[1m[i] " + msg + "\033[0m"
def critical(msg): def critical(msg):
return "\n\033[91m[!] " + msg + "\033[0m" return "\033[91m[!] " + msg + "\033[0m"
def warning(msg): def warning(msg):
return "\033[93m[i] " + msg + "\033[0m" return "\033[93m[i] " + msg + "\033[0m"

View File

@ -14,6 +14,7 @@ class Wordpress:
index = None index = None
agent = False agent = False
users = {} users = {}
files = set()
def __init__(self, url, user_agent, nocheck, max_threads): def __init__(self, url, user_agent, nocheck, max_threads):
print info("URL: %s" % url) print info("URL: %s" % url)
@ -113,6 +114,7 @@ class Wordpress:
r = requests.get(self.url + 'readme.html', headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + 'readme.html', headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r): if "200" in str(r):
self.files.add('readme.html')
# Basic version fingerprinting # Basic version fingerprinting
regex = 'Version (.*)' regex = 'Version (.*)'
@ -130,6 +132,7 @@ class Wordpress:
def is_debug_log(self): def is_debug_log(self):
r = requests.get(self.url + 'debug.log', headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + 'debug.log', headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r) and not "404" in r.text : if "200" in str(r) and not "404" in r.text :
self.files.add('debug.log')
print critical( "Debug log file found: %s" % (self.url + 'debug.log') ) print critical( "Debug log file found: %s" % (self.url + 'debug.log') )
@ -138,10 +141,26 @@ class Wordpress:
description : determine if there is any unsafe wp-config backup description : determine if there is any unsafe wp-config backup
""" """
def is_backup_file(self): def is_backup_file(self):
backup = ['wp-config.php~', 'wp-config.php.save', '.wp-config.php.bck', 'wp-config.php.bck', '.wp-config.php.swp', 'wp-config.php.swp', 'wp-config.php.swo', 'wp-config.php_bak', 'wp-config.bak', 'wp-config.php.bak', 'wp-config.save', 'wp-config.old', 'wp-config.php.old', 'wp-config.php.orig', 'wp-config.orig', 'wp-config.php.original', 'wp-config.original', 'wp-config.txt', 'wp-config.php.txt', 'wp-config.backup', 'wp-config.php.backup', 'wp-config.copy', 'wp-config.php.copy', 'wp-config.tmp', 'wp-config.php.tmp', 'wp-config.zip', 'wp-config.php.zip', 'wp-config.db', 'wp-config.php.db', 'wp-config.dat','wp-config.php.dat', 'wp-config.tar.gz', 'wp-config.php.tar.gz', 'wp-config.back', 'wp-config.php.back', 'wp-config.test', 'wp-config.php.test'] backup = [
'wp-config.php~', 'wp-config.php.save', '.wp-config.php.bck',
'wp-config.php.bck', '.wp-config.php.swp', 'wp-config.php.swp',
'wp-config.php.swo', 'wp-config.php_bak', 'wp-config.bak',
'wp-config.php.bak', 'wp-config.save', 'wp-config.old',
'wp-config.php.old', 'wp-config.php.orig', 'wp-config.orig',
'wp-config.php.original', 'wp-config.original', 'wp-config.txt',
'wp-config.php.txt', 'wp-config.backup', 'wp-config.php.backup',
'wp-config.copy', 'wp-config.php.copy', 'wp-config.tmp',
'wp-config.php.tmp', 'wp-config.zip', 'wp-config.php.zip',
'wp-config.db', 'wp-config.php.db', 'wp-config.dat',
'wp-config.php.dat', 'wp-config.tar.gz', 'wp-config.php.tar.gz',
'wp-config.back', 'wp-config.php.back', 'wp-config.test',
'wp-config.php.test', "wp-config.php.1","wp-config.php.2",
"wp-config.php.3", "wp-config.php._inc", "wp-config_inc"]
for b in backup: for b in backup:
r = requests.get(self.url + b, headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + b, headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r) and not "404" in r.text : if "200" in str(r) and not "404" in r.text :
self.files.add(b)
print critical("A wp-config.php backup file has been found in: %s" % (self.url + b) ) print critical("A wp-config.php backup file has been found in: %s" % (self.url + b) )
@ -151,7 +170,8 @@ class Wordpress:
""" """
def is_xml_rpc(self): def is_xml_rpc(self):
r = requests.get(self.url + "xmlrpc.php", headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + "xmlrpc.php", headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r) and "404" in r.text : if r.status_code == 405 :
self.files.add("xmlrpc.php")
print info("XML-RPC Interface available under: %s " % (self.url+"xmlrpc.php") ) print info("XML-RPC Interface available under: %s " % (self.url+"xmlrpc.php") )
@ -166,6 +186,7 @@ class Wordpress:
for directory, name in zip(directories,dir_name): for directory, name in zip(directories,dir_name):
r = requests.get(self.url + directory, headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + directory, headers={"User-Agent":self.agent}, verify=False)
if "Index of" in r.text: if "Index of" in r.text:
self.files.add(directory)
print warning("%s directory has directory listing enabled : %s" % (name, self.url + directory)) print warning("%s directory has directory listing enabled : %s" % (name, self.url + directory))
@ -176,6 +197,7 @@ class Wordpress:
def is_robots_text(self): def is_robots_text(self):
r = requests.get(self.url + "robots.txt", headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + "robots.txt", headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r) and not "404" in r.text : if "200" in str(r) and not "404" in r.text :
self.files.add("robots.txt")
print info("robots.txt available under: %s " % (self.url+"robots.txt") ) print info("robots.txt available under: %s " % (self.url+"robots.txt") )
lines = r.text.split('\n') lines = r.text.split('\n')
for l in lines: for l in lines:
@ -191,6 +213,7 @@ class Wordpress:
for f in files: for f in files:
r = requests.get(self.url + f, headers={"User-Agent":self.agent}, verify=False) r = requests.get(self.url + f, headers={"User-Agent":self.agent}, verify=False)
if "200" in str(r) and not "404" in r.text : if "200" in str(r) and not "404" in r.text :
self.files.add(f)
print info("%s available under: %s " % (f, self.url+f) ) print info("%s available under: %s " % (f, self.url+f) )
""" """
@ -233,4 +256,5 @@ class Wordpress:
print "Themes : %s" % self.themes print "Themes : %s" % self.themes
print "Agent : %s" % self.agent print "Agent : %s" % self.agent
print "Users : %s" % self.users print "Users : %s" % self.users
print "Files : %s" % self.files
print "---------------------------" print "---------------------------"

View File

@ -0,0 +1,28 @@
version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8001:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes:
db_data:
#docker-compose up -d

View File

@ -33,9 +33,10 @@ if __name__ == "__main__":
parser.add_argument('--nocheck', action ='store_const', const='nocheck',dest='nocheck', default=False, help="Check for a Wordpress instance") 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('--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('--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('--usernames', action ='store', dest='usernames', default='', help="Usernames to bruteforce separated with a ','")
parser.add_argument('--users-list', action ='store', dest='users_list', default=None, help="Users list for 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") parser.add_argument('--passwords-list', action ='store', dest='passwords_list', default=None, help="Passwords list for bruteforce")
parser.add_argument('--debug', action ='store_const', const='debug', dest='debug', default=False, help="Enable a debugging flag")
results = parser.parse_args() results = parser.parse_args()
# Check wordpress url # Check wordpress url
@ -63,5 +64,9 @@ if __name__ == "__main__":
# Load plugins for more functions # Load plugins for more functions
Load_Plugins(wp) Load_Plugins(wp)
# Debug
if results.debug:
wp.to_string()
else: else:
parser.print_help() parser.print_help()