Skip to content

Commit ad716d7

Browse files
committed
update: remove XSS cleaner and remove XSS vulnerabilities
We've been mostly relying on the 3rd party xss cleaner to make sure user submitted content is clean. This PR fixes up any leftover holes in the bbcode parser that allow xss vulnerabilities, and as a result, the 3rd party library isn't needed anymore. It cleans responsibly by first, running `htmlspecialchars()` over the content, followed by sanitizing the untrusted urls and whitelisting their protocol.
1 parent 849b751 commit ad716d7

18 files changed

+152
-127
lines changed

app/Helpers/Bbcode.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class Bbcode
153153
'block' => true,
154154
],
155155
'namedquote' => [
156-
'openBbcode' => '/^\[quote=([^<>"]*?)\]/i',
156+
'openBbcode' => '/^\[quote=(.*?)\]/i',
157157
'closeBbcode' => '[/quote]',
158158
'openHtml' => '<blockquote><i class="fas fa-quote-left"></i> <cite>Quoting $1:</cite><p>',
159159
'closeHtml' => '</p></blockquote>',
@@ -278,6 +278,9 @@ class Bbcode
278278
*/
279279
public function parse(?string $source, bool $replaceLineBreaks = true): string
280280
{
281+
$source ??= '';
282+
$source = htmlspecialchars($source, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
283+
281284
// Replace all void elements since they don't have closing tags
282285
$source = str_replace('[*]', '<li>', (string) $source);
283286
$source = str_replace('[hr]', '<hr>', $source);
@@ -320,7 +323,7 @@ public function parse(?string $source, bool $replaceLineBreaks = true): string
320323
$source ?? ''
321324
);
322325
$source = preg_replace_callback(
323-
'/\[video="youtube"]([a-z0-9_-]{11})\[\/video]/i',
326+
'/\[video=&quot;youtube&quot;]([a-z0-9_-]{11})\[\/video]/i',
324327
static fn ($matches) => '<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/'.$matches[1].'?rel=0" allow="autoplay; encrypted-media" allowfullscreen></iframe>',
325328
$source ?? ''
326329
);

app/Helpers/Linkify.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@ public function linky(string $text): string
2727
$validator = new Validator(
2828
false, // bool - if should use top level domain to match urls without scheme
2929
[], // string[] - array of blacklisted schemes
30-
[], // string[] - array of whitelisted schemes
30+
[
31+
'http',
32+
'https',
33+
'irc',
34+
'ftp',
35+
'sftp',
36+
'magnet',
37+
], // string[] - array of whitelisted schemes
3138
true // bool - if should match emails (if match by TLD set to "false" - will match only "mailto" urls)
3239
);
3340

app/Http/Livewire/BbcodeInput.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
use App\Helpers\Bbcode;
2020
use Livewire\Component;
21-
use voku\helper\AntiXSS;
2221

2322
class BbcodeInput extends Component
2423
{
@@ -39,13 +38,13 @@ final public function mount(string $name, string $label, bool $required = false,
3938
$this->name = $name;
4039
$this->label = $label;
4140
$this->isRequired = $required;
42-
$this->contentBbcode = $content === null ? (old($name) ?? '') : htmlspecialchars_decode($content);
41+
$this->contentBbcode = $content ?? old($name) ?? '';
4342
}
4443

4544
final public function updatedIsPreviewEnabled(): void
4645
{
4746
if ($this->isPreviewEnabled) {
48-
$this->contentHtml = (new Bbcode())->parse(htmlspecialchars((new AntiXSS())->xss_clean($this->contentBbcode), ENT_NOQUOTES));
47+
$this->contentHtml = (new Bbcode())->parse($this->contentBbcode);
4948
}
5049
}
5150

app/Http/Resources/ChatMessageResource.php

+4-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use App\Helpers\Bbcode;
2020
use hdvinnie\LaravelJoyPixels\LaravelJoyPixels;
2121
use Illuminate\Http\Resources\Json\JsonResource;
22-
use voku\helper\AntiXSS;
2322

2423
/**
2524
* @mixin \App\Models\Message
@@ -33,17 +32,14 @@ class ChatMessageResource extends JsonResource
3332
*/
3433
public function toArray($request): array
3534
{
36-
$emojiOne = app()->make(LaravelJoyPixels::class);
35+
$emojiOne = new LaravelJoyPixels();
3736

3837
$bbcode = new Bbcode();
38+
$logger = $bbcode->parse($this->message);
39+
$logger = $emojiOne->toImage($logger);
3940

4041
if ($this->user_id == 1) {
41-
$logger = $bbcode->parse('<div class="align-left"><div class="chatTriggers">'.$this->message.'</div></div>');
42-
$logger = $emojiOne->toImage($logger);
4342
$logger = str_replace('a href="/#', 'a trigger="bot" class="chatTrigger" href="/#', $logger);
44-
} else {
45-
$logger = $bbcode->parse('<div class="align-left">'.$this->message.'</div>');
46-
$logger = $emojiOne->toImage($logger);
4743
}
4844

4945
return [
@@ -52,7 +48,7 @@ public function toArray($request): array
5248
'user' => new ChatUserResource($this->whenLoaded('user')),
5349
'receiver' => new ChatUserResource($this->whenLoaded('receiver')),
5450
'chatroom' => new ChatRoomResource($this->whenLoaded('chatroom')),
55-
'message' => (new AntiXSS())->xss_clean($logger),
51+
'message' => $logger,
5652
'created_at' => $this->created_at->toIso8601String(),
5753
'updated_at' => $this->updated_at->toIso8601String(),
5854
];

app/Models/Article.php

-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use App\Traits\Auditable;
2222
use Illuminate\Database\Eloquent\Factories\HasFactory;
2323
use Illuminate\Database\Eloquent\Model;
24-
use voku\helper\AntiXSS;
2524

2625
/**
2726
* App\Models\Article.
@@ -69,14 +68,6 @@ public function comments(): \Illuminate\Database\Eloquent\Relations\MorphMany
6968
return $this->morphMany(Comment::class, 'commentable');
7069
}
7170

72-
/**
73-
* Set The Articles Content After Its Been Purified.
74-
*/
75-
public function setContentAttribute(?string $value): void
76-
{
77-
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
78-
}
79-
8071
/**
8172
* Parse Content And Return Valid HTML.
8273
*/

app/Models/Comment.php

-9
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use Illuminate\Database\Eloquent\Builder;
2424
use Illuminate\Database\Eloquent\Factories\HasFactory;
2525
use Illuminate\Database\Eloquent\Model;
26-
use voku\helper\AntiXSS;
2726

2827
/**
2928
* App\Models\Comment.
@@ -111,14 +110,6 @@ public function scopeParent(Builder $builder): void
111110
$builder->whereNull('parent_id');
112111
}
113112

114-
/**
115-
* Set The Articles Content After Its Been Purified.
116-
*/
117-
public function setContentAttribute(?string $value): void
118-
{
119-
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
120-
}
121-
122113
/**
123114
* Parse Content And Return Valid HTML.
124115
*/

app/Models/Message.php

-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use App\Helpers\Bbcode;
2020
use Illuminate\Database\Eloquent\Factories\HasFactory;
2121
use Illuminate\Database\Eloquent\Model;
22-
use voku\helper\AntiXSS;
2322

2423
/**
2524
* App\Models\Message.
@@ -91,14 +90,6 @@ public function chatroom(): \Illuminate\Database\Eloquent\Relations\BelongsTo
9190
return $this->belongsTo(Chatroom::class);
9291
}
9392

94-
/**
95-
* Set The Chat Message After Its Been Purified.
96-
*/
97-
public function setMessageAttribute(string $value): void
98-
{
99-
$this->attributes['message'] = htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
100-
}
101-
10293
/**
10394
* Parse Content And Return Valid HTML.
10495
*/

app/Models/Note.php

-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use App\Traits\Auditable;
2121
use Illuminate\Database\Eloquent\Factories\HasFactory;
2222
use Illuminate\Database\Eloquent\Model;
23-
use voku\helper\AntiXSS;
2423

2524
/**
2625
* App\Models\Note.
@@ -79,14 +78,6 @@ public function staffuser(): \Illuminate\Database\Eloquent\Relations\BelongsTo
7978
]);
8079
}
8180

82-
/**
83-
* Set Message After It's Been Purified.
84-
*/
85-
public function setMessageAttribute(?string $value): void
86-
{
87-
$this->attributes['message'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
88-
}
89-
9081
/**
9182
* Parse Message And Return Valid HTML.
9283
*/

app/Models/Playlist.php

-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use App\Traits\Auditable;
2222
use Illuminate\Database\Eloquent\Factories\HasFactory;
2323
use Illuminate\Database\Eloquent\Model;
24-
use voku\helper\AntiXSS;
2524

2625
/**
2726
* App\Models\Playlist.
@@ -78,14 +77,6 @@ public function comments(): \Illuminate\Database\Eloquent\Relations\MorphMany
7877
return $this->morphMany(Comment::class, 'commentable');
7978
}
8079

81-
/**
82-
* Set The Playlists Description After It's Been Purified.
83-
*/
84-
public function setDescriptionAttribute(?string $value): void
85-
{
86-
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
87-
}
88-
8980
/**
9081
* Parse Description And Return Valid HTML.
9182
*/

app/Models/Post.php

-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use App\Traits\Auditable;
2222
use Illuminate\Database\Eloquent\Factories\HasFactory;
2323
use Illuminate\Database\Eloquent\Model;
24-
use voku\helper\AntiXSS;
2524

2625
/**
2726
* App\Models\Post.
@@ -156,14 +155,6 @@ public function scopeAuthorized(
156155
);
157156
}
158157

159-
/**
160-
* Set The Posts Content After Its Been Purified.
161-
*/
162-
public function setContentAttribute(?string $value): void
163-
{
164-
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
165-
}
166-
167158
/**
168159
* Parse Content And Return Valid HTML.
169160
*/

app/Models/PrivateMessage.php

-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use App\Helpers\Linkify;
2121
use Illuminate\Database\Eloquent\Factories\HasFactory;
2222
use Illuminate\Database\Eloquent\Model;
23-
use voku\helper\AntiXSS;
2423

2524
/**
2625
* App\Models\PrivateMessage.
@@ -64,14 +63,6 @@ public function conversation(): \Illuminate\Database\Eloquent\Relations\BelongsT
6463
return $this->belongsTo(Conversation::class);
6564
}
6665

67-
/**
68-
* Set The PM Message After Its Been Purified.
69-
*/
70-
public function setMessageAttribute(string $value): void
71-
{
72-
$this->attributes['message'] = htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
73-
}
74-
7566
/**
7667
* Parse Content And Return Valid HTML.
7768
*/

app/Models/TicketNote.php

-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
use App\Helpers\Linkify;
2020
use Illuminate\Database\Eloquent\Model;
21-
use voku\helper\AntiXSS;
2221

2322
/**
2423
* App\Models\TicketNote.
@@ -59,14 +58,6 @@ public function ticket(): \Illuminate\Database\Eloquent\Relations\BelongsTo
5958
return $this->belongsTo(Ticket::class);
6059
}
6160

62-
/**
63-
* Set Message After It's Been Purified.
64-
*/
65-
public function setMessageAttribute(?string $value): void
66-
{
67-
$this->attributes['message'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
68-
}
69-
7061
/**
7162
* Parse Message And Return Valid HTML.
7263
*/

app/Models/Torrent.php

-9
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
use Illuminate\Database\Eloquent\Model;
3030
use Illuminate\Database\Eloquent\SoftDeletes;
3131
use Laravel\Scout\Searchable;
32-
use voku\helper\AntiXSS;
3332

3433
/**
3534
* App\Models\Torrent.
@@ -751,14 +750,6 @@ public function trump(): \Illuminate\Database\Eloquent\Relations\HasOne
751750
return $this->hasOne(TorrentTrump::class);
752751
}
753752

754-
/**
755-
* Set The Torrents Description After Its Been Purified.
756-
*/
757-
public function setDescriptionAttribute(?string $value): void
758-
{
759-
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
760-
}
761-
762753
/**
763754
* Parse Description And Return Valid HTML.
764755
*/

app/Models/TorrentRequest.php

-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use App\Traits\Auditable;
2222
use Illuminate\Database\Eloquent\Factories\HasFactory;
2323
use Illuminate\Database\Eloquent\Model;
24-
use voku\helper\AntiXSS;
2524

2625
/**
2726
* App\Models\TorrentRequest.
@@ -215,14 +214,6 @@ public function claim(): \Illuminate\Database\Eloquent\Relations\HasOne
215214
return $this->hasOne(TorrentRequestClaim::class, 'request_id');
216215
}
217216

218-
/**
219-
* Set The Requests Description After Its Been Purified.
220-
*/
221-
public function setDescriptionAttribute(?string $value): void
222-
{
223-
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
224-
}
225-
226217
/**
227218
* Parse Description And Return Valid HTML.
228219
*/

app/Models/User.php

-17
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
use Illuminate\Foundation\Auth\User as Authenticatable;
2929
use Illuminate\Notifications\Notifiable;
3030
use Laravel\Fortify\TwoFactorAuthenticatable;
31-
use voku\helper\AntiXSS;
3231

3332
/**
3433
* App\Models\User.
@@ -1181,14 +1180,6 @@ public function getFormattedBufferAttribute(): string
11811180
return StringHelper::formatBytes($bytes);
11821181
}
11831182

1184-
/**
1185-
* Set The Users Signature After It's Been Purified.
1186-
*/
1187-
public function setSignatureAttribute(?string $value): void
1188-
{
1189-
$this->attributes['signature'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
1190-
}
1191-
11921183
/**
11931184
* Returns the HTML of the user's signature.
11941185
*/
@@ -1199,14 +1190,6 @@ public function getSignatureHtmlAttribute(): string
11991190
return (new Linkify())->linky($bbcode->parse($this->signature));
12001191
}
12011192

1202-
/**
1203-
* Set The Users About Me After It's Been Purified.
1204-
*/
1205-
public function setAboutAttribute(?string $value): void
1206-
{
1207-
$this->attributes['about'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
1208-
}
1209-
12101193
/**
12111194
* Parse About Me And Return Valid HTML.
12121195
*/

composer.json

-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"spatie/ssl-certificate": "^2.6.8",
4242
"symfony/dom-crawler": "^6.4.16",
4343
"theodorejb/polycast": "dev-master",
44-
"voku/anti-xss": "^4.1.42",
4544
"vstelmakh/url-highlight": "^3.1.1"
4645
},
4746
"require-dev": {

0 commit comments

Comments
 (0)