1211chef: Gedrehte Bilddateien abhängig vom Endgerät - wie begegnet ihr der Sachlage

beschäftige mich grade mit einer webbasierten handyversion, u.a. mit bildupload. lade ich bilder mit meinem eierphone hoch, erscheinen sie korrekt gedreht. schau ich sie mir dann am desktop an oder auf nem android stehen die bilder auf dem kopf oder liegen seitlich.

das problem ist jetzt folgendes: ich könnte dem user, zb. mit image::magick eine funktion zur verfügung stellen, mit der er nach dem upload die bilder richtig drehen kann. aber zb. ein eierphone-nutzer sieht gar nicht dass das bild gedreht ist, da das eierphone das intern richtig stellt und ihm die gedrehte version als richtig darstellt.

ich frage mich jetzt, ob, wo, wie diese informationen zb. in nem jpg gespeichert werden und wie ich die misere am besten abfangen kann bevor sie entsteht.

ich hoffe sehr ihr könnt mir was dazu sagen und wir können das ausdiskutieren.

so kann ich das nicht lassen.

gruss gust

ps: portale wie eba* kleinanzeigen haben das im griff, also muss es auch für mich möglich sein.

pps: allen erst mal nen schönes we ::bier::party::fun:: ... ich kuck mir dann am montag eure lösungen an ;D. also schon mal tausend dank für eure hilfe

akzeptierte Antworten

  1. ich frage mich jetzt, ob, wo, wie diese informationen zb. in nem jpg gespeichert werden und wie ich die misere am besten abfangen kann bevor sie entsteht.

    Exif-Daten: Was das ist:

    https://de.wikipedia.org/wiki/Exchangeable_Image_File_Format

    Wie man die löscht erfährst Du bei der Suchmaschine Deines am wenigsten unerhörten Misstrauens.

    beschäftige mich grade mit einer webbasierten handyversion, u.a. mit bildupload.

    Wenn Dir die Poblematik der Exif-Daten nicht bekannt ist, dann fürchte ich ganz übles. Einen "Bildupload" sollte kein Anfänger machen, da versagen ganz andere und bauen Sicherheitslöcher ein, die so groß wie ein Universum sind.

    1. Die Vorgehensweise ist in PHP und Perl identisch. Nur die Funktionen heißen anders.

      1. Die folgenden PHP-Funktionen sorgen dafür, dass wirklich Grafiken des angegeben Typs geladen werden:

      • imagecreatefromgif() → imagecopyresized() → imagegif()
      • imagecreatefrompng() → imagecopyresized() → imagepng()
      • imagecreatefromjpeg() → imagecopyresized() → imagejpeg()

      2. Du kannst also zugleich das Bild in Originalgröße, bestimmte Größen sowie eventuell gewünschte Thumbnails erzeugen. Wenn das nicht klappt, dann war es kein Bild…

      3. Dabei werden auch die störenden bzw. die Privatsphäre schädigenden Exif-Daten gelöscht. Also auch die Angaben zur Rotation.

      4. Vergiss nicht, dass Du stets den Dateiname bestimmst. md5( $Originaldaten ) ist eine Möglichkeit.

      5. Lösche nach dem Kopieren den Upload aus dem Temp-Verzeichnis.

  2. ich kuck mir dann am montag eure lösungen an

    Kennst Du schon CPAN? Solltest Du mal kuckn echt der Knaller!

  3. @@1211chef

    die bilder richtig drehen

    slightly related: XKCD 😂

    LLAP 🖖

    --
    „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
  4. Hallo 1211chef,

    ein funktionierendes Vorgehen ist: ImageMagicks convert mit -auto-orient das Bild richtig drehen lassen und mit -strip die EXIF-Daten zu entfernen.

    So wird das Bild in jedem Gerät gleich dargestellt, und, sofern die EXIF-Daten nicht lügen, auch in der richtigen Orientierung.

    LG,
    CK

    1. Hallo 1211chef,

      ein funktionierendes Vorgehen ist: ImageMagicks convert mit -auto-orient das Bild richtig drehen lassen und mit -strip die EXIF-Daten zu entfernen.

      So wird das Bild in jedem Gerät gleich dargestellt, und, sofern die EXIF-Daten nicht lügen, auch in der richtigen Orientierung.

      LG,
      CK

      hallo christian, yepp, passt ::thumbsUP:: deine tipps sind immer wieder hervorragend! das ganze sieht in perl jetz mal auszugsweise in der testvers. so aus.

      was kann ich noch zum thema fileprüfung besser/sicherer machen?

      
      #!/usr/bin/perl
      
      use [allerhandzeugs]
      use File::Type;
      use Image::Magick;
      
        my $q         =      new CGI;
        my $ft        =      File::Type->new();  
        my $img 		  =      Image::Magick->new();
        my $ID 	      = 	   $q->param	("ID");
        my $machwas 	= 	   $q->param	("machwas");
      
        my $fname 	  = 	   $q->param	("upload");
        my $handle 	  = 	   $q->upload	("upload");
      
      if ($machwas)
      {
        $fname 		    =      ~s/(?:\\|\/)([^\\\/]+)$/$1/g;
        my ($a,$end) 	= 	   split(/\.$+/, $fname);
        $end			    =	     lc($end);
        $fname		    =	     $ID.time().".".$end;
        
            open (FILE, ">../../pics/$fname") or die "Can't create file: $!"; my $buffer;
            while (read($handle,$buffer,2048)) { print FILE $buffer; } close (FILE);
      	  
            my $type 	= 	$ft->mime_type("../../pics/$fname");
      	    my $gr		= 	-s("../../pics/$fname");
      	
      	    if ( ($type ne "image/jpeg") && ($type ne "image/png") && ($type ne "image/x-png") ) 
      		  { print header('text/plain'); print"L-In Error :: falsches Dateiformat $type";	
      	    unlink("../../pics/$fname"); goto end; }
      	  
      	  
      	  $img->Read("../../pics/$fname") || die "Cannot read $fname!";
      
      	  my ($wo,$ho)    =    $img->Get('width','height'); 
          my $max         =    600; 
          my $wn          =    $wo; 
          my $hn          =    $ho; 
          my $ratio       =    $wo/$ho;
      
      	  if ( ($wo > $max) || ($ho > $max) ) {
      	  if ($ratio > 1) { $wn = $max; $hn = int($max / $ratio); } else { $wn = int($max * $ratio); 
          $hn = $max; } $img->Sample("$wn x $hn"); }
      
      	  my $text="© by IchBins";
      
      	  $img->Strip; 
          $img->AutoOrient;
      
      	  $img->Annotate(text=>$text,font=>'courier',pen=>'black',undercolor=>'#FFFFFFBB',
          pointsize=>30,gravity=>'South',geometry=>'+20 +20');
      
      	  $img->Write("../../pics/$fname") || die "Cannot write $fname!";
      }
      
      
      1. Dein

          > my $fname 	  = 	   $q->param	("upload");
          > open (FILE, ">../../pics/$fname")
        

        ist ein schwerwiegender Fehler, die Sicherheit betreffend! Und ein Speichern macht auch keinen Sinn wenn der Type nicht passt. MFG

        PS: Was File::Type macht weißt Du?

      2. Hallo 1211chef,

        # […]
          my $fname 	  = 	   $q->param	("upload");
        

        Hier setzt du $fname auf den Parameter aus der URL.

          $fname 		    =      ~s/(?:\\|\/)([^\\\/]+)$/$1/g;
        

        Hier ersetzt du Zeichen.

          my ($a,$end) 	= 	   split(/\.$+/, $fname);
          $end			    =	     lc($end);
          $fname		    =	     $ID.time().".".$end;
        

        Und hier schnappst du dir die Endung und ersetzt den Dateinamen durch eine ID, deinen Timestamp und die Endung. Die ID kommt wieder aus der URL. Und ID ist auch nicht escaped noch wird sonstwie sichergestellt, dass dort keine Steuerzeichen drin sind.

        Warum machst du nicht einfach sowas?

        use File::Basename;
        
        my $ID = $q->param("ID");
        
        $ID =~ tr/A-Za-z_.-//dc;
        $ID =~ s/\.\.//g;
        
        my ($filename, $path, $suffix) = fileparse($q->param("upload"), qr/\.[^.]*/);
        my $fname = $ID . time() . $suffix;
        

        Wenn $ID nummerisch sein muss, dann sogar besser prüfen, dass der Inhalt auch wirklich nummerisch ist.

          while (read($handle,$buffer,2048)) { print FILE $buffer; } close (FILE);
        

        Hier besser

        use File::Copy;
        
        copy($handle, "../../pics/$fname");
        
          if ( ($type ne "image/jpeg") && ($type ne "image/png") && ($type ne "image/x-png") ) 
            { print header('text/plain'); print"L-In Error :: falsches Dateiformat $type";	
          unlink("../../pics/$fname"); goto end; }
        

        Lieber vor dem kopieren prüfen und gar nicht erst an den Zielort kopieren.

        Der Rest sieht OK aus.

        LG,
        CK

        1. hallo christian,

          die ID ist in diesem fall eine nummer, die in der sql-datenbank unter dem jew. user vorhanden sein muss. die ID schicke ich bei jeder aktion als input type hidden mit, nachdem sich der user mit passwort eingeloggt hat. wenn die ID nicht mit der DB übereinstimmt breche ich das script schon ganz oben komplett ab. denkst du das ist ausreichend?

          zu deinem beispiel: an welcher stelle findet bei dir die fileprüfung statt? ich dachte immer ich muss eine datei erst auf dem server speichern um sie überprüfen zu können. ich kann der reihenfolge deines beispiels nicht folgen.

          zudem hab ich an anderer stelle noch ein weiteres problem, und zwar die anpassung der schriftgrösse in annotate (pointsize). bei hochformatbildern wird die schrift grösser bzw. im querformat kleiner. weiss nicht woran das liegen könnte. eine density angabe bewirkt auch nix. ich könnte schon pointsize in abhängigkeit von ratio verändern, aber ist das eine echte lösung? zitat aus imagemagic.org "die abhängigkeit zwischen density und pointsize wird nur ein gelernter grafiker verstehen". ich bin nicht mal gelernter programmierer ::seufz::

          allen erst mal eine schönes wochenende

          1. Hallo 1211chef,

            die ID ist in diesem fall eine nummer, die in der sql-datenbank unter dem jew. user vorhanden sein muss. die ID schicke ich bei jeder aktion als input type hidden mit, nachdem sich der user mit passwort eingeloggt hat. wenn die ID nicht mit der DB übereinstimmt breche ich das script schon ganz oben komplett ab. denkst du das ist ausreichend?

            Das hängt davon ab wie du prüfst, ob die ID in der Datenbank existiert 😀 da gibt es fiese Details. Du musst sicherstellen dir da keine SQL-Injection einzufangen und den Wert nicht aus Versehen zu ändern.

            Wenn du allerdings alle Details berücksichtigt hast, dann reicht das, ja.

            zu deinem beispiel: an welcher stelle findet bei dir die fileprüfung statt?

            Welche? Auf Content-Type und Inhalt? Vor dem kopieren.

            ich dachte immer ich muss eine datei erst auf dem server speichern um sie überprüfen zu können.

            Die Datei wird bereits auf dem Server gespeichert sein. Ich denke nicht, dass Perl den Inhalt im Speicher hält; das wäre eine wunderbare Möglichkeit für DoS.

            Zu deinem ImageMagick-Problem kann ich leider nichts sagen, sorry.

            LG,
            CK

            1. Ich denke nicht, dass Perl den Inhalt im Speicher hält; das wäre eine wunderbare Möglichkeit für DoS.

              CGI.pm schreibt temporäre Dateien. Das Modul ist Opensource da kann jeder reingucken wie das da gemacht wird. Was DoS betrifft: Ein Webserver nach CGI/1.1 Standard parst die Header aus dem Socket und legt dieses dann um nach STDOUT so daß nachgelagerte Prozesse den Body aus STDIN lesen lönnen. Gleichzeitig setzt der Webserver eine Umgebungsvariable CONTENT_LENGTH welche die Anzahl der aus STDIN zu lesenden Bytes angibt. Genau hier setzt man an wenn man in einem nachgelagerten Prozess eine DoS vermeiden will. Mit Perl hat das nichts zu tun und auch nicht damit ob ein Message Body im RAM gehalten wird. MFG

          2. moin,

            die ID ist in diesem fall eine nummer, die in der sql-datenbank unter dem jew. user vorhanden sein muss. die ID schicke ich bei jeder aktion als input type hidden mit, nachdem sich der user mit passwort eingeloggt hat. wenn die ID nicht mit der DB übereinstimmt breche ich das script schon ganz oben komplett ab. denkst du das ist ausreichend?

            Wenn die ID serverseitig vorliegt ist es unsinnig sie über HTTP zu schleifen. Und was das Speichern von Dateien betrifft: Die Lösung heißt Quarantäne. Also Speichern der Dateien in einem Verzeichnis wo sie keinen Schaden anrichten können einfach nur fortlaufend numeriert wobei diese Nummern serverseitig vergeben werden. Wenn Du ohnehin eine DB Anbindung hast, gibt es auch fortlaufende Nummern, die Metadaten müssen sowieso erfasst werden.

            MFG

      3. Moin,

        $fname = ~s/(?:\\|\/)([^\\\/]+)$/$1/g;
        

        liefert übrigens eine Fehlermeldung: Use of uninitialized value $_ in substitution (s///) at..

        MFG

  5. hallo, erst mal danke für die vielen anregungen (und warnungen).

    ich hatte mich mit der sicherheitsproblematik schon mal befasst, ist aber lange her. mit meinem kenntnissen werde ich das nicht 100%tig in den griff bekommen. wobei das leben generell nie risikofrei ist ;D

    ich muss mir das alte perl script nochmal genau durcharbeiten. habe damals File:Type verwendet, vorprüfungen gemacht, den dateinamen ersetzt, die dateirechte auf 664 gesetzt, an die finale stelle verschoben und die temp gelöscht.

    mit Exif-Daten hab ich mich damals nicht beschäftigt (wie man sieht).

    den aktuellen uploadbereich will ich in einer passwortgeschützten umgebung einsetzen und es haben nur wenige leute darauf zugriff, die ich auch alle persönlich kenne. das risiko sollte dadurch schon mal etwas minimiert sein.

    trotz allem werde ich natürlich versuchen die sache einigermaßen sicher zu machen.

    was sagt ihr zum perl modul File::Type und meiner vorgehensweise? was kann ich besser machen in sachen risikoprüfung?

    gruss gust

    1. So müsste es gehen (Hab lange kein Perl mehr gemacht...):

      use Image::Magick;
      function imagecopy( $from, $to ) {
          $image = new Image::Magick;
          if ( $image->Read( $from ) ) {
              return $image->Write( filename=>$to );
          }
      }
      

      Das kann, wie beschrieben, noch mehr: Image::Magick

      Die Drehung (und noch viel mehr) kannst Du aus dem Original mit ExifTool auslesen.

    2. was sagt ihr zum perl modul File::Type und meiner vorgehensweise?

      Das ermittelt den Content-Type anhand einer sog. Magic Number und ist damit nicht verläßlich.

      MFG