Skip to content

Commit 731d504

Browse files
author
Chris Bateman
committed
Merge branch 'fix/XSSMitigation' into 'develop'
fix: mitigate XSS issues See merge request 2pisoftware/cosine/core!432
2 parents 7a2cd8e + a7af55d commit 731d504

144 files changed

Lines changed: 735 additions & 597 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

system/classes/DbObject.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public function getSelectOptionTitle()
193193
} elseif (property_exists(get_class($this), "name")) {
194194
$title = $this->name;
195195
}
196-
return $title;
196+
return StringSanitiser::sanitise($title);
197197
}
198198

199199
/**

system/classes/History.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class History
2020

2121
/**
2222
* This function adds a history value to the SESSION
23-
* @param String $name
23+
* @param string $name
2424
*/
2525
public static function add($name, Web $w = null, $object = null)
2626
{
@@ -59,7 +59,7 @@ public static function add($name, Web $w = null, $object = null)
5959
*
6060
* @param string $key (optional)
6161
* @param int $length (optional)
62-
* @return Array the history
62+
* @return array the history
6363
*/
6464
public static function get($key = null, $length = 0)
6565
{

system/classes/HtmlBootstrap5.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public static function form($data, $action = null, $method = "POST", $submitTitl
161161
case "password":
162162
$size = !empty($field[4]) ? $field[4] : '';
163163
$required = !empty($field[5]) ? $field[5] : '';
164-
$buffer .= '<input' . $readonly . ' style="width:100%;" type="' . $type . '" name="' . $name . '" value="' . htmlspecialchars($value) . '" size="' . $size . '" id="' . $name . '" ' . $required . '/>';
164+
$buffer .= '<input' . $readonly . ' style="width:100%;" type="' . $type . '" name="' . $name . '" value="' . $value . '" size="' . $size . '" id="' . $name . '" ' . $required . '/>';
165165
break;
166166
case "autocomplete":
167167
$options = !empty($field[4]) ? $field[4] : '';
@@ -394,7 +394,7 @@ public static function multiColForm($data, $action = null, $method = "POST", $su
394394
case "email":
395395
case "tel":
396396
$size = !empty($field[4]) ? $field[4] : null;
397-
$buffer .= '<input' . $readonly . ' class="form-control" type="' . $type . '" name="' . $name . '" value="' . (empty($value) ? '' : htmlspecialchars($value)) .
397+
$buffer .= '<input' . $readonly . ' class="form-control" type="' . $type . '" name="' . $name . '" value="' . (empty($value) ? '' : $value) .
398398
'" size="' . $size . '" id="' . $name . '" ' . $required . " />";
399399
break;
400400
case "autocomplete":
@@ -1019,4 +1019,26 @@ public static function timePicker($name, $value = null, $size = null, $required
10191019
{
10201020
return '<input class="form-control" type="time" name="' . $name . '" value="' . $value . '" size="' . $size . '" id="' . $name . '" ' . $required . ' />';
10211021
}
1022+
1023+
/**
1024+
* Create a single select autocomplete widget
1025+
*
1026+
* @param <type> $data
1027+
* @param <type> $value
1028+
* @param <type> $class
1029+
*/
1030+
public static function autocomplete($name, $options, $value = null, $class = null, $style = null, $minLength = 1, $required = null)
1031+
{
1032+
return (new \Html\Form\Html5Autocomplete([
1033+
"id|name" => "title",
1034+
"class" => "form-control " . $class,
1035+
"label" => "Title",
1036+
"maxItems" => 1,
1037+
"value" => $value,
1038+
"required" => !!$required,
1039+
"style" => $style,
1040+
"minLength" => $minLength,
1041+
"options" => $options,
1042+
]))->__toString();
1043+
}
10221044
}

system/classes/StringSanitiser.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
class StringSanitiser {
4+
/**
5+
* Converts input data to encoded entities for safe display to screen
6+
*
7+
* @param ?string $data
8+
* @return string
9+
*/
10+
public static function sanitise(?string $string, ?int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string
11+
{
12+
if (!$string) {
13+
return '';
14+
}
15+
return htmlspecialchars(string: $string, flags: $flags, double_encode: false);
16+
}
17+
18+
/**
19+
* Strips default Quill HTML tags from the input string
20+
*
21+
* @param ?string $string
22+
* @param ?array $tags
23+
* @return string
24+
*/
25+
public static function stripTags(?string $string, ?array $tags = []): string
26+
{
27+
if (!$string) {
28+
return '';
29+
}
30+
31+
if (empty($tags)) {
32+
$tags = (new \Html\Cmfive\QuillEditor())->tag_allow_list;
33+
}
34+
35+
return strip_tags($string, $tags);
36+
}
37+
38+
/**
39+
* Strips anything that is not alphanumeric from the input string
40+
*
41+
* @param ?string $string
42+
* @return string
43+
*/
44+
public static function stripNonAlphaNumeric(?string $string): string
45+
{
46+
if (!$string) {
47+
return '';
48+
}
49+
return preg_replace('/[^a-zA-Z0-9]/', '', $string) ?? '';
50+
}
51+
52+
/**
53+
* Escapes quotes of the input string
54+
*
55+
* @param ?string $string
56+
* @return string
57+
*/
58+
public static function escapeQuotes(?string $string): string
59+
{
60+
if (!$string) {
61+
return '';
62+
}
63+
return addslashes($string);
64+
}
65+
}

system/classes/html/cmfive/CodeMirrorEditor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ public function setValues(array $values): self
4646

4747
public function __toString()
4848
{
49-
return '<textarea name="' . $this->name . '" id="' . $this->id . '" style="display:none"></textarea><div class="code-mirror-target" cm-value=\'' . $this->value . '\' id="' . $this->id . '">' . '</div>';
49+
return '<textarea name="' . $this->name . '" id="' . $this->id . '" style="display:none">' . $this->value . '</textarea><div class="code-mirror-target" data-id="' . $this->id . '">' . '</div>';
5050
}
5151
}

system/classes/html/cmfive/QuillEditor.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class QuillEditor extends \Html\Form\InputField
1212

1313
public $options = ["theme" => "snow"];
1414

15+
public $tag_allow_list = ["<h1>", "<h2>", "<h3>", "<p>", "<strong>", "<em>", "<u>", "<ol>", "<ul>", "<li>", "<a>", "<img>", "<blockquote>", "<code>", "<pre>", "<br>", "<hr>"];
16+
1517
/**
1618
* Value of the textarea
1719
* @var string
@@ -29,8 +31,16 @@ public function setOptions(array $options = []): self
2931
return $this;
3032
}
3133

34+
public function setTagAllowList(array $tag_allow_list = []): self
35+
{
36+
$this->tag_allow_list = $tag_allow_list;
37+
return $this;
38+
}
39+
3240
public function __toString()
3341
{
34-
return '<textarea name="' . $this->name . '" id="' . $this->id . '" style="display:none">' . $this->value . '</textarea><div class="quill-editor" data-quill-options=\'' . json_encode($this->options) . '\' id="quill_' . $this->id . '">' . $this->value . '</div>';
42+
$value = strip_tags($this->value ?? '', $this->tag_allow_list);
43+
44+
return '<textarea name="' . $this->name . '" id="' . $this->id . '" style="display:none">' . $value . '</textarea><div class="quill-editor" data-quill-options=\'' . json_encode($this->options) . '\' id="quill_' . $this->id . '">' . $value . '</div>';
3545
}
3646
}

system/classes/html/form/Autocomplete.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,13 @@ public function setRequired($required) {
124124
return $this;
125125
}
126126

127-
/**
128-
* Sets the boolean readonly attribute
129-
*
130-
* @param string $readonly 'true' for readonly fields
131-
* @return \Html\Form\Autocomplete this
132-
*/
133-
public function setReadOnly($readonly) {
127+
/**
128+
* Sets the boolean readonly attribute
129+
*
130+
* @param string $readonly 'true' for readonly fields
131+
* @return \Html\Form\Autocomplete this
132+
*/
133+
public function setReadOnly($readonly) {
134134
$this->readonly = $readonly;
135135

136136
return $this;

system/classes/html/form/Html5Autocomplete.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace Html\Form;
44

5-
use LogService;
6-
75
/**
86
* HTML5 Autocomplete using Tom-Select on frontend.
97
* Renders an text <input> field with a dropdown for possible values,
@@ -108,7 +106,7 @@ public function __toString()
108106
continue;
109107
}
110108

111-
$buffer .= $field . "='" . addslashes(is_array($value) ? implode(",", $value) : $value) . "' ";
109+
$buffer .= $field . "='" . \StringSanitiser::escapeQuotes(is_array($value) ? implode(",", $value) : $value) . "' ";
112110
}
113111

114112
$buffer .=
@@ -117,15 +115,11 @@ public function __toString()
117115
json_encode([
118116
"options" => $this->options ?
119117
array_map(
120-
fn($val) =>
121-
array_map(
122-
fn($inner) => htmlspecialchars($inner),
123-
$this->convertOption($val)
124-
),
118+
fn($val) => $this->convertOption($val),
125119
$this->options
126120
)
127121
: null,
128-
"maxItems" => !isset($this->maxItems) ? $this->maxItems : 1,
122+
"maxItems" => $this->maxItems,
129123
"items" => $this->value,
130124
"source" => $this->source,
131125
"create" => $this->canCreate,
@@ -144,7 +138,7 @@ public function __toString()
144138
"onItemRemove" => $this->onItemRemove,
145139
"onItemCreate" => $this->onItemCreate,
146140
]
147-
]) .
141+
], JSON_HEX_APOS | JSON_HEX_QUOT) .
148142
"' ";
149143

150144
return $buffer . '/>';
@@ -174,6 +168,11 @@ private function convertOption($val)
174168
"value" => $val,
175169
"text" => $val,
176170
];
171+
} else if (is_array($val) && count($val) === 2) {
172+
return [
173+
"value" => $val[0],
174+
"text" => $val[1],
175+
];
177176
} else {
178177
// can't log cause don't have $w
179178
// LogService::getInstance($w)->setLogger("html5autocomplete")->error("option did not match format", $val);

0 commit comments

Comments
 (0)