Headline
CVE-2023-3080: Changeset 2924014 for wp-mail-catcher – WordPress Plugin Repository
The WP Mail Catcher plugin for WordPress is vulnerable to Stored Cross-Site Scripting via an email subject in versions up to, and including, 2.1.2 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
wp-mail-catcher/trunk/WpMailCatcher.php
r2922550
r2924014
7
7
Description: Logging your mail will stop you from ever losing your emails again! This fast, lightweight plugin (under 140kb in size!) is also useful for debugging or backing up your messages.
8
8
Author: James Ward
9
Version: 2.1.2
9
Version: 2.1.3
10
10
Author URI: https://jamesward.io
11
11
Donate link: https://paypal.me/jamesmward
wp-mail-catcher/trunk/readme.txt
r2922550
r2924014
3
3
Tags: mail logging, email log, email logger, logging, email logging, mail, crm
4
4
Requires at least: 4.7
5
Tested up to: 6.2.2
5
Tested up to: 6.2.3
6
6
Requires PHP: 7.4
7
Stable tag: 2.1.2
7
Stable tag: 2.1.3
8
8
License: GNU General Public License v3.0
9
9
License URI: https://raw.githubusercontent.com/JWardee/wp-mail-catcher/master/LICENSE
…
…
95
95
\== Changelog ==
96
96
97
\= 2.1.3 =
98
99
\- Fix: Improved HTML email detection
100
\- Fix: Improved XSS filtering
101
97
102
\= 2.1.2 =
98
103
wp-mail-catcher/trunk/src/GeneralHelper.php
r2922541
r2924014
28
28
public static $reviewLink;
29
29
public static $actionNameSpace;
30
public static $htmlEmailHeader = 'content-type: text/html;';
30
public static $htmlEmailHeader = 'content-type: text/html';
31
31
32
32
public static function setSettings()
…
…
133
133
}
134
134
135
public static function sanitiseForQuery($value)
135
public static function sanitiseForDbQuery($value)
136
136
{
137
137
switch (gettype($value)) {
…
…
149
149
}
150
150
151
public static function sanitiseHtmlspecialchars($input): string
152
{
153
return htmlspecialchars(
154
$input,
155
ENT\_QUOTES | ENT\_SUBSTITUTE | ENT\_HTML401,
156
null,
157
false
158
);
151
private static function getAllowedTags()
152
{
153
$tags = wp\_kses\_allowed\_html('post');
154
$tags\['style'\] = \[\];
155
return $tags;
156
}
157
158
public static function filterHtml($value)
159
{
160
return wp\_kses($value, self::getAllowedTags());
159
161
}
160
162
…
…
167
169
global $wpdb;
168
170
169
$urls = self::sanitiseForQuery($urls);
171
$urls = self::sanitiseForDbQuery($urls);
170
172
171
173
$sql = "SELECT DISTINCT post\_id
wp-mail-catcher/trunk/src/Loggers/BuddyPress.php
r2920307
r2924014
45
45
return \[
46
46
'time' => time(),
47
'email\_to' => GeneralHelper::arrayToString($tos),
48
'subject' => $bpMail->get\_subject(),
49
'message' => $this->sanitiseInput($bpMail->get\_content()),
47
'email\_to' => GeneralHelper::filterHtml(GeneralHelper::arrayToString($tos)),
48
'subject' => GeneralHelper::filterHtml($bpMail->get\_subject()),
49
'message' => GeneralHelper::filterHtml($bpMail->get\_content()),
50
50
'backtrace\_segment' => json\_encode($this->getBacktrace('bp\_send\_email')),
51
51
'status' => 1,
wp-mail-catcher/trunk/src/Loggers/LogHelper.php
r2922541
r2924014
143
143
}
144
144
145
protected function sanitiseInput($input): string
146
{
147
return htmlspecialchars(
148
$input,
149
ENT\_QUOTES | ENT\_SUBSTITUTE | ENT\_HTML401,
150
null,
151
false
152
);
153
}
154
155
protected function sanitiseAndRemoveScripts($input): string
156
{
157
return preg\_replace(
158
'#<script(.\*?)>(.\*?)</script>#is',
159
'',
160
GeneralHelper::sanitiseHtmlspecialchars($input)
161
);
162
}
163
164
145
/\*\*
165
146
\* Get the details of the method that originally triggered wp\_mail
wp-mail-catcher/trunk/src/Loggers/WpMail.php
r2922541
r2924014
43
43
return \[
44
44
'time' => time(),
45
'email\_to' => $this->sanitiseInput(GeneralHelper::arrayToString($args\['to'\])),
46
'subject' => $this->sanitiseInput($args\['subject'\]),
47
'message' => $this->sanitiseAndRemoveScripts($args\['message'\]),
45
'email\_to' => GeneralHelper::filterHtml(GeneralHelper::arrayToString($args\['to'\])),
46
'subject' => GeneralHelper::filterHtml($args\['subject'\]),
47
'message' => GeneralHelper::filterHtml($args\['message'\]),
48
48
'backtrace\_segment' => json\_encode($this->getBacktrace()),
49
49
'status' => 1,
wp-mail-catcher/trunk/src/MailAdminTable.php
r2922541
r2924014
30
30
31
31
return self::$instance;
32
}
33
34
private function runHtmlSpecialChars($value)
35
{
36
$value = GeneralHelper::filterHtml($value);
37
38
return htmlspecialchars(
39
$value,
40
ENT\_QUOTES | ENT\_SUBSTITUTE | ENT\_HTML401,
41
null,
42
false
43
);
32
44
}
33
45
…
…
59
71
60
72
$subjectDecoded = base64\_decode($subjectEncoded);
61
$subjectDecoded = GeneralHelper::sanitiseHtmlspecialchars($subjectDecoded);
73
$subjectDecoded = $this->runHtmlSpecialChars($subjectDecoded);
74
62
75
return '<span class="asci-help" data-hover-message="' . \_\_("This subject was base64 decoded") . '">
63
76
<a href="' . $this->asciSubjectHelpLink . '" target="\_blank">(?)</a>
…
…
75
88
$subjectDecoded = quoted\_printable\_decode($subjectEncoded);
76
89
$subjectDecoded = base64\_decode($subjectEncoded);
77
$subjectDecoded = GeneralHelper::sanitiseHtmlspecialchars($subjectDecoded);
90
$subjectDecoded = $this->runHtmlSpecialChars($subjectDecoded);
91
78
92
return '<span class="asci-help" data-hover-message="' . \_\_("This subject was quoted printable decoded") . '">
79
93
<a href="' . $this->asciSubjectHelpLink . '" target="\_blank">(?)</a>
…
…
82
96
}
83
97
84
return GeneralHelper::sanitiseHtmlspecialchars($subject);
98
return $this->runHtmlSpecialChars($subject);
85
99
}
86
100
…
…
126
140
\];
127
141
128
return sprintf('%1$s %2$s', GeneralHelper::sanitiseHtmlspecialchars($item\['email\_to'\]), $this->row\_actions($actions));
142
$emailTo = $this->runHtmlSpecialChars($item\['email\_to'\]);
143
144
return sprintf('%1$s %2$s', $emailTo, $this->row\_actions($actions));
129
145
}
130
146
wp-mail-catcher/trunk/src/Models/Logs.php
r2922546
r2924014
85
85
\* Sanitise each value in the array
86
86
\*/
87
array\_walk\_recursive($args, 'WpMailCatcher\\GeneralHelper::sanitiseForQuery');
87
array\_walk\_recursive($args, 'WpMailCatcher\\GeneralHelper::sanitiseForDbQuery');
88
88
89
89
$sql = "SELECT " . implode(',', $columnsToSelect) . "
…
…
181
181
} elseif (isset($result\['additional\_headers'\])) {
182
182
$result\['is\_html'\] = GeneralHelper::doesArrayContainSubString(
183
$result\['additional\_headers'\],
184
GeneralHelper::$htmlEmailHeader
183
str\_replace(' ', '', $result\['additional\_headers'\]),
184
str\_replace(' ', '', GeneralHelper::$htmlEmailHeader)
185
185
);
186
}
187
188
if (isset($result\['message'\])) {
189
$result\['message'\] = stripslashes(htmlspecialchars\_decode($result\['message'\]));
190
}
191
192
if (isset($result\['subject'\])) {
193
$result\['subject'\] = stripslashes(htmlspecialchars\_decode($result\['subject'\]));
194
}
195
196
if (isset($result\['email\_to'\])) {
197
$result\['email\_to'\] = stripslashes(htmlspecialchars\_decode($result\['email\_to'\]));
198
186
}
199
187
…
…
239
227
240
228
$ids = GeneralHelper::arrayToString($ids);
241
$ids = GeneralHelper::sanitiseForQuery($ids);
229
$ids = GeneralHelper::sanitiseForDbQuery($ids);
242
230
243
231
$wpdb->query("DELETE FROM " . $wpdb->prefix . GeneralHelper::$tableName . "
wp-mail-catcher/trunk/src/Models/Mail.php
r2920307
r2924014
21
21
22
22
add\_filter('wp\_mail\_content\_type', $updateContentType, self::$contentTypeFilterPriority);
23
24
if (isset($log\['message'\])) {
25
$log\['message'\] = GeneralHelper::filterHtml($log\['message'\]);
26
}
27
28
if (isset($log\['subject'\])) {
29
$log\['subject'\] = GeneralHelper::filterHtml($log\['subject'\]);
30
}
23
31
24
32
wp\_mail(
…
…
54
62
return in\_array($key, GeneralHelper::$csvExportLegalColumns);
55
63
}, ARRAY\_FILTER\_USE\_KEY);
64
65
if (isset($log\['message'\])) {
66
$log\['message'\] = GeneralHelper::filterHtml($log\['message'\]);
67
}
68
69
if (isset($log\['subject'\])) {
70
$log\['subject'\] = GeneralHelper::filterHtml($log\['subject'\]);
71
}
56
72
57
73
if (isset($log\['attachments'\]) && !empty($log\['attachments'\]) && is\_array($log\['attachments'\])) {
wp-mail-catcher/trunk/src/Views/HtmlMessage.php
r2920307
r2924014
1
1
<?php
2
2
3
echo $log\['message'\] ?? '';
3
use WpMailCatcher\\GeneralHelper;
4
5
echo GeneralHelper::filterHtml($log\['message'\] ?? '');