CVE-2023-25802 (published 2023-03-13T20:15Z)

Roxy-WI is a Web interface for managing Haproxy, Nginx, Apache, and Keepalived servers. Versions prior to 6.3.6.0 don't correctly neutralize `dir/../filename` sequences, such as `/etc/nginx/../passwd`, allowing an actor to gain information about a server. Version 6.3.6.0 has a patch for this issue.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-668
cwe_name: Exposure of Resource to Wrong Sphere
description: The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
extended_description: 
            Resources such as files and directories may be inadvertently exposed through mechanisms such as insecure permissions, or when a program accidentally operates on the wrong object. For example, a program may intend that private files can only be provided to a specific user. This effectively defines a control sphere that is intended to prevent attackers from accessing these private files. If the file permissions are insecure, then parties other than the user will be able to access those files.
            A separate control sphere might effectively require that the user can only access the private files, but not any other files on the system. If the program does not ensure that the user is only requesting private files, then the user might be able to access other files on the system.
            In either case, the end result is that a resource has been exposed to the wrong party.
         
         

Fix information:
repo_url: https://github.com/hap-wi/roxy-wi
commit_details: repo_url: https://github.com/hap-wi/roxy-wi
author: Aidaho
author_date: 2023-02-22 10:36:20+03:00
msg: v6.3.6.0

Changelog: https://roxy-wi.org/changelog#6_3_6
repository: repo_url: https://github.com/hap-wi/roxy-wi
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: hap-wi
file_changes: filename: logs.py
change_type: ModificationType.MODIFY
diff: @@ -78,6 +78,8 @@ def show_roxy_log(
 
 	log_file = checkAjaxInput(log_file) if log_file is not None else log_file
 
+	if '..' in log_file: return 'error: nice try'
+
 	if service in ('nginx', 'haproxy', 'apache', 'keepalived'):
 		syslog_server_enable = sql.get_setting('syslog_server_enable')
 		if syslog_server_enable is None or syslog_server_enable == 0:

programming_language: Python
filename: ssh.py
change_type: ModificationType.MODIFY
diff: @@ -51,16 +51,16 @@ def ssh_connect(server_ip):
 def create_ssh_cred() -> None:
 	from jinja2 import Environment, FileSystemLoader
 
-	user_group = roxywi_common.get_user_group()
 	name = common.checkAjaxInput(form.getvalue('new_ssh'))
-	name = f'{name}_{user_group}'
 	enable = common.checkAjaxInput(form.getvalue('ssh_enable'))
 	group = common.checkAjaxInput(form.getvalue('new_group'))
+	group_name = sql.get_group_name_by_id(group)
 	username = common.checkAjaxInput(form.getvalue('ssh_user'))
 	password = common.checkAjaxInput(form.getvalue('ssh_pass'))
 	page = common.checkAjaxInput(form.getvalue('page'))
 	page = page.split("#")[0]
 	lang = roxywi_common.get_user_lang()
+	name = f'{name}_{group_name}'
 
 	if username is None or name is None:
 		print(error_mess)
@@ -91,6 +91,10 @@ def create_ssh_cread_api(name: str, enable: str, group: str, username: str, pass
 
 
 def upload_ssh_key(name: str, user_group: str, key: str) -> bool:
+	if '..' in name:
+		print('error: nice try')
+		return False
+
 	try:
 		key = paramiko.pkey.load_private_key(key)
 	except Exception as e:

programming_language: Python
filename: logs.yml
change_type: ModificationType.MODIFY
diff: @@ -11,4 +11,5 @@
     src: /var/www/haproxy-wi/app/scripts/ansible/roles/haproxy/templates/logrotate.conf.j2
     dest: /etc/logrotate.d/haproxy.conf
     force: no
+  when: (ansible_facts['os_family'] == "RedHat" or ansible_facts['os_family'] == 'CentOS')
   ignore_errors: yes
\ No newline at end of file

programming_language: Markdown
filename: new_ssh.html
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@
 			<td>
 				<select id="sshgroup-{{ssh.id}}" name="sshgroup-{{ssh.id}}">
 					{% for group in groups %}
-						{% if ssh.5 == group.group_id %}
+						{% if ssh.groups == group.group_id %}
 							<option value="{{ group.group_id }}" selected>{{ group.name }}</option>
 						{% else %}
 							<option value="{{ group.group_id }}">{{ group.name }}</option>

programming_language: HTML
filename: base.html
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,7 @@
 		<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
 		<meta http-equiv="Pragma" content="no-cache" />
 		<meta http-equiv="Expires" content="0" />
-		<meta id="translate" style="display: none;" data-delete="{{lang.words.delete|title()}}" data-cancel="{{lang.words.cancel|title()}}"  data-add="{{lang.words.add|title()}}" \
+		<meta id="translate" style="display: none;" data-delete="{{lang.words.delete|title()}}" data-cancel="{{lang.words.cancel|title()}}" data-add="{{lang.words.add|title()}}"
 			  data-superAdmin_pass="{{lang.phrases.superAdmin_pass}}" data-superAdmin_services="{{lang.phrases.superAdmin_services}}" data-change="{{lang.words.change|title()}}"
 			  data-change2="{{lang.words.change2|title()}}" data-password="{{lang.words.password}}" data-groups="{{lang.words.groups}}" data-save="{{lang.words.save|title()}}"
 			  data-manage="{{lang.words.manage|title()}}" data-services3="{{lang.words.services3}}" data-logout="{{lang.words.logout|title()}}"
@@ -14,7 +14,9 @@
 			  data-just_save="{{lang.words.just|title()}} {{lang.words.save}}" data-upload_and_restart="{{lang.phrases.upload_and_restart|title()}}"
 			  data-are_you_sure="{{lang.phrases.are_you_sure}}" data-upload_and_reload="{{lang.phrases.upload_and_reload|title()}}" data-select_server="{{lang.errors.select_server}}"
 			  data-empty_name="{{lang.errors.empty_name}}" data-edit="{{lang.words.edit|title()}}" data-close="{{lang.words.close|title()}}" data-server_info="{{lang.phrases.server_info}}"
-			  data-generated_config="{{lang.words.generated|title()}} {{lang.words.config}}" />
+			  data-generated_config="{{lang.words.generated|title()}} {{lang.words.config}}" data-restart="{{lang.words.restart|title()}}" data-start="{{lang.words.start|title()}}"
+			  data-stop="{{lang.words.stop|title()}}" data-reload="{{lang.words.reload|title()}}" data-user_groups="{{lang.phrases.user_groups}}" data-settings="{{lang.words.settings|title()}}"
+			  data-for="{{lang.words.for}}" />
 		{% if title == 'Login page' %}
 		<meta name="viewport" content="width=device-width, user-scalable=1">
 		{% endif %}

programming_language: HTML
filename: hapservers.html
change_type: ModificationType.MODIFY
diff: @@ -419,7 +419,7 @@
 	{% endfor %}
 {% endif %}
 <div id="dialog-confirm" style="display: none;">
-  <p><span class="ui-icon ui-icon-alert" style="float:left; margin:3px 12px 20px 0;"></span>Are you sure?</p>
+  <p><span class="ui-icon ui-icon-alert" style="float:left; margin:3px 12px 20px 0;"></span>{{lang.phrases.are_you_sure}}</p>
 </div>
 <div id="dialog-settings-service" style="display: none;"></div>
 <script>

programming_language: HTML
filename: admin_add_user.html
change_type: ModificationType.MODIFY
diff: @@ -1,30 +1,30 @@
 {% from 'include/input_macros.html' import input, checkbox %}
 <tr>
 	<td class="padding20">
-		New user
+		{{lang.words.name|title()}}
 		<span class="need-field">*</span>
 	</td>
 	<td>
 		{{ input('new-username') }}
 			{% if ldap_enable == 1 %}
-				<a href="#" title="Search user in AD" id="search_ldap_user">Search user in AD</a>
+				<a href="#" title="{{lang.phrases.search_id_ad}}" id="search_ldap_user">{{lang.phrases.search_in_ad}}</a>
 			{% endif %}
 	</td>
 </tr>
 <tr>
 	<td class="padding20">
-		Password
+		{{lang.words.password|title()}}
 		<span class="need-field">*</span>
 	</td>
 	<td>{{ input('new-password', type='password') }}</td>
 </tr>
 <tr>
-	<td class="padding20">Active</td>
+	<td class="padding20">{{lang.words.active|title()}}</td>
 	<td>{{ checkbox('activeuser', checked='checked') }}</td>
 </tr>
 <tr>
 	<td class="padding20">
-		Email
+		{{lang.words.email|title()}}
 		<span class="need-field">*</span>
 	</td>
 	<td>{{ input('new-email', type='email') }}</td>

programming_language: HTML
filename: admin_ssh.html
change_type: ModificationType.MODIFY
diff: @@ -31,7 +31,7 @@
         <td>
             <select id="sshgroup-{{ssh.id}}" name="sshgroup-{{ssh.id}}">
                 {% for group in groups %}
-                {% if ssh.5 == group.group_id %}
+                {% if ssh.groups == group.group_id %}
                 <option value="{{ group.group_id }}" selected>{{ group.name }}</option>
                 {% else %}
                 <option value="{{ group.group_id }}">{{ group.name }}</option>

programming_language: HTML
filename: en.html
change_type: ModificationType.MODIFY
diff: @@ -292,6 +292,7 @@
     "port_check": "port check",
     "possible_service_name": "Possible service name",
     "server_info": "Server info",
+    "user_groups": "User groups",
     }
 %}
 {% set roles = {
@@ -541,6 +542,7 @@
     "desc": "description",
     "login": "login",
     "role": "role",
+    "roles": "roles",
     "subs": "subscription",
     "show_all": "show all",
     "plan": "plan",

programming_language: JavaScript
filename: ru.html
change_type: ModificationType.MODIFY
diff: @@ -294,6 +294,7 @@
     "port_check": "проверка порта",
     "possible_service_name": "Возможное название службы",
     "server_info": "Информация о сервере",
+    "user_groups": "Пользовательские группы",
     }
 %}
 {% set roles = {
@@ -545,6 +546,7 @@
     "desc": "описание",
     "login": "логин",
     "role": "роль",
+    "roles": "роли",
     "subs": "подписка",
     "show_all": "показать все",
     "plan": "план",

programming_language: JavaScript
filename: overview.js
change_type: ModificationType.MODIFY
diff: @@ -409,19 +409,22 @@ $( function() {
 	});
 });
 function confirmAjaxAction(action, service, id) {
+	var cancel_word = $('#translate').attr('data-cancel');
+	var action_word = $('#translate').attr('data-'+action);
 	$( "#dialog-confirm" ).dialog({
 		resizable: false,
 		height: "auto",
 		width: 400,
 		modal: true,
-		title: "Are you sure you want to "+ action + " " + id + "?",
-		buttons: {
-			"Sure": function() {
-				$( this ).dialog( "close" );
-				if(service == "haproxy") {
+		title: action_word + " " + id + "?",
+		buttons: [{
+			text: action_word,
+			click: function () {
+				$(this).dialog("close");
+				if (service == "haproxy") {
 					ajaxActionServers(action, id);
-					if(action == "restart" || action == "reload") {
-						if(localStorage.getItem('restart')) {
+					if (action == "restart" || action == "reload") {
+						if (localStorage.getItem('restart')) {
 							localStorage.removeItem('restart');
 							$("#apply").css('display', 'none');
 						}
@@ -437,11 +440,13 @@ function confirmAjaxAction(action, service, id) {
 				} else if (service == "waf_nginx") {
 					ajaxActionWafNginxServers(action, id)
 				}
-			},
-			Cancel: function() {
+			}
+		}, {
+			text: cancel_word,
+			click: function() {
 				$( this ).dialog( "close" );
 			}
-		}
+		}]
 	});
 }
 function updateHapWIServer(id, service_name) {
@@ -628,6 +633,10 @@ function showSubOverview() {
 	} );
 }
 function serverSettings(id, name) {
+	var cancel_word = $('#translate').attr('data-cancel');
+	var save_word = $('#translate').attr('data-save');
+	var settings_word = $('#translate').attr('data-settings');
+	var for_word = $('#translate').attr('data-for');
 	var service = $('#service').val();
 	$.ajax({
 		url: "options.py",
@@ -649,16 +658,19 @@ function serverSettings(id, name) {
 					height: "auto",
 					width: 400,
 					modal: true,
-					title: "Settings for " + name,
-					buttons: {
-						"Save": function () {
+					title: settings_word + " "+for_word+" " + name,
+					buttons: [{
+						text: save_word,
+						click: function () {
 							$(this).dialog("close");
 							serverSettingsSave(id, name, service, $(this));
-						},
-						Cancel: function () {
+						}
+					}, {
+						text: cancel_word,
+						click: function () {
 							$(this).dialog("close");
 						}
-					}
+					}]
 				});
 			}
 		}

programming_language: JavaScript