CVE-2021-25988 (Ruby, published 2021-12-29T09:15Z)
Description: value: In “ifme”, versions 1.0.0 to v7.31.4 are vulnerable against stored XSS vulnerability (notifications section) which can be directly triggered by sending an ally request to the admin.
Severity: LOW (CVSS v3 base score: 5.4)
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/ifmeorg/ifme
Files changed:
app/controllers/notifications_controller.rb
app/helpers/notifications_helper.rb
spec/factories/notification.rb
spec/helpers/notifications_helper_spec.rb
spec/models/notification_spec.rb
spec/requests/notifications_spec.rb
Commit message: [#2052] Fix stored XSS in Notifications


Vulnerable code:
      name: data[:user],
      name: data[:user]
    link_html = "<a href=\"#{link}\">#{data[:user]}</a>"
      name: data[:user],
      name: data[:user],
data = {
  cutoff: false,
  user: 'Julia Nguyen',
  comment: 'Hello',
  typename: 'typename',
  type: 'type_comment_moment',
  typeid: 1,
  commentable_id: 1
}
    user_id { 1 }

    trait :with_user do
      association :user, factory: :user
    end
    it 'returns correct link' do
    it 'returns correct link' do
    it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
      it 'returns correct link' do
    notification = build(:notification)
    let!(:other_user_notification) { create(:notification, user: other_user) }

      let!(:notification) { create(:notification, user: user) }
      let!(:notification_two) { create(:notification, user: user) }
        '<a id="MyString" href="/moments/1">Julia Nguyen commented "Hello" on typename</a>'
        { fetch_notifications: [notification_link, notification_link] }.to_json

Fixed code:
    data[:email] = User.find(data[:user_id]).email # In case data[:name] is invalid
      name: name_or_email(data),
      name: name_or_email(data)
    link_html = "<a href=\"#{link}\">#{name_or_email(data)}</a>"
      name: name_or_email(data),
      name: name_or_email(data),

  def name_or_email(data)
    sanitize(data[:user]).presence || data[:email]
  end
    data = {
      cutoff: false,
      user: 'Almond Butters',
      comment: 'Hello',
      typename: 'typename',
      type: 'type_comment_moment',
      typeid: 1,
      commentable_id: 1,
    }
    it 'returns the correct link' do

    it 'sanitizes and returns the correct link' do
      data = {
        cutoff: false,
        user: '<IFRAME SRC="javascript:alert(document.domain);"></IFRAME>',
        email: 'julia@example.com',
        comment: 'Hello',
        typename: 'typename',
        type: 'type_comment_moment',
        typeid: 1,
        commentable_id: 1
      }
      expect(comment_link(uniqueid, data)).to eq('<a id="uniqueid" href="/moments/1">julia@example.com commented "Hello" on typename</a>')
    end
    it 'returns the correct link' do

    it 'sanitizes and returns the correct link' do
      data = {
        user: '<IFRAME SRC="javascript:alert(document.domain);"></IFRAME>',
        email: 'julia@example.com',
        uid: 'uid'
      }
      expect(accepted_ally_link(uniqueid, data)).to eq('<a id="uniqueid" href="/profile?uid=uid">julia@example.com accepted your ally request!</a>')
    end
    it 'returns the correct link' do

    it 'sanitizes and returns the correct link' do
      data = {
        user: '<IFRAME SRC="javascript:alert(document.domain);"></IFRAME>',
        email: 'julia@example.com',
        uid: 'uid',
        user_id: 1
      }
      expect(new_ally_request_link(uniqueid, data)).to eq('<div id="uniqueid"><div>&lt;a href=&quot;/profile?uid=uid&quot;&gt;julia@example.com&lt;/a&gt; sent an ally request!</div><div><a rel="nofollow" data-method="post" href="/allies/add?ally_id=1">Accept</a> | <a data-confirm="Are you sure?" rel="nofollow" data-method="post" href="/allies/remove?ally_id=1">Reject</a></div></div>')
    end
    let(:dirty_data) do
      {
        type: type,
        user: '<IFRAME SRC="javascript:alert(document.domain);"></IFRAME>',
        email: 'julia@example.com',
        group: 'Group name',
        group_id: 1
      }
    end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(group_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/groups/1">julia@example.com created a group "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(group_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/groups/1">julia@example.com joined your group "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(group_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/groups/1">julia@example.com became a leader of "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(group_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/groups/1">julia@example.com is no longer a leader of "Group name"</a>')
      end
    let(:dirty_data) do
      {
        type: type,
        user: '<IFRAME SRC="javascript:alert(document.domain);"></IFRAME>',
        email: 'julia@example.com',
        group: 'Group name',
        typename: 'Meeting name',
        group_id: 1,
        typeid: 1
      }
    end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(meeting_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/meetings/1">julia@example.com created a new meeting "Meeting name" for "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(meeting_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/groups/1">julia@example.com has cancelled "Meeting name" for "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(meeting_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/meetings/1">julia@example.com has updated "Meeting name" for "Group name"</a>')
      end
      it 'returns the correct link' do

      it 'sanitizes and returns the correct link' do
        expect(meeting_link(uniqueid, dirty_data)).to eq('<a id="uniqueid" href="/meetings/1">julia@example.com has joined "Meeting name" for "Group name"</a>')
      end
  let(:user) { create(:user1) }

    notification = build(:notification, user: user)
      let!(:notification) {
        create(
          :notification,
          user: user,
          data: {
            cutoff: false,
            user: 'Almond Butters',
            comment: 'Hello',
            typename: 'typename',
            type: 'type_comment_moment',
            typeid: 1,
            commentable_id: 1,
            user_id: other_user.id
          }.to_json
        )
      }
        '<a id="MyString" href="/moments/1">Almond Butters commented "Hello" on typename</a>'
        { fetch_notifications: [notification_link] }.to_json