Skip to content

Latest commit

 

History

History
561 lines (410 loc) · 23 KB

6.Seguretat_en_PHP.md

File metadata and controls

561 lines (410 loc) · 23 KB

Seguretat en PHP (i altres coses)

Taula de continguts

Ocultant dades d'entorn

Video

Una de les pràctiques habituals i necessàries en les aplicacions PHP és l'emmagatzematge de les variables d'entorn i la seua posterior recuperació en el codi font de les pàgines.

Per a poder incorporar d'una manera àgil les variables d'entorn en aplicacions PHP usarem una llibreria que ja ve preparada amb les funcionalitats bàsiques que podrem arribar a necessitar en el dia a dia. La llibreria es diu "PHP dotenv" i bàsicament permet la lectura senzilla de les variables d'entorn en arxius d'extensió ".*env".

L'ús de variables d'entorn en les aplicacions web és important per a la separació del codi per responsabilitats: no hem de mesclar el codi de l'aplicació amb els valors de configuració.

Les variables de configuració canviaran generalment segons l'entorn d'execució, és a dir, quan una aplicació està sent executada en diferents servidors. Per exemple, el servidor local per a desenvolupament i el servidor remot on es col·locarà el lloc web accessible als usuaris d'Internet. Per tant, separar-les en arxius independents permetrà que el mateix codi funcione en qualsevol lloc, sense necessitat de modificacions, independentment dels valors de configuració que es tinguen en cada entorn.

Pensem en els valors de connexió amb la base de dades (host, user, password…). Aqueixos valors segurament seran diferents en el servidor de desenvolupament i en el servidor de producció. Altres elements que seria important tindre separats en variables d'entorn són les credencials d'accés a diversos serveis, claus APIs (API Keys), el nom del host on s'està executant l'aplicació, etc.

Artxius .env

Els arxius .env es poden considerar un estàndard per a l'emmagatzematge de variables d'entorn. Aquests arxius tenen un format molt senzill i fàcil d'escriure i de llegir.

La sintaxi dels .*env conté parells clau (nom de variable) i valor, separats per un caràcter "=". Cada variable en una línia.

MODE="development"
HOST="projecte.my"

DB_USER="batoi"
DB_PASSWORD="1234"
DB_HOST="localhost"

L'arxiu .env generalment ho col·locaràs en l'arrel del teu projecte, en un arxiu sense nom. La ruta del .env no ha d'estar accessible pel Servidor Web, sinó que ha de ser un directori per damunt, perquè els usuaris no puguen accedir a la configuració de les nostres variables d'entorn. Fixa't també que el nom de l'arxiu començarà amb un punt i després les tres lletres "env". En sistemes com Linux o Mac, l'arxiu que comença per "." es considera ocult.

El que és important és que aquests arxius no es troben a l'abast dels usuaris, per la qual cosa mai haurien d'estar dins de la carpeta de publicació, sinó algun directori per damunt en el servidor. És a dir, es col·locarà en l'arrel del projecte, però mai dins de la carpeta arrel de publicació, perquè si foren allí els usuaris podrien accedir a aqueixos valors component la ruta com "example.com/.env". Òbviament, els valors de configuració desitgem que estiguen segurs, per la qual cosa no han de ser accessibles pel públic en general.

Llibreria PHP DOTENV

PHP dotenv https://github.com/vlucas/phpdotenv fa la tasca d'obrir l'arxiu on les variables d'entorn s'emmagatzemen i processar el seu contingut, per a produir les variables d'entorn i consumir-les còmodament dins de les aplicacions.

Pots instal·lar fàcilment mitjançant

composer require vlucas/phpdotenv

Càrrega d'arxius amb variables d'entorn

PHP dotenv ens permet tindre diversos arxius de configuració. Per a accedir al contingut dels arxius .env utilitzem el següent codi:

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

Accés a les variables d'entorn

Una vegada que hem carregat els arxius de variables d'entorn, estaran disponibles les seues variables i valors en el codi de les aplicacions per mitjà de diversos mètodes diferents.

La funció getenv(), en la qual passem la cadena de la variable d'entorn que volem accedir.

A través del *array *superglobal $*ENV A través del *array *superglobal $*SERVER

$s3_bucket = getenv('S3_BUCKET');
$s3_bucket = $_ENV['S3_BUCKET'];
$s3_bucket = $_SERVER['S3_BUCKET'];

Sessions i seguretat

Video

Com Http és un protocol sense estat, les diferents peticions d'un client a un servidor són independents, no estan relacionades entre si. Per associar-les s'utilitzen les sessions.

El terme sessió fa referència al conjunt d'informació relativa a un usuari concret. Aquesta informació pot ser tan simple com el nom del propi usuari, o més complexa, com els articles que ha dipositat en la cistella de compra d'una tenda online.

Cada usuari diferent d'un lloc web té la seua pròpia informació de sessió.Per a distingir una sessió d'una altra s'usen els identificadors de sessió (SID). Un SID és un atribut que s'assigna a cadascun dels visitants d'un lloc web i ho identifica. Si el servidor web utilitza el SID d'un usuari, per a relacionar-ho amb la informació que posseeix sobre ell, que es manté en la sessió de l'usuari.

El procés de maneig de sessions en PHP està automatitzat.

Accedir a les dades de la sessió

  • Una vegada oberta la sessió, usem la variable superglobal $_SESSION per a afegir informació a la sessió de l'usuari, o per a accedir a la informació emmagatzemada en la sessió
  • Per exemple, per a explicar el nombre de vegades que l'usuari visita la pàgina, podem fer:
// Iniciem la sessió o recuperem l'anterior sessió existent 
session_start(); 
// Comprovem si la variable ja existeix 
if (isset($_SESSION['visites']))
   $_SESSION['visites']++; 
else
   $_SESSION['visites'] = 0;

Eliminar una variable de la sessió

  • Per a eliminar una variable de la sessió usarem la funció unset()
   unset($_SESSION['visites']);
  • Per a tancar la sessió de forma manual utilitzarem la funció session_destroy()
  • Abans hem d'eliminar les variables de la sessió de la següent forma:
    • $_SESSION = [];
Objectes i arrays

Si volem guardar un objecte o un array en una variable de sessió primer l'hem de transformar amb serialize() i despres, quan el recuperem, haurem d'utilitzar unserialize()

Autenticació amb sessions

Video

  • Probablement, l'ús més estès del control de sessions siga el seguiment d'usuaris autenticats a través d'un mecanisme d'inici de sessió. Per a açò necessitarem un formulari de login.

register.php

<?php
if (isPost() && cfsr()){
        try {
            // Comprovació d'errors
        } catch ( CheckFieldException $e) {
            $errors[$e->getField()] = $e->getMessage();
        }

       if (!count($errors)){
           $password = password_hash($password,PASSWORD_DEFAULT );
           $query->insert('users',compact('name','email','password'));
           header('Location: /');
       }
    }
    require_once('register.view.php');
?>

login.php

 <?php 
 	session_start()
	if (isPost() && cfsr()){
       //comprovació d'errors
       if (!count($errors)){
           $user = $query->login('users',$email,$password);
           $_SESSION['user'] = serialize($user);
           header('Location: /');
       }
    }

    require_once('login.view.php'); ```
 
logout.php
 
```php
<?php
    session_start();
    unset($_SESSION['user']);
    session_destroy();
    header('Location: /');

index.php

<?php
	session_start();
   $user = unserialize($_SESSION['user']);
   if (!$user) {
        header('Location: /login.php');
   }
   ...  

queryBuilder.php

<?php
	public function login($table,$email,$password){
        $stpdo = $this->conn->prepare("SELECT * FROM $table WHERE email = :email");
        $stpdo->bindValue(":email",$email);
        $stpdo->execute();
        $user = $stpdo->fetch(\PDO::FETCH_OBJ);
        if (password_verify($password, $user->password)) return $user;
        return null;
    }    

La informació d'autenticació s'ha d'utlitzar en un protocol com a HTTPS que permeta xifrar les comunicacions amb el servidor web i amb contrasenya protegida amb hash.

Enviament de correu electronic

Video

Encara que la funció mail() permet l'enviament de correus electrònics, és habitual utilitzar alguna llibreria que s'ocupe dels detalls del format.

 composer require phpmailer/phpmailer

o modificant el composer.json per afegir la linea "phpmailer/phpmailer": "~6.1" dins del require

 "require": {
        "filp/whoops": "^2.4",
        "phpmailer/phpmailer": "~6.1"
    },

i executant els composer update

En principi es possible enviar un correu utilitzant la configuració de sendmail (en Linux) o un servidor SMTP local, en la pràctica els filtres antispam fan que no arriben els correus enviats des de servidors no registrats correctament. A més des de les aules tampoc es pot fer.

Si no es disposa d'un servidor de correu en Internet, l'opció més comoda per tal d'enviar un correu és utilitzar un compte de Gmail. En Gmail cal activar l'opció "Permetre aplicacions meyns segures" en la secció d'ajustos de compte. Açò va a canviar en breu i no es podrà utilitzar sinó que haurem d'identificar-nos amb Oauth. Teniu informació disponible en el github de php mailer.

El següent programa permet enviar un correu mitjançant Google:

<?php 	
 	use PHPMailer\PHPMailer\PHPMailer;
	use PHPMailer\PHPMailer\SMTP;
	use PHPMailer\PHPMailer\Exception;
 	require "vendor/autoload.php";
 	
	try {
	    //Server settings
	    $mail->SMTPDebug = SMTP::DEBUG_SERVER;                      // Enable verbose debug output
	    $mail->isSMTP();                                            // Send using SMTP
	    $mail->Host       = 'smtp.gmail.com';                    // Set the SMTP server to send through
	    $mail->SMTPAuth   = true;                                   // Enable SMTP authentication
	    $mail->Username   = '2daw2021batoi@gmail.com';                     // SMTP username
	    $mail->Password   = 'batoi_1234';                               // SMTP password
	    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;         // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
	    $mail->Port       = 587;                                    // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above

	    //Recipients
	    $mail->setFrom('from@example.com', 'Mailer');
	    $mail->addAddress('joe@example.net', 'Joe User');     // Add a recipient
	    $mail->addAddress('ellen@example.com');               // Name is optional
	    $mail->addReplyTo('info@example.com', 'Information');
	    $mail->addCC('cc@example.com');
	    $mail->addBCC('bcc@example.com');

	    // Attachments
	    $mail->addAttachment('/var/tmp/file.tar.gz');         // Add attachments
	    $mail->addAttachment('/tmp/image.jpg', 'new.jpg');    // Optional name

	    // Content
	    $mail->isHTML(true);                                  // Set email format to HTML
	    $mail->Subject = 'Here is the subject';
	    $mail->Body    = 'This is the HTML message body <b>in bold!</b>';
	    $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

	    $mail->send();
	    echo 'Message has been sent';
	} catch (Exception $e) {
	    echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
	}

On us he possat unes credencials que podeu gastar per al curs i no tenir que obrir un compte cadascú.

Composer

Composer ens resol dos problemes:

  • Gestionar les dependències amb llibreries de tercers (N'hi ha prou que declarem les dependències i Composer s'encarregarà de descarregar i instal·lar tot el que siga necessari)
  • Autoloading del nostre codi (Ja no haurem de fer més requires, Composer ho farà per nosaltres)

Gestió de les dependències

  • La instal·lació de les llibreries sempre és local per a cada projecte
  • S'instal·len en un directori del projecte
  • Per defecte aqueix directori és vendor/
  • Passos:
    • Declarar les llibreries de les quals depèn el projecte
    • Composer descàrrega i instal·la automàticament les versions correctes de cadascuna d'aqueixes llibreries i les seues dependències

Instal·lació

  • Es pot instal·lar localment per a cada projecte o globalment
  • Anem a instal·lar-ho globalment:
$curl -sS https://getcomposer.org/installer | php  
sudo mv composer.phar /usr/local/bin/composer
  • Hem descarregat composer i ho hem mogut a un directori que està dins del PATH. A partir d'ara, podem executar Composer simplement escrivint composer

Preparar l'arxiu composer.json

  • Necessari per a usar composer en un projecte
  • Consisteix en una sèrie d'estructures d'informació niades
  • Exemple:
 {"require": { "monolog/monolog": "1.2." } }
  • require mapea noms de paquets (en l'exemple, monolog/monolog) amb versions de paquets (en aquest cas, 1.2.)

Exemple d'ús

  • Estem desenvolupant un projecte i necessitem una llibreria per a guardar missatges de log
  • Decidim utilitzar la llibreria monolog
  • Per a incloure aquesta llibreria:
    • Anem a crear un arxiu composer.json
{ "require": {"monolog/monolog": "1.0." } } 

Estem indicant que el projecte depèn d'un paquet anomenat monolog/monolog i que li serveix qualsevol versió la numeració de la qual comence per 1.0

Noms de paquets

  • Està format per dues parts:
    • La primera indica qui és la seua "vendor" o creador
    • La segona indica el nom del projecte
  • Sovint les dues parts són idèntiques
  • El nom del creador és important per a evitar col·lisions entre projectes amb el mateix nom

Versions de paquets

  • En l'exemple anterior, la versió requerida de la llibreria és 1.0.
  • Es pot utilitzar qualsevol versió de la branca 1.0 (com per exemple 1.0.0, 1.0.2 o 1.0.20)
  • Aquesta versió és equivalent a =1.0 > <1.1
  • Les versions requerides es poden especificar de moltes maneres:
    • Versió exacta: com per exemple 1.0.2
    • Rang de versions: S'usen operadors de comparació: >, >=, <, <=, !=. ( Exemples: >=1.0 o >=1.0,<2.0)
    • Comodins: La versió 1.0. per exemple és equivalent a =1.0, ><1.1
    • La següent versió significativa: que s'indica mitjançant l'operador ~ i s'interpreta de la següent manera: ~1.2 és equivalent a =1.2, ><2.0, mentre que ~1.2.3 és equivalent a =1.2.3, ><1.3 (útil per a aquells projectes que segueixen el versionat semàntic)

Versionat semàntic (Semver)

  • Els nombres de versió i la forma en què canvien informen sobre el que va ser modificat d'una versió a una altra
  • Veure l'especificació en el següent document:

Instal·lant les dependències

  • Executem el següent comando:
 composer install
  • Es generarà el directori vendor/ amb les llibreries de les quals depèn el projecte
  • MOLT IMPORTANT: Hem d'afegir el directori vendor/ a l'arxiu .gitignore
  • El comando també crea un arxiu composer.lock

L'arxiu composer.lock

  • Guarda la versió exacta que s'ha instal·lat de cada llibreria
  • El projecte es fixa a unes determinades versions
  • Tant el composer.lock com el composer.json han d'estar en el repositori
  • El comando install comprova primer si existeix l'arxiu composer.lock, i si existeix, descàrrega exactament les versions que s'indiquen en aqueix arxiu
  • Si treballem en equip, tot l'equip tindrà les mateixes versions

Actualitzar versions

  • Si tenim l'arxiu composer.lock sempre s'instal·laran les mateixes versions de les llibreries
  • Per a actualitzar a noves versions, usem el comando update
  • Fa que composer cerque les versions més recents de les llibreries
  • Sempre que seguisquen complint les restriccions de les versions indicades en l'arxiu composer.json
  • També actualitza l'arxiu composer.lock
  • Si solament volem instal·lar o actualitzar una dependència, podem indicar el seu nom després del comando:
 composer update monolog/monolog

Afegint dependències

  • El comando require afig noves dependències en l'arxiu composer.json
    • composer require
  • Ens preguntarà què llibreries volem afegir
  • Després d'afegir aquestes noves dependències, s'instal·len o actualitzen les dependències que siguen necessàries
  • Podem passar les noves dependències com a argument del comando
 composer require monolog/monolog:1.

Packagist

  • Repositori central de Composer (lloc del que s'obtenen els paquets)
  • Lloc web: http://packagist.org

Càrrega automàtica de classes

  • Normalment les llibreries proporcionen informació sobre la càrrega automàtica de les seues classes
  • Composer genera un arxiu vendor/autoload.php
  • Incloent aquest arxiu en el projecte, podem utilitzar qualsevol classe instal·lada a través de Composer sense haver d'incloure-la explícitament:
 require 'vendor/autoload.php';

Ús de la llibreria

  • Per a usar la llibreria Monolog podem usar les seues classes i Composer s'encarregarà de carregar-les:
$log = new Monolog\Logger('name'); 

$log->pushHandler(
new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING) );

$log->addWarning('Foo');

Espais de noms

  • Són una manera d'encapsular elements
  • Podemos fer un simil entre els namespaces i els directoris:
    • Els directoris serveixen per a agrupar fitxers relacionats
    • El fitxer foo.txt pot existir en els directoris /home/greg i /home/un altre, però no poden coexistir dues còpies de foo.txt en el mateix directori
    • Per a accedir al fitxer foo.txt fóra del directori /home/greg, s'ha d'anteposar el nom del directori al nom del fitxer, emprant el separador de directoris per a així obtenir /home/greg/foo.txt
  • Aquest mateix principi s'estén als espais de noms en el món de la programació

Introducció als namespaces

  • Els namespaces són una característica de PHP que s'introdueix en la versió 5.3
  • L'ús de namespaces és molt convenient per dos motius:
    • El conflicte de noms entre el codi que es crea i les classes/funciones/constants internes de PHP o les classes/funciones/constants de tercers
    • La capacitat de sobrenomenar (o abreujar) Noms_Extra_Llargs millorant la llegibilitat del codi font
  • Només usarem namespaces per a noms de classes

Definir espais de noms

  • Només les classes (incloent abstractes i traits), interfícies, funcions i constants es veuen afectats per espais de noms
  • Es declaren utilitzant la paraula reservada namespace
  • S'ha de declarar a l'inici del fitxer, abans que qualsevol altre codi
  • Només hi ha una excepció: la paraula reservada declare
namespace MiProyecto;

const CONNECTAR_OK = 1; 

class Connexió { / ... / } 

function connectar() { / ... / }

Ubicació de la declaració

  • Tot el que no siga codi PHP no pot precedir a la declaració de l'espai de noms
 <html> <?php namespace MiProyecto;
 
  // error fatal - el espacio de nombres debe ser la primera sentencia del script ?>

Declarar subespacios de noms

  • Un nom d'un espai de noms es pot definir amb subnivells
namespace MiProyecto\Sub\Nivell;

const CONNECTAR_OK = 1; 

class Connexió { / ... / } 

function connectar() { / ... / }

Ús de namespaces en els nostres projectes

  • Cada aplicació que creiem tindrà un espai de noms diferent
  • Podrem separar els subnoms del namespace amb el caràcter \
  • Per exemple:
namespace CursoPhp7\Core; Class Request { ... }
  • La classe Request estarà dins de l'espai de noms CursoPhp7\Core

Usar elements que estan dins de namespaces

  • Per a poder usar la classe Request de l'exemple anterior haurem d'indicar el
nom complet CursoPhp7\Core\Request
  • Per a abreujar els usos podem utilitzar la instrucció use que permet indicar els espais de noms que anem a utilitzar:
use CursoPhp7\Core\Request;
  • Ara ja podrem usar la classe Request sense indicar la ruta completa
  • També podem definir un àlies per a una classe de la següent forma:
use CursoPhp7\Core\Request as Req;
  • Ara podrem usar la classe Request com Req
  • És una molt bona pràctica fer coincidir els namespaces amb els directoris on es troben els arxius
  • Açò ens permetrà utilitzar el autoloader de Composer que veurem més endavant
  • En el nostre projecte tots els arxius pengen del directori \
  • Més endavant, veurem que podem associar el nom del directori de nostre projecte amb un espai de noms, per exemple, CursoPhp7.
  • Per tant, els namespaces començaran per CursoPhp7\
  • Per exemple:
    • CursoPhp7\App\Controllers o CursoPhp7\Core o CursoPhp7\Database o ...

Afegir els uses

  • En tots els llocs on utilitzem un namespace, haurem d'afegir l'use de l'espai de noms corresponent
  • A més, quan utilitzem una classe d'alguna llibreria o del propi PHP, com per exemple PDO, haurem d'afegir-li una \ davant per a indicar que la classe està en l'espai de noms arrel
return $statement->fetchAll(
\PDO::FETCH_CLASS, 'Agenda\\Entities\\'.$classEntity);

Autoloading amb Composer

  • Cada vegada que afegim una nova classe a la nostra aplicació hem d'inserir el require corresponent en el nostre bootstrap.php
  • Per a evitar açò podem utilitzar l'eina composer amb la seua funcionalitat de autoloading
Càrrega automàtica de les nostres classes
  • La càrrega automàtica de classes en composer es controla mitjançant la propietat autoload de l'arxiu composer.json
  • Es permeten quatre valors:
    • El carregador de classes que segueix l'estàndard PSR-0
    • El carregador de classes que segueix l'estàndard PSR-4 (substitueix a l'anterior)
    • El mapa de classes (classmap)
    • Els arxius individuals (files)
  • El mètode recomanat és el carregador PSR-4 perquè és el més flexible (no fa falta per exemple regenerar el carregador de classes quan s'afig una nova classe al projecte)

Ús de PSR-4

  • Podemos consultar l'especificació en la següent url:
  • Es basa que els noms dels namespaces han de coincidir amb els directoris on se situen els arxius
  • Per a activar-ho afegirem la clau autoload indicant l'opció psr-4 i amb quin directori es mapea el namespace principal
Exemple
{
"autoload": {"psr-4": {"Acme\\": "src/"} } }
  • Composer crearà un carregador automàtic de classes per al namespace Acme que seguirà les normes de l'estàndard PSR-4 de PHP
  • El directori src/ estarà en l'arrel del projecte, al mateix nivell que el directori vendor/
  • Contindrà el codi del projecte
  • Així, l'arxiu src/Acme/Foo.php hauria de contenir la classe Acme\Foo
  • Després d'afegir l'opció autoload, cal executar el comando composer dump-autoload perquè es regenere l'arxiu vendor/autoload.php

Incloure el autoloader

  • Perquè tot funcione correctament caldrà incloure el fitxer autoload.php en el controlador frontal (index.php) abans d'incloure el bootstrap.php
  • Ara podrem eliminar tots els requires de classes que tinguem en el bootstrap.php
  • El autoload s'encarregarà cercar els fitxers i utilitzar-los