Profilfoto: Upload nach Inhalt pruefen, Loeschen nur per POST
- Slim::storeCroppedImage() vertraute dem vom Client gemeldeten Typ und uebernahm die Endung aus dem Dateinamen; "shell.php" mit Typ image/png landete als .php im public-Disk. Jetzt entscheidet getimagesizefromstring() ueber die Bytes, die Endung kommt aus dem geprueften Typ. - Foto loeschen war ein GET-Link und liess sich per fremdem <img src> ausloesen (CSRF); jetzt POST-Formular mit Token. - Ohne Konto (users.account_id) bricht Upload/Loeschen mit 404 ab statt mit einem Fatal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
22dcc0f972
commit
e009b11259
5 changed files with 90 additions and 14 deletions
|
|
@ -21,6 +21,12 @@ class ProfileImageController extends Controller
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Das Foto haengt am Konto (users.account_id). Ohne Konto gibt es nichts
|
||||
// zu speichern — sonst liefe der Zugriff darunter in einen Fatal.
|
||||
if (! $user->account) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if (! Request::get('profile_image_consent')) {
|
||||
\Session()->flash('alert-danger', __('account.profile_image_consent_required'));
|
||||
|
||||
|
|
@ -66,7 +72,7 @@ class ProfileImageController extends Controller
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user->account->profile_image) {
|
||||
if ($user->account?->profile_image) {
|
||||
\Storage::disk('public')->delete('images/profile/'.$user->account->profile_image);
|
||||
|
||||
$user->account->profile_image = null;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,17 @@ class Slim
|
|||
/**
|
||||
* Speichert ein Slim-Cropper-Upload (Base64-Payload aus getImages()) im
|
||||
* public-Disk und liefert Dateiname + Metadaten — gemeinsame Logik für
|
||||
* Shop- und Profilfoto-Upload. Null bei nicht erlaubtem Dateityp.
|
||||
* Shop- und Profilfoto-Upload. Null, wenn der Inhalt kein JPEG/PNG ist.
|
||||
*
|
||||
* Zwei Dinge sind hier sicherheitsrelevant und dürfen nicht vereinfacht
|
||||
* werden:
|
||||
*
|
||||
* 1. `output.type` ist eine ANGABE DES CLIENTS aus dem geposteten JSON, kein
|
||||
* geprüfter Inhaltstyp. Massgeblich ist deshalb, was
|
||||
* getimagesizefromstring() in den Bytes findet.
|
||||
* 2. Die Endung kommt IMMER aus dem geprüften Typ, nie aus dem übermittelten
|
||||
* Dateinamen: {@see sanitizeFileName()} lässt jede Endung stehen, ein
|
||||
* "foto.php" landete sonst als ausführbarer Dateiname im public-Disk.
|
||||
*
|
||||
* @param array $image Ein Eintrag aus {@see getImages()}
|
||||
* @return array{filename: string, originalname: string, ext: string, mime: string, size: mixed}|null
|
||||
|
|
@ -26,19 +36,31 @@ class Slim
|
|||
{
|
||||
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
|
||||
|
||||
if (! isset($allowed[$image['output']['type']])) {
|
||||
$declaredType = $image['output']['type'] ?? null;
|
||||
$data = $image['output']['data'] ?? null;
|
||||
|
||||
if (! is_string($declaredType) || ! isset($allowed[$declaredType]) || ! is_string($data) || $data === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = uniqid().'_'.Slim::sanitizeFileName($image['output']['name']);
|
||||
$info = @getimagesizefromstring($data);
|
||||
$mime = is_array($info) ? ($info['mime'] ?? null) : null;
|
||||
|
||||
\Storage::disk('public')->put(rtrim($directory, '/').'/'.$name, $image['output']['data']);
|
||||
if (! is_string($mime) || ! isset($allowed[$mime])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ext = $allowed[$mime];
|
||||
$base = Slim::sanitizeFileName(pathinfo((string) ($image['output']['name'] ?? ''), PATHINFO_FILENAME));
|
||||
$name = uniqid().'_'.$base.'.'.$ext;
|
||||
|
||||
\Storage::disk('public')->put(rtrim($directory, '/').'/'.$name, $data);
|
||||
|
||||
return [
|
||||
'filename' => $name,
|
||||
'originalname' => $image['output']['name'],
|
||||
'ext' => $allowed[$image['output']['type']],
|
||||
'mime' => $image['output']['type'],
|
||||
'originalname' => $image['output']['name'] ?? '',
|
||||
'ext' => $ext,
|
||||
'mime' => $mime,
|
||||
'size' => $image['input']['size'] ?? null,
|
||||
];
|
||||
}
|
||||
|
|
@ -118,7 +140,7 @@ class Slim
|
|||
|
||||
if (isset($data->output)) {
|
||||
|
||||
$outputDate = null;
|
||||
$outputData = null;
|
||||
if (isset($data->output->image)) {
|
||||
$outputData = Slim::getBase64Data($data->output->image);
|
||||
} elseif (isset($data->output->field)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue