[PHP] Map vol foto's, alleen zichtbaar wat PHP serveert

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • michaelboon82
  • Registratie: December 2010
  • Laatst online: 17-09 08:18
Beste medetweakers,

Is het mogelijk om een map die vol staat met foto's, zo te beveiligen dat de bezoeker niet elke willekeurige foto kan opvragen? Maar wel dat PHP kan aangeven welke er getoond mogen worden?

Bijvoorbeeld:

In deze map staan allemaal foto's.
https://www.metlichtgeschreven.nl/2017/11/17/foto/

zoals:
https://www.metlichtgesch...k_Joanne_Kristiaan_01.jpg
https://www.metlichtgesch...k_Joanne_Kristiaan_02.jpg
https://www.metlichtgesch...k_Joanne_Kristiaan_03.jpg
https://www.metlichtgesch...k_Joanne_Kristiaan_04.jpg
etc.

Nu wil ik foto 01 en foto 02 serveren aan de bezoeker, maar de bezoeker mag niet foto 03 en foto 04 kunnen opvragen ondanks dat deze foto's wel online staan.

Een mogelijkheid is om de foto's buiten ROOT te zetten en via PHP en headers de foto's op te vragen. Maar als je dit met +100 foto's doet merk ik dat het niet altijd even lekker loopt.
Ook zou ik elke foto een random nummer (Md5 ofzo) kunnen geven zodat de gebruiker niet het exacte adres naar andere foto's weet, maar dat maakt mijn overige code ook weer lastiger.
Als laatste weet ik dat de foto's die niet gezien mogen worden in een andere map gezet kunnen worden en dat PHP de foto's kopieert naar de map 'foto', maar dat zie ik mijzelf ook niet zo snel implementeren.

Alle tips zijn welkom!
Misschien is er iets waar ik totaal niet aan gedacht had.
Misschien iets met beveiligen met .htaccess en dat alleen PHP erbij kan?

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
michaelboon82 schreef op dinsdag 12 december 2017 @ 17:44:
Maar als je dit met +100 foto's doet merk ik dat het niet altijd even lekker loopt.
Well, there's your problem... :? Heb je al eens gekeken waar dat aan ligt dan?

In alle gevallen zul je ergens moeten bepalen of iemand wel of niet foto X mag bekijken. Dat kan met .htaccess e.d. maar is nogal (behoorlijk) statisch. In PHP heb je veel meer flexibiliteit en kun je a.d.h.v. de sessie en rechten en weet-ik-veel bepalen of gebruiker Y foto X mag bekijken en kun je dus ook makkelijk maken dat gebruiker Z die weer niet mag zien. Je kunt een MD5 of andere hash gebruiken, maar als ik dan die foto kan zien en ik stuur de URL door naar Pietje dan kan hij die foto ook zien (ja, ik kan ook mijn login delen met Pietje of gewoon de foto meteen sturen i.p.v. de url enz. maar dat hou je toch).

Onderscheid maken met de twee mapjes methode die je aanhaalt kan natuurlijk ook; dan heb je gewoon een private en public mapje bijvoorbeeld. En .htaccess of niet; ik zou ze sowieso altijd buiten de webroot zetten.

[ Voor 74% gewijzigd door RobIII op 12-12-2017 17:58 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 17-09 18:39

Matis

Rubber Rocket

Bijna alle file sharing applicaties werken met een unieke random string (token) welke gekoppeld wordt aan één of meerdere bestand(en). Alleen mensen met de juiste token kunnen één of meerdere bestanden zien.

Één tabel met tokens, één tabel met bestanden en een koppeltabel om een many-to-many te genereren.

Er zijn mensen die het opslaan van bestanden in een database verafschuwen, maar ik vind het altijd prettig werken. Ze staan niet fysiek op de server, maar in een tabel in de database.

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • CyBeR
  • Registratie: September 2001
  • Niet online

CyBeR

💩

Als je zelf de server beheert waar dit om gaat (of de hoster ondersteunt het) en 't is er een op basis van Apache, nginx of lighttpd, is de beste manier het gebruiken van X-sendfile headers (of equivalent). Dan maak je met PHP de beslissing of een gebruiker ergens wel of niet bij kan, en geef je vervolgens aan Apache door (middels een header, X-Sendfile meestal) dat 'ie een file moet doorsturen.

Zie hier voor de apache-variant: https://tn123.org/mod_xsendfile/

Een andere manier (ook goed) is trouwens die foto's niet opslaan op je webserver maar bij een dienst als Amazon S3, en dan in je PHP links genereren. Die zijn volgens tijdsafhankelijk, waardoor ze niet eeuwig werken.

[ Voor 19% gewijzigd door CyBeR op 12-12-2017 19:00 ]

All my posts are provided as-is. They come with NO WARRANTY at all.


Acties:
  • 0 Henk 'm!

  • michaelboon82
  • Registratie: December 2010
  • Laatst online: 17-09 08:18
@RobIII
Bij het serveren van foto's buiten de ROOT via $_GET en headers merk ik dat er bij +150 foto's met een grootte van pak-em-beet 350k hij niet alles meer wil ophalen en bijvoorbeeld de laatste 30 stuks een kruisje geeft. Tot 100 gaat het allemaal wel ok.

Het is gewoon een basisscriptje met extra beveiliging dat er geen andere bestanden buiten de ROOT geplukt kunnen worden. Omdat ik merk dat 150 foto's met simpelweg <img> en foto's die wel binnen ROOT staan wel allemaal worden ingeladen was ik benieuwd of er een oplossing is waarbij de foto's gewoon binnen ROOT konden blijven. Het lijkt wel alsof het PHP scriptje ineens stopt met serveren via headers; geen idee waar dat aan kan liggen.

Ik had ook nog een scriptje waarmee je de foto via PHP codeert naar een Base64 bestand en die dan serveert. Dat werkt wel prima met 150 bestanden maar je krijgt een enorme html 8)7

PHP:
1
2
3
foreach(glob(path/outside/root/'.'*.[jJ][pP][gG]') as $file) {
    echo '<img src="data:image/png;base64,'.base64_encode(file_get_contents($file)).'">';
}


In ieder geval bedankt voor het meedenken!
Ik zal e.e.a. gaan afwegen.

@Matis
Database wil ik liever vermijden, maar toch bedankt voor de tip!

@CyBeR
X-sendfile schijnt niet meer onderhouden te worden?
Ik heb in ieder geval zelf geen beheer over de server maar ik zou het altijd kunnen vragen aan de hoster. In S3 heb ik mijzelf nog niet verdiept...ik ben vrij rookie met programmeren dus het is voor mij een grote stap. Ik zal je opties in overweging nemen, bedankt voor je tips!

[ Voor 4% gewijzigd door michaelboon82 op 13-12-2017 14:04 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Misschien de pagina's opdelen in meerdere pagina's zodat je geen 150 per pagina hebt? Of lazy-loading gebruiken - dat verdeelt de load op de server ook aardig.

Acties:
  • 0 Henk 'm!

  • Jantje2000
  • Registratie: Februari 2016
  • Laatst online: 17:16
kun je niet de map waar de foto's in staan andere rechten geven. Als je dan bijvoorbeeld de file permissions op 700 zet (alleen de owner mag alles) dan is het in principe toch ook afgeschermd?
Of is dat niet extreem veilig?

De wet van Murphy: Alles wat fout kan gaan zal fout gaan.


Acties:
  • 0 Henk 'm!

  • borft
  • Registratie: Januari 2002
  • Laatst online: 15-09 16:33
hangt er vanaf wie de owner is natuurlijk ;)

Acties:
  • 0 Henk 'm!

Verwijderd

-

[ Voor 100% gewijzigd door Verwijderd op 19-10-2019 15:07 . Reden: Leeg ivm privacy ]


Acties:
  • 0 Henk 'm!

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

michaelboon82 schreef op woensdag 13 december 2017 @ 14:02:
Bij het serveren van foto's buiten de ROOT via $_GET en headers merk ik dat er bij +150 foto's met een grootte van pak-em-beet 350k hij niet alles meer wil ophalen en bijvoorbeeld de laatste 30 stuks een kruisje geeft. Tot 100 gaat het allemaal wel ok.
Dan itereer je toch over de afbeeldingen heen in stappen van maximaal 100/120? :?

Acties:
  • 0 Henk 'm!

  • mcDavid
  • Registratie: April 2008
  • Laatst online: 09-09 17:48
michaelboon82 schreef op woensdag 13 december 2017 @ 14:02:
@RobIII
Bij het serveren van foto's buiten de ROOT via $_GET en headers merk ik dat er bij +150 foto's met een grootte van pak-em-beet 350k hij niet alles meer wil ophalen en bijvoorbeeld de laatste 30 stuks een kruisje geeft. Tot 100 gaat het allemaal wel ok.
Wat doe je dan precies? want als je dit zegt klinkt het klinkt alsof je probeert al die foto's in één script in te laden of iets dergelijks. Dat is nergens voor nodig. Je kunt gewoon een scriptje maken dat één foto proxiet met readfile() of iets dergelijks, en in je HTML oneindig veel calls naar dat scriptje doen.

Kortom ik heb het idee dat je een workaround zit te zoeken voor een probleem wat je zelf gecreëerd hebt, in plaats van het probleem zelf op te lossen.

Als je wat (psuedo) code voorbeelden post van wat je precies doet, kunnen we je denk ik beter de goeie kant op sturen.

Acties:
  • 0 Henk 'm!

  • RedHat
  • Registratie: Augustus 2000
  • Laatst online: 17-09 20:43
ik weet niet of het (technisch) gezien kan, maar kun je niet in een database bijhouden welke bestanden er zijn (buiten de root) en apache owner maken (buiten de root) en dan met file_get_contents het bestand serveren?

Acties:
  • 0 Henk 'm!

  • michaelboon82
  • Registratie: December 2010
  • Laatst online: 17-09 08:18
@mcDavid

Hierbij de code die bij mij rond de 150 items kruisjes gaat geven. Ik heb geen idee waarom niet. Een goede oplossing met buiten de ROOT is ook uitstekend; graag zelfs, maar omdat met de 'normale' methode wel zonder problemen 150 images aangeroepen kunnen worden was mijn vraag of er ook een dergelijke mogelijkheid is binnen de ROOT.

In onderstaande code vraag ik alle jpg-bestanden op in een map, maar normaliter kan ik met PHP bepalen welke ze mogen zien en welke niet.

PHP: test.php
1
2
3
foreach(glob('/path/outside/root/*.[jJ][pP][gG]') as $file) {
    echo '<img src="image.php?image='.basename($file).'">';
}


PHP: image.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$image = '/path/outside/root/'.$_GET['image'];
if (!$image){
    header('HTTP/1.1 400 Bad Request'); 
    exit();
}
// Beveiligingen; dir kan geen ':' bevatten, bestanden kunnen geen '..' of '<' bevatten, bestand moet beginnen met '/' en bestand moet een image zijn
if ($image{0} != '/' || strpos(dirname($image), ':') || preg_match('/(\.\.|<|>)/', $image)){
    header('HTTP/1.1 400 Bad Request');
    exit();
}
$size = GetImageSize($image); $mime = $size['mime'];
if (substr($mime, -4) != 'jpeg'){
    header('HTTP/1.1 400 Bad Request');
    exit();
}
header("Content-Type: image/jpeg");
readfile($image);


Maar ik bedenk me net dat een gebruiker dan ook gewoon foto 02 en foto 03 kan ophalen ook al toon ik ze niet met PHP |:( Het beste is om met 2 mapjes te werken dan denk ik...
Mocht ik wel gebruik gaan maken van headers dan moet ik kijken of slow-loading een optie is, maar dat is volgens mij weer JS :/

@iedereen:
Bedankt allemaal voor het meedenken.
Het is een hersenspinsel van mij maar de uitvoering zal nog niet zo makkelijk gaan als dat ik dacht. Ik ben ook een leek met PHP en JS maar ik zal alle suggesties en kritiek gebruiken om te leren.

Back to the drawing board zullen we maar zeggen.

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

michaelboon82 schreef op woensdag 13 december 2017 @ 17:36:
PHP: test.php
1
2
3
foreach(glob('/path/outside/root/*.[jJ][pP][gG]') as $file) {
    echo '<img src="image.php?image='.basename($file).'">';
}
Ik zou de glob doen voordat je gaat itereren binnen de foreach, nu gaat hij elke keer 'globben' en pakt dan de volgende. Ik denk dat dat ook wel veel tijd gaat schelen.

[ Voor 55% gewijzigd door CH4OS op 14-12-2017 09:02 ]


Verwijderd

Nee, die glob wordt maar 1 keer uitgevoerd - zo werkt foreach in PHP.

  • mcDavid
  • Registratie: April 2008
  • Laatst online: 09-09 17:48
Er kan wel het eea gezegd worden over dat scriptje (zoals de if (!image) die unreachable is) maar in principe zou dit gewoon moeten werken. Al moet je natuurlijk wel een vorm van beveiliging inbouwen zodat bezoekers alléén de afbeeldingen kunnen opvragen waar ze ook rechten toe hebben.

Als je "kruisjes" krijgt zou ik eens onderzoeken met je web inspector wat die requests tegenhoudt. Misschien één of andere ratelimiter?

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
michaelboon82 schreef op woensdag 13 december 2017 @ 17:36:
Hierbij de code die bij mij rond de 150 items kruisjes gaat geven.
Denk meer hier aan:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (preg_match('/\\.\\.|<|>|:/', $_GET['image']) || !preg_match('/\\.jpg$/Di', $_GET['image'])) {
    header('HTTP/1.1 400 Bad Request'); 
    exit('Invalid image');
}

$image = '/path/outside/root/'.$_GET['image'];

if (!is_file($image)) {
    header('HTTP/1.1 404 Not Found'); 
    exit('Image not found');
}

if (!is_readable($image)) {
    header('HTTP/1.1 403 Forbidden'); 
    exit('Image not readable');
}

Maak je niet druk, dat doet de compressor maar


  • mcDavid
  • Registratie: April 2008
  • Laatst online: 09-09 17:48
altijd handig, een onleesbare regex zonder uitleg dumpen met de melding "doe maar zo" ;(.

Acties:
  • +1 Henk 'm!

  • CyberJack
  • Registratie: Augustus 2002
  • Laatst online: 03-09 14:36
mcDavid schreef op donderdag 14 december 2017 @ 12:43:
altijd handig, een onleesbare regex zonder uitleg dumpen met de melding "doe maar zo" ;(.
Regexen kunnen inderdaad wat raar en ingewikkeld overkomen (en ze kunnen ook behoorlijk ingewikkeld gemaakt worden). Een regex kan je trouwens wat inzichterlijke maken met tools als: https://regexper.com/
PHP:
1
preg_match('/\\.\\.|<|>|:/', $_GET['image'])
De eerst regex controleert of de afbeelding naam niet de tekens: .. < > of : bevat. (die regex kan ook korter/netter maar dat terzijde). Met deze check wordt geprobeerd een path traversal attack te voorkomen. In het kort zou dit er voor moeten zorgen dat alleen bestanden uit de ''/path/outside/root/' directory (of onderliggende directories) gebruikt kunnen worden. (Voorbeeld op regexper).
PHP:
1
!preg_match('/\\.jpg$/Di', $_GET['image'])
De 2de regex controleerd of de opgegeven bestandsnaam eindigt op ".jpg". De !preg_match zorgt er voor dat als deze niet matched het script stopt.

Deze regex heeft echter nog 2 extra opties welke achter de laatste / staan (de delimiter).

De 'D' betekenen dat de $ alleen het einde van de regex pattern aangeeft (en niet het eind van de string waar deze normaal voor gebruikt wordt). Ik heb zo even geen idee waar die voor nodig is.
Houd er rekening mee dat regexper alleen javascript regex ondersteund en dat de optie 'D' daar niet geaccepteerd wordt.

De 'i' zorgt ervoor dat de de regex niet case-sensitive is en dat dus ook de extensie ".JpG" mag.

[ Voor 5% gewijzigd door CyberJack op 19-12-2017 11:09 ]

https://bottenberg.dev

Pagina: 1