Merge pull request #3222 from Roardom/bbcode-improvements

(Update) Remove XSS cleaner and remove XSS vulnerabilities
This commit is contained in:
HDVinnie
2025-01-19 23:01:33 -05:00
committed by GitHub
18 changed files with 152 additions and 127 deletions

View File

@@ -153,7 +153,7 @@ class Bbcode
'block' => true,
],
'namedquote' => [
'openBbcode' => '/^\[quote=([^<>"]*?)\]/i',
'openBbcode' => '/^\[quote=(.*?)\]/i',
'closeBbcode' => '[/quote]',
'openHtml' => '<blockquote><i class="fas fa-quote-left"></i> <cite>Quoting $1:</cite><p>',
'closeHtml' => '</p></blockquote>',
@@ -278,6 +278,9 @@ class Bbcode
*/
public function parse(?string $source, bool $replaceLineBreaks = true): string
{
$source ??= '';
$source = htmlspecialchars($source, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
// Replace all void elements since they don't have closing tags
$source = str_replace('[*]', '<li>', (string) $source);
$source = str_replace('[hr]', '<hr>', $source);
@@ -320,7 +323,7 @@ class Bbcode
$source ?? ''
);
$source = preg_replace_callback(
'/\[video="youtube"]([a-z0-9_-]{11})\[\/video]/i',
'/\[video=&quot;youtube&quot;]([a-z0-9_-]{11})\[\/video]/i',
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>',
$source ?? ''
);

View File

@@ -27,7 +27,14 @@ class Linkify
$validator = new Validator(
false, // bool - if should use top level domain to match urls without scheme
[], // string[] - array of blacklisted schemes
[], // string[] - array of whitelisted schemes
[
'http',
'https',
'irc',
'ftp',
'sftp',
'magnet',
], // string[] - array of whitelisted schemes
true // bool - if should match emails (if match by TLD set to "false" - will match only "mailto" urls)
);

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Helpers\Bbcode;
use Livewire\Component;
use voku\helper\AntiXSS;
class BbcodeInput extends Component
{
@@ -39,13 +38,13 @@ class BbcodeInput extends Component
$this->name = $name;
$this->label = $label;
$this->isRequired = $required;
$this->contentBbcode = $content === null ? (old($name) ?? '') : htmlspecialchars_decode($content);
$this->contentBbcode = $content ?? old($name) ?? '';
}
final public function updatedIsPreviewEnabled(): void
{
if ($this->isPreviewEnabled) {
$this->contentHtml = (new Bbcode())->parse(htmlspecialchars((new AntiXSS())->xss_clean($this->contentBbcode), ENT_NOQUOTES));
$this->contentHtml = (new Bbcode())->parse($this->contentBbcode);
}
}

View File

@@ -19,7 +19,6 @@ namespace App\Http\Resources;
use App\Helpers\Bbcode;
use hdvinnie\LaravelJoyPixels\LaravelJoyPixels;
use Illuminate\Http\Resources\Json\JsonResource;
use voku\helper\AntiXSS;
/**
* @mixin \App\Models\Message
@@ -33,17 +32,14 @@ class ChatMessageResource extends JsonResource
*/
public function toArray($request): array
{
$emojiOne = app()->make(LaravelJoyPixels::class);
$emojiOne = new LaravelJoyPixels();
$bbcode = new Bbcode();
$logger = $bbcode->parse($this->message);
$logger = $emojiOne->toImage($logger);
if ($this->user_id == 1) {
$logger = $bbcode->parse('<div class="align-left"><div class="chatTriggers">'.$this->message.'</div></div>');
$logger = $emojiOne->toImage($logger);
$logger = str_replace('a href="/#', 'a trigger="bot" class="chatTrigger" href="/#', $logger);
} else {
$logger = $bbcode->parse('<div class="align-left">'.$this->message.'</div>');
$logger = $emojiOne->toImage($logger);
}
return [
@@ -52,7 +48,7 @@ class ChatMessageResource extends JsonResource
'user' => new ChatUserResource($this->whenLoaded('user')),
'receiver' => new ChatUserResource($this->whenLoaded('receiver')),
'chatroom' => new ChatRoomResource($this->whenLoaded('chatroom')),
'message' => (new AntiXSS())->xss_clean($logger),
'message' => $logger,
'created_at' => $this->created_at->toIso8601String(),
'updated_at' => $this->updated_at->toIso8601String(),
];

View File

@@ -21,7 +21,6 @@ use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Article.
@@ -69,14 +68,6 @@ class Article extends Model
return $this->morphMany(Comment::class, 'commentable');
}
/**
* Set The Articles Content After Its Been Purified.
*/
public function setContentAttribute(?string $value): void
{
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Content And Return Valid HTML.
*/

View File

@@ -23,7 +23,6 @@ use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Comment.
@@ -111,14 +110,6 @@ class Comment extends Model
$builder->whereNull('parent_id');
}
/**
* Set The Articles Content After Its Been Purified.
*/
public function setContentAttribute(?string $value): void
{
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Content And Return Valid HTML.
*/

View File

@@ -19,7 +19,6 @@ namespace App\Models;
use App\Helpers\Bbcode;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Message.
@@ -91,14 +90,6 @@ class Message extends Model
return $this->belongsTo(Chatroom::class);
}
/**
* Set The Chat Message After Its Been Purified.
*/
public function setMessageAttribute(string $value): void
{
$this->attributes['message'] = htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Content And Return Valid HTML.
*/

View File

@@ -20,7 +20,6 @@ use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Note.
@@ -79,14 +78,6 @@ class Note extends Model
]);
}
/**
* Set Message After It's Been Purified.
*/
public function setMessageAttribute(?string $value): void
{
$this->attributes['message'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Message And Return Valid HTML.
*/

View File

@@ -21,7 +21,6 @@ use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Playlist.
@@ -78,14 +77,6 @@ class Playlist extends Model
return $this->morphMany(Comment::class, 'commentable');
}
/**
* Set The Playlists Description After It's Been Purified.
*/
public function setDescriptionAttribute(?string $value): void
{
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Description And Return Valid HTML.
*/

View File

@@ -21,7 +21,6 @@ use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\Post.
@@ -156,14 +155,6 @@ class Post extends Model
);
}
/**
* Set The Posts Content After Its Been Purified.
*/
public function setContentAttribute(?string $value): void
{
$this->attributes['content'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Content And Return Valid HTML.
*/

View File

@@ -20,7 +20,6 @@ use App\Helpers\Bbcode;
use App\Helpers\Linkify;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\PrivateMessage.
@@ -64,14 +63,6 @@ class PrivateMessage extends Model
return $this->belongsTo(Conversation::class);
}
/**
* Set The PM Message After Its Been Purified.
*/
public function setMessageAttribute(string $value): void
{
$this->attributes['message'] = htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Content And Return Valid HTML.
*/

View File

@@ -18,7 +18,6 @@ namespace App\Models;
use App\Helpers\Linkify;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\TicketNote.
@@ -59,14 +58,6 @@ class TicketNote extends Model
return $this->belongsTo(Ticket::class);
}
/**
* Set Message After It's Been Purified.
*/
public function setMessageAttribute(?string $value): void
{
$this->attributes['message'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Message And Return Valid HTML.
*/

View File

@@ -29,7 +29,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Scout\Searchable;
use voku\helper\AntiXSS;
/**
* App\Models\Torrent.
@@ -751,14 +750,6 @@ class Torrent extends Model
return $this->hasOne(TorrentTrump::class);
}
/**
* Set The Torrents Description After Its Been Purified.
*/
public function setDescriptionAttribute(?string $value): void
{
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Description And Return Valid HTML.
*/

View File

@@ -21,7 +21,6 @@ use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use voku\helper\AntiXSS;
/**
* App\Models\TorrentRequest.
@@ -215,14 +214,6 @@ class TorrentRequest extends Model
return $this->hasOne(TorrentRequestClaim::class, 'request_id');
}
/**
* Set The Requests Description After Its Been Purified.
*/
public function setDescriptionAttribute(?string $value): void
{
$this->attributes['description'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse Description And Return Valid HTML.
*/

View File

@@ -28,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use voku\helper\AntiXSS;
/**
* App\Models\User.
@@ -1181,14 +1180,6 @@ class User extends Authenticatable implements MustVerifyEmail
return StringHelper::formatBytes($bytes);
}
/**
* Set The Users Signature After It's Been Purified.
*/
public function setSignatureAttribute(?string $value): void
{
$this->attributes['signature'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Returns the HTML of the user's signature.
*/
@@ -1199,14 +1190,6 @@ class User extends Authenticatable implements MustVerifyEmail
return (new Linkify())->linky($bbcode->parse($this->signature));
}
/**
* Set The Users About Me After It's Been Purified.
*/
public function setAboutAttribute(?string $value): void
{
$this->attributes['about'] = $value === null ? null : htmlspecialchars((new AntiXSS())->xss_clean($value), ENT_NOQUOTES);
}
/**
* Parse About Me And Return Valid HTML.
*/

View File

@@ -41,7 +41,6 @@
"spatie/ssl-certificate": "^2.6.8",
"symfony/dom-crawler": "^6.4.16",
"theodorejb/polycast": "dev-master",
"voku/anti-xss": "^4.1.42",
"vstelmakh/url-highlight": "^3.1.1"
},
"require-dev": {

View File

@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class () extends Migration {
public function up(): void
{
DB::table('articles')
->lazyById()
->each(function (object $article): void {
/** @var object{id: int, content: string} $article */
DB::table('articles')
->where('id', '=', $article->id)
->update([
'content' => htmlspecialchars_decode($article->content),
]);
});
DB::table('comments')
->lazyById()
->each(function (object $comment): void {
/** @var object{id: int, content: string} $comment */
DB::table('comments')
->where('id', '=', $comment->id)
->update([
'content' => htmlspecialchars_decode($comment->content),
]);
});
DB::table('messages')
->lazyById()
->each(function (object $message): void {
/** @var object{id: int, message: string} $message */
DB::table('messages')
->where('id', '=', $message->id)
->update([
'message' => htmlspecialchars_decode($message->message),
]);
});
DB::table('user_notes')
->lazyById()
->each(function (object $userNote): void {
/** @var object{id: int, message: string} $userNote */
DB::table('user_notes')
->where('id', '=', $userNote->id)
->update([
'message' => htmlspecialchars_decode($userNote->message),
]);
});
DB::table('playlists')
->lazyById()
->each(function (object $playlist): void {
/** @var object{id: int, description: string} $playlist */
DB::table('playlists')
->where('id', '=', $playlist->id)
->update([
'description' => htmlspecialchars_decode($playlist->description),
]);
});
DB::table('posts')
->lazyById()
->each(function (object $post): void {
/** @var object{id: int, content: string} $post */
DB::table('posts')
->where('id', '=', $post->id)
->update([
'content' => htmlspecialchars_decode($post->content),
]);
});
DB::table('private_messages')
->lazyById()
->each(function (object $privateMessage): void {
/** @var object{id: int, message: string} $privateMessage */
DB::table('private_messages')
->where('id', '=', $privateMessage->id)
->update([
'message' => htmlspecialchars_decode($privateMessage->message),
]);
});
DB::table('ticket_notes')
->lazyById()
->each(function (object $ticketNote): void {
/** @var object{id: int, message: string} $ticketNote */
DB::table('ticket_notes')
->where('id', '=', $ticketNote->id)
->update([
'message' => htmlspecialchars_decode($ticketNote->message),
]);
});
DB::table('torrents')
->lazyById()
->each(function (object $torrent): void {
/** @var object{id: int, description: string} $torrent */
DB::table('torrents')
->where('id', '=', $torrent->id)
->update([
'description' => htmlspecialchars_decode($torrent->description),
]);
});
DB::table('requests')
->lazyById()
->each(function (object $request): void {
/** @var object{id: int, description: string} $request */
DB::table('requests')
->where('id', '=', $request->id)
->update([
'description' => htmlspecialchars_decode($request->description),
]);
});
DB::table('users')
->lazyById()
->each(function (object $user): void {
/** @var object{id: int, about: string, signature: string} $user */
DB::table('users')
->where('id', '=', $user->id)
->update([
'about' => htmlspecialchars_decode($user->about),
'signature' => htmlspecialchars_decode($user->signature),
]);
});
}
};

View File

@@ -470,11 +470,6 @@ parameters:
count: 1
path: app/Http/Resources/ChatMessageResource.php
-
message: "#^Unable to resolve the template type TXssCleanInput in call to method voku\\\\helper\\\\AntiXSS\\:\\:xss_clean\\(\\)$#"
count: 1
path: app/Http/Resources/ChatMessageResource.php
-
message: "#^Method App\\\\Http\\\\Resources\\\\ChatRoomResource\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1