Dans cet article, j’ai cherché à sécuriser votre thème ou votre plug-in en procédant à une purification et à une validation appropriées des données. Dans cet article, nous aborderons un autre aspect important de la sécurité WordPress: les fonctionnalités et les fonctionnalités..
Lors du développement d'un plug-in (et dans une moindre mesure, de thèmes), vous constaterez souvent que vous souhaitez autoriser un utilisateur à effectuer diverses actions: supprimer, éditer ou mettre à jour des publications, des catégories, des options ou même d'autres utilisateurs. Le plus souvent, vous ne voulez que certains utilisateurs autorisés à effectuer ces actions. Pour cela, WordPress utilise deux concepts: rôles et capacités.
Supposons que nous voulions un bouton de suppression frontal pour supprimer rapidement les publications. Ce qui suit crée un lien où que nous utilisions wptuts_frontend_delete_link ()
dans la boucle.
function wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); echo "Supprimer";
Ensuite, pour traiter l'action de suppression:
if (isset ($ _ REQUEST ['action']) && $ _REQUEST ['action'] == 'wptuts_frontend_delete') add_action ('init', 'wptuts_frontend_delete_post'); function wptuts_frontend_delete_post () // Récupère l'ID de la publication. $ post_id = (isset ($ _ REQUEST ['post'])?? (int) $ _REQUEST ['post']: 0); // Pas d'article? Oh bien… si (vide ($ post_id)) retourne; // Supprimer le post wp_trash_post ($ post_id); // redirection vers la page d'administration $ redirect = admin_url ('edit.php'); wp_redirect ($ redirect); sortie;
Ensuite, lorsqu'un utilisateur clique sur le lien "supprimer", la publication est supprimée et l'utilisateur est redirigé vers l'écran de l'administrateur de la publication..
Le problème avec le code ci-dessus est qu’il n’effectue aucune vérification d’autorisation: toute personne peut consulter le lien et supprimer un message - pas seulement poster
variable de requête, ils peuvent supprimer n'importe quel poste. Tout d'abord, nous voulons nous assurer que seulement les personnes qui veulent supprimer des messages peuvent supprimer des messages.
Lorsqu'un utilisateur est inscrit sur votre site WordPress, un rôle lui est attribué: il peut s'agir admin
, éditeur
ou abonné
. Chaque rôle se voit attribuer des capacités, par exemple edit_posts
, edit_others_posts
, delete_posts
ou manage_options
. Quel que soit le rôle attribué à un utilisateur, il hérite de ces fonctionnalités: les capacités sont attribuées aux rôles, pas aux utilisateurs.
Ces fonctionnalités déterminent les parties de l'écran d'administration auxquelles ils peuvent accéder et ce qu'ils peuvent et ne peuvent pas faire pendant leur séjour. Il est important de noter que lors de la vérification des autorisations, vous vérifiez la capacité, et ne pas le rôle. Des fonctions peuvent être ajoutées ou supprimées aux rôles. Vous ne pouvez donc pas supposer qu'un utilisateur 'administrateur' doit être capable de gérer les options du site. Vous devez vérifier spécifiquement si l'utilisateur actuel dispose réellement du manage_options
aptitude.
Par exemple, vous devriez généralement éviter:
if (current_user_can ('admin')) // Faites quelque chose que seuls les utilisateurs capables de gérer les options devraient être en mesure de faire.
Au lieu, vérifier la capacité (ou capacités):
if (current_user_can ('manage_options')) // Faites quelque chose que seuls les utilisateurs capables de gérer les options devraient pouvoir faire.
L'ajout et la suppression de fonctionnalités est très simple. WordPress fournit le add_cap
et remove_cap
méthodes pour la WP_Role
objet. Par exemple, pour ajouter la fonctionnalité 'perform_xyz' au rôle d'éditeur:
$ editor = get_role ('editor'); $ editor-> add_cap ('perform_xzy');
De même pour supprimer une capacité:
$ editor = get_role ('editor'); $ editor-> remove_cap ('perform_xzy');
Les fonctionnalités d'un rôle sont stockées dans la base de données. Vous ne devez donc effectuer cette opération qu'une seule fois (par exemple, lorsque votre plug-in est activé ou désinstallé)..
Si vous souhaitez que votre plug-in fournisse des paramètres permettant aux utilisateurs de modifier les fonctionnalités d'autres fonctionnalités (fonctionnalités liées aux fonctionnalités de votre plug-in), une fonction utile consiste à utiliser get_editable_roles ()
qui renvoie un tableau filtré de rôles. Cela ne devrait pas être utilisé à la place de la vérification des autorisations, mais permet à votre utilisateur du plugin de limiter les rôles pouvant être édités par un rôle donné. Par exemple, les éditeurs peuvent être autorisés à modifier des utilisateurs, mais uniquement les auteurs..
Les capacités que nous avons vues jusqu'à présent sont appelées capacités «primitives» - et elles sont attribuées à différents rôles. Puis il y a méta capacités, qui ne sont pas attribués à des rôles, mais mappent plutôt à des capacités primitives requises du rôle de l'utilisateur actuel. Par exemple, étant donné un identifiant de publication - nous voudrons peut-être demander à un utilisateur de modifier ce poster?
if (current_user_can ('edit_post', 61)) // Faites quelque chose que seuls les utilisateurs pouvant modifier le post 61 devraient pouvoir faire.
le edit_post
la capacité n'est attribuée à aucun rôle (la capacité primitive, edit_posts
, Cependant, WordPress vérifie les rôles primitifs dont cet utilisateur a besoin pour lui accorder l’autorisation de modifier ce message. Par exemple, si l’utilisateur actuel est l’auteur du message, il a besoin de la edit_posts
aptitude. S'ils ne le sont pas, ils ont besoin du edit_others_posts
aptitude. Dans les deux cas, si le poste est publié, ils auront également besoin de la edit_published_posts
aptitude. De cette façon, les méta-capacités sont mappées sur une ou plusieurs capacités primitives.
Lorsque vous enregistrez un type de publication, les fonctionnalités cochées sont par défaut les mêmes que pour les publications. Cependant, vous pouvez spécifier vos propres capacités:
register_post_type ('event', array (… 'capacités' => array (// Méta possibilités 'edit_post' => 'edit_event', 'read_post' => 'read_event', 'delete_post' => 'delete_event', // primitif capacités 'edit_posts' => 'edit_events', 'edit_others_posts' => 'edit_others_events', 'publish_posts' => 'edit_others_events', 'read_private_posts' => 'read_private_events',),…));
Ensuite, pour vérifier si l'utilisateur actuel est autorisé à modifier des publications:
if (current_user_can ('edit_events'))) // Faites quelque chose que seuls les utilisateurs pouvant modifier des événements doivent pouvoir faire.
et pour vérifier si l'utilisateur actuel peut éditer un événement particulier:
if (current_user_can ('edit_event', $ post_id)) Faites quelque chose que seuls les utilisateurs pouvant modifier $ post_id devraient pouvoir faire.
toutefois - edit_event
(comme read_event
et delete_event
) est une méta-capacité et nous devons donc mapper sur les capacités primitives pertinentes. Pour ce faire, nous utilisons le map_meta_cap
filtre.
La logique est expliquée dans les commentaires, mais essentiellement, nous vérifions d’abord que la méta-capacité est liée à notre type de publication d’événement et que l’ID de publication transmis fait référence à un événement. Ensuite, nous utilisons un commutateur
déclaration pour traiter chaque méta capacité et ajouter des rôles à la $ primitive_caps
tableau. Ce sont ces capacités dont l'utilisateur actuel aura besoin pour obtenir l'autorisation, et exactement ce dont il dépend du contexte..
add_filter ('map_meta_cap', 'wptuts_event_meta_cap', 10,4); function wptuts_event_meta_cap ($ primitive_caps, $ meta_cap, $ user_id, $ args) // Si la méta-capacité n'est pas basée sur un événement, ne rien faire. if (! in_array ($ meta_cap, array ('edit_event', 'delete_event', 'read_event'))) return $ primitive_caps; // Vérifier que le post est de type post. $ post = get_post ($ args [0]); $ post_type = get_post_type_object ($ post-> post_type); if ('event'! = $ post_type) return $ primitive_caps; $ primitive_caps = array (); switch ($ meta_cap): case 'edit_event': if ($ post-> post_author == $ user_id) // l'utilisateur est l'auteur de la publication if ('publish' == $ post-> post_status) // L'événement est publié: nécessite la capacité 'edit_published_events' $ primitive_caps [] = $ post_type-> cap-> edit_published_posts; elseif ('corbeille' == $ post-> post_status) if ('publish' == get_post_meta ($ post-> ID, '_wp_trash_meta_status', true)) // L'événement est une publication publiée supprimée nécessitant 'edit_published_events' possibilité $ primitive_caps [] = $ post_type-> cap-> edit_published_posts; else $ primitive_caps [] = $ post_type-> cap-> edit_posts; else // L'utilisateur tente de modifier un message appartenant à quelqu'un d'autre. $ primitive_caps [] = $ post_type-> cap-> edit_others_posts; // Si le message est publié ou privé, des majuscules sont nécessaires. if ('publish' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_published_posts; elseif ('private' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_private_posts; Pause; case 'read_event': if ('private'! = $ post-> post_status) // Si la publication n'est pas privée, nécessite simplement une capacité de lecture $ primitive_caps [] = $ post_type-> cap-> read; elseif ($ post-> post_author == $ user_id) // La publication est privée, mais l'utilisateur actuel est l'auteur $ primitive_caps [] = $ post_type-> cap-> read; else // La publication est privée et l'utilisateur actuel n'est pas l'auteur $ primitive_caps [] = $ post_type-> cap-> read_private_post; Pause; case 'delete_event': if ($ post-> post_author == $ user_id) // l'utilisateur actuel est l'auteur, nécessite la fonction delete_events $ primitive_caps [] = $ post_type-> cap-> delete_posts; else // L'utilisateur actuel n'est pas l'auteur. Requiert la capacité delete_others_events $ primitive_caps [] = $ post_type-> cap-> delete_others_posts; // Si post est publié, nécessite également la fonction delete_published_posts if ('publish' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> delete_published_posts; Pause; interrupteur d'extrémité; return $ primitive_caps;
Pour revenir à notre lien de suppression frontal, nous souhaitons ajouter le contrôle de capacité suivant. Ajoutez ceci juste au-dessus du wp_trash_post
appelle wptuts_frontend_delete_post
.
if (! current_user_can ('delete_post', $ post_id)) return;
La vérification des capacités ci-dessus garantit que seuls les utilisateurs autorisés à supprimer cette poste, sont en mesure de supprimer ce poste. Mais supposons que quelqu'un vous incite à visiter ce lien. Vous disposez des capacités nécessaires pour supprimer involontairement le message. Il est clair que nous devons vérifier que l'utilisateur actuel a l'intention d'effectuer l'action. Nous faisons cela à travers des nonces.
L'analogie est celle d'un attaquant qui veut donner des instructions à quelqu'un. Le contrôle des capacités est le destinataire qui demande à voir d'abord un ID. Mais que se passe-t-il si l'attaquant glisse les instructions dans votre main? Le destinataire les exécutera volontiers (vous avez, après tout, la permission de donner de telles instructions).
Un nonce est comme un sceau sur une enveloppe qui vérifie que vous êtes bien l'expéditeur. Le sceau est unique pour chaque utilisateur. Par conséquent, si l'attaquant glissait ces instructions dans votre main, le destinataire pourrait inspecter le sceau et voir qu'il ne vous appartenait pas. Cependant, les sceaux peuvent être forgés - ainsi un nonce change chaque fois que vous donnez des instructions. Ce sceau est 'pour la circonstance'(d'où le nom) ou en d'autres termes, temporaire.
Donc, si quelqu'un vous envoie le lien de suppression, il contiendra son nonce et échouera donc à la vérification de nonce. Les nonces sont généralement utilisés à des fins uniques, mais la mise en œuvre des nonces dans WordPress est légèrement différente: le nonce change en fait toutes les 12 heures et chaque nonce est valide pendant 24 heures. Vous pouvez changer cela avec le nonce_life
filtre qui filtre la vie d'un nonce en secondes (donc normalement 86400)
add_filter ('nonce_life', 'wptuts_change_nonce_hourly'); function wptuts_change_nonce_hourly ($ nonce_life) // Change la durée de vie de nonce en 1 heure de retour 60 * 60;
(mais 24 heures doivent être suffisamment sécurisées). Plus important encore, les nonces doivent être propres aux instructions elles-mêmes et à tous les objets auxquels elles se rapportent (par exemple, supprimer un message et l'identifiant du message)..
WordPress prend une clé secrète (vous pouvez la trouver dans votre fichier de configuration) et la hache avec les parties suivantes:
wptuts_frontend_delete_61
Pour créer un nonce, vous pouvez utiliser wp_create_nonce ($ action)
où $ action
est expliqué ci-dessus. WordPress ajoute ensuite le tick et l'ID utilisateur et le hache avec la clé secrète.
Vous envoyez ensuite ce nonce avec l'action et toutes les autres données nécessaires à l'exécution de cette action. Vérifier le nonce est très simple.
// $ nonce est la valeur de nonce reçue avec l'action. $ action est ce que nous avons utilisé pour générer la nonce wp_verify_nonce ($ nonce, $ action); // retourne vrai ou faux
où $ nonce
est la valeur de nonce reçue et $ action
est l'action demandée comme ci-dessus. WordPress génère ensuite le nonce en utilisant le $ action
et vérifie si elle correspond à la donnée $ nonce
variable. Si quelqu'un vous avait envoyé le lien, son nom a été généré avec son identifiant et sera donc différent du vôtre..
Sinon, si le nonce a été publié ou ajouté en tant que variable de requête, avec le nom $ name
:
check_admin_referer ($ action, $ name);
Si le nonce est invalide, il arrêtera toute action ultérieure et affichera 'Êtes-vous sûr?' message.
WordPress facilite particulièrement l'utilisation des nonces: pour les formulaires utilisables wp_nonce_field ($ action, $ name)
. Cela génère un champ caché avec le nom $ name
et la forme générée par nonce $ action
comme sa valeur.
Pour les URL, vous pouvez utiliser wp_nonce_url ($ url, $ action)
. Cela prend la donnée $ url
et le retourne avec la variable de requête _wpnonce
ajouté, avec le nonce généré comme valeur.
Dans notre exemple:
function wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); $ nonce = 'wptuts_frontend_delete_'. get_the_ID (); écho "Effacer";
Lequel (pour le message avec l'ID 61) génère un nonce avec action wptuts_frontend_delete_61
. Puis juste au dessus du poubelle
appelle wptuts_frontend_delete_post
, nous pouvons vérifier le nonce:
check_admin_referer ('wptuts_frontend_delete _'. $ post_id, '_wpnonce');
L'utilisation de nonces dans les requêtes AJAX est légèrement plus compliquée. Les nonces étant générées côté serveur, la valeur nonce doit être imprimée sous forme de variable javascript pour être envoyée avec la demande AJAX. Pour ce faire, vous pouvez utiliser wp_localize_script
. Supposons que vous ayez enregistré un script appelé wptuts_myjs
qui contient une requête AJAX.
wp_enqueue_script ('wptuts_myjs'); wp_localize_script ('wptuts_myjs', 'wptuts_ajax', array ('url' => admin_url ('admin-ajax.php'), // URL vers la page de gestion de WordPress ajax 'nonce' => wp_create_nonce ('my_nonce_action'));
Ensuite, dans notre script 'wptuts_myjs':
$ .ajax (url: wptuts_ajax.url, dataType: 'json', data: action: 'mon_ajax_action', _ajax_nonce: wptuts_ajax.nonce,,…);
Enfin, dans votre rappel AJAX:
check_ajax_referer ('my_nonce_action');
Normalement, un nonce par formulaire (ou par demande) est suffisant. Cependant, avec WordPress, le contexte est légèrement compliqué. Par exemple, lorsque vous mettez à jour un message, WordPress effectuera des vérifications d’autorisation et de nonce - vous n’avez donc probablement pas besoin de vérifier un nonce pour votre fonction accrochée save_post
qui traite de votre méta-boîte personnalisée? Pas si: save_post
peut être déclenché dans d’autres instances, dans différents contextes ou événement manuellement - en fait chaque fois que wp_update_post
s'appelle en fait. Pour être sûr que les données que vous recevez proviennent de votre metabox vous devriez utiliser votre propre nonce.
Bien sûr, si vous utilisez plus d'un nonce dans un formulaire, il est important de lui attribuer un nom unique, c'est-à-dire un nom unique pour le champ masqué contenant la valeur de nonce. Si plusieurs nonces d'un formulaire ont le même nom, l'un l'emporte sur l'autre, et le contrôle échoue quelque part.
Ainsi, lors de la création d'un nonce pour votre metabox, assurez-vous de lui attribuer un nom unique:
function my_metabox_callback ($ post) $ name = 'mon_nom_nonce'; // Assurez-vous que c'est unique, préfixez-le avec votre nom de plug-in / thème $ action = 'my_action_xyz _'. $ Post-> ID; // C'est l'action nonce wp_nonce_field ($ action, $ name); // Votre meta box…
Alors pour votre save_post
meta-box:
add_action ('save_post', 'my_metabox_save_post'); function my_metabox_save_post ($ post_id) // Vérifiez que ce n'est pas une sauvegarde automatique si (défini ('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; // Vérifier que vos données ont été envoyées - cela permet de vérifier que nous avons l'intention de traiter notre metabox si (! Isset ($ _ POST ['mon_nonce_nom'])) return; // Vérifier les autorisations si (! Current_user_can ('edit_post', $ post_id)) return; // Enfin, vérifiez le nonce check_admin_referer ('my_action_xyz _'. $ Post_id, 'my_nonce_name'); // effectuer des actions
Si vous traitez des données provenant de plusieurs métabox, vous devriez idéalement avoir un nonce pour chaque. Les méta-boîtes peuvent être supprimées et, si la méta-boîte contenant le nonce est supprimée, aucune demande valide ne sera transmise. Par conséquent, le traitement d’autres métaboxes qui en dépendent ne se produira pas.
Désormais, seuls les utilisateurs privilégiés peuvent supprimer des publications - et nous disposons de méthodes en place pour empêcher les attaquants de les inciter à visiter le lien. Actuellement, cependant, le lien apparaît pour tout le monde - nous devrions le ranger de sorte qu'il n'apparaisse que pour les utilisateurs autorisés à l'utiliser! J'ai quitté cette étape en dernier parce que cacher le lien aux utilisateurs non privilégiés n'apporte aucun avantage en termes de sécurité.. L'obscurité n'est pas la sécurité.
Nous devons supposer que l'attaquant est capable d'inspecter complètement le code source (que ce soit WordPress, un thème ou un plug-in) - et si c'est le cas, masquer simplement le lien ne fait rien: il peut simplement construire l'URL lui-même. Les contrôles de capabilité l’empêchent, car même avec l’URL, ils n’ont pas les cookies qui leur donnent l’autorisation. De plus, les nonces les empêchent de vous inciter à visiter l'URL en exigeant un nonce valide..
Donc, comme exercice complètement esthétique, nous incluons un contrôle de capacité à l’intérieur du wptuts_frontend_delete_link ()
une fonction:
function wptuts_frontend_delete_link () if (current_user_can ('delete_post', get_the_ID ())) $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ()); echo "Supprimer";
Il est important de se rappeler que les fonctionnalités indiquent l'autorisation et que les nonces vérifient l'intention. Les deux sont nécessaires pour sécuriser votre plug-in, mais l'un n'implique pas l'autre. Parfois, WordPress gère ces vérifications pour vous, par exemple lors de l'utilisation de l'API de configuration. Cependant, lors de l’accrochage save_post
il est nécessaire d'effectuer ces vérifications.
Bonne codage!