Loofah Ruby Library Documentation

repository·main·Indexed 21 days ago

https://github.com/flavorjones/loofah

Loofah is a Ruby library built on Nokogiri for manipulating and transforming HTML and XML documents and fragments. It provides a framework for custom scrubbers and a toolkit for HTML sanitization based on the html5lib safelist, featuring built-in strategies such as strip, prune, escape, and whitewash. It can serve as a replacement for Rails' strip_tags and sanitize view helpers and includes specialized transformations for hyperlinks, text cleaning, and structural fixes.

Tokens
10.1K
Snippets
29
Records
44
Agent score
76%

What's inside Loofah

  1. Overview of Loofah

    main

    Loofah is a Ruby library designed for manipulating and transforming HTML and XML documents and fragments. It is built on top of Nokogiri and serves two primary purposes:

    1. General Document Transformation: A framework for writing custom scrubbers to transform XML, XHTML, and HTML documents.
    2. HTML Sanitization: A toolkit providing built-in sanitization transformations based on html5lib's safelist.

    For Rails users, Loofah can be used to replace the standard strip_tags and sanitize view helper methods. Additionally, loofah-activerecord is available for Active Record extensions.

  2. Understand the CDATA vs PCDATA XSS vulnerability

    main

    A class of XSS vulnerabilities exists when using an HTML4 parser (like libxml2) to sanitize content that is later parsed by an HTML5-compliant browser.

    The Mechanism:

    1. HTML4 Parser Behavior: When parsing script or style tags, the HTML4 parser creates CDATA nodes. When these documents are serialized, the CDATA payload is written literally without escaping characters like <, >, or &.
    2. HTML5 Parser Behavior: An HTML5 parser may treat that literal CDATA payload as PCDATA (structural data). If the CDATA contains a nested <script> tag that was invalid in the HTML4 context (e.g., inside a <select> or <style> tag), the HTML5 parser might promote it to a real element, leading to script execution.

    Key Distinction:

    • CDATA: A string literal where characters like < and > are treated as text and not structural.
    • PCDATA: Data that determines the structure of the document (where < and > are interpreted as tags).
  3. Built-in HTML transformations

    main

    Beyond sanitization, Loofah includes several common document transformations:

    • Hyperlink modifications: Add rel="nofollow" or target="_blank" attributes to all hyperlinks.
    • Text cleaning: Remove unprintable characters from text nodes.
    • Structural fixes: Specialized transformations, such as closing a <p> tag and opening a new one when <br><br> is encountered inside a <p> tag.
    • Plain text formatting: Convert markup to plain text with configurable whitespace handling around block elements.
  4. Built-in HTML sanitization transformations

    main

    Loofah provides several built-in scrubbing strategies for sanitizing HTML:

    • Strip: Removes unsafe tags but preserves their inner text.
    • Prune: Removes unsafe tags and their entire subtrees (completely removing all traces).
    • Escape: Converts unsafe tags and their subtrees into escaped entities (e.g., leaving behind &lt; and &gt;).
    • Whitewash: Cleans the markup by removing all attributes and namespaced nodes.
  5. XSS vulnerability in `select`, `svg`, and `math` with `style` tags

    main

    A specific XSS attack vector exists when certain tags like <select>, <svg>, or <math> are allowed in a safelist alongside <style> tags.

    When an HTML5 parser encounters a <style> tag inside these 'foreign context' elements, it may treat the CDATA payload differently than an HTML4 parser. This can cause malicious payloads (like <script> or <img onerror=...>):

    1. To be inserted directly into the DOM as active elements.
    2. To be 'lifted' out of the parent context and parsed as siblings to the parent, bypassing sanitization.

    Example (SVG): Inputting <svg><style><script>alert(1)</script></style></svg> can result in a DOM where the <script> is active and executable by the browser.

    Example (Math): Inputting <math><style><img src=x onerror=alert(1)></style></math> can result in the <img> tag being parsed as a sibling to <math>, executing the onerror payload.

    Mitigation: Avoid combining select, svg, or math tags with style tags in your sanitization safelists.

    # Vulnerable configuration in rails-html-sanitizer < 1.4.3
    input = "<svg><style><script>alert(1);</script></style></svg"
    tags = %w(svg style)
    # Resulting HTML may still contain executable script
  6. Understand the behavior of recursive sanitization for CDATA and style tags

    main

    Loofah's sanitization strategy involves recursively scrubbing content within certain nodes to prevent XSS attacks that hide payloads inside CDATA or <style> tags.

    When a node (like a CDATA node or a <style> tag in certain environments) is identified as needing further escaping, Loofah:

    1. Extracts the text content.
    2. Creates a new Loofah fragment from that text.
    3. Runs the scrubber on that fragment.
    4. Replaces the original node with a new text node containing the sanitized content.

    Key behaviors to note:

    • Tag Removal: If a payload like <script> is found inside a <style> tag, the recursive scrubbing will result in the <script> tag being removed entirely.
    • Attribute Scrubbing: If an <img> tag is allowed within a <style> tag, the scrubber will still remove dangerous attributes like onerror.
    • Entity Escaping: This process introduces entity escaping on nested tags (e.g., <script> becomes &lt;script&gt;) to ensure they are treated as plain text rather than executable code.
    # Example of how recursive scrubbing handles a script inside a style tag
    input = "<select><style><script>alert(1)</script></style></select>"
    tags = %w(select style)
    # Result: "<select><style>alert(1)</style></select>"
  7. Observe backwards-incompatible changes in style tag sanitization

    main

    Due to the move toward recursive sanitization to prevent XSS, there is a backwards-incompatible change regarding how special characters are handled within <style> tags.

    Previously, special characters like > were left unescaped. In the proposed/current behavior, they are escaped into HTML entities.

    Comparison:

    • Input: <style>div > span { background: "red"; }</style>
    • Old Behavior (v1.4.2/v1.4.3): <style>div > span { background: "red"; }</style>
    • New Behavior: <style>div &gt; span { background: "red"; }</style>

    Note: This behavior is expected to revert once Loofah fully transitions to HTML5 sanitization.

  8. Understand the nested script tag vulnerability and fix

    main

    In older versions of Loofah (pre-v2.2.0), nested <script> tags could bypass sanitization. For example, an input like <div><script><script src='malicious.js'></script></div> would result in <div><script src='malicious.js'></div>, leaving the malicious script active.

    Loofah fixed this by recursively sanitizing CDATA nodes. This ensures that even if a script tag is nested inside another element that is being stripped, the content is re-processed.

    Warning: Extremely deep nesting of script tags can trigger a stack level too deep exception due to the recursive nature of the fix.

    # Vulnerable behavior in Loofah < 2.2.0
    input = "<div><script><script src='malicious.js'></script></div>"
    Loofah.fragment(input).scrub!(:strip).to_html 
    # => "<div><script src='malicious.js'></div>"
    
    # Fixed behavior in Loofah >= 2.2.0
    input = "<div><script><script src='malicious.js'></script></div>"
    Loofah.fragment(input).scrub!(:strip).to_html 
    # => "<div></div>"
  9. Parse HTML and XML Documents or Fragments

    main

    Loofah provides module methods to parse strings into document or fragment objects. Choosing between a document and a fragment depends on whether you expect a DOCTYPE and a single root node.

    HTML5

    Use Loofah.html5_document for full documents (includes html, head, and body tags) or Loofah.html5_fragment for snippets (no html/body tags, multiple roots allowed).

    HTML4

    Use Loofah.html4_document or Loofah.html4_fragment. Note that Loofah.document and Loofah.fragment are currently aliases to these HTML4 methods.

    XML

    Use Loofah.xml_document for a single root node with a DOCTYPE, or Loofah.xml_fragment for multiple root nodes without a DOCTYPE.

    Warning: HTML5 functionality is not available on JRuby or with Nokogiri versions < 1.14.0. It is strongly recommended to explicitly use .html5_document or .html5_fragment to ensure future compatibility.

    # HTML5
    Loofah.html5_document(unsafe_html).is_a?(Nokogiri::HTML5::Document)         # => true
    Loofah.html5_fragment(unsafe_html).is_a?(Nokogiri::HTML5::DocumentFragment) # => true
    
    # HTML4
    Loofah.html4_document(unsafe_html).is_a?(Nokogiri::HTML4::Document)         # => true
    Loofah.html4_fragment(unsafe_html).is_a?(Nokogiri::HTML4::DocumentFragment)   # => true
    
    # XML
    Loofah.xml_document(bad_xml).is_a?(Nokogiri::XML::Document)                 # => true
    Loofah.xml_fragment(bad_xml).is_a?(Nokogiri::XML::DocumentFragment)         # => true
  10. Mitigate CDATA-based XSS in HTML4 documents

    main

    To prevent XSS attacks caused by HTML4/HTML5 parser mismatches, Loofah implements a strategy of escaping special characters within CDATA nodes.

    Implementation Detail: Loofah applies CGI.escapeHTML to all CDATA nodes created by Nokogiri's HTML4 parser.

    Side Effect: This approach introduces a minor backwards-incompatibility: special characters inside valid HTML4 style tags will now be entity-escaped (e.g., < becomes &lt;). This is considered an acceptable trade-off for security, as this behavior is resolved once the system defaults to an HTML5 parser.

  11. Control traversal with Scrubber::STOP and Scrubber::CONTINUE

    main

    In a top-down scrubber, you can control whether the traversal continues into a node's children by returning specific constants from your block or scrub method:

    • Loofah::Scrubber::STOP: Terminates the traversal for the current node's subtree (the children of the current node will not be visited).
    • Loofah::Scrubber::CONTINUE: Indicates that the subtree should continue to be traversed (this is the implicit behavior if you return nothing or if you are using the default top-down mode).