CVE-2020-29159 (Ruby, published 2020-12-28T08:15Z)
Description: value: An issue was discovered in Zammad before 3.5.1. The default signup Role (for newly created Users) can be a privileged Role, if configured by an admin. This behvaior was unintended.
Severity: MEDIUM (CVSS v3 base score: 4.9)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/zammad/zammad
Files changed:
app/models/role.rb
db/migrate/20120101000001_create_base.rb
db/migrate/20201005113317_role_signup_column.rb
db/seeds/permissions.rb
spec/db/migrate/role_signup_column_spec.rb
Commit message: Maintenance: Provide allow_signup column to define the signup permissions for roles and disable new permissions by default as signup permission.


Vulnerable code:
    all_permissions = Permission.all.pluck(:id)
    admin_permissions = Permission.where('name LIKE ? OR name = ?', 'admin%', 'ticket.agent').pluck(:id) # admin.*/ticket.agent permissions
    normal_permissions = (all_permissions - admin_permissions) | (admin_permissions - all_permissions) # all other permissions besides admin.*/ticket.agent
    return true if default_at_signup != true # means if default_at_signup = false, no need further checks
    return true if self.permission_ids.all? { |i| normal_permissions.include? i } # allow user to choose only normal permissions
    raise Exceptions::UnprocessableEntity, 'Cannot set default at signup when role has admin or ticket.agent permissions.'
      t.string :name,                   limit: 255, null: false
      t.string :note,                   limit: 500, null: true
      t.string :preferences,            limit: 10_000, null: true
      t.boolean :active,                               null: false, default: true
      t.timestamps limit: 3, null: false
  name:        'user_preferences',
  note:        'User Preferences',
  preferences: {},
  name:        'user_preferences.password',
  note:        'Change %s',
  preferences: {
  name:        'user_preferences.notifications',
  note:        'Manage %s',
  preferences: {
  name:        'user_preferences.access_token',
  note:        'Manage %s',
  preferences: {
  name:        'user_preferences.language',
  note:        'Change %s',
  preferences: {
  name:        'user_preferences.linked_accounts',
  note:        'Manage %s',
  preferences: {
  name:        'user_preferences.device',
  note:        'Manage %s',
  preferences: {
  name:        'user_preferences.avatar',
  note:        'Manage %s',
  preferences: {
  name:        'user_preferences.calendar',
  note:        'Access to %s',
  preferences: {
  name:        'user_preferences.out_of_office',
  note:        'Change %s',
  preferences: {
  name:        'ticket.customer',
  note:        'Access to Customer Tickets based on current_user and organization',
  preferences: {},

Fixed code:
    return true if !default_at_signup
    forbidden_permissions = permissions.reject(&:allow_signup)
    return true if forbidden_permissions.blank?

    raise Exceptions::UnprocessableEntity, "Cannot set default at signup when role has #{forbidden_permissions.join(', ')} permissions."
      t.string :name,          limit: 255, null: false
      t.string :note,          limit: 500, null: true
      t.string :preferences,   limit: 10_000, null: true
      t.boolean :active,       null: false, default: true
      t.boolean :allow_signup, null: false, default: false
      t.timestamps limit: 3,   null: false
class RoleSignupColumn < ActiveRecord::Migration[5.2]
  def change

    # return if it's a new setup
    return if !Setting.exists?(name: 'system_init_done')

    add_column :permissions, :allow_signup, :boolean, null: false, default: false

    signup_permissions = [
      'user_preferences',
      'user_preferences.password',
      'user_preferences.notifications',
      'user_preferences.access_token',
      'user_preferences.language',
      'user_preferences.linked_accounts',
      'user_preferences.device',
      'user_preferences.avatar',
      'user_preferences.calendar',
      'user_preferences.out_of_office',
      'ticket.customer',
    ]

    Permission.where(name: signup_permissions).update(allow_signup: true)

    Role.where(default_at_signup: true).find_each do |role|
      role.permissions.where.not(name: signup_permissions).find_each do |permission|
        role.permission_revoke(permission.name)
      end
    end
  end
end
  name:         'user_preferences',
  note:         'User Preferences',
  preferences:  {},
  allow_signup: true,
  name:         'user_preferences.password',
  note:         'Change %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.notifications',
  note:         'Manage %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.access_token',
  note:         'Manage %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.language',
  note:         'Change %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.linked_accounts',
  note:         'Manage %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.device',
  note:         'Manage %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.avatar',
  note:         'Manage %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.calendar',
  note:         'Access to %s',
  preferences:  {
  allow_signup: true,
  name:         'user_preferences.out_of_office',
  note:         'Change %s',
  preferences:  {
  allow_signup: true,
  name:         'ticket.customer',
  note:         'Access to Customer Tickets based on current_user and organization',
  preferences:  {},
  allow_signup: true,
require 'rails_helper'

RSpec.describe RoleSignupColumn, type: :db_migration, db_strategy: :reset do
  context 'when a role contains signup permissions' do
    let!(:role) do
      role = create(:role)
      role.permission_grant('user_preferences.password')
      role.permission_grant('ticket.agent')
      role.update_column(:default_at_signup, true)
      role
    end

    before do
      without_column(:permissions, column: :allow_signup)
      migrate
    end

    it 'has password permission' do
      expect(role.reload.permissions.map(&:name)).to include('user_preferences.password')
    end

    it 'has no agent permission' do
      expect(role.reload.permissions.map(&:name)).not_to include('ticket.agent')
    end

    it 'has permission with allow_signup set correctly' do
      expect(Permission.find_by(name: 'user_preferences.password').allow_signup).to be true
    end
  end
end