CVE-2022-0527 (Ruby, published 2022-02-09T05:15Z)
Description: value: Cross-site Scripting (XSS) - Stored in GitHub repository chatwoot/chatwoot prior to 2.2.0.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/chatwoot/chatwoot
Files changed:
app/controllers/api/v1/accounts/campaigns_controller.rb
app/models/campaign.rb
spec/builders/campaigns/campaign_conversation_builder_spec.rb
spec/controllers/api/v1/accounts/campaigns_controller_spec.rb
spec/controllers/api/v1/accounts/inboxes_controller_spec.rb
spec/controllers/api/v1/widget/campaigns_controller_spec.rb
spec/listeners/campaign_listener_spec.rb
spec/models/campaign_spec.rb
Commit message: fix: Ongoing campaign URL validation (#3890)



Vulnerable code:
    @campaign.update(campaign_params)
  let(:campaign) { create(:campaign, inbox: inbox, account: account) }
      let!(:campaign) { create(:campaign, account: account) }
    let(:campaign) { create(:campaign, account: account) }
    let!(:campaign) { create(:campaign, account: account) }
    let!(:campaign) { create(:campaign, account: account) }
      let!(:campaign) { create(:campaign, account: account, inbox: inbox) }
        create(:campaign, account: account)
  let!(:campaign_1) { create(:campaign, inbox: web_widget.inbox, enabled: true, account: account) }
  let!(:campaign_2) { create(:campaign, inbox: web_widget.inbox, enabled: false, account: account) }
  let(:campaign) { create(:campaign, inbox: inbox, account: account) }
    let(:campaign) { build(:campaign, display_id: nil) }
    let!(:campaign) { create(:campaign, campaign_status: :completed) }

Fixed code:
    @campaign.update!(campaign_params)
require 'uri'
  validate :validate_url
  def validate_url
    return unless trigger_rules['url']

    errors.add(:url, 'invalid') if inbox.inbox_type == 'Website' && !url_valid?(trigger_rules['url'])
  end

  def url_valid?(url)
    url = begin
      URI.parse(url)
    rescue StandardError
      false
    end
    url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
  end

  let(:campaign) { create(:campaign, inbox: inbox, account: account, trigger_rules: { url: 'https://test.com' }) }
      let!(:campaign) { create(:campaign, account: account, trigger_rules: { url: 'https://test.com' }) }
    let(:campaign) { create(:campaign, account: account, trigger_rules: { url: 'https://test.com' }) }
      it 'creates a new ongoing campaign' do
        post "/api/v1/accounts/#{account.id}/campaigns",
             params: { inbox_id: inbox.id, title: 'test', message: 'test message', trigger_rules: { url: 'https://test.com' } },
             headers: administrator.create_new_auth_token,
             as: :json

        expect(response).to have_http_status(:success)
        expect(JSON.parse(response.body, symbolize_names: true)[:title]).to eq('test')
      end

      it 'throws error when invalid url provided for ongoing campaign' do
        post "/api/v1/accounts/#{account.id}/campaigns",
             params: { inbox_id: inbox.id, title: 'test', message: 'test message', trigger_rules: { url: 'javascript' } },
             headers: administrator.create_new_auth_token,
             as: :json

        expect(response).to have_http_status(:unprocessable_entity)
      end

    let!(:campaign) { create(:campaign, account: account, trigger_rules: { url: 'https://test.com' }) }
    let!(:campaign) { create(:campaign, account: account, trigger_rules: { url: 'https://test.com' }) }
      let!(:campaign) { create(:campaign, account: account, inbox: inbox, trigger_rules: { url: 'https://test.com' }) }
        create(:campaign, account: account, trigger_rules: { url: 'https://test.com' })
  let!(:campaign_1) { create(:campaign, inbox: web_widget.inbox, enabled: true, account: account, trigger_rules: { url: 'https://test.com' }) }
  let!(:campaign_2) { create(:campaign, inbox: web_widget.inbox, enabled: false, account: account, trigger_rules: { url: 'https://test.com' }) }
  let(:campaign) { create(:campaign, inbox: inbox, account: account, trigger_rules: { url: 'https://test.com' }) }
    let(:account) { create(:account) }
    let(:website_channel) { create(:channel_widget, account: account) }
    let(:website_inbox) { create(:inbox, channel: website_channel, account: account) }
    let(:campaign) { build(:campaign, inbox: website_inbox, display_id: nil, trigger_rules: { url: 'https://test.com' }) }
    let(:account) { create(:account) }
    let(:web_widget) { create(:channel_widget, account: account) }
    let!(:campaign) { create(:campaign, inbox: web_widget.inbox, campaign_status: :completed, trigger_rules: { url: 'https://test.com' }) }