typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml

Verifed by r2c
Community Favorite
profile photo of semgrepsemgrep
Author
64,103
Download Count*

Detection of dangerouslySetInnerHTML from non-constant definition. This can inadvertently expose users to cross-site scripting (XSS) attacks if this comes from user-provided input. If you have to use dangerouslySetInnerHTML, consider using a sanitization library such as DOMPurify to sanitize your HTML.

Run Locally

Run in CI

Defintion

rules:
  - id: react-dangerouslysetinnerhtml
    message: Detection of dangerouslySetInnerHTML from non-constant definition. This
      can inadvertently expose users to cross-site scripting (XSS) attacks if
      this comes from user-provided input. If you have to use
      dangerouslySetInnerHTML, consider using a sanitization library such as
      DOMPurify to sanitize your HTML.
    metadata:
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      references:
        - https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html
      category: security
      confidence: MEDIUM
      technology:
        - react
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: MEDIUM
      impact: MEDIUM
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    languages:
      - typescript
      - javascript
    severity: WARNING
    mode: taint
    pattern-sources:
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  function ...({..., $X, ...}) { ... }
              - pattern-inside: |
                  function ...(..., $X, ...) { ... }
          - focus-metavariable: $X
          - pattern-not-inside: |
              $F. ... .$SANITIZEUNC(...)
    pattern-sinks:
      - patterns:
          - focus-metavariable: $X
          - pattern-either:
              - pattern: |
                  {...,dangerouslySetInnerHTML: {__html: $X},...}
              - pattern: |
                  <$Y ... dangerouslySetInnerHTML={{__html: $X}} />
          - pattern-not: |
              <$Y ... dangerouslySetInnerHTML={{__html: "..."}} />
          - pattern-not: |
              {...,dangerouslySetInnerHTML:{__html: "..."},...}
          - metavariable-pattern:
              patterns:
                - pattern-not: |
                    {...}
              metavariable: $X
          - pattern-not: |
              <... {__html: "..."} ...>
          - pattern-not: |
              <... {__html: `...`} ...>
    pattern-sanitizers:
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  import $S from "underscore.string"
                  ...
              - pattern-inside: |
                  import * as $S from "underscore.string"
                  ...
              - pattern-inside: |
                  import $S from "underscore.string"
                  ...
              - pattern-inside: |
                  $S = require("underscore.string")
                  ...
          - pattern-either:
              - pattern: $S.escapeHTML(...)
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  import $S from "dompurify"
                  ...
              - pattern-inside: |
                  import { ..., $S,... } from "dompurify"
                  ...
              - pattern-inside: |
                  import * as $S from "dompurify"
                  ...
              - pattern-inside: |
                  $S = require("dompurify")
                  ...
              - pattern-inside: |
                  import $S from "isomorphic-dompurify"
                  ...
              - pattern-inside: |
                  import * as $S from "isomorphic-dompurify"
                  ...
              - pattern-inside: |
                  $S = require("isomorphic-dompurify")
                  ...
          - pattern-either:
              - patterns:
                  - pattern-inside: |
                      $VALUE = $S(...)
                      ...
                  - pattern: $VALUE.sanitize(...)
              - patterns:
                  - pattern-inside: |
                      $VALUE = $S.sanitize
                      ...
                  - pattern: $S(...)
              - pattern: $S.sanitize(...)
              - pattern: $S(...)
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  import $S from 'xss';
                  ...
              - pattern-inside: |
                  import * as $S from 'xss';
                  ...
              - pattern-inside: |
                  $S = require("xss")
                  ...
          - pattern: $S(...)
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  import $S from 'sanitize-html';
                  ...
              - pattern-inside: |
                  import * as $S from "sanitize-html";
                  ...
              - pattern-inside: |
                  $S = require("sanitize-html")
                  ...
          - pattern: $S(...)
      - patterns:
          - pattern-either:
              - pattern-inside: |
                  $S = new Remarkable()
                  ...
          - pattern: $S.render(...)

Examples

react-dangerouslysetinnerhtml.jsx

import DOMPurify from "dompurify"
import sanitize from "xss"

function TestComponent1() {
    // ok:react-dangerouslysetinnerhtml
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

function TestComponent2(foo) {
    // ruleid:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: foo.bar},a:b};
    return React.createElement('div', params);
}

// ok:react-dangerouslysetinnerhtml
{collaborationSectionData.paragraphs.map((item, i) => (
  <li key={`collaboration-p-${i}`} dangerouslySetInnerHTML={{
    __html: item,}}>
  </li>
))}

function TestComponent3() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={{__html: params}} />;
}


function OkComponent1() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(foo)}} />;
}



function OkComponent2() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={DOMPurify.sanitize(createMarkup())} />;
}

function OkComponent3() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: sanitize(foo)},a:b};
    return React.createElement('div', params);
}

function OkComponent4() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: "hi"},a:b};
    return React.createElement('div', params);
}

function OkComponent5() {
    // ok:react-dangerouslysetinnerhtml
  return <li class="foobar" selected={true} />;
}

function OkComponent6() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: "test123", style: {color: 'red'}};
    return React.createElement('div', params);
}

react-dangerouslysetinnerhtml.tsx

import DOMPurify from "dompurify"
import sanitize from "xss"

function TestComponent1() {
    // ok:react-dangerouslysetinnerhtml
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

function TestComponent2(foo) {
    // ruleid:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: foo.bar},a:b};
    return React.createElement('div', params);
}

// ok:react-dangerouslysetinnerhtml
{collaborationSectionData.paragraphs.map((item, i) => (
  <li key={`collaboration-p-${i}`} dangerouslySetInnerHTML={{
    __html: item,}}>
  </li>
))}

function TestComponent3() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={{__html: params}} />;
}


function OkComponent1() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(foo)}} />;
}



function OkComponent2() {
    // ok:react-dangerouslysetinnerhtml
  return <li className={"foobar"} dangerouslySetInnerHTML={DOMPurify.sanitize(createMarkup())} />;
}

function OkComponent3() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: sanitize(foo)},a:b};
    return React.createElement('div', params);
}

function OkComponent4() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: "hi"},a:b};
    return React.createElement('div', params);
}

function OkComponent5() {
    // ok:react-dangerouslysetinnerhtml
  return <li class="foobar" selected={true} />;
}

function OkComponent6() {
    // ok:react-dangerouslysetinnerhtml
    let params = {smth: "test123", style: {color: 'red'}};
    return React.createElement('div', params);
}