Mogelijkheden en Nonces

In dit vorige artikel heb ik gekeken naar het helpen beveiligen van uw thema of plug-in door middel van passende gegevensreiniging en -validatie. In dit artikel zullen we kijken naar een ander belangrijk aspect van WordPress-beveiliging: capabilities en nonces.

Bij het ontwikkelen van een plug-in (en in mindere mate thema's) zult u vaak merken dat u een gebruiker verschillende acties wilt laten uitvoeren: berichten, categorieën, opties of zelfs andere gebruikers verwijderen, bewerken of bijwerken. Vaker wel dan niet wilt u alleen bepaalde geautoriseerde gebruikers om deze acties uit te voeren. Hiervoor gebruikt WordPress twee concepten: rollen en mogelijkheden.


Front-end Delete Link: een eenvoudig voorbeeld

Laten we veronderstellen dat we een knop voor verwijderen van de voorkant willen om snel berichten te verwijderen. Het volgende maakt een link waar we ook gebruiken wptuts_frontend_delete_link () in de lus.

 functie wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); echo "Verwijderen"; 

Vervolgens om de verwijderactie te verwerken:

 if (isset ($ _ REQUEST ['action']) && $ _REQUEST ['action'] == 'wptuts_frontend_delete') add_action ('init', 'wptuts_frontend_delete_post');  function wptuts_frontend_delete_post () // Krijg de ID van het bericht. $ post_id = (isset ($ _ REQUEST ['post'])? (int) $ _REQUEST ['post']: 0); // Geen bericht? Nou ja ... als (leeg ($ post_id)) terugkomt; // Verwijder bericht wp_trash_post ($ post_id); // Doorverwijzen naar admin pagina $ redirect = admin_url ('edit.php'); wp_redirect ($ redirect); Uitgang; 

Wanneer een gebruiker vervolgens op de link 'verwijderen' klikt, wordt het bericht verwijderd en wordt de gebruiker omgeleid naar het beheerdersdashboard..

Het probleem met de bovenstaande code is dat er geen toestemming wordt gecontroleerd: iedereen kan de link bezoeken en een bericht verwijderen - niet alleen dat, maar ook door de post queryvariabele ze kunnen elk bericht verwijderen. Allereerst willen we ervoor zorgen dat enkel en alleen mensen die we berichten willen kunnen verwijderen, kunnen berichten verwijderen.


Machtigingen, rollen en mogelijkheden

Wanneer een gebruiker is geregistreerd op uw WordPress-site, krijgt deze een rol toegewezen: deze kan zijn beheerder, editor of abonnee. Aan elke rol zijn bijvoorbeeld capaciteiten toegewezen edit_posts, edit_others_posts, delete_posts of manage_options. Welke rol een gebruiker ook krijgt, ze erven die mogelijkheden: capaciteiten worden toegewezen aan rollen, niet aan gebruikers.

Deze functies bepalen welke delen van het beheerdersscherm ze kunnen openen en wat ze wel en niet kunnen doen terwijl ze daar zijn. Het is belangrijk op te merken dat bij het controleren van machtigingen u de mogelijkheid controleert, en niet de rol. Mogelijkheden kunnen aan rollen worden toegevoegd of verwijderd, en dus kun je niet aannemen dat een 'admin'-gebruiker de opties van de site moet kunnen beheren - je moet specifiek controleren of de huidige gebruiker daadwerkelijk over de juiste opties beschikt manage_options geschiktheid.

Bijvoorbeeld, dat zou u in het algemeen moeten doen vermijden:

 if (current_user_can ('admin')) // Doe iets dat alleen gebruikers die opties kunnen beheren zouden moeten kunnen doen. 

In plaats daarvan, controleer de mogelijkheden (of mogelijkheden):

 if (current_user_can ('manage_options')) // Doe iets dat alleen gebruikers kunnen doen die opties kunnen beheren. 

Capaciteiten toevoegen / verwijderen

Het toevoegen en verwijderen van mogelijkheden is heel eenvoudig. WordPress biedt de add_cap en remove_cap methoden voor de WP_Role voorwerp. Bijvoorbeeld om de mogelijkheid 'perform_xyz' toe te voegen aan de editorrol:

 $ editor = get_role ('editor'); $ Editor-> add_cap ( 'perform_xzy');

Vergelijkbaar met het verwijderen van een mogelijkheid:

 $ editor = get_role ('editor'); $ Editor-> remove_cap ( 'perform_xzy');

De functies van een rol worden opgeslagen in de database - u hoeft dit dus slechts één keer uit te voeren (bijvoorbeeld wanneer uw plug-in is geactiveerd of gedeïnstalleerd).

Als u wilt dat uw plug-in instellingen biedt waarmee gebruikers de mogelijkheden van anderen kunnen bewerken (functies die betrekking hebben op de functionaliteit van uw plug-in), dan is een nuttige functie om get_editable_roles () die een gefilterde array van rollen retourneert. Dit moet niet worden gebruikt in plaats van machtigingscontroles, maar uw plugingebruiker kan wel bepalen welke rollen kunnen worden bewerkt door een bepaalde rol. Het kan bijvoorbeeld redacteuren worden toegestaan ​​om gebruikers te bewerken - maar alleen auteurs.

Meta-mogelijkheden

De mogelijkheden die we tot nu toe hebben gezien, worden 'primitieve' functies genoemd en deze worden toegewezen aan verschillende rollen. En dan zijn er metamogelijkheden, die niet zijn toegewezen aan rollen, maar in plaats daarvan toewijzen aan primitieve mogelijkheden die vereist zijn voor de rol van de huidige gebruiker. Bijvoorbeeld, gezien een post-ID - we zouden willen vragen heeft een gebruiker de mogelijkheid om te bewerken deze post?

 if (current_user_can ('edit_post', 61)) // Doe iets dat alleen gebruikers kunnen doen die bericht 61 kunnen bewerken. 

De bericht bewerken vermogen is niet toegewezen aan een rol (het primitieve vermogen, edit_posts, is echter) - in plaats daarvan controleert WordPress welke primitieve rollen deze gebruiker nodig heeft om hen toestemming te geven om deze post te bewerken. Als de huidige gebruiker bijvoorbeeld de auteur van het bericht is, vereisen deze de edit_posts vermogen. Als ze dat niet zijn, hebben ze de edit_others_posts vermogen. In beide gevallen, als de post wordt gepubliceerd, zullen ze ook de edit_published_posts vermogen. Op deze manier worden metacapaciteiten toegewezen aan een of meer primitieve mogelijkheden.

Wanneer u een berichttype registreert, zijn de functies die worden gecontroleerd standaard dezelfde als die voor berichten. U kunt echter uw eigen mogelijkheden specificeren:

 register_post_type ('event', array (... 'capabilities' => array (// Meta-mogelijkheden 'edit_post' => 'edit_event', 'read_post' => 'read_event', 'delete_post' => 'delete_event', // Primitive capabilities 'edit_posts' => 'edit_events', 'edit_others_posts' => 'edit_others_events', 'publish_posts' => 'edit_others_events', 'read_private_posts' => 'read_private_events',), ...));

Vervolgens om te controleren of de huidige gebruiker de rechten heeft om berichten te bewerken:

 if (current_user_can ('edit_events')) // Doe iets dat alleen gebruikers kunnen doen die evenementen kunnen bewerken. 

en om te controleren of de huidige gebruiker een bepaalde gebeurtenis kan bewerken:

 if (current_user_can ('edit_event', $ post_id)) // Doe iets dat alleen gebruikers die $ post_id kunnen bewerken, zouden moeten kunnen doen. 

Echter - edit_event (net zoals read_event en delete_event) is een metacapaciteit en daarom moeten we in kaart brengen in de relevante primitieve mogelijkheden. Om dit te doen gebruiken we de map_meta_cap filter.

De logica wordt uitgelegd in de opmerkingen, maar in wezen controleren we eerst of de metacapaciteit betrekking heeft op ons gebeurtenistekstposttype en dat de doorgegeven post-ID verwijst naar een gebeurtenis. Vervolgens gebruiken we een schakelaar verklaring om met elke metacapaciteit om te gaan en rollen toe te voegen aan de $ primitive_caps matrix. Het zijn deze mogelijkheden die de huidige gebruiker nodig heeft als ze toestemming moeten krijgen - en precies wat ze afhankelijk zijn van de context.

 add_filter ('map_meta_cap', 'wptuts_event_meta_cap', 10,4); functie wptuts_event_meta_cap ($ primitive_caps, $ meta_cap, $ user_id, $ args) // Als meta-capability niet op gebeurtenissen gebaseerd is, doe niets. if (! in_array ($ meta_cap, array ('edit_event', 'delete_event', 'read_event'))) return $ primitive_caps;  // Controlepost is van het berichttype. $ 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) // Gebruiker is berichtauteur if ('publiceren' == $ post-> post_status) // Evenement wordt gepubliceerd: vereisen de mogelijkheid 'edit_published_events' $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  elseif ('trash' == $ post-> post_status) if ('publish' == get_post_meta ($ post-> ID, '_wp_trash_meta_status', true)) // Evenement is een prullenbak met geposte berichten vereist 'edit_published_events' capability $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  else $ primitive_caps [] = $ post_type-> cap-> edit_posts;  else // De gebruiker probeert een bericht van iemand anders te bewerken. $ primitive_caps [] = $ post_type-> cap-> edit_others_posts; // Als het bericht is gepubliceerd of privé is, zijn extra petten vereist. 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;  pauze; case 'read_event': if ('private'! = $ post-> post_status) // Als de post niet privé is, hoef je alleen leesvaardigheid te vragen $ primitive_caps [] = $ post_type-> cap-> read;  elseif ($ post-> post_author == $ user_id) // Bericht is privé, maar de huidige gebruiker is auteur $ primitive_caps [] = $ post_type-> cap-> lezen;  else // Bericht is privé en de huidige gebruiker is niet de auteur $ primitive_caps [] = $ post_type-> cap-> read_private_post;  pauze; case 'delete_event': if ($ post-> post_author == $ user_id) // Huidige gebruiker is auteur, vereist delete_events mogelijkheid $ primitive_caps [] = $ post_type-> cap-> delete_posts;  else // Huidige gebruiker is niet de auteur, vereist delete_others_events mogelijkheid $ primitive_caps [] = $ post_type-> cap-> delete_others_posts;  // Als bericht is gepubliceerd, moet u ook de mogelijkheid delete_published_posts hebben als ('publiceren' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> delete_published_posts;  pauze; eindschakelaar; return $ primitive_caps; 

Terugkomend op onze link voor het verwijderen van de front-end, willen we de volgende controle van de mogelijkheden toevoegen. Voeg dit toe net boven de wp_trash_post inbellen wptuts_frontend_delete_post.

 als (! current_user_can ('delete_post', $ post_id)) terugkomt;

nonces

De bovenstaande mogelijkheidscheck zorgt ervoor dat alleen gebruikers die toestemming hebben om te verwijderen dat post, kunnen die post verwijderen. Maar stel dat iemand je een trucje geeft om die link te bezoeken. Je hebt de nodige mogelijkheden, zodat je het bericht ongewild verwijdert. Het is duidelijk dat we moeten controleren of de huidige gebruiker van plan is om de actie uit te voeren. We doen dit door nonces.

De analogie is dat een aanvaller wat instructies aan iemand wil geven. Capabilitychecks is de ontvanger die eist eerst wat ID te zien. Maar wat als de aanvaller de instructies in uw hand glijdt? De ontvanger voert ze graag uit (u hebt immers toestemming om dergelijke instructies te geven).

Een nonce is als een verzegeling op een envelop die verifieert dat u de daadwerkelijke afzender was. De verzegeling is uniek voor elke gebruiker, dus als de aanvaller die instructies in uw hand heeft geglipt, kan de ontvanger de verzegeling inspecteren en zien dat deze niet van u is. Zeehonden kunnen echter worden gesmeed, dus een tegenvaller verandert telkens wanneer u instructies afgeeft. Dit zegel is 'voor de nonce'(vandaar de naam) of met andere woorden, tijdelijk.

Dus als iemand je de verwijderlink stuurt, zal deze hun nonce bevatten en zal de nonce check mislukken. Meestal zijn nonces slechts één gebruik, maar de implementatie van nonces door WordPress is iets anders: de nonce verandert in feite elke 12 uur en elke nonce is 24 uur geldig. U kunt dit wijzigen met de nonce_life filter dat de levensduur van een nonce in seconden filtert (dus normaal 86400)

 add_filter ('nonce_life', 'wptuts_change_nonce_hourly'); functie wptuts_change_nonce_hourly ($ nonce_life) // Verander de nonce levensduur tot 1 uur retour 60 * 60; 

(maar 24 uur moet voldoende beveiligd zijn). Belangrijker is dat nonces uniek moeten zijn voor de instructies zelf en voor alle objecten waar ze betrekking op hebben (bijvoorbeeld het verwijderen van een bericht en de post-ID).

Hoe nonces worden gegenereerd

WordPress neemt een geheime sleutel (je vindt het in je configuratiebestand) en hashes het samen met de volgende delen:

  • actie - dit identificeert op unieke wijze de actie. Dit omvat de actie en, indien van toepassing, het object-ID waarop u de actie toepast om: voor het verwijderen van de post met ID 61 kunnen we de nonce actie instellen wptuts_frontend_delete_61
  • gebruikersnaam - ID die de gebruikers-ID identificeert. Dit maakt de nonce uniek voor elke gebruiker.
  • Kruis aan - De 'tick' markeert de voortgang in de tijd. Het neemt elke 12 uur toe (of de helft van wat het non-actief leven is). Hierdoor verandert de nonce elke 12 uur.

Om een ​​nonce te creëren, kunt u gebruiken wp_create_nonce ($ actie) waar $ actie wordt hierboven uitgelegd. WordPress voegt vervolgens het vinkje en gebruikers-ID toe en hashes het met de geheime sleutel.

Vervolgens verzendt u deze nonce samen met de actie en alle andere gegevens die u nodig hebt om die actie uit te voeren. Het controleren van de nonce is heel eenvoudig.

 // $ nonce is de nonce-waarde die met de actie wordt ontvangen. $ actie is wat we gebruikten om de nonce wp_verify_nonce ($ nonce, $ action) te genereren; // Retourneert true of false

waar $ nonce is de ontvangen nonce-waarde en $ actie is de gevraagde actie zoals hierboven. WordPress genereert vervolgens de nonce met behulp van de $ actie en controleert of het overeenkomt met het gegeven $ nonce variabel. Als iemand je de link heeft gestuurd, is hun nonce gegenereerd met hun ID en zal deze dus anders zijn dan de jouwe.

Als alternatief, als de nonce is gepost of toegevoegd als een queryvariabele, met naam name $:

 check_admin_referer ($ actie, $ naam);

Als de nonce ongeldig is, zal het elke verdere actie stoppen en een 'Weet je het zeker?'bericht.

WordPress maakt het vooral gemakkelijk om nonces te gebruiken: voor formulieren die je kunt gebruiken wp_nonce_field ($ actie, $ name). Dit genereert een verborgen veld met naam name $ en de nonce generated vorm $ actie als zijn waarde.

Voor URL's die u kunt gebruiken wp_nonce_url ($ url, $ actie). Dit neemt het gegeven $ url en retourneert het met de queryvariabele _wpnonce toegevoegd, met de gegenereerde nonce als waarde.

In ons voorbeeld:

 functie wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); $ nonce = 'wptuts_frontend_delete_'. get_the_ID (); echo "Verwijderen ";

Welke (voor de post met ID 61) een nonce met actie genereert wptuts_frontend_delete_61. Dan net boven de uitschot inbellen wptuts_frontend_delete_post, we kunnen de nonce controleren:

 check_admin_referer ('wptuts_frontend_delete _'. $ post_id, '_wpnonce');

Nonces gebruiken in AJAX-aanvragen

Het gebruik van nonces in AJAX-verzoeken is iets meer betrokken. Nonces worden aan de serverzijde gegenereerd, dus de nonce-waarde moet worden afgedrukt als een javascript-variabele die samen met het AJAX-verzoek moet worden verzonden. Om dit te doen, kunt u gebruiken wp_localize_script. Laten we aannemen dat je een script hebt geregistreerd met de naam wptuts_myjs die een AJAX-aanvraag bevat.

 wp_enqueue_script (wptuts_myjs); wp_localize_script ('wptuts_myjs', 'wptuts_ajax', array ('url' => admin_url ('admin-ajax.php'), // URL naar WordPress ajax-afhandelingspagina 'nonce' => wp_create_nonce ('my_nonce_action')));

En dan in ons 'wptuts_myjs' script:

 $ .ajax (url: wptuts_ajax.url, dataType: 'json', data: action: 'my_ajax_action', _ajax_nonce: wptuts_ajax.nonce,, ...);

Eindelijk, binnen uw AJAX callback:

 check_ajax_referer (my_nonce_action);

Meer dan één nonce gebruiken

Normaal gesproken volstaat één nonce per formulier (of per aanvraag). Met WordPress is de context echter enigszins gecompliceerd. Als u bijvoorbeeld een bericht bijwerkt, voert WordPress machtigingen en nonce-checks uit. U hoeft dus waarschijnlijk geen nonce te controleren op uw functie die is vastgezet op save_post die zich bezighoudt met je aangepaste metabox? Niet zo: save_post kan in andere gevallen, in verschillende contexten of gebeurtenissen handmatig worden geactiveerd, in feite altijd wp_update_post wordt in feite genoemd. Om er zeker van te zijn dat de gegevens die u ontvangt, afkomstig zijn jouw metabox moet je je eigen nonce gebruiken.

>

Natuurlijk, als u meer dan één nonce in een formulier gebruikt, is het belangrijk dat u uw nonce een unieke naam geeft - dat is een unieke naam voor het verborgen veld dat de nonce-waarde bevat. Als meerdere nonces in een formulier dezelfde naam hebben, zal de ene de andere overnemen en de andere een fout die moet worden doorgegeven mislukken.

Dus als u een nonce creëert voor uw metabox, zorg er dan voor dat u deze een unieke naam geeft:

 function my_metabox_callback ($ post) $ name = 'my_nonce_name'; // Zorg ervoor dat dit uniek is, voeg het toe met uw plug-in / themanaam $ action = 'my_action_xyz _'. $ Post-> ID; // Dit is de nonce actie wp_nonce_field ($ actie, $ naam); // Uw metabox ...

Dan voor jouw save_post meta-box:

 add_action (save_post ',' my_metabox_save_post); function my_metabox_save_post ($ post_id) // Controleer of het geen automatische opslag is als (gedefinieerd ('DOING_AUTOSAVE') && DOING_AUTOSAVE) terugkomt; // Controleer of uw gegevens zijn verzonden - dit helpt controleren of we van plan zijn onze metabox te verwerken als (! Isset ($ _ POST ['my_nonce_name'])) terugkomt; // Controleer machtigingen als (! Current_user_can ('edit_post', $ post_id)) terugkomt; // Controleer tenslotte de nonce check_admin_referer ('my_action_xyz _'. $ Post_id, 'my_nonce_name'); // Acties uitvoeren

Als u te maken hebt met gegevens van meer dan één metabox, zou u idealiter voor elke fiche een tegenvaller moeten hebben. Metaboxen kunnen worden verwijderd en als de metabox die de nonce bevat, wordt verwijderd, zullen er niet eens geldige aanvragen worden doorgegeven. Bijgevolg zal elke verwerking van andere metaboxen die op die nonce berusten, niet optreden.


schoonheidsleer

Nu kunnen alleen geprivilegieerde gebruikers berichten verwijderen - en we hebben methodes om te voorkomen dat aanvallers hen misleiden om de link te bezoeken. Momenteel verschijnt de link echter voor iedereen - we moeten dit opruimen zodat het alleen verschijnt voor gebruikers die het mogen gebruiken! Ik heb deze stap voor het laatst verlaten omdat het verbergen van de link van onbevoegde gebruikers geen enkel beveiligingsvoordeel heeft. Duisternis is geen beveiliging.

We moeten aannemen dat de aanvaller de broncode (of het nu WordPress, thema of plug-in) volledig kan inspecteren - en als dat zo is, doet het simpelweg verbergen van de link alleen niets: ze kunnen eenvoudig de URL zelf construeren. Capaciteitscontroles voorkomen dit, omdat ze, zelfs met de URL, niet over de cookies beschikken die hen toestemming geven. Ook voorkomen nonces dat ze u in de val lokken om de URL te bezoeken door een geldige nonce te eisen.

Dus, als een volledig esthetische oefening, nemen we een bekwaamheidscontrole op in de wptuts_frontend_delete_link () functie:

 functie 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 "Verwijderen"; 

Samenvatting

Het is belangrijk om te onthouden dat de mogelijkheden de toestemming en niet-bindende intentie aangeven. Beide zijn nodig om je plug-in veilig te houden, maar de ene impliceert niet de andere. Soms verwerkt WordPress deze controles voor u - bijvoorbeeld bij het gebruik van de instellingen-API. Echter, bij het inhaken save_post het is noodzakelijk om deze controles uit te voeren.

Happy codering!