<?php
// Official Script Homepage: http://www.clfsrpm.net/xss/
// This is free software. No guarantees are expressed or implied that this
// software does what it is suppose to do.
// The author(s) of this software are NOT responsible for consequences of
// it's use.
// Anyone who distributes this software is NOT responsible for consequences
// of it's use unless the distributor specifies otherwise.
// USE AT YOUR OWN RISK.
// Demonstration version.
// Note that the CSP spefication is NOT yet stable so this may need some
// modification when it is. When I think it is ready for deployment it
// will be released as version 1.0
//
// Copyright (c) 2009 Michael A. Peters <hide@address.com>
// All rights reserved.
// Distributed under the terms and conditions of the Common Public License 1.0
// http://www.opensource.org/licenses/cpl1.0.php
// A text version of that license should have accompanied this distribution.
//
// Patches welcome with the understanding that sending them to me gives me
// permission to use them.
// KNOWN BUGS - see http://www.clfsrpm.net/xss/index.php#Coleoptera
// for idna support - include this in the script that includes this
// script or uncomment next line
//include_once('idna_convert.class.php');
class cspfilter {
/* Public Variables
* Set them in your script after initiating new instance of class */
// not used by the class
public $version = "0.25";
// parameters for Content Security Policy
public $csp = Array('allow' => 'none',
'img-src' => '',
'media-src' => '',
'script-src' => '',
'object-src' => '',
'frame-src' => '',
'frame-ancestors' => '',
'report-uri' => '',
'style-src' => '');
// if you are going to use makeCSP() - do you want to send a header?
// if left false, it just adds a meta tag to document. Set to true
// to actually send a header.
// Since no browser implements CSP yet - calling makeCSP() is currently
// rather pointless.
public $cspHeader = false;
// set to true to only allow script nodes in document head
public $scriptOnlyInHead = false;
// the host used for filtering when a csp setting is set to self
public $httphost;
// array of event Attributes you don't want eaten by class.
// Note that if script is not allowed, whitelist ignored.
public $eventWhitelist = Array();
// option log file location for policy violations - must exist and
// be writable by the web server
public $policyLogFile = '';
// private variables
private $resourceTypes = Array('img-src','media-src','script-src','object-src','frame-src','style-src');
// internal resolution of csp policy
private $pv = Array('allow' => 'none',
'img-src' => '',
'media-src' => '',
'script-src' => '',
'object-src' => '',
'frame-src' => '',
'frame-ancestors' => '',
'report-uri' => '',
'style-src' => '');
private $cspContent;
private $blacklist;
private $base = "";
private $eventAttributes = Array('onabort','onactivate',
'onafterprint','onafterupdate','onbeforeactivate',
'onbeforecopy','onbeforecut','onbeforedeactivate',
'onbeforeeditfocus','onbeforepaste','onbeforeprint',
'onbeforeunload','onbeforeupdate','onblur',
'onbounce','oncellchange','onchange',
'onclick','oncontextmenu','oncontrolselect',
'oncopy','oncut','ondataavailable',
'ondatasetchanged','ondatasetcomplete','ondblclick',
'ondeactivate','ondrag','ondragend',
'ondragenter','ondragleave','ondragover',
'ondragstart','ondrop','onerror',
'onerrorupdate','onfilterchange','onfinish',
'onfocus','onfocusin','onfocusout',
'onhelp','onkeydown','onkeypress',
'onkeyup','onlayoutcomplete','onload',
'onlosecapture','onmousedown','onmouseenter',
'onmouseleave','onmousemove','onmouseout',
'onmouseover','onmouseup','onmousewheel',
'onmove','onmoveend','onmovestart',
'onpaste','onpropertychange','onreadystatechange',
'onreset','onresize','onresizeend',
'onresizestart','onrowenter','onrowexit',
'onrowsdelete','onrowsinserted','onscroll',
'onselect','onselectionchange','onselectstart',
'onstart','onstop','onsubmit',
'onunload');
private $occurOnce = Array('html','head','title','body');
// isindex appears to be head only in html 3.2 - 4.0 doesn't specify head only
private $headOnly = Array('base','isindex','link','meta','style');
private $noChildren = Array('base','basefont','br','col','embed','frame',
'hr','img','input','isindex','link','meta','param','script');
private $policyTriggered = false;
// whether or not to use idna
private $useidna = false;
//private var $triggerDOM;
// public functions
public function cspfilter($input) {
$this->dom = $input;
// I highly recommend you overwrite the value that gets set below
// with one you set and know is clean. Do it the same way you
// set other public variables
//
$this->httphost = $_SERVER["HTTP_HOST"];
//
// for violation reporting
$this->triggerDOM = new DOMDocument("1.0","UTF-8");
$string = $_SERVER['REQUEST_METHOD'] . " " . $_SERVER['REQUEST_URI'] . " " . $_SERVER['SERVER_PROTOCOL'];
$report = $this->triggerDOM->createElement("cspfilter-report");
$wrapped = $this->triggerDOM->createTextNode($string);
$request = $this->triggerDOM->createElement("request");
$request->appendChild($wrapped);
$report->appendChild($request);
// headers
$rqheaders = "";
if (function_exists('apache_request_headers')) {
foreach (apache_request_headers() as $name => $value) {
$keyArray[] = strtolower($name);
$harray[] = $value;
}
} else {
$keyArray[] = 'host';
$harray[] = $this->httphost;
if (isset($_SERVER['HTTP_ACCEPT'])) {
$keyArray[] = 'accept';
$harray[] = $_SERVER['HTTP_ACCEPT'];
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$keyArray[] = 'user-agent';
$harray[] = $_SERVER['HTTP_USER_AGENT'];
}
}
for ($i=0; $i<sizeof($keyArray); $i++) {
$fooA = explode('-',$keyArray[$i]);
for ($j=0;$j<sizeof($fooA);$j++) {
$fooA[$j] = ucfirst($fooA[$j]);
}
$headername = implode('-',$fooA);
$rqheaders .= $headername . ": " . $harray[$i] . "\n";
}
$rqheaders = preg_replace('/\n$/','',$rqheaders);
$headers = $this->triggerDOM->createElement("request-headers",$rqheaders);
$report->appendChild($headers);
$this->triggerDOM->appendChild($report);
// punycode
if (class_exists('idna_convert')) {
$this->useidna = true;
$this->IDN = new idna_convert();
}
}
public function processData() {
// chews the data - call after inputDom function
$this->getFilterOptions();
$this->makeBlackList();
$this->walkTheDog();
$this->getBase();
$this->deleteExtraOnce();
$this->deleteNotInHead();
$this->deleteIllegitimateChildren();
$this->metaFirst();
$this->forbiddenTags();
$this->sendReport();
}
public function makeCSP() {
// creates the meta tag or send the header
// not called within the class, call it from your script
if (strlen($this->csp['allow']) == 0) {
$this->cspContent = "allow none";
} else {
$this->cspContent = "allow " . $this->csp['allow'];
}
for ($i=0; $i<sizeof($this->resourceTypes); $i++) {
$type = $this->resourceTypes[$i];
if (strlen($this->csp[$type]) > 0) {
$this->cspContent .= "; $type " . $this->csp[$type];
}
}
if (strlen($this->csp['frame-ancestors']) > 0) {
$this->cspContent .= "; frame-ancestors " . $this->csp['frame-ancestors'];
}
if (strlen($this->csp['report-uri']) > 0) {
$this->cspContent .= "; report-uri " . $this->csp['report-uri'];
}
if ($this->cspHeader == true) {
$header = "X-Content-Security-Policy: " . $this->cspContent;
header($header);
} else {
$meta = $this->dom->createElement("meta");
$meta->setAttribute("http-equiv","X-Content-Security-Policy");
$meta->setAttribute("content",$this->cspContent);
$headtags = $this->dom->getElementsByTagName("head");
$head = $headtags->item(0);
//
// damn I wish there was a prependChild function
//
$newHead = $this->dom->createElement('head');
$newHead->appendChild($meta);
// copy attributes
if ($head->hasAttribute("xmlns")) {
$xmlnsValue = $head->getAttribute("xmlns");
$newHead->setAttribute("xmlns",$xmlnsValue);
}
$attributes = $head->attributes;
foreach ($attributes as $attribute) {
if (strlen($attribute->namespaceURI) > 0) {
// namespaced attribute
$NS = $attribute->namespaceURI;
$NSname = $attribute->prefix . ":" . $attribute->name;
$NSvalue = $attribute->value;
$newHead->setAttributeNS($NS,$NSname,$NSvalue);
} elseif(strcmp($attribute->name,'xmlns') != 0) {
$name = $attribute->name;
$value = $head->getAttribute($attribute->name);
$newHead->setAttribute($name,$value);
}
}
// get all children from old head
$children = $head->childNodes;
foreach ($children as $child) {
// clone node and add it to newHead EXCEPT for existing cspfilter meta
$copyChild = true;
if ($child->nodeType == XML_ELEMENT_NODE) {
if (strcmp($child->tagName,'meta') == 0) {
if ($child->hasAttribute('http-equiv')) {
$equiv = strtolower($child->getAttribute('http-equiv'));
if (strcmp($equiv,'x-content-security-policy') == 0) {
$copyChild = false;
}
}
}
}
if ($copyChild == true) {
$newChild = $child->cloneNode(true);
$newHead->appendChild($newChild);
}
}
// replace the old head
$head->parentNode->replaceChild($newHead,$head);
}
}
// this next function only exists for live test page
public function displayViolationReport() {
if ($this->policyTriggered == true) {
$this->triggerDOM->formatOutput = true;
$report = $this->triggerDOM->saveXML();
//$report = preg_replace('/^<\?xml[^>]*>\n/','',$report,1);
return $report;
} else {
return "";
}
}
private function cleanHostString($input) {
$pre[] ='/^self$/';
$post[] =$this->httphost;
$pre[] ='/\sself\s/';
$post[] =" $this->httphost ";
$pre[] ='/\sself$/';
$post[] =" $this->httphost";
$pre[] ='/^self\s/';
$post[] ="$this->httphost ";
return preg_replace($pre,$post,$input);
}
private function getFilterOptions() {
$foo = strtolower(trim($this->csp['allow']));
$this->csp['allow'] = preg_replace('/\s+/',' ',$foo);
for ($i=0; $i<sizeof($this->resourceTypes); $i++) {
$type = $this->resourceTypes[$i];
$foo = strtolower(trim($this->csp[$type]));
$this->csp[$type] = preg_replace('/\s+/',' ',$foo);
}
$foo = trim($this->csp['frame-ancestors']);
$this->csp['frame-ancestors'] = preg_replace('/\s+/',' ',$foo);
$this->csp['report-uri'] = trim($this->csp['report-uri']);
if (strlen($this->csp['report-uri']) > 0) {
if (! $this->checkSource($this->csp['report-uri'],$this->httphost)) {
$string = "Invalid report-uri: " . $this->csp['report-uri'];
$this->csp['report-uri'] = '';
$this->policyReport($string);
}
}
if (strlen($this->csp['allow']) == 0) {
$this->csp['allow'] = 'none';
}
$this->pv['allow'] = $this->cleanHostString($this->csp['allow']);
for ($i=0; $i<sizeof($this->resourceTypes); $i++) {
$type = $this->resourceTypes[$i];
if (strlen($this->csp[$type]) > 0) {
$this->pv[$type] = $this->cleanHostString($this->csp[$type]);
} else {
$this->pv[$type] = $this->pv['allow'];
}
}
if ($this->scriptOnlyInHead == true) {
$this->headOnly[] = "script";
}
}
private function makeBlackList() {
// creates black list of attributes we don't allow, taking whitelist
// into account
$this->blacklist = " ";
for ($i=0; $i<sizeof($this->eventAttributes); $i++) {
$white = false;
if (strcmp($this->pv['script-src'],'none') != 0) {
for ($j=0; $j<sizeof($this->eventWhitelist); $j++) {
if (strcmp($this->eventAttributes[$i],$this->eventWhitelist[$j]) == 0) {
$white = true;
} // end specific check if in whitelist
} // end general check if in whitelist
} // end check if script set to none
if ($white == false) {
$this->blacklist .= $this->eventAttributes[$i] . " ";
}
} // end walk through eventAttributes
} // end of function
private function obfus($input,$src=0) {
// this function only intended to operates on attribute values
//
// based on http://kallahar.com/smallprojects/php_xss_filter_function.php
// which is public domain. So as far as I'm concerned, this function
// is public domain.
//
// should I add \t \r \n to the first preg_replace ?? Need to check for them somewhere
// to avoid script: dodging.
$return = $input;
for ($i=0;$i<9;$i++) {
$ser[] = '/\x0' . $i . '/i';
$ser[] = '/\x1' . $i . '/i';
}
$ser[] = '/\x0b/i';
$ser[] = '/\x0c/i';
$ser[] = '/\x0e/i';
$ser[] = '/\x0f/i';
$ser[] = '/\x19/i';
$return = preg_replace($ser,'',$return);
//$return = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $return);
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '1234567890!@#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) {
$return = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $return);
$return = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $return);
}
$scriptArray = Array('javascript','mocha','vbscript');
$found = true;
while ($found == true) {
$return_before = $return;
for ($i = 0; $i < sizeof($scriptArray); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($scriptArray[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|\s';
$pattern .= '|(�{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $scriptArray[$i][$j];
}
$pattern .= '/i';
$return = preg_replace($pattern, $scriptArray[$i], $return);
if (strcmp($return_before,$return) == 0) {
$found = false;
}
}
}
$search = '/(javascript|mocha|vbscript):[^\s]*/i';
$return = preg_replace($search,'',$return);
if ($src != 0) {
// make sure relative src attributes start with only one slash
$return = preg_replace('/^\s*[\/]+/','/',$return);
}
if (strcmp($input,$return) != 0) {
$this->policyReport("Attribute Obfuscation");
$return = preg_replace('/^\s+/','',$return);
}
return $return;
}
private function filterAttributes($node) {
// filters the attribute names and content
if ($node->hasAttribute("xmlns")) {
$xmlnsValue = $node->getAttribute("xmlns");
$saniAtt[] = 'xmlns';
$saniVal[] = $xmlnsValue;
$oldAtt[] = 'xmlns';
}
$attributes = $node->attributes;
foreach ($attributes as $attribute) {
$pattern = '/[^a-z0-9-]+/i';
$clean = strtolower(preg_replace($pattern,'',$attribute->name));
if (strcmp($clean,$attribute->name) != 0) {
$this->policyReport("Invalid Attribute Name");
}
if (strlen($attribute->namespaceURI) > 0) {
// namespaced attribute
$NS[] = $attribute->namespaceURI;
$NSsaniAtt[] = $attribute->prefix . ":" . $clean;
$NSsaniVal[] = $this->obfus($attribute->value);
$NSoldAtt[] = $attribute->name;
} elseif(strcmp($attribute->name,'xmlns') != 0) {
$saniAtt[] = $clean;
$oldAtt[] = $attribute->name;
if (strcmp($clean,"value") == 0) {
// don't mutilate value attributes
$saniVal[] = $attribute->value;
} else {
switch ($clean) {
case 'src':
$saniVal[] = $this->obfus($attribute->value,1);
break;
case 'data':
$saniVal[] = $this->obfus($attribute->value,1);
break;
case 'code':
$saniVal[] = $this->obfus($attribute->value,1);
break;
default:
$saniVal[] = $this->obfus($attribute->value);
} // end of switch
} // end check for value attribute
} // end check if namespaced
} // end attributes loop
if (isset($oldAtt)) {
for ($i=0; $i<sizeof($oldAtt);$i++) {
$node->removeAttribute($oldAtt[$i]);
}
}
if (isset($saniAtt)) {
for ($i=0; $i<sizeof($saniAtt);$i++) {
$check = " " . $saniAtt[$i] . " ";
if (substr_count($this->blacklist, $check) == 0) {
$node->setAttribute($saniAtt[$i],$saniVal[$i]);
} else {
$string = "Blacklisted Event Attribute: " . $saniAtt[$i];
$this->policyReport($string);
}
}
}
if (isset($NSoldAtt)) {
for ($i=0; $i<sizeof($NSoldAtt);$i++) {
$node->removeAttributeNS($NS[$i],$NSoldAtt[$i]);
}
}
if (isset($NSsaniAtt)) {
for ($i=0; $i<sizeof($NSsaniAtt);$i++) {
$node->setAttributeNS($NS[$i],$NSsaniAtt[$i],$NSsaniVal[$i]);
}
}
}
private function changeNodeElement($node,$element) {
// ugh - why doesn't php let you change the element tag easily ??
$newNode = $this->dom->createElement($element);
// get all attributes from old node
if ($node->hasAttribute("xmlns")) {
$xmlnsValue = $node->getAttribute("xmlns");
$newNode->setAttribute("xmlns",$xmlnsValue);
}
$attributes = $node->attributes;
foreach ($attributes as $attribute) {
if (strlen($attribute->namespaceURI) > 0) {
// namespaced attribute
$NS = $attribute->namespaceURI;
$NSname = $attribute->prefix . ":" . $attribute->name;
$NSvalue = $attribute->value;
$newNode->setAttributeNS($NS,$NSname,$NSvalue);
} elseif(strcmp($attribute->name,'xmlns') != 0) {
$name = $attribute->name;
$value = $node->getAttribute($attribute->name);
$newNode->setAttribute($name,$value);
}
}
// get all children from old node
$children = $node->childNodes;
foreach ($children as $child) {
// clone node and add it to newNode
$newChild = $child->cloneNode(true);
$newNode->appendChild($newChild);
}
// replace the old node with the newNode
$node->parentNode->replaceChild($newNode,$node);
}
private function checkNodeTag($node) {
$tag = $node->tagName;
$pattern = '/[^a-z0-9]+/i';
$clean = preg_replace($pattern,'',$tag);
$newTag = strtolower($clean);
if ($tag != $newTag) {
$this->policyReport("Invalid Node Name");
$this->changeNodeElement($node,$newTag);
}
}
private function walkTheDog() {
// Makes sure all element tags and attribute names are lower case.
// also triggers filter dodging checks and event attribute filtering
$elements = $this->dom->getElementsByTagName("*");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
$this->filterAttributes($element);
$this->checkNodeTag($element);
}
}
private function getBase() {
$elements = $this->dom->getElementsByTagName("base");
foreach ($elements as $element) {
if ($element->hasAttribute('href')) {
$this->base = $element->getAttribute('href');
}
}
}
private function mycompare($needle,$haystack) {
$hayOne = substr($haystack,0,1);
if (strcmp($hayOne,"*") == 0) {
$haystack = preg_replace('/^[\*]/','',$haystack);
$length = (0 - strlen($haystack));
$needle = substr($needle,$length);
}
if (strcmp($needle,$haystack) == 0) {
return 1;
} else {
return 0;
}
}
private function checkSource($src,$pv,$base='') {
if ($this->useidna == true) {
$src = utf8_encode($src);
$src = $this->IDN->encode($src);
if (strlen($base) > 0) {
$base = utf8_encode($base);
$base = $this->IDN->encode($base);
}
}
// returns true if source is valid
if ($pv == "none") {
return false;
} elseif ($pv == "*") {
return true;
} else {
$src = trim(strtolower($src));
// replace http:// and https:// with a |
$src = preg_replace('/^\s*http[s]{0,1}:\/\//','|',$src);
if (strlen($base) > 0) {
$base = preg_replace('/^\s*http[s]{0,1}:\/\//','',$base);
} else {
$base = $this->httphost;
}
// replace src that don't start with a | with the $base
$src = preg_replace('/^[^\|].*/',$base,$src);
// remove the | and make sure at least one / exists
$src = preg_replace('/^[\|]/','',$src) . "/";
$farray = explode('/',$src);
$src = $farray[0];
$allowed_array = explode(" ",$pv);
$match = 0;
for ($i=0; $i<sizeof($allowed_array); $i++) {
$match = $match + $this->mycompare($src,$allowed_array[$i]);
}
if ($match == 0) {
return false;
} else {
return true;
}
}
}
private function getElementsByAttribute($attribute) {
// not currently used - but keeping here anyway in case I decide to at some point
$tags = $this->dom->getElementsByTagName("*");
foreach ($tags as $tag) {
if ($tag->hasAttribute($attribute)) {
$return[] = $tag;
}
}
if (! isset($return)) {
$return = Array();
}
return $return;
}
private function deleteExtraOnce() {
// nukes extra elements that are only suppose to occure once - IE body tag
for ($i=0;$i<sizeof($this->occurOnce);$i++) {
$elements = $this->dom->getElementsByTagName($this->occurOnce[$i]);
for ($j = $elements->length; --$j > 0; ) {
$element = $elements->item($j);
$element->parentNode->removeChild($element);
$string = "Extra " . $this->occurOnce[$i] . " Element";
$this->policyReport($string);
}
}
}
private function forbiddenTags() {
// nukes tags that are forbidden by the CSP
$this->filterImg();
$this->filterScript();
$this->filterObject();
$this->filterEmbed();
$this->multimedia();
$this->filterApplet();
$this->filterFrame();
$this->filterStyle();
}
private function nodeToDiv($node) {
$div = $this->dom->createElement("div");
$children = $node->childNodes;
foreach ($children as $child) {
// clone node and add it to newNode
$clone = true;
if ($child->nodeType == XML_ELEMENT_NODE) {
if (strcmp($child->tagName,"param") == 0) {
$clone = false;
}
}
if ($clone == true) {
$newChild = $child->cloneNode(true);
$div->appendChild($newChild);
}
}
// replace the old node with the newNode
$node->parentNode->replaceChild($div,$node);
}
private function metaFirst() {
$heads = $this->dom->getElementsByTagName('head');
$head = $heads->item(0);
$metaLegal = true;
$children = $head->childNodes;
foreach ($children as $child) {
if ($child->nodeType == XML_ELEMENT_NODE) {
$tag = $child->tagName;
if ($tag == 'meta') {
if ($metaLegal == false) {
$child->parentNode->removeChild($child);
$this->policyReport('meta tag after non-meta tag in head');
}
} else {
$metaLegal = false;
}
}
}
}
private function filterImg() {
$elements = $this->dom->getElementsByTagName("img");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("src")) {
$src = trim($element->getAttribute("src"));
if (! $this->checkSource($src,$this->pv['img-src'],$this->base)) {
if ($element->hasAttribute("alt")) {
$altTag = $element->getAttribute("alt");
$txt = $this->dom->createTextNode($altTag);
$element->parentNode->replaceChild($txt,$element);
} else {
$element->parentNode->removeChild($element);
}
$this->policyReport($src,'img-src');
}
}
}
}
private function filterStyle() {
$elements = $this->dom->getElementsByTagName("link");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
// is the element a style sheet ??
if ($element->hasAttribute('type')) {
$type = strtolower(trim($element->getAttribute('type')));
if (strcmp($type,'text/css') == 0) {
// it's a style sheet
if ($element->hasAttribute('href')) {
$src = trim($element->getAttribute('href'));
if (! $this->checkSource($src,$this->pv['style-src'],$this->base)) {
$element->parentNode->removeChild($element);
$this->policyReport($src,'style-src');
}
}
}
}
}
}
private function filterScript() {
$elements = $this->dom->getElementsByTagName("script");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("src")) {
$src = trim($element->getAttribute("src"));
if (! $this->checkSource($src,$this->pv['script-src'],$this->base)) {
$element->parentNode->removeChild($element);
$this->policyReport($src,'script-src');
}
} else {
// no src - yank it
$element->parentNode->removeChild($element);
$this->policyReport('Script with no URI');
}
}
}
private function filterObject() {
$elements = $this->dom->getElementsByTagName("object");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("data")) {
$data = trim($element->getAttribute("data"));
} elseif ($element->hasAttribute("src")) {
$data = trim($element->getAttribute("src"));
}
if ($element->hasAttribute("codebase")) {
$codebase = trim($element->getAttribute("src"));
}
if (! isset($data)) {
$element->parentNode->removeChild($element);
$this->policyReport('Object with no URI');
} else {
if (! isset($codebase)) {
$codebase = '';
}
$res = $this->checkSource($data,$this->pv['object-src'],$codebase);
if ($res == false) {
if ($element->hasChildNodes()) {
$this->nodeToDiv($element);
} else {
$element->parentNode->removeChild($element);
}
$this->policyReport($data,'object-src');
}
} // end check if data set
} // end for loop
} // end function
private function filterEmbed() {
$elements = $this->dom->getElementsByTagName('embed');
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("src")) {
$src = trim($element->getAttribute("src"));
if (! $this->checkSource($src,$this->pv['object-src'],$this->base)) {
$element->parentNode->removeChild($element);
$this->policyReport($src,'object-src');
}
} else {
$element->parentNode->removeChild($element);
$string = "Embed with no URI";
$this->policyReport($string);
} // end if has attribute source
} // end for loop
} // end function
private function multimedia() {
$multi = Array('audio','video');
for ($i=0;$i<sizeof($multi);$i++) {
$elements = $this->dom->getElementsByTagName($multi[$i]);
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("src")) {
$src = trim($element->getAttribute("src"));
if (! $this->checkSource($src,$this->pv['media-src'],$this->base)) {
if ($element->hasChildNodes()) {
$this->nodeToDiv($element);
} else {
$element->parentNode->removeChild($element);
}
$this->policyReport($src,'media-src');
}
} else {
if ($element->hasChildNodes()) {
$this->nodeToDiv($element);
} else {
$element->parentNode->removeChild($element);
}
$string = ucfirst($multi[$i]) . " with no URI";
$this->policyReport($string);
}
}
}
}
private function filterApplet() {
$elements = $this->dom->getElementsByTagName("applet");
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("code")) {
$data = trim($element->getAttribute("code"));
} elseif ($element->hasAttribute("src")) {
$data = trim($element->getAttribute("src"));
}
if ($element->hasAttribute("codebase")) {
$codebase = trim($element->getAttribute("src"));
}
if (! isset($data)) {
$element->parentNode->removeChild($element);
$this->policyReport('Applet with no URI');
} else {
if (isset($codebase)) {
$res = $this->checkSource($data,$this->pv['object-src'],$codebase);
} else {
$res = $this->checkSource($data,$this->pv['object-src']);
}
if ($res == false) {
if ($element->hasChildNodes()) {
$this->nodeToDiv($element);
} else {
$element->parentNode->removeChild($element);
}
$this->policyReport($data,'object-src');
}
} // end check if data set
} // end for loop
} // end function
private function filterFrame() {
$frmScope = Array('frame','iframe');
for ($i=0; $i<2; $i++) {
$elements = $this->dom->getElementsByTagName($frmScope[$i]);
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
if ($element->hasAttribute("src")) {
$src = trim($element->getAttribute("src"));
if (! $this->checkSource($src,$this->pv['frame-src'],$this->base)) {
if ($element->hasChildNodes()) {
$this->nodeToDiv($element);
} else {
$element->parentNode->removeChild($element);
}
$this->policyReport($src,'frame-src');
}
} else {
$element->parentNode->removeChild($element);
$string = ucfirst($frmScope[$i]) . " with no URI";
$this->policyReport($string);
}
}
}
}
private function deleteNotInHead() {
// nukes elements that are suppose to be in head but are not
for ($i=0;$i<sizeof($this->headOnly);$i++) {
$elements = $this->dom->getElementsByTagName($this->headOnly[$i]);
for ($j = $elements->length; --$j >= 0; ) {
$element = $elements->item($j);
$parent = $element->parentNode->tagName;
if ($parent != "head") {
$string = $this->headOnly[$i] . " element not in head";
$element->parentNode->removeChild($element);
$this->policyReport($string);
}
}
}
}
private function deleteIllegitimateChildren() {
// nukes children of elements that ain't suppose to have children
for ($i=0;$i<sizeof($this->noChildren);$i++) {
$elements = $this->dom->getElementsByTagName($this->noChildren[$i]);
foreach ($elements as $element) {
$children = $element->childNodes;
for ($j = $children->length; --$j >= 0; ) {
$child = $children->item($j);
$element->removeChild($child);
$string = "Illegal child node for " . $this->noChildren[$i];
$this->policyReport($string);
}
}
}
}
private function policyReport($blocked,$policy='') {
$elements = $this->triggerDOM->getElementsByTagName('cspfilter-report');
$report = $elements->item(0);
if (strlen($policy) > 0) {
$blocked = $this->triggerDOM->createElement("blocked-uri",$blocked);
$blocked->setAttribute($policy,$this->pv[$policy]);
} else {
$blocked = $this->triggerDOM->createElement("misc",$blocked);
}
$report->appendChild($blocked);
$this->policyTriggered = true;
}
private function sendReport() {
if ($this->policyTriggered == true) {
if (file_exists($this->policyLogFile)) {
$this->writeReport();
} elseif (strlen($this->csp['report-uri']) > 0) {
$this->httpPostReport();
}
}
}
private function writeReport() {
$this->triggerDOM->formatOutput = true;
$report = $this->triggerDOM->saveXML();
$report = preg_replace('/^<\?xml[^>]*>\n/','',$report,1);
$fh = fopen($this->policyLogFile, 'a');
fwrite($fh, $report);
fclose($fh);
}
private function httpPostReport() {
$rui = $this->csp['report-uri'];
$report = $this->triggerDOM->saveXML();
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL,$rui);
curl_setopt($handle, CURLOPT_POST, 1);
curl_setopt($handle, CURLOPT_POSTFIELDS,$report);
curl_setopt($handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
$result = curl_exec ($handle);
curl_close ($handle);
}
/* ---- */
} // end of class
?>