| @@ -72,9 +72,16 @@ | ||
| 72 | 72 | } |
| 73 | 73 | |
| 74 | 74 | public function to($email, $name = '') |
| 75 | 75 | { |
| 76 | + // wp_mail() splits a To string on commas, so a display name carrying a | |
| 77 | + // comma/semicolon injects extra recipients; angle brackets/quotes reframe | |
| 78 | + // the address and CR/LF inject headers. Strip them before building it. | |
| 76 | 79 | if ($name) { |
| 80 | + $name = trim(preg_replace('/[,;<>"\r\n\t]+/', ' ', $name)); | |
| 81 | + } | |
| 82 | + | |
| 83 | + if ($name) { | |
| 77 | 84 | $this->to = $name . ' <' . $email . '>'; |
| 78 | 85 | } else { |
| 79 | 86 | $this->to = $email; |
| 80 | 87 | } |
| @@ -124,22 +131,27 @@ | ||
| 124 | 131 | } else { |
| 125 | 132 | $headers[] = 'Content-Type: text/plain; charset=UTF-8'; |
| 126 | 133 | } |
| 127 | 134 | |
| 135 | + // A CR/LF in any header value starts an attacker-controlled header line. | |
| 136 | + $stripCrlf = function ($value) { | |
| 137 | + return str_replace(["\r", "\n"], '', $value); | |
| 138 | + }; | |
| 139 | + | |
| 128 | 140 | if ($this->from) { |
| 129 | - $headers[] = 'From: ' . $this->from; | |
| 141 | + $headers[] = 'From: ' . $stripCrlf($this->from); | |
| 130 | 142 | } |
| 131 | 143 | |
| 132 | 144 | if ($this->cc) { |
| 133 | - $headers[] = 'Cc: ' . implode(',', $this->cc); | |
| 145 | + $headers[] = 'Cc: ' . $stripCrlf(implode(',', $this->cc)); | |
| 134 | 146 | } |
| 135 | 147 | |
| 136 | 148 | if ($this->bcc) { |
| 137 | - $headers[] = 'Bcc: ' . implode(',', $this->bcc); | |
| 149 | + $headers[] = 'Bcc: ' . $stripCrlf(implode(',', $this->bcc)); | |
| 138 | 150 | } |
| 139 | 151 | |
| 140 | 152 | if ($this->replyTo) { |
| 141 | - $headers[] = 'Reply-To: ' . $this->replyTo; | |
| 153 | + $headers[] = 'Reply-To: ' . $stripCrlf($this->replyTo); | |
| 142 | 154 | } |
| 143 | 155 | |
| 144 | 156 | return wp_mail($this->to, $this->subject, $this->body, $headers); |
| 145 | 157 | } |