Skip to content

Commit

Permalink
Improve html sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
turbo124 committed Jan 4, 2025
1 parent 97ae948 commit 2a9bf35
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 3 deletions.
3 changes: 2 additions & 1 deletion app/Services/Pdf/PdfBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public function build(): self
public function getCompiledHTML($final = false)
{
$this->cleanHtml();

$html = $this->document->saveHTML();

return str_replace('%24', '$', $html);
Expand All @@ -99,7 +100,7 @@ private function cleanHtml(): self
$dangerous_elements = [
'iframe', 'form', 'object', 'embed',
'applet', 'audio', 'video',
'frame', 'frameset', 'base','svg'
'frame', 'frameset', 'base', 'svg'
];

$dangerous_attributes = [
Expand Down
6 changes: 6 additions & 0 deletions app/Services/Pdf/PdfDesigner.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public function build(): self
$this->template = file_get_contents(config('ninja.designs.base_path') . strtolower($this->service->config->design->name) . '.html');
}

// Remove NULL bytes
$this->template = str_replace("\0", '', $this->template);

// Remove UTF-7 BOM
$this->template = preg_replace('/^\\+ADw-/', '', $this->template);

return $this;
}

Expand Down
21 changes: 19 additions & 2 deletions app/Services/PdfMaker/Design.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,33 @@ public function __construct(string $design = null, array $options = [])
public function html(): ?string
{
if ($this->design == 'custom.html') {
return $this->composeFromPartials(
$design = $this->composeFromPartials(
$this->options['custom_partials']
);

// Remove NULL bytes
$design = str_replace("\0", '', $design);
// Remove UTF-7 BOM
$design = preg_replace('/^\\+ADw-/', '', $design);

return $design;

}

$path = $this->options['custom_path'] ?? config('ninja.designs.base_path');

return file_get_contents(
$design = file_get_contents(
$path . $this->design
);


// Remove NULL bytes
$design = str_replace("\0", '', $design);
// Remove UTF-7 BOM
$design = preg_replace('/^\\+ADw-/', '', $design);

return $design;

}

public function elements(array $context, string $type = 'product'): array
Expand Down
126 changes: 126 additions & 0 deletions app/Services/Template/TemplateService.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,137 @@ public function parseVariables(): self
*/
public function save(): self
{
$this->cleanHtml();

$this->compiled_html = str_replace('%24', '$', $this->document->saveHTML());

return $this;
}

private function cleanHtml(): self
{
if (!$this->document || !$this->document->documentElement) {
return $this;
}

$dangerous_elements = [
'iframe', 'form', 'object', 'embed',
'applet', 'audio', 'video',
'frame', 'frameset', 'base', 'svg'
];

$dangerous_attributes = [
'onabort', 'onblur', 'onchange', 'onclick', 'ondblclick',
'onerror', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup',
'onload', 'onmousedown', 'onmousemove', 'onmouseout',
'onmouseover', 'onmouseup', 'onreset', 'onresize',
'onselect', 'onsubmit', 'onunload'
];

// Function to recursively check nodes
$removeNodes = function ($node) use (&$removeNodes, $dangerous_elements, $dangerous_attributes) {
if (!$node) {
return;
}

// Store children in array first to avoid modification during iteration
$children = [];
if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
$children[] = $child;
}
}

// Process each child
foreach ($children as $child) {
$removeNodes($child);
}

// Only process element nodes
if ($node instanceof \DOMElement) {
// Remove dangerous elements
if (in_array(strtolower($node->tagName), $dangerous_elements)) {
if ($node->parentNode) {
$node->parentNode->removeChild($node);
}
return;
}

// Remove dangerous attributes
$attributes_to_remove = [];
foreach ($node->attributes as $attr) {
$attr_name = strtolower($attr->name);
$attr_value = strtolower($attr->value);

// Remove event handlers
if (in_array($attr_name, $dangerous_attributes) || strpos($attr_name, 'on') === 0) {
$attributes_to_remove[] = $attr->name;
continue;
}

// Remove dangerous URLs/protocols
if (in_array($attr_name, ['data', 'href', 'meta', 'link'])) {
if (preg_match('/(javascript|data|file|ftp|jar|dict|gopher|ldap|smb|php|alert|prompt|confirm):|\/\/\/\/+|127\.0\.0\.1|localhost/i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}
}else if ($attr_name === 'src') {
// For src attributes, only block dangerous protocols but allow data:image
if (preg_match('/(javascript|file|ftp|jar|dict|gopher|ldap|smb|php):|\/\/\/\/+|127\.0\.0\.1|localhost/i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}
// Additional check for data: URLs - only allow image types
if (strpos($attr_value, 'data:') === 0 && !preg_match('/^data:image\//i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}

// Check for localhost references
if (preg_match('/localhost|127\.|0\.0\.0\.0|::1|0:0:0:0:0:0:0:1/i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}

}elseif ($attr_name === 'style') {

if (preg_match('/(expression|javascript|behavior|vbscript):|url\s*\(|import|@import|eval\s*\(|-moz-binding|behavior|expression/i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}

}

// Remove expressions
if (preg_match('/expression|javascript:|vbscript:|livescript:/i', $attr_value)) {
$attributes_to_remove[] = $attr->name;
continue;
}
}

// Remove the collected dangerous attributes
foreach ($attributes_to_remove as $attr) {
$node->removeAttribute($attr);
}
}
};

try {
$removeNodes($this->document->documentElement);
} catch (\Exception $e) {
info('Error cleaning HTML: ' . $e->getMessage());

// Clear the document to prevent unsanitized content
$this->document = new \DOMDocument();

// Throw sanitized exception to alert calling code
throw new \RuntimeException('HTML sanitization failed');

}

return $this;
}

/**
* compose
*
Expand Down

0 comments on commit 2a9bf35

Please sign in to comment.