CVE-2021-3768 (PHP, published 2021-09-06T12:15Z)
Description: value: bookstack is vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
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/bookstackapp/bookstack
Files changed:
app/Util/HtmlContentFilter.php
tests/Entity/PageContentTest.php
Commit message: Added extra HTML filtering of dangerous content

In particular, That around the casing of dangerous values within
attributes. This uses some xpath translation to handle different casing
in contains searching.


Vulnerable code:
        $badLinks = $xPath->query('//*[contains(@href, \'javascript:\')]');
        $badForms = $xPath->query('//*[contains(@action, \'javascript:\')] | //*[contains(@formaction, \'javascript:\')]');
        $metaTags = $xPath->query('//meta[contains(@content, \'url\')]');
        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
    public function test_iframe_js_and_base64_urls_are_removed()
            $pageView->assertElementNotContains('.page-content', '<a id="xss">');

Fixed code:
        $badLinks = $xPath->query('//*[' . static::xpathContains('@href', 'javascript:') . ']');
        $badForms = $xPath->query('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']');
        $metaTags = $xPath->query('//meta[' . static::xpathContains('@content', 'url') . ']');
        $badIframes = $xPath->query('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]');
    /**
     * Create a xpath contains statement with a translation automatically built within
     * to affectively search in a cases-insensitive manner.
     */
    protected static function xpathContains(string $property, string $value): string
    {
        $value = strtolower($value);
        $upperVal = strtoupper($value);
        return 'contains(translate(' . $property . ', \'' . $upperVal . '\', \'' . $value . '\'), \'' . $value . '\')';
    }

    public function test_js_and_base64_src_urls_are_removed()
            '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
            '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
            '<iframe src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
            '<img src="javascript:alert(document.cookie)"/>',
            '<img src="JavAScRipT:alert(document.cookie)"/>',
            '<img src="JavAScRipT:alert(document.cookie)"/>',
            '<img SRC=" javascript: alert(document.cookie)"/>',
            '<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
            '<img src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
            '<img src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
            '<iframe SRCdoc="<script>window.alert(document.cookie)</script>"></iframe>',
            '<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>',
            $pageView->assertElementNotContains('.page-content', '<img');
            '<a id="xss" href="JaVaScRiPt: alert(document.cookie)>Click me</a>',
            '<a id="xss" href=" JaVaScRiPt: alert(document.cookie)>Click me</a>',
            $pageView->assertElementNotContains('.page-content', '<a id="xss"');
            '<form ><button id="xss" formaction="JaVaScRiPt:alert(document.domain)">Click me</button></form>',
            '<form id="xss" action="JaVaScRiPt:alert(document.domain)"><input type=submit value=Submit></form>',
            '<meta http-equiv="refresh" ConTeNt="0; url=//external_url">',
            '<meta http-equiv="refresh" content="0; UrL=//external_url">',
            '<p OnCliCk="console.log(\'test\')">Hello</p>',
            '\<a onclick="alert(document.cookie)"\>xss link\</a\>',