Outils pour utilisateurs

Outils du site


php:filehosting

A simple, minimalist, personal file/image hosting script

Presentation

I'm tired of image hosting services. Sites like imageshack.us are riddled with advertising. Sites like imgur.com delete images with no warning (Yes, it has happened to me several times). And some of them even want to sign-up for “premium” accounts ? WTF ?

I decided to create my own, minimalist image hosting script for my own website. In fact, this script can be used to upload & host any kind of file.

This is very simple.

  • Only you can upload files (using the password)
  • Anybody can see the images or download the files.

Instructions

  1. Save the following script as upload.php.
  2. Change the password ($PASSWORD='toto';)
  3. Put the file in the root of your website.
  4. Enjoy !

Files will be saved in the files subdirectory. You can change the directory name in the source ($SUBDIR='files').

Source

upload.php
<html><head><style type="text/css">
<!-- body { font-family: "Trebuchet MS",Verdana,Arial,Helvetica,sans-serif; font-size: 10pt; background-color: #eee;} -->
</style>
<?php 
// A simple, minimalist, personal file/image hosting script. - version 0.5
// Only you can upload a file or image, using the password ($PASSWORD).
// Anyone can see the images or download the files.
// Files are stored in a subdirectory (see $SUBDIR).
// This script is public domain.
// Source: http://sebsauvage.net/wiki/doku.php?id=php:imagehosting
$PASSWORD='toto';
$SUBDIR='files'; // subdirectory where to store files and images.
if (!is_dir($SUBDIR)) 
{
    mkdir($SUBDIR,0705); chmod($SUBDIR,0705);
    $h = fopen($SUBDIR.'/.htaccess', 'w') or die("Can't create .htaccess file.");
    fwrite($h,"Options -ExecCGI\nAddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi");
    fclose($h);
    $h = fopen($SUBDIR.'/index.html', 'w') or die("Can't create index.html file.");
    fwrite($h,'<html><head><meta http-equiv="refresh" content="0;url='.$_SERVER["SCRIPT_NAME"].'"></head><body></body></html>');
    fclose($h);
}
$scriptname = basename($_SERVER["SCRIPT_NAME"]);
if (isset($_FILES['filetoupload']) && isset($_POST['password']))
{   
    sleep(3); // Reduce brute-force attack effectiveness.
    if ($_POST['password']!=$PASSWORD) { print 'Wrong password.'; exit(); }
    $filename = $SUBDIR.'/'.basename( $_FILES['filetoupload']['name']); 
    if (file_exists($filename)) { print 'This file already exists.'; exit(); }
    if(move_uploaded_file($_FILES['filetoupload']['tmp_name'], $filename)) 
    {
        $serverport=''; if ($_SERVER["SERVER_PORT"]!='80') { $serverport=':'.$_SERVER["SERVER_PORT"]; }
        $fileurl='http://'.$_SERVER["SERVER_NAME"].$serverport.dirname($_SERVER["SCRIPT_NAME"]).'/'.$SUBDIR.'/'.basename($_FILES['filetoupload']['name']);
        echo 'The file/image was uploaded to <a href="'.$fileurl.'">'.$fileurl.'</a>';
    } 
    else { echo "There was an error uploading the file, please try again !"; }
    echo '<br><br><a href="'.$scriptname.'">Upload another file.</a>';
    exit();
}  
print <<<EOD
<form method="post" action="$scriptname" enctype="multipart/form-data">        
    File/image to upload: <input type="file" name="filetoupload" size="60">
    <input type="hidden" name="MAX_FILE_SIZE" value="256000000"><br>
    Password: <input type="password" name="password"><br>
    <input type="submit" value="Send">   
</form>
<small>Self-hosting php script by <a href="http://sebsauvage.net/wiki/doku.php?id=php:filehosting">sebsauvage.net</a></small>
EOD;
?>
</body>
</html>

Please note that you are limited by the php upload file size limit (see php.ini) and the maximum POST size accepted by Apache (see you apache configuration). You can use phpinfo() to find this limit (Search for post_max_size and upload_max_filesize).

Version history

  • 0.1 : First version.
  • 0.2 : Moved php script out of the data directory. Added proper htaccess file for better security (prevent execution of uploaded files such as .php).
  • 0.3 : Accessing the data directory now redirects to the upload script.
  • 0.4 : Added a sleep() to reduce brute-force attack effectiveness.
  • 0.5 : Path correction (thanks to Elouan Pignet)

Screenshot

Discussion

odile
, 2011/02/16 10:58

Merci, merci! J'ai un site depuis 1997! mais je ne sais pas programmer… Votre fichier est nickel et me facilitera la vie. PS: j'ai francisé les textes et si vous en souhaitez cette version, me la demander.

Thomas Pat Kowalski
, 2012/04/22 11:55

Salut ! Moi j'aimerais bien la version française :P

baracouda
, 2011/02/16 15:11

Simple, clair et super utile, c'est comme ça que j'aime les scripts php ! Ceux qui veulent peuvent le modifier facilement pour l'adapter à leurs besoins.

Je viens de le mettre sur ma dedibox et je vais pouvoir me passer des imageshack & co pour publier mes photos sur EOS-numerique :)

Merci à toi ! :)

JeromeJ
, 2011/02/17 00:33

Salut,

un petit anti-force brute pourrait être nécessaire quand on voit l'efficacité de ces protections et la tendance des gens à mettre des mot de passes de plus en plus débiles ;)

Sébastien SAUVAGE
, 2011/02/17 11:34

Ok… j'ajoute ça. Un délai de 3 secondes devrait être suffisant. Si vous avez d'autres idées, n'hésitez pas à proposer.

JeromeJ
, 2011/02/18 21:22

Le sleep que tu as ajouté ralonge le temps de chargement de la page mais on peut faire plusieurs requêtes simultanément non ? Donc au bout du compte ça change rien (si je ne me trompe pas =) )

Perso j'aurais utilisé des sessions pour identifier qui a fait un essai récemment et limiter le nombre d'essais par minute mais bon c'est pas infaillible non plus: On peut facilement se faire passer pour quelqu'un d'autre (existe-t-il un moyen sûre d'identifier quelqu'un ?)

oliverpool
, 2011/02/19 12:44

Une idée un peu tordue pour se battre contre la force brute : créer un fichier indiquant qu'une requête est en traitement au début du script et le supprimer à la fin (avec les 3 secondes). En rajoutant au début du fichier while (isset($file_anti_brute)) {sleep(2);} ca devrait serieusement ralentir l'attaquant.

Qu'en pensez-vous ?

Sébastien SAUVAGE
, 2011/02/20 23:31

Oui, je sais pour les requêtes simultanées. En effet la protection pourrait être meilleure. Mais je ne voulais pas aller jusqu'à une captcha.

Le sessions, ça ne marcherait pas: il suffirait que son client accepte les cookies (ça se programme très bien en python), et les jette à l'essai suivant.

L'IP pourrait à la limite marcher, du genre: Limiter le nombre d'envoi de formulaire par minute et par IP.

T2
, 2011/02/17 01:08

Hello Seb,

Toutes mes félicitations. En publiant le programme, cela rendra service à d'autres. No comment: comme d'hab'! Bonne continuation.

Ps: pense à checker tes MP sur CCM quand t'auras un peu de temps ;)

, 2011/02/21 22:08

Aucun contrôle sur le fichier envoyé ? (MIME) … was uploaded to ($fileurl - possible XSS ?) Pour éviter les doublons, possible de préfixer (timestamp).

Sébastien SAUVAGE
, 2011/02/22 08:20

Aucun contrôle MIME sur le fichier envoyé. C'est le serveur web qui déterminera le type MIME à servir au moment du téléchargement.

Pour le $fileurl, tu as raison, il n'est pas correctement échappé, mais bon celui qui obtient cette page connaît le mode de passe. Le propriétaire du site ne devrait pas essayer de faire une attaque XSS sur son propre site (et puis le formulaire est en POST).

Pour éviter les doublons, possible de préfixer (timestamp).

Oui je pourrais générer un identifiant unique, de manière à pouvoir uploader des fichiers de même nom, mais j'ai préféré garder le nom: Cela permet à celui qui upload de choisir des noms explicites.

Minipipo1
, 2011/02/24 22:51

Salut,

Tu peux simplement mettre une condition pour vérifier si le fichier existe déjà sous ce nom ; et si c'est le cas, tu rajoutes un préfixe ;-).

Sinon je n'ai pas compris à quoi servait le .htaccess dans le répertoire files ?

Sébastien SAUVAGE
, 2011/03/03 13:55

Le .htaccess sert à empêcher l'exécution de scripts dans le répertoires files. Afin d'éviter que quelqu'un puisse uploader un .php et l'exécuter.

BradPatt
, 2011/03/09 05:39

Tu pourrais aussi ajouter un champ dans le formulaire permettant de choisir quel sera le nom du fichier enregistré.

Sébastien SAUVAGE
, 2011/03/09 16:07

oui… ou non. On peut aussi renommer le fichier avant de l'uploader. Je ne pense pas que ça soit plus lourd. En tous cas, mon but était de garder le script le plus minimaliste possible, donc de limiter la complexité du formulaire.

Tom
, 2011/03/31 21:27

Juste un petit message pour te remercier pour ce petit script simple et efficace. Le top ! Longue vie à ton site.

Frederic Collard
, 2011/05/10 11:08

Sébastien,

Merci pour ce petit script simple, court et efficace. Il fait ce qu'il doit faire et rien d'autre. Si tu en as d'autre du genre, je suis preneur.

zouzou
, 2011/05/15 03:15

Bonjour, juste un petit lien vers un script similaire quoiqu'un peu plus complet que je viens de découvrir. Il n'y a pas par défaut de système de mot de passe empêchant l'upload par des tiers mais c'est facilement rajoutable. Cela peut peut-être servir à d'autres :) http://projets.kd2.org/p/fotoo-hosting/

Sébastien SAUVAGE
, 2011/05/17 14:12

Oui je connais la galerie de BohwaZ, il m'en avait parlé. Je cherchais seulement à faire minimaliste et fonctionel :-)

Newbbie babykiller
, 2011/06/10 16:58

salut Sébastien

merci pour tes lumières qui illuminent ma newbbie connaissance informatique.

Guenhwyvar
, 2011/06/10 22:45

Combien tu paries que certains laisseront 'toto' en mot de passe ? :D

H3bus
, 2011/06/13 03:29

Bonjour,

Super script, je me suis permis d'ajouter une authentification par htaccess par login et mdp, et une fonction qui détecte si le fichier est une image, et qui en crée une miniature pour l'affichage sur les forums phpBB, ainsi qu'une page qui permet de lister les fichier présents sur le serveur.

Je suis en train de rajouter un back office, pour ajouter un utilisateur dans le htaccess (avec le mdp en crypté of course).

Bonne soirée ;)

Sébastien SAUVAGE
, 2011/06/14 10:29

oh… woao. Chouette.

call5
, 2012/06/24 08:58

bonjour, je ne sais pas si tu passe encore ici mais si jamais tu peux partager ta modif ça me sera très utile. merci

Edouard
, 2012/02/14 11:58

Je ne fais que passer pour tester la tête qu'à mon vizhash sur ton wiki. Si il est trop moche, j'ouvre un rapport de bug :)

Edouard
, 2012/02/14 11:59

ça va je l'aime bien !

Sébastien SAUVAGE
, 2012/03/15 14:11

:-)

Guenhwyvar
, 2012/04/25 22:38

Pourquoi ne pas mettre le sleep(3) dans le if juste en dessous, comme ça : if ($_POST['password']!=$PASSWORD) { print 'Wrong password.';sleep(3); exit(); } Il aurait la même utilité anti-bruteforce, mais ne gênerait pas un utilisateur normal (qui connaitrait donc le mot de passe).

Bertrand
, 2012/07/13 11:19

Super ! Un grand merci pour ce script simple et efficace ! Je tournais autour de la question depuis longtemps, et je serais prêt à faire une petite donation :-)

Par contre, dans l'idéal pour mes besoins, je souhaiterais avoir un effacement “automatique” des fichiers au bout d'un certain temps. Plusieurs stratégies me conviendraient : * Idéal : spécifier un “time-to-live” au moment du dépôt * Bien : délai par défaut réglable par une constante du programme Dans mon cas, la gestion des effacements (cleanup) lors d'une opération d'upload me suffit.

Dans tous les cas Bravo d'avoir partagé votre script ! Cordialement, Bertrand

Farnsworse
, 2012/08/03 16:35

Pas mal de détails à voir

  • L'anti-bruteforce est relativement inutile dans le cas d'attaques parallèles et ne ralentira que la première vague de tentatives
  • Selon la doc de PHP, le champ de formulaire MAX_FILE_SIZE doit se trouver avant le champ file
  • Ce même champs ne sert qu'à indiquer au navigateur la taille max du fichier. Le script doit tester $_FILES['filetoupload']['error'] et pas seulement se fier à la limite du php.ini
  • Manque la fermeture de <head> et l'ouverture de <body>
  • Manque un test de $_FILES['filetoupload']['error']
  • $fileurl peut ête simplifié en $fileurl = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/' . $filename; rendant $serverport inutile. Le port est indiqué dans $_SERVER['HTTP_HOST'] s'il est différent de 80
  • Le second paramètre de mkdir à un comportement imprévisible et est inutile vu qu'on fait un chmod juste après
  • Il faudrait tester $_SERVER['SERVER_SOFTWARE'] pour ne pas créer un .htaccess sur un serveur web non Apache
Farnsworse
, 2012/08/03 16:42

Dans mon troisième point, je voulais dire $_FILES['filetoupload']['size']

Hugues
, 2013/04/04 04:43

Super pratique, merci !

Il manque juste une option pour remplacer un fichier deja existant et ce serait parfait !

ouinouinnn
, 2013/04/12 09:50

Bonjour, j'ai trouvé ce petit snippet pour aider à la protection contre les attaques en parallèle :

session_start()

if(isset($_SESSION['ip']) && $_SESSION['last_post'] + MININTERVAL < time()) die('too early');

$_SESSION['last_post'] = time();

$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];

store the message

onisahkku
, 2014/12/11 22:12

Je ne parviens pas à modifier le script pour y ajouter moi-même une valeur incrémentée ou un timestamp dans le cas d'un upload en doublon. Un peu d'aide serait-elle possible ? (Je ne trouve pas cela non plus sur les forums, ne sachant pas quoi chercher exactement)

Entrer votre commentaire. La syntaxe wiki est autorisée:
  ____   _____   ____  __  __   ___ 
 / __ \ / ___/  / __/ / / / /  / _ \
/ /_/ // (_ /  _\ \  / /_/ /  / ___/
\____/ \___/  /___/  \____/  /_/
 
php/filehosting.txt · Dernière modification: 2014/07/12 13:26 (modification externe)