VYPR
Critical severity9.8NVD Advisory· Published Apr 24, 2026· Updated Apr 27, 2026

CVE-2026-33076

CVE-2026-33076

Description

Roxy-WI is a web interface for managing Haproxy, Nginx, Apache and Keepalived servers. Prior to version 8.2.6.4, the haproxy_section_save interface presents a vulnerability that could lead to remote code execution due to path traversal and writing into scheduled tasks. Version 8.2.6.4 fixes the issue.

Affected products

1
  • cpe:2.3:a:roxy-wi:roxy-wi:*:*:*:*:*:*:*:*
    Range: <8.2.6.4

Patches

1
aecc79719590

v8.2.6.4: Update version migration and improve IP/DNS validation in routes

https://github.com/roxy-wi/roxy-wiAidahoMar 17, 2026via nvd-ref
3 files changed · +54 8
  • app/modules/config/common.py+2 0 modified
    @@ -1,4 +1,5 @@
     import app.modules.db.sql as sql
    +import app.modules.common.common as common
     import app.modules.roxy_wi_tools as roxy_wi_tools
     
     get_config_var = roxy_wi_tools.GetConfigVar()
    @@ -41,6 +42,7 @@ def generate_config_path(service: str, server_ip: str) -> str:
     	This method generates the configuration path for a given service and server IP address. It combines the service name, server IP address, current date, and file format to create the path
     	*. The file format is determined by calling the `get_file_format` method and the configuration directory is obtained using the `get_config_dir` method.
     	"""
    +	server_ip = common.is_ip_or_dns(server_ip)
     	file_format = get_file_format(service)
     	config_dir = get_config_dir(service)
     	return f"{config_dir}/{server_ip}-{get_date.return_date('config')}.{file_format}"
    
  • app/modules/db/migrations/v8_2_6_4_update_version.py+22 0 added
    @@ -0,0 +1,22 @@
    +from playhouse.migrate import *
    +from app.modules.db.db_model import connect, Version
    +
    +migrator = connect(get_migrator=1)
    +
    +
    +def up():
    +    """Apply the migration."""
    +    try:
    +        Version.update(version='8.2.6.4').execute()
    +    except Exception as e:
    +        print(f"Error updating version: {str(e)}")
    +        raise e
    +
    +
    +def down():
    +    """Roll back the migration."""
    +    try:
    +        Version.update(version='8.2.6.3').execute()
    +    except Exception as e:
    +        print(f"Error rolling back migration: {str(e)}")
    +        raise e
    
  • app/routes/config/routes.py+30 8 modified
    @@ -1,7 +1,10 @@
     import os
    +from typing import Union
     
     from flask import render_template, request, g, jsonify
     from flask_jwt_extended import jwt_required, get_jwt
    +from flask_pydantic import validate
    +from pydantic import IPvAnyAddress
     
     from app.routes.config import bp
     import app.modules.db.sql as sql
    @@ -18,7 +21,7 @@
     import app.modules.service.haproxy as service_haproxy
     import app.modules.server.server as server_mod
     from app.views.service.views import ServiceConfigView, ServiceConfigVersionsView
    -from app.modules.roxywi.class_models import DataStrResponse
    +from app.modules.roxywi.class_models import DataStrResponse, DomainName
     
     bp.add_url_rule('/<service>/<server_id>', view_func=ServiceConfigView.as_view('config_view_ip'), methods=['POST'])
     bp.add_url_rule('/<service>/<server_id>/versions', view_func=ServiceConfigVersionsView.as_view('config_version'), methods=['DELETE'])
    @@ -38,6 +41,7 @@ def show_config(service):
         config_file_name = request.json.get('config_file_name')
         configver = request.json.get('configver')
         server_ip = request.json.get('serv')
    +    server_ip = common.is_ip_or_dns(server_ip)
         claims = get_jwt()
         edit_section = request.json.get('edit_section')
     
    @@ -52,6 +56,7 @@ def show_config(service):
     @check_services
     def show_config_files(service):
         server_ip = request.form.get('serv')
    +    server_ip = common.is_ip_or_dns(server_ip)
         config_file_name = request.form.get('config_file_name')
     
         try:
    @@ -150,7 +155,9 @@ def config(service, serv, edit, config_file_name, new):
     @bp.route('/versions/<service>/<server_ip>', methods=['GET'])
     @check_services
     @get_user_params(disable=1)
    -def versions(service, server_ip):
    +@validate()
    +def versions(service, server_ip: Union[IPvAnyAddress, DomainName]):
    +    server_ip = str(server_ip)
         roxywi_auth.page_for_admin(level=3)
     
         kwargs = {
    @@ -174,7 +181,9 @@ def list_of_version(service):
     @bp.route('/versions/<service>/<server_ip>/<configver>', methods=['GET'])
     @check_services
     @get_user_params(disable=1)
    -def show_version(service, server_ip, configver):
    +@validate()
    +def show_version(service, server_ip: Union[IPvAnyAddress, DomainName], configver):
    +    server_ip = str(server_ip)
         roxywi_auth.page_for_admin(level=3)
     
         kwargs = {
    @@ -189,7 +198,9 @@ def show_version(service, server_ip, configver):
     @bp.route('/versions/<service>/<server_ip>/<configver>/save', methods=['POST'])
     @check_services
     @get_user_params()
    -def save_version(service, server_ip, configver):
    +@validate()
    +def save_version(service, server_ip: Union[IPvAnyAddress, DomainName], configver):
    +    server_ip = str(server_ip)
         roxywi_auth.page_for_admin(level=3)
         config_dir = config_common.get_config_dir('haproxy')
         configver = config_dir + configver
    @@ -215,7 +226,9 @@ def save_version(service, server_ip, configver):
     
     @bp.route('/section/haproxy/<server_ip>')
     @get_user_params()
    -def haproxy_section(server_ip):
    +@validate()
    +def haproxy_section(server_ip: Union[IPvAnyAddress, DomainName]):
    +    server_ip = str(server_ip)
         cfg = config_common.generate_config_path('haproxy', server_ip)
         error = config_mod.get_config(server_ip, cfg)
         kwargs = {
    @@ -232,7 +245,9 @@ def haproxy_section(server_ip):
     
     @bp.route('/section/haproxy/<server_ip>/<section>')
     @get_user_params()
    -def haproxy_section_show(server_ip, section):
    +@validate()
    +def haproxy_section_show(server_ip: Union[IPvAnyAddress, DomainName], section):
    +    server_ip = str(server_ip)
         cfg = config_common.generate_config_path('haproxy', server_ip)
         error = config_mod.get_config(server_ip, cfg)
         start_line, end_line, config_read = section_mod.get_section_from_config(cfg, section)
    @@ -264,7 +279,9 @@ def haproxy_section_show(server_ip, section):
     
     @bp.route('/section/haproxy/<server_ip>/save', methods=['POST'])
     @get_user_params()
    -def haproxy_section_save(server_ip):
    +@validate()
    +def haproxy_section_save(server_ip: Union[IPvAnyAddress, DomainName]):
    +    server_ip = str(server_ip)
         hap_configs_dir = config_common.get_config_dir('haproxy')
         cfg = config_common.generate_config_path('haproxy', server_ip)
         config_file = request.json.get('config')
    @@ -277,6 +294,9 @@ def haproxy_section_save(server_ip):
             config_file = ''
             save = 'reload'
     
    +    if '..' in config_file or '..' in oldcfg:
    +        return jsonify({'error': 'error: .. is not allowed'})
    +
         config_file = section_mod.rewrite_section(start_line, end_line, oldcfg, config_file)
     
         try:
    @@ -340,5 +360,7 @@ def show_compare(service, server_ip):
     
     @bp.route('/map/haproxy/<server_ip>/show')
     @get_user_params()
    -def show_map(server_ip):
    +@validate()
    +def show_map(server_ip: Union[IPvAnyAddress, DomainName]):
    +    server_ip = str(server_ip)
         return service_haproxy.show_map(server_ip, g.user_params['group_id'])
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.