def do_set(self, arg): 'Sets a global parameter of WPJsonScanner' parser = ArgumentParser(prog='set', description='sets global parameters for WPJsonScanner') parser.add_argument("what", choices=['target', 'proxy', 'cookies', 'credentials'], help='the parameter to set') parser.add_argument("value", type=str, help='the new value of the parameter (for cookies, set as cookie string: "n1=v1; n2=v2")') args = parser.custom_parse_args(arg) if args is None: return if args.what == 'target': self.target = args.value if re.match(r'^https?://.*$', self.target) is None: self.target = "http://" + self.target if re.match(r'^.+/$', self.target) is None: self.target += "/" InteractiveShell.prompt = Console.red + self.target + Console.normal + " > " print("target = %s" % args.value) self.scanner = WPApi(self.target, session=self.session) Console.log_info("Cache is erased but session stays the same (with cookies and authorization)") elif args.what == 'proxy': self.session.set_proxy(args.value) print("proxy = %s" % args.value) elif args.what == 'cookies': self.session.set_cookies(args.value) print("Cookies set!") elif args.what == "credentials": authorization_list = args.value.split(':') if len(authorization_list) == 1: authorization = (authorization_list[0], '') elif len(authorization_list) >= 2: authorization = (authorization_list[0], ':'.join(authorization_list[1:])) self.session.set_creds(authorization) print("Credentials set!") print()
def fetch_obj(self, obj_type, obj_id, cache=True, json=None, csv=None): """ Displays and exports (if relevant) the object fetched by ID :param obj_type: the type of the object :param obj_id: the ID of the obj :param cache: whether to use the cache of not :param json: json export filename :param csv: csv export filename """ prop = self.get_fetch_or_list_type(obj_type) print(prop["obj_name"] + " details") try: obj = self.scanner.get_obj_by_id(obj_type, obj_id, use_cache=cache) if len(obj) == 0: Console.log_info(prop["obj_name"] + " not found\n") else: prop["display_func"](obj, details=True) if len(prop["additional_info"].keys()) > 0: InteractiveShell.export_decorator(prop["export_func"], False, "", json, csv, obj, prop["additional_info"]) else: InteractiveShell.export_decorator(prop["export_func"], False, "", json, csv, obj) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") except IOError as e: Console.log_error("Could not open %s for writing" % e.filename) print()
def export_namespaces(namespaces, fmt, filename): """ **NOT IMPLEMENTED** Exports namespaces in specified format to specified file. :param namespaces: the namespaces to export :param fmt: the export format (JSON or CSV) :return: the length of the list written to the file """ Console.log_info("Namespaces export not available yet") return 0
def do_search(self, arg): 'Looks for specific keywords in the WordPress API' parser = ArgumentParser(prog='search', description='searches something from the server') parser.add_argument("--type", "-t", action="append", choices=[ 'all', 'post', #'post-revision', #'wp-block', 'category', 'tag', 'page', 'comment', 'media', 'user', #'theme', #'search-result', ], help='the types to look for (default all)', dest='what' ) parser.add_argument("keywords", help='the keywords to look for') parser.add_argument("--json", "-j", help="list and store as json to the specified file(s)") parser.add_argument("--csv", "-c", help="list and store as csv to the specified file(s)") parser.add_argument("--limit", "-l", type=int, help="limit the number of results") parser.add_argument("--start", "-s", type=int, help="start at the given index") args = parser.custom_parse_args(arg) if args is None: return what_types = WPApi.convert_obj_types_to_list(args.what) results = self.scanner.search(what_types, args.keywords, args.start, args.limit) print() for k, v in results.items(): prop = self.get_fetch_or_list_type(k, plural=True) print(prop["obj_name"] + " details") if len(v) == 0: Console.log_info("No result") else: try: prop["display_func"](v) InteractiveShell.export_decorator( prop["export_func"], len(what_types) > 1 or WPApi.ALL_TYPES in what_types, prop["obj_name"].lower(), args.json, args.csv, v ) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") except IOError as e: Console.log_error("Could not open %s for writing" % e.filename) print()
def main(): parser = argparse.ArgumentParser( description= """Reads a WP-JSON API on a WordPress installation to retrieve a maximum of publicly available information. These information comprise, but not only: posts, comments, pages, medias or users. As this tool could allow to access confidential (but not well-protected) data, it is recommended that you get first a written permission from the site owner. The author won\'t endorse any liability for misuse of this software""", epilog= """(c) 2018 Mickaël "Kilawyn" Walter. This program is licensed under the MIT license, check LICENSE.txt for more information""") parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + version) parser.add_argument('target', type=str, help='the base path of the WordPress installation to ' 'examine') parser.add_argument('-i', '--info', dest='info', action='store_true', help='dumps basic information about the WordPress ' 'installation') parser.add_argument('-e', '--endpoints', dest='endpoints', action='store_true', help='dumps full endpoint documentation') parser.add_argument('-p', '--posts', dest='posts', action='store_true', help='lists published posts') parser.add_argument('--export-posts', dest='post_export_folder', action='store', help='export posts to a specified destination folder') parser.add_argument('-u', '--users', dest='users', action='store_true', help='lists users') parser.add_argument('-t', '--tags', dest='tags', action='store_true', help='lists tags') parser.add_argument('-c', '--categories', dest='categories', action='store_true', help='lists categories') parser.add_argument('-m', '--media', dest='media', action='store_true', help='lists media objects') parser.add_argument('-g', '--pages', dest='pages', action='store_true', help='lists pages') parser.add_argument('--export-pages', dest='page_export_folder', action='store', help='export pages to a specified destination folder') parser.add_argument('-r', '--crawl-ns', dest='crawl_ns', action='store', help='crawl all GET routes of the specified namespace ' 'or all namespaces if all is specified') parser.add_argument('-a', '--all', dest='all', action='store_true', help='dumps all available information from the ' 'target API') parser.add_argument('-S', '--search', dest='search', action='store', help='search for a string on the WordPress instance. ' 'If one or several flag in agpmctu are set, search ' 'only on these') parser.add_argument('--proxy', dest='proxy_server', action='store', help='define a proxy server to use, e.g. for ' 'enterprise network or debugging') parser.add_argument('--auth', dest='credentials', action='store', help='define a username and a password separated by ' 'a colon to use them as basic authentication') parser.add_argument( '--cookies', dest='cookies', action='store', help='define specific cookies to send with the request ' 'in the format cookie1=foo; cookie2=bar') parser.add_argument('--no-color', dest='nocolor', action='store_true', help='remove color in the output (e.g. to pipe it)') args = parser.parse_args() motd = """ _ _______ ___ _____ | | | | ___ \|_ | / ___| | | | | |_/ / | | ___ ___ _ __ \ `--. ___ _ __ __ _ _ __ ___ _ __ | |/\| | __/ | |/ __|/ _ \| '_ \ `--. \/ __| '__/ _` | '_ \ / _ \ '__| \ /\ / | /\__/ /\__ \ (_) | | | /\__/ / (__| | | (_| | |_) | __/ | \/ \/\_| \____/ |___/\___/|_| |_\____/ \___|_| \__,_| .__/ \___|_| | | |_| WPJsonScraper v%s By Mickaël \"Kilawyn\" Walter Make sure you use this tool with the approval of the site owner. Even if these information are public or available with proper authentication, this could be considered as an intrusion. Target: %s """ % (version, args.target) print(motd) if args.nocolor: Console.wipe_color() Console.log_info("Testing connectivity with the server") target = args.target if re.match(r'^https?://.*$', target) is None: target = "http://" + target if re.match(r'^.+/$', target) is None: target += "/" proxy = None if args.proxy_server is not None: proxy = args.proxy_server cookies = None if args.cookies is not None: cookies = args.cookies authorization = None if args.credentials is not None: authorization_list = args.credentials.split(':') if len(authorization_list) == 1: authorization = (authorization_list[0], '') elif len(authorization_list) >= 2: authorization = (authorization_list[0], ':'.join(authorization_list[1:])) session = RequestSession(proxy=proxy, cookies=cookies, authorization=authorization) try: connectivity_check = session.get(target) Console.log_success("Connection OK") except Exception as e: exit(0) # Quite an ugly check to launch a search on all parameters edible # Should find something better (maybe in argparser doc?) if args.search is not None and not (args.all | args.posts | args.pages | args.users | args.categories | args.tags | args.media): Console.log_info("Searching on all available sources") args.posts = True args.pages = True args.users = True args.categories = True args.tags = True args.media = True scanner = WPApi(target, session=session, search_terms=args.search) if args.info or args.all: try: basic_info = scanner.get_basic_info() Console.log_info("General information on the target") InfoDisplayer.display_basic_info(basic_info) except NoWordpressApi: Console.log_error("No WordPress API available at the given URL " "(too old WordPress or not WordPress?)") exit() if args.posts or args.all: try: Console.log_info("Post list") posts_list = scanner.get_all_posts() InfoDisplayer.display_posts(posts_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.pages or args.all: try: Console.log_info("Page list") pages_list = scanner.get_all_pages() InfoDisplayer.display_pages(pages_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.users or args.all: try: Console.log_info("User list") users_list = scanner.get_all_users() InfoDisplayer.display_users(users_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.endpoints or args.all: try: Console.log_info("API endpoints") basic_info = scanner.get_basic_info() InfoDisplayer.display_endpoints(basic_info) except NoWordpressApi: Console.log_error("No WordPress API available at the given URL " "(too old WordPress or not WordPress?)") exit() if args.categories or args.all: try: Console.log_info("Category list") categories_list = scanner.get_all_categories() InfoDisplayer.display_categories(categories_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.tags or args.all: try: Console.log_info("Tags list") tags_list = scanner.get_all_tags() InfoDisplayer.display_tags(tags_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.media or args.all: try: Console.log_info("Media list") media_list = scanner.get_all_media() InfoDisplayer.display_media(media_list) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.crawl_ns is None and args.all: args.crawl_ns = "all" if args.crawl_ns is not None: try: if args.crawl_ns == "all": Console.log_info("Crawling all namespaces") else: Console.log_info("Crawling %s namespace" % args.crawl_ns) ns_data = scanner.crawl_namespaces(args.crawl_ns) InfoDisplayer.display_crawled_ns(ns_data) except NSNotFoundException: Console.log_error("The specified namespace was not found") except Exception as e: print(e) if args.post_export_folder is not None: try: posts_list = scanner.get_all_posts() tags_list = scanner.get_all_tags() categories_list = scanner.get_all_categories() users_list = scanner.get_all_users() print() post_number = Exporter.export_posts(posts_list, args.post_export_folder, tags_list, categories_list, users_list) if post_number > 0: Console.log_success("Exported %d posts to %s" % (post_number, args.post_export_folder)) except WordPressApiNotV2: Console.log_error("The API does not support WP V2") if args.page_export_folder is not None: try: pages_list = scanner.get_all_pages() users_list = scanner.get_all_users() print() page_number = Exporter.export_posts(pages_list, args.page_export_folder, None, None, users_list) if page_number > 0: Console.log_success("Exported %d pages to %s" % (page_number, args.page_export_folder)) except WordPressApiNotV2: Console.log_error("The API does not support WP V2")