Odczytywanie informacji EXIF zdjęcia w PHP

Większość aparatów cyfrowych w pliku ze zdjęciem zapisuje nagłówki informujące o parametrach zrobionego zdjęcia, użytego aparatu. Dane te są zapisane w formacie IPTC i zawierają takie dane jak: datę i czas zrobienia zdjęcia, czas naświetlania, przysłonę, ogniskową, ISO, program paratu, nazwę producenta i modelu aparatu etc…

Informacje te można odczytywać i modyfikować w programach graficznych, standard IPTC szczególnie przydatny jest w fotografii stockowej (opis wykorzystania standardu IPTC).

Dane zapisane w pliku JPEG/TIFF możemy w prosty sposób odczytać w skrypcie PHP. Dostępna jest specjalna bibloteka EXIF, która nam to umożliwia. Aby móc korzystać z tej biblioteki PHP musi być skompilowane z opcjami –enable-exif i –enable-mbstring.

Poniżej przykładowa funkcja, zwracająca informacje o zdjęciu

/**
* Metoda zwraca informacje o zdjęciu
*
* @param string $file - sciezka do zdjecia
* @return array od mixed - informacje o zdjeciu
* @access public
*/
function getImageInfo( $file ) {
	$exif = exif_read_data( $file, "IFD0" );
	if( $exif ) {
		$return exif_read_data( $file, 0, true );
	}
}

Funkcja zwraca tablice z danymi, które możemy zaprezentować na stronie przy zdjęciu.
Poniżej przykładowy wynik powyższej metody.

Array
(
    [FILE] => Array
        (
            [FileName] => IMG_7378.jpg
            [FileDateTime] => 1198952415
            [FileSize] => 1240033
            [FileType] => 2
            [MimeType] => image/jpeg
            [SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF
        )

    [COMPUTED] => Array
        (
             => width="2664" height="2304"
            [Height] => 2304
            [Width] => 2664
            [IsColor] => 1
            [ByteOrderMotorola] => 0
            [CCDWidth] => 17mm
            [ApertureFNumber] => f/18.0
            [Thumbnail.FileType] => 2
            [Thumbnail.MimeType] => image/jpeg
        )

    [IFD0] => Array
        (
            [ImageDescription] => Red Tomato isolated on white background
            [Make] => Canon
            [Model] => Canon EOS 350D DIGITAL
            [Orientation] => 1
            [XResolution] => 2400000/10000
            [YResolution] => 2400000/10000
            [ResolutionUnit] => 2
            [Software] => Adobe Photoshop CS3 Windows
            [DateTime] => 2007:12:29 19:20:14
            [Artist] => Marcin Mamala
            [Exif_IFD_Pointer] => 296
        )

    [THUMBNAIL] => Array
        (
            [Compression] => 6
            [XResolution] => 72/1
            [YResolution] => 72/1
            [ResolutionUnit] => 2
            [JPEGInterchangeFormat] => 798
            [JPEGInterchangeFormatLength] => 3028
        )

    [EXIF] => Array
        (
            [ExposureTime] => 1/20
            [FNumber] => 18
            [ExposureProgram] => 1
            [ISOSpeedRatings] => 100
            [ExifVersion] => 0221
            [DateTimeOriginal] => 2007:12:29 18:06:50
            [DateTimeDigitized] => 2007:12:29 18:06:50
            [ShutterSpeedValue] => 4321928/1000000
            [ApertureValue] => 833985/100000
            [ExposureBiasValue] => 0/2
            [MaxApertureValue] => 434375/100000
            [MeteringMode] => 5
            [Flash] => 16
            [FocalLength] => 43
            [ColorSpace] => 65535
            [ExifImageWidth] => 2664
            [ExifImageLength] => 2304
            [FocalPlaneXResolution] => 3456000/874
            [FocalPlaneYResolution] => 2304000/582
            [FocalPlaneResolutionUnit] => 2
            [CustomRendered] => 0
            [ExposureMode] => 1
            [WhiteBalance] => 0
            [SceneCaptureType] => 0
        )

)

Bot alexa.com i znikające dane w serwisach.

Dostałem ostatnio zgłoszenie, że w aplikacji, którą jakis czas temu napisałem zniknęło część kluczowych danych. Mniejsza z tym, jakie to były dane, powiedzmy ze chodzi o dane kont użytkowników, których usunięcie pociągnęło za sobą kaskadowe skasowanie się wielu innych danych z tabel zależnych.Sytuacja wydała się dosyć dziwna i podejżana. Pierwsza myśl, która zawitała w mej głowie to „pewnie, któryś z adminów niechcąco skasował” było to prawdopodobne tym bardziej, że kiedyś miało juz miejsce podobne zdarzenie. Tym razem jednak żaden z administratorów nie przyznał się do omyłkowego skasowania danych.

Kolejny powód zniknięcia danych o jakim pomyślałem to włam jakiegoś pseudo-hakera i celowe zniszczenie danych. Zaglądam do access-logów serwera z tego feralnego dnia, grepuje po pliku logowania i widze, że logowania pochdziły jedynie ze sprawdzonych IP. Hmm…gerpuje więc po pliku zawierającym sktypt kasujący głowne dane i oczom moim ukazuje się kilka lini, każda postaci:

user 64.208.172.180 - - [29/Oct/2007:17:28:46 +0100] "GET /skrypt_kasujacy.php?userID=15 HTTP/1.0" 302 - "-" "ia_archiver"

Jakim cudem ktoś wywołał (z powodzeniem) skrypt kasujący dane bez wcześniejszej poprawnej autoryzacji logowania? Od razu sprawdzam źródła aplikacji, czy aby jest dobrze zabezpieczona i… jest zabezpieczona poprawnie. wtf?

Totalnie zdziwiony stwierdzilem, że trzeba troche poguglać na temat 64.208.172.180 i „ia_archiver”. Po krótkiej chwili okazało się, że nazwa user agenta „ia_archiver” odpowiada nazwie z jaką się przedstawia bot alexa.com, aby mieć pewność, że ktoś się pod niego nie podszywa weryfikuje także IP, które potwierdza, że była to wizyta z serwera crawlera alexa.

Dobra to juz wiem, że dane nie skasował żaden haker tylko bot, ale to wcalenie jest pocieszająca myśl, to jest tragedia gdyż wynka, że moja aplikacja jest tak dziurawa, że nawet boty potrafią ją zaindeksować! Masakra…

Drążąc temat dalej dowiedziałem się, że bot alexa nie jest zwykłym botem jakich na pęczki w internecie gdyż oprócz latania jak głupek po wszystkich stronach www bieże informacje o stronach do odwiedzenia z Alexa Toolbar, który jaki dodatek można zainstalować w przeglądarce internetowej, także w ten sposób może zaindeksować strony, do których nie prowadzi żaden link a także te, które wymagają autoryzacji.
I tak właśnie było w tym wypadku, jeden z administratorów aplikacji miał zainstalowany alexa toolbar w swojej przeglądarce i przez to nawet gdy poruszał się w sekcji wymagającej autoryzacji to cały czas alexa była poprzez toolbar informowana o URLach przez niego odwiedzanych. W ten sposób w pewnym momencie bot zaindeksował stronę z listą użytkowników, przeparsował ją i wyciągnął linki prowadzące do usunięcia kont a następnie je wywołał i tym samym usunął konta.

Jak się okazało wywołanie linków kasujących dane zawsze miało miejsce w momencie gdy administrator był zalogowany w systemie. Domyślam się, że jakimś cudem bot poprzez toolbar przechwytywał zautoryzowaną sesje użytkownika i dzięki temu uzyskiwał dostęp do stron, do których dostępu mieć nie powinien!

Jak się zabezpieczyć?
Najlepiej stworzyć plik robots.txt w katalogu glównym aplikacji i wpisać w nim instrukcje nie pozwalającą botom na odwiedzani plików administracyjnych.

user-agent: *
Disallow: /admin_pages*

Ukrycie nazwy ściąganego pliku

Czasami chcemy udostępnić ściąganie plików jednak nie chcemy udostępniać bezpośredniego linków do pliku.

Możemy zrobić plik przechodni download.php, który będzie przyjmował np. identyfikator pliku do ściągnięcia, na jego podstawie odczytamy lokalizacje pliku, odczytamy go i prześlemy do przeglądarki.

$file = $className->getFileByID( $_GET["id] );
header( "Cache-control: private" );
header( "Content-Type: application/octet-stream" );
header( "Content-Length: ".filesize( $file ) );
header( "Content-Disposition: attachment; filename=".basename( $file ) );
header( "Connection: Close" );
readfile( $file )

post_max_size a tablice $_POST i $_FILES

W przypadku gdy mamy formularz umożliwiający przesłanie danych (do bazy lub gdziekolwiek), który ma możliwość załączenia pliku przy walidacji powinniśmy uwzględnić sytuację gdy uploadowany plik(i) nie przekracza maksymalnego rozmiaru tablicy $_POST

Można sumować wartość poszczególnych plików $_FILES[„name”][„size”] i porownywać jej wielkość z wartością ustawioną w post_max_size w php.ini ale jest to rozwiązanie działające tylko na serwerze na którym się aktualnie pisze kod (inne serwer przecież mogą mieć inną konfigurację)

Ale jest lepsze rozwiązanie: Sprawdzamy czy tablica $_POST, którą otrzymaliśmy jest pusta i czy tablica $_FILES nie jest pusta. Jeżeli okaże się to prawdą to oznacza, że nastąpiła próba wgrania co najmniej 1 pliku i suma przekroczyła aktualną wartość post_max_size przez co zresetowała tabelę $_POST.
Zapisujemy błąd do sesji i przekierowywujemy na formularz.

if( ( is_array( $_POST ) && count( $_POST ) == 0 ) && ( is_array( $_FILES ) && count( $_FILES ) != 0 ) ) {
  $errors["MAX_FILE_SIZE"] = "Suma wielkości wgrywanych plików jest zbyt duża";
  $session->setVar( "errors", $errors );
  header( "Location: plik_z_formularzem.php" );
  exit;
}