CVE-2019-17579 (Other, published 2019-10-14T15:15Z)
Description: value: SonarSource SonarQube before 7.8 has XSS in project links on account/projects.
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/SonarSource/sonarqube
Files changed:
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaLink.css
server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx
server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx
server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/styles.css
server/sonar-web/src/main/js/apps/projectLinks/utils.ts
Commit message: SONAR-11867, SSF-74 Fix XSS in project links on account/projects


Vulnerable code:
import { sortBy } from 'lodash';
import ProjectLinkIcon from '../../../components/icons-components/ProjectLinkIcon';
  const links = sortBy(project.links, 'type');
      {links.length > 0 && (
            {links.map(link => (
              <li key={link.type}>
                <a
                  className="link-with-icon"
                  href={link.href}
                  rel="nofollow"
                  target="_blank"
                  title={link.name}>
                  <ProjectLinkIcon type={link.type} />
                </a>
              </li>
import { Link } from 'react-router';
import Level from '../../../../components/ui/Level';

const BASE = { key: 'key', links: [], name: 'name' };
  const project = { ...BASE };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-key').text()).toBe('key');
    output
      .find(Link)
  const project = { ...BASE, description: 'bla' };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-description').text()).toBe('bla');
  const project = { ...BASE };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-description').length).toBe(0);
  expect(output.find('.account-project-quality-gate').length).toBe(0);
  expect(output.find('.account-project-links').length).toBe(0);
  const project = { ...BASE, lastAnalysisDate: '2016-05-17' };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-analysis DateFromNow')).toHaveLength(1);
  const project = { ...BASE };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-analysis').text()).toContain(
  const project = { ...BASE, qualityGate: 'ERROR' };
  const output = shallow(<ProjectCard project={project} />);
    output
      .find(Level)
  const project = {
    ...BASE,
    links: [{ name: 'n', type: 't', href: 'h' }]
  };
  const output = shallow(<ProjectCard project={project} />);
  expect(output.find('.account-project-links').find('li').length).toBe(1);
    const { link } = this.props;
          target="_blank">
          {getLinkName(link)}
.copy-paste-link .overview-key {
  width: 90%;
}

.copy-paste-link .close {
  color: #000;
  border-bottom: 0;
  height: 100%;
  display: inline-block;
  margin-left: 5px;
  box-sizing: border-box;
}

.copy-paste-link .close svg {
  vertical-align: sub;
}

    ...sortBy(unknown, link => link.name!.toLowerCase())

Fixed code:
import MetaLink from '../../overview/meta/MetaLink';
import { orderLinks } from '../../projectLinks/utils';
  const { links } = project;

  const orderedLinks: T.ProjectLink[] = orderLinks(
    links.map((link, i) => {
      const { href, name, type } = link;
      return {
        id: `link-${i}`,
        name,
        type,
        url: href
      };
    })
  );

      {orderedLinks.length > 0 && (
            {orderedLinks.map(link => (
              <MetaLink iconOnly={true} key={link.id} link={link} />
  const wrapper = shallowRender();
  expect(wrapper.find('.account-project-key').text()).toBe('key');
    wrapper
      .find('Link')
  const wrapper = shallowRender({ description: 'bla' });
  expect(wrapper.find('.account-project-description').text()).toBe('bla');
  const wrapper = shallowRender();
  expect(wrapper.find('.account-project-description').length).toBe(0);
  expect(wrapper.find('.account-project-quality-gate').length).toBe(0);
  expect(wrapper.find('.account-project-links').length).toBe(0);
  const wrapper = shallowRender({ lastAnalysisDate: '2016-05-17' });
  expect(wrapper.find('.account-project-analysis DateFromNow')).toHaveLength(1);
  const wrapper = shallowRender();
  expect(wrapper.find('.account-project-analysis').text()).toContain(
  const wrapper = shallowRender({ qualityGate: 'ERROR' });
    wrapper
      .find('Level')
  const wrapper = shallowRender({
    links: [{ name: 'name', type: 'type', href: 'href' }]
  });
  expect(wrapper.find('MetaLink').length).toBe(1);

function shallowRender(project: Partial<T.MyProject> = {}) {
  return shallow(<ProjectCard project={{ key: 'key', links: [], name: 'name', ...project }} />);
}
/*
 * SonarQube
 * Copyright (C) 2009-2019 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
.copy-paste-link .overview-key {
  width: 80%;
}

.copy-paste-link .close {
  color: #000;
  border-bottom: 0;
  height: 100%;
  display: inline-block;
  margin-left: 5px;
  box-sizing: border-box;
}

.copy-paste-link .close svg {
  vertical-align: sub;
}
import './MetaLink.css';
  iconOnly?: boolean;
    const { iconOnly, link } = this.props;
    const linkTitle = getLinkName(link);
          target="_blank"
          title={linkTitle}>
          {!iconOnly && linkTitle}
  expect(shallow(<MetaLink iconOnly={true} link={link} />)).toMatchSnapshot();
    title="Foo"
    title="Foo"
    title="Foo"
    title="Foo"
exports[`should match snapshot 2`] = `
<li>
  <a
    className="link-with-icon"
    href="http://example.com"
    rel="nofollow noreferrer noopener"
    target="_blank"
    title="Foo"
  >
    <ProjectLinkIcon
      className="little-spacer-right"
      type="foo"
    />
  </a>
</li>
`;

    title="Dangerous"
    ...sortBy(unknown, link => link.name && link.name.toLowerCase())