Safer uploads or don’t trust a browser
Yoan Blanc — Thu 31 March 2011 — PHP, Python, libmagic, Security
We did tell you to no trust the browser right? Or the user/bot behind it. And to filter, check, sanitize everything that comes from him, her or it.
So you check the basics like GET
and POST
arguments. And maybe you check the values coming for the cookies as well. But do you check the file uploading? You know that type
arguments sent. This how a request look like:
POST /…
…
Content-Type: multipart/form-data; boundary=boundary
--boundary
Content-Disposition: form-data; name="key"
value
--boundary
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png
…Your PNG image here…
--boundary--
The Content-Type
field is guessed by the browser and most of the time on the file extension. But it can decide to send whatever it wants like application/octet-stream which just says that it’s a binary file.
So, you shall not trust it and always verify what you get using the proper tool, like the UNIX command
file PHP ships
with it and Python has a wrapper
called magic. That how you use it in Python in a WSGI application (using WebOb).
import magic
from webob import Request
def application(environ, start_response):
req = Request(environ, charset="utf-8")
# sent from the browser
uploaded_file = req.POST.get("file")
# what we actually got
guessed_type = uploaded_file.type
magic_type = magic.from_buffer(uploaded_file.read(1024),
mime=True)
uploaded_file.seek(0)
"… whatever else …"
This way you can really white list the file you users are uploading and prevent them for sending you .mov, .doc or whatever when they think they are uploading a new avatar for their profile. True story.
The code is on github
On vous a certainement dit et répété de vérifier tout ce qui provient de vos utilisateurs, juste ? Alors vous vérifiez les GET
et les POST
, et parfois aussi les cookies mais avez-vous pensé aux envois de fichiers ?
Aha, non ! C’est bien ce que je pensais. En gros, pour faire simple on peut vous faire parvenir n’importe quoi en vous disant que c’est n’importe quoi d’autre. Tiens une image alors que je t’envoie un document LibreOffice (c’est comme Office, si si). Et ça se fait très certainement sans en avoir conscience car c’est le navigateur qui rempli cette information et de manière générale il la devine à partir de l’extension du fichier.
C’est pourquoi il est important d’exclure cette donnée et d’aller la repêcher par ses propres moyens. En PHP, par exemple, ça se fait avec finfo qui est très proche de la commande UNIX file
, bien connue. Los.
<?php
$file = $_FILES['file'];
// ce que nous donne le navigateur
$guessed_type = $file['type'];
// ce que file nous donne
$finfo = new finfo(FILEINFO_MIME);
$magic_type = $finfo->file($file['tmp_name']);
Un petit cas concret de problème que nous avons rencontré. Une gif animée enregistrée en tant que jpg dont on désire obtenir une miniature avec Imagick risque de vous donner autant d’images qu’il y a de frames (si bien entendu vous supportez aussi les gif animées, ce que nous faisons).
Et vous éviterez aussi de retrouver trop de bordel dans les avatars de vos utilisateurs.
Le code se trouve sur github