In my case I am saving a URL into my database via Eloquent Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class PageLink extends Model
{
use HasFactory;
protected $table='links';
public function setUrlAttribute(string $url):void
{
$saneUrl = trim($url);
$saneUrl = filter_var($url, FILTER_SANITIZE_URL);
$saneUrl = preg_replace("/javascript:.*/","",$saneUrl);
$saneUrl = htmlspecialchars($url);
$saneUrl = Str::of($url)->stripTags()->toString();
/**
* Regex was found into https://stackoverflow.com/a/36564776
* The idea is to extract the url from attack vectors such as:
*
* "http://example.com\"/> <IMG SRC= onmouseover=\"alert('xxs')\"/>
*
* This allows me to make the entry safe
*/
preg_match_all('/https?\:\/\/[^\" ]+/i', $saneUrl, $match);
if(is_array($match[0]) && isset($match[0][0])){
$saneUrl=$match[0][0];
}elseif (is_string($match[0])){
$saneUrl=$match[0];
}else {
$saneUrl="";
}
$this->attributes['url']=$saneUrl;
}
}
And in my blade view it is rendered as:
<a href="{{$link->url}}">{{$link->url}}</a>
But also will be provided via AJAX API as well (that is under development). Therefore I want to sanitize my input upon a valid URL. I do not validate it here because my controller does this for me:
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
Route::post("/url",function(Request $request){
$validator = Validator::create($request->all(),[
'url'=>"required|url"
],[
'url'=>"Δεν δώσατε έναν έγγυρο σύνδεσμο"
]);
if($validator->fails()){
return new JsonResponse(['msg'=>$validator->error()],400);
}
$link = new Link();
$link->url = $request->get('url');
try{
$link->save();
}catch(\Exception $e){
report($e);
return new JsonResponse(["msg"=>"Link fails to be saved"],500);
}
return new JsonResponse($link,201);
});
And here is a unit test for the model:
namespace Tests\Feature\Unit\Model;
use App\Models\Link;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UserPageLinkTest extends TestCase
{
use RefreshDatabase;
public static function urlProvider(): array
{
return [
['http://example.com<script>alert("XSS");</script>'],
["http://example.com\" onfocus=\"alert(\"Hello\")\""],
["https://example.com\' onfocus='alert('Hello')'"],
["<script>alert(\"XSS\")</script>"],
["http://example.com\"/> <IMG SRC= onmouseover=\"alert('xxs')\"/>"],
["javascript:alert(String.fromCharCode(88,83,83))"]
];
}
/**
* @dataProvider urlProvider
*/
public function testUrlSanitizedFromXss($input): void
{
$model = new Link();
$model->url = $input;
$saneUrl = $model->url;
$this->assertNotEquals($saneUrl,$input);
}
}
The idea behind the code is to make a minimal/lightweight CMS that manages some fixed pages.
In my case could I omit any other filter and just use a regular expression pattern and still be safe from XSS attacks in the input?
JaVaScRiPt:orjavajavascriptscript:orjava<new line>script:This is why @YourCommonSense's answer is so important. Otherwise you'll be playing an endless cat-and-mouse game. \$\endgroup\$