summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2026-03-06 11:06:13 -0600
committergrothedev <grothedev@gmail.com>2026-03-06 11:06:13 -0600
commit4e4827e9f5ef58085e032a88276dc8ae2b4462b0 (patch)
tree0f818a0e0b1fec7e8495e3175b14bc44627aed6d
parent0609605200821c58b2b3f49248998fc10ea71f97 (diff)
fix some security issues
-rw-r--r--app/Http/Controllers/SiteController.php14
-rwxr-xr-xresources/views/template.blade.php1
-rwxr-xr-xroutes/web.php92
3 files changed, 51 insertions, 56 deletions
diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php
index a970bde..0d6aa6c 100644
--- a/app/Http/Controllers/SiteController.php
+++ b/app/Http/Controllers/SiteController.php
@@ -112,17 +112,13 @@ class SiteController extends Controller
public function search4chan(Request $req){
$query = $req->input('query');
- $board = "";
Log::info('search4chan()');
- if ( $req->input('board') != null) {
- $board = $req->input('board').'/'.$query;
- }
- $cmd = "python ./4chansearch.py ${query}";
- if ( $board != "" ){
- $cmd .= "-b ${board}";
+
+ $cmd = 'python ./4chansearch.py ' . escapeshellarg($query);
+ if ($req->input('board') != null) {
+ $cmd .= ' -b ' . escapeshellarg($req->input('board'));
}
- $shellcmd = escapeshellcmd($cmd);
- exec($shellcmd, $res, $ret);
+ exec($cmd, $res, $ret);
return $res;
}
diff --git a/resources/views/template.blade.php b/resources/views/template.blade.php
index 20a25f8..4305656 100755
--- a/resources/views/template.blade.php
+++ b/resources/views/template.blade.php
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TG</title>
+ <meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="stylesheet" href="/css/home.css">
@yield('head')
</head>
diff --git a/routes/web.php b/routes/web.php
index 7e418bb..8370fd2 100755
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,6 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\SiteController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\WritingController;
@@ -13,69 +14,44 @@ Route::get('/', function () {
return view('home');
});
-Route::get('/env', [SiteController::class, 'env']);
-
Route::get('/dq', [SiteController::class, 'duneQuote']);
-//temporarily desabled for security
-//Route::post('/f', [SiteController::class, 'uploadFiles']); //TODO later use file resource or FPR service
Route::get('/f/{path}', function($path){
+ // Sanitize: strip directory traversal, only allow basename
+ $path = basename($path);
$storpath = storage_path('app/public/uploads/' . $path);
if (!file_exists($storpath)){
- return response()->json(['error' => 'file not found ' . $storpath]);
+ abort(404, 'File not found.');
}
- $mime = Storage::mimeType($storpath);
-
- //return response()->json([
- // 'success' => true,
- // 'message' => 'file uploaded',
- // 'url' => $storpath,
- //]);
-
-// this is how you would return the file which would download the file in browser
+ $mime = Storage::mimeType('public/uploads/' . $path);
return response()->file($storpath, [
'Content-Type' => $mime,
'Content-Disposition' => 'inline; filename="'.$path.'"'
]);
});
-/*Route::get('/yt', function () {
- return view('youtube');
-});*/
-
-// Get video info and direct stream URL (recommended approach)
-//Route::post('/yt/info', [YouTubeController::class, 'getVideoInfo']);
-// Stream video through Laravel proxy (with range support)
-//Route::get('/yt/stream', [YouTubeController::class, 'streamVideo']);
-// Stream directly via yt-dlp (not recommended - no seeking support)
-//Route::get('/yt/stream-direct', [YouTubeController::class, 'streamDirect']);
-// Get available formats for a video
-//Route::post('/yt/formats', [YouTubeController::class, 'getFormats']);
-
Route::get('/mu/{path}', function($path){
+ // Sanitize: strip directory traversal, only allow basename
+ $path = basename($path);
$storpath = storage_path('app/public/band/' . $path);
if (!file_exists($storpath)){
- return response()->json(['error' => 'file not found ' . $storpath]);
+ abort(404, 'File not found.');
}
- $mime = Storage::mimeType($storpath);
+ $mime = Storage::mimeType('public/band/' . $path);
return response()->file($storpath, [
'Content-Type' => $mime,
'Content-Disposition' => 'inline; filename="'.$path.'"'
]);
});
-Route::resource('w', WritingController::class);
-Route::resource('l', LinkController::class)->only(['index', 'show', 'create']);
-Route::resource('f', \App\Http\Controllers\FileController::class);
+// Public read-only routes
+Route::resource('w', WritingController::class)->only(['index', 'show']);
+Route::resource('l', LinkController::class)->only(['index', 'show']);
+Route::resource('f', \App\Http\Controllers\FileController::class)->only(['index', 'show']);
Route::get('/test', [SiteController::class, 'test']);
-Route::get('/4', [SiteController::class, 'search4chan']);
-
-Route::get('/dashboard', function () {
- return view('dashboard');
-})->middleware(['auth', 'verified'])->name('dashboard');
-
+// Auth-protected routes
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
@@ -83,36 +59,49 @@ Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/dashboard/stats', [DashboardController::class, 'statistics'])->name('dashboard.stats');
Route::resource('l', LinkController::class)->only(['create', 'store', 'edit', 'update', 'destroy']);
+ Route::resource('w', WritingController::class)->only(['create', 'store', 'edit', 'update', 'destroy']);
+ Route::resource('f', \App\Http\Controllers\FileController::class)->only(['create', 'store', 'edit', 'update', 'destroy']);
});
+// Admin-only routes
Route::middleware(['auth', \App\Http\Middleware\Admin::class])->group(function () {
Route::get('/admin', [AdminController::class, 'index'])->name('admin');
Route::post('/l/import', [LinkController::class, 'import'])->name('l.import');
+ Route::get('/env', [SiteController::class, 'env']);
+ Route::get('/4', [SiteController::class, 'search4chan']);
});
+// Session updates - auth required, whitelisted keys only
Route::post('/update-session', function(Request $req){
if ($req->input('key') && $req->input('value')){
+ $allowed = ['theme', 'locale', 'sidebar'];
+ if (!in_array($req->input('key'), $allowed)) {
+ abort(403, 'Session key not allowed.');
+ }
session()->put($req->input('key'), $req->input('value'));
return redirect()->back();
}
-});
+})->middleware('auth');
require __DIR__.'/auth.php';
-Route::get('/toy/{v}', function($v){
+Route::get('/toy/{v?}', function($v = null){
if (is_null($v)){
return view('toys.index');
}
+ // Only allow alphanumeric and hyphens
+ if (!preg_match('/^[a-zA-Z0-9\-]+$/', $v)) {
+ abort(404);
+ }
+ if (!view()->exists('toys.'.$v)) {
+ abort(404);
+ }
return view('toys.'.$v);
});
-// Audio stream endpoint - streams from ffmpeg
+// Audio stream - requires auth to prevent abuse
Route::get('/stream/audio', function() {
- // Configure ffmpeg command to output mp3 stream
- // Example: stream from microphone or other source
- //$command = 'ffmpeg -re -i /dev/null -f mp3 -';
- //$command = 'ffmpeg -re -f pulse -i defaul -f mp3 -';
- $command = 'ffmpeg -re -i http://somafm.com/vaporwaves.pls :-f mp3 -';
+ $command = 'ffmpeg -re -i http://somafm.com/vaporwaves.pls -f mp3 -';
return response()->stream(function() use ($command) {
$handle = popen($command, 'r');
@@ -132,8 +121,17 @@ Route::get('/stream/audio', function() {
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no'
]);
-});
+})->middleware('auth');
Route::get('/newtab', function() {
return view('newtab');
});
+
+// Catch-all: only allow specific whitelisted views
+Route::get('/{v}', function($v){
+ $allowed = ['notes', 'kyanite', 'marked', 'v'];
+ if (!in_array($v, $allowed)) {
+ abort(404);
+ }
+ return view($v);
+})->where('v', '[a-zA-Z0-9\-]+');