Evner og nonces

I denne forrige artikkelen så jeg på å hjelpe med å holde temaet ditt eller plugin-modulen sikret gjennom passende data sanitisering og validering. I denne artikkelen ser vi på et annet viktig aspekt av WordPress-sikkerhet: evner og nonces.

Når du utvikler en plugin (og i mindre grad temaer), vil du ofte finne at du vil tillate en bruker å utføre ulike handlinger: Slett, rediger eller oppdater innlegg, kategorier, alternativer eller til og med andre brukere. Oftere vil du bare at visse autoriserte brukere skal utføre disse handlingene. For dette bruker WordPress to konsepter: roller og evner.


Front-End Delete Link: Et enkelt eksempel

La oss anta at vi vil ha en sluttknapp for å slette innlegg raskt. Følgende oppretter en kobling hvor vi bruker wptuts_frontend_delete_link () inne i løkken.

 funksjon wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); ekko "Slett"; 

Deretter behandler du slettingen:

 hvis (isset ($ _ REQUEST ['action']) && $ _REQUEST ['action'] == 'wptuts_frontend_delete') add_action ('init', 'wptuts_frontend_delete_post');  funksjon wptuts_frontend_delete_post () // Få ID for innlegget. $ post_id = (isset ($ _ REQUEST ['post'])? (int) $ _REQUEST ['innlegg']: 0); // Ingen innlegg? Oh well ... hvis (tomt ($ post_id)) tilbake; // Slett post wp_trash_post ($ post_id); // Omdirigere til admin side $ redirect = admin_url ('edit.php'); wp_redirect ($ omdirigering); exit; 

Da når en bruker klikker koblingen "slett", blir posten søppel og brukeren blir omdirigert til innleggets administrasjonsskjerm.

Problemet med koden ovenfor er at den ikke utfører noen tillatelse sjekker: alle kan besøke lenken og slette et innlegg - ikke bare det, men ved å endre post spørringsvariabelen de kan slette et innlegg. Først av alt, vil vi sørge for at bare folk som vi ønsker å kunne slette innlegg, er i stand til å slette innlegg.


Tillatelser, roller og evner

Når en bruker er registrert på ditt WordPress-nettsted, får de en rolle: dette kan være admin, redaktør eller abonnenten. Hver rolle er tilordnet evner, for eksempel edit_posts, edit_others_posts, delete_posts eller manage_options. Uansett hvilken rolle en bruker er tildelt, arver de disse evnene: evner er tildelt roller, ikke brukere.

Disse funksjonene dikterer hvilke deler av admin skjermen de kan få tilgang til, og hva de kan og ikke kan gjøre mens de er der. Det er viktig å merke seg at når du kontrollerer tillatelser, sjekker du evnen, og ikke rollen. Muligheter kan legges til eller fjernes i roller, og du kan derfor ikke anta at en admin-bruker burde kunne administrere nettstedets alternativer - du må spesifikt sjekke om den nåværende brukeren faktisk har manage_options evne.

For eksempel, generelt bør du unngå:

 hvis (current_user_can ('admin')) // Gjør noe som bare brukere som kan administrere alternativer, bør kunne gjøre. 

I stedet, sjekk muligheten (eller evner):

 hvis (current_user_can ('manage_options')) // Gjør noe som bare brukere som kan administrere alternativer burde kunne gjøre. 

Legg til / fjern evner

Å legge til og fjerne muligheter er veldig enkelt. WordPress gir add_cap og remove_cap metoder for WP_Role gjenstand. For eksempel å legge til funksjonen 'perform_xyz' til redaktørrollen:

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

På samme måte som å fjerne en evne:

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

En rolle evner lagres i databasen - slik at du bare trenger å utføre dette en gang (for eksempel når plugin-modulen er aktivert eller avinstallert).

Hvis du vil at plugin-modulen din skal gi innstillinger slik at brukerne kan redigere andres funksjoner (evner som relaterer til plugin-funksjonaliteten din), er det en nyttig funksjon å bruke get_editable_roles () som returnerer et filtrert utvalg av roller. Dette bør ikke brukes i stedet for tillatelse sjekker, men tillater plugin-brukeren å begrense hvilke roller som kan redigeres av en gitt rolle. For eksempel kan redaktører ha lov til å redigere brukere - men bare forfattere.

Meta Capabilities

Evnene vi har sett så langt, heter "primitive" evner - og disse er tildelt ulike roller. Så er det meta evner, som ikke er tildelt roller, men i stedet kart til primitive evner som kreves av den nåværende brukerens rolle. For eksempel, gitt en post-ID - vi vil kanskje spørre, har en bruker muligheten til å redigere dette post?

 if (current_user_can ('edit_post', 61)) // Gjør noe som bare brukere som kan redigere innlegg 61, burde kunne gjøre. 

De edit_post evnen er ikke tildelt noen rolle (den primitive evnen, edit_posts, men er) - i stedet sjekker WordPress hvilke primitive roller denne brukeren krever for å gi dem tillatelse til å redigere dette innlegget. For eksempel, hvis den nåværende brukeren er postforfatteren, krever de edit_posts evne. Hvis de ikke er det, krever de edit_others_posts evne. I begge tilfeller, hvis innlegget blir publisert, vil de også kreve edit_published_posts evne. På denne måten er metakapasiteter kartlagt til en eller flere primitive evner.

Når du registrerer en innleggstype, er standardene som er merket, de samme som for innlegg. Du kan imidlertid angi dine egne evner:

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

Så for å sjekke om den nåværende brukeren har tillatelse til å redigere innlegg:

 if (current_user_can ('edit_events')) // Gjør noe som bare brukere som kan redigere hendelser skal kunne gjøre. 

og for å kontrollere om den nåværende brukeren kan redigere en bestemt hendelse:

 hvis (current_user_can ('edit_event', $ post_id)) // Gjør noe som bare brukere som kan redigere $ post_id burde kunne gjøre. 

derimot - edit_event (som read_event og delete_event) er en metapapasitet, og derfor må vi kartlegge til de relevante primitive evner. For å gjøre det bruker vi map_meta_cap filter.

Logikken er forklart i kommentarene, men i det hele tatt kontrollerer vi først at metakapasiteten er relatert til vår posttype, og at passordet posten refererer til en hendelse. Deretter bruker vi en bytte om uttalelse for å håndtere hver meta evne og legge til roller til $ primitive_caps array. Det er disse evnene som den nåværende brukeren trenger hvis de skal få tillatelse - og nøyaktig hva de er avhengige av konteksten.

 add_filter ('map_meta_cap', 'wptuts_event_meta_cap', 10,4); funksjon wptuts_event_meta_cap ($ primitive_caps, $ meta_cap, $ user_id, $ args) // Hvis meta-evne ikke er hendelsesbasert, gjør ingenting. hvis (! in_array ($ meta_cap, array ('edit_event', 'delete_event', 'read_event'))) return $ primitive_caps;  // Sjekk innlegget er av posttype. $ post = get_post ($ args [0]); $ post_type = get_post_type_object ($ post-> post_type); hvis ('event'! = $ post_type) return $ primitive_caps;  $ primitive_caps = array (); bytte ($ meta_cap): tilfelle 'edit_event': hvis ($ post-> post_author == $ user_id) // Bruker er postforfatter hvis ('publiser' == $ post-> post_status) // Event er publisert: krever 'edit_published_events' evne $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  elseif ('søppel' == $ post-> post_status) if ('publiser' == get_post_meta ($ post-> ID, '_wp_trash_meta_status', sant)) // Event er et søppelpost som publiseres som publisert, krever 'edit_published_events' mulighet $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  ellers $ primitive_caps [] = $ post_type-> cap-> edit_posts;  else // Brukeren prøver å redigere et innlegg som tilhører noen andre. $ primitive_caps [] = $ post_type-> cap-> edit_others_posts; // Hvis innlegget er publisert eller privat, er det nødvendig med ekstra kapper. hvis ('publiser' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  elseif ('private' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_private_posts;   gå i stykker; tilfelle 'read_event': if ('private'! = $ post-> post_status) // Hvis innlegget ikke er privat, trenger du bare lesefunksjon $ primitive_caps [] = $ post_type-> cap-> read;  elseif ($ post-> post_author == $ user_id) // Post er privat, men nåværende bruker er forfatter $ primitive_caps [] = $ post_type-> cap-> read;  ellers // Post er privat, og nåværende bruker er ikke forfatteren $ primitive_caps [] = $ post_type-> cap-> read_private_post;  gå i stykker; tilfelle 'delete_event': hvis ($ post-> post_author == $ user_id) // Nåværende bruker er forfatter, krever delete_events evne $ primitive_caps [] = $ post_type-> cap-> delete_posts;  else // Nåværende bruker er ikke forfatteren, krever delete_others_events evne $ primitive_caps [] = $ post_type-> cap-> delete_others_posts;  // Hvis innlegg publiseres, krever også delete_published_posts evne hvis ('publiser' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> delete_published_posts;  gå i stykker; END; returnere $ primitive_caps; 

Tilbake til vår forside-slett-kobling, vi vil legge til følgende mulighetskontroll. Legg dette bare over wp_trash_post ring inn wptuts_frontend_delete_post.

 hvis (! current_user_can ('delete_post', $ post_id)) returnere;

Nonces

Ovennevnte funksjonskontroll sikrer at kun brukere som har tillatelse til å slette at post, kan slette det innlegget. Men antar at noen tricks deg til å besøke den linken. Du har de nødvendige evnene, slik at du uheldigvis sletter innlegget. Klart må vi kontrollere at den nåværende brukeren har til hensikt å utføre handlingen. Vi gjør dette gjennom nonces.

Analysen er at en angriper vil gi noen instruksjoner til noen. Kapasitetskontroller er at mottakeren kreves å se noen ID først. Men hva hvis angriperen slipper instruksjonene i hånden din? Mottakeren vil gjerne bære dem ut (du har jo tillatelse til å gjøre slike instruksjoner).

En nonce er som et segl på en konvolutt som bekrefter at du var den faktiske avsenderen. Tetningen er unik for hver bruker, så hvis angriperen slipper disse instruksjonene i hånden, kan mottakeren inspisere forseglingen og se at den ikke er din. Forsegling kan imidlertid smidges - slik at en nonce endres hver gang du overleverer instruksjoner. Denne tetningen er 'for nonce'(derav navnet) eller med andre ord, midlertidig.

Så hvis noen sender deg slettekoblingen, vil den inneholde deres nonce og så vil mislykkes nonce checken. Vanligvis er nonces en bruk, men WordPress 'implementering av nonces er litt annerledes: nonceen endres hver 12. time, og hver nonce er gyldig i 24 timer. Du kan endre dette med nonce_life filter som filtrerer en nonce liv i sekunder (så normalt 86400)

 add_filter ('nonce_life', 'wptuts_change_nonce_hourly'); funksjon wptuts_change_nonce_hourly ($ nonce_life) // Endre ikke liv til 1 time retur 60 * 60; 

(men 24 timer skal være tilstrekkelig sikker). Enda viktigere, nonces burde være unike for instruksjonene selv, og eventuelle objekter de forholder seg til (for eksempel å slette et innlegg og post-ID).

Hvor Nonces Genereres

WordPress tar en hemmelig nøkkel (du finner den i konfigurasjonsfilen din) og har det sammen med følgende deler:

  • handling - Dette unikt identifiserer handlingen. Dette inkluderer handlingen, og hvis det er aktuelt, objekt-ID-en du bruker tiltaket til: f.eks. for å slette innlegget med ID 61 kan vi sette nonce-handlingen til å være wptuts_frontend_delete_61
  • bruker-ID - ID som identifiserer bruker-IDen. Dette gjør nonce unike for hver bruker.
  • sett kryss - "Tick" markerer fremgang i tide. Det øker hver 12. time (eller halvparten av hva livet er). Dette gjør at nissene endres hver 12. time.

For å lage en nonce, kan du bruke wp_create_nonce ($ handling) hvor $ handling er forklart ovenfor. WordPress legger deretter til tick- og bruker-ID-en og har det med den hemmelige nøkkelen.

Du sender deretter denne nesen sammen med handlingen og eventuelle andre data du trenger for å utføre denne handlingen. Å sjekke nonce er veldig enkelt.

 // $ nonce er nonce verdien mottatt med handlingen. $ action er det vi pleide å generere nonce wp_verify_nonce ($ nonce, $ action); // Returnerer sant eller falskt

hvor $ nonce er mottatt nonce verdi og $ handling er den forespurte handlingen som ovenfor. WordPress genererer deretter nonce ved hjelp av $ handling og sjekker om den samsvarer med oppgaven $ nonce variabel. Hvis noen hadde sendt deg linken, vil deres nonce blitt generert med sin ID og så vil det være annerledes enn ditt.

Alternativt, hvis nonce ble postet eller lagt til som en spørringsvariabel, med navn $ name:

 check_admin_referer ($ action, $ navn);

Hvis nonce er ugyldig, vil det stoppe enhver videre handling og vise en 'Er du sikker?' budskap.

WordPress gjør det spesielt enkelt å bruke nonces: For skjemaer du kan bruke wp_nonce_field ($ action, $ navn). Dette genererer et skjult felt med navn $ name og ikke-generert form $ handling som sin verdi.

For nettadresser du kan bruke wp_nonce_url ($ url, $ handling). Dette tar det gitte $ url og returnerer den med spørringsvariabelen _wpnonce lagt til, med den genererte nonce som sin verdi.

I vårt eksempel:

 funksjon wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); $ nonce = 'wptuts_frontend_delete_'. get_the_ID (); ekko "Slett ";

Hvilken (for innlegget med ID 61) genererer en nonce med handling wptuts_frontend_delete_61. Så like over søppel ring inn wptuts_frontend_delete_post, vi kan sjekke nonce:

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

Bruke Nonces i AJAX-forespørsler

Å bruke nonces i AJAX-forespørsler er litt mer involvert. Nonces genereres server side, så nonce verdien må skrives ut som en javascript variabel som skal sendes sammen med AJAX forespørselen. For å gjøre dette kan du bruke wp_localize_script. La oss anta at du har registrert et skript som heter wptuts_myjs som inneholder en AJAX-forespørsel.

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

Deretter inne i vårt wptuts_myjs-skript:

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

Til slutt, inne i AJAX tilbakeringingen din:

 check_ajax_referer ( 'my_nonce_action');

Bruke mer enn en nonce

Normalt er en nonce per skjema (eller per forespørsel) tilstrekkelig. Men med WordPress er konteksten litt komplisert. For eksempel når du oppdaterer et innlegg, vil WordPress utføre tillatelse og ikke-sjekker - så antagelig trenger du ikke å sjekke en nonce for funksjonen din hekta på lagre post som omhandler din tilpassede meta-boks? Ikke så: lagre post kan utløses i andre tilfeller, i ulike sammenhenger eller hendelser manuelt - faktisk når wp_update_post kalles faktisk. For å være sikker på at dataene du mottar kommer fra din metabox du burde bruke din egen nonce.

>

Selvfølgelig, hvis du bruker mer enn en nonce i et skjema, er det viktig at du gir din nonce et unikt navn - det er et unikt navn for det skjulte feltet som inneholder nonce-verdien. Hvis flere nonces i et skjema har samme navn, vil man over-ri den andre og et sted en sjekk som skal passere, mislykkes.

Så når du lager en nonce for metaboxen din, må du sørge for at du gir det et unikt navn:

 funksjon my_metabox_callback ($ post) $ name = 'my_nonce_name'; // Forsikre deg om at dette er unikt, prefiks det med din plugin / tema navn $ action = 'my_action_xyz _'. $ Post-> ID; // Dette er nonce action wp_nonce_field ($ action, $ name); // Meta-boksen din ...

Så for din lagre post meta-box:

 ADD_ACTION ( 'save_post', 'my_metabox_save_post'); fungere my_metabox_save_post ($ post_id) // Sjekk det ikke en automatisk lagring hvis (definert ('DOING_AUTOSAVE') && DOING_AUTOSAVE) returnere; // Sjekk at dataene dine er sendt - dette bekrefter at vi har til hensikt å behandle metaboxen vår hvis (! Isset ($ _ POST ['my_nonce_name']) returnerer; // Kontroller tillatelser hvis (! Current_user_can ('edit_post', $ post_id)) returnere; // Endelig sjekke nonce check_admin_referer ('my_action_xyz _'. $ Post_id, 'my_nonce_name'); // Utfør handlinger

Hvis du arbeider med data fra mer enn en metabox - bør du helst ha en nonce for hver enkelt. Meta bokser kan fjernes, og hvis meta boksen som inneholder nonce er fjernet, vil ikke engang gyldige forespørsler passere. Følgelig vil enhver behandling av andre metaboxer som er avhengig av det ikke forekomme.


estetikk

Nå kan bare privilegerte brukere slette innlegg - og vi har metoder for å hindre at angriperne slår dem inn i å besøke lenken. For øyeblikket vises imidlertid lenken for alle - vi bør rydde opp dette slik at det bare vises for brukere som har lov til å bruke det! Jeg har forlatt dette trinnet sist fordi det ikke er noen sikkerhetsfordeler overhodet å skjule koblingen fra ubehøvlede brukere. Uklarhet er ikke sikkerhet.

Vi må anta at angriperen er i stand til å fullstendig inspisere kildekoden (det være seg WordPress, tema eller plugin) - og i så fall kan det bare være å skjule linken alene, ingenting: de kan bare lage URLen selv. Kapasitetskontroll forhindrer dette fordi, selv med nettadressen, har de ikke informasjonskapsler som gir dem tillatelse. I tillegg hindrer nonces dem fra å lure deg til å besøke nettadressen ved å kreve en gyldig nonce.

Så, som en fullstendig estetisk øvelse, inkluderer vi en kapasitetskontroll inne i wptuts_frontend_delete_link () funksjon:

 funksjon 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 ();)); ekko "Slett"; 

Sammendrag

Det er viktig å huske at evner indikerer tillatelse og ikke-bekreftelsesintensjon. Begge er nødvendige for å holde plugin-modulen sikker, men det innebærer ikke den andre. Noen ganger håndterer WordPress disse kontrollene for deg - for eksempel når du bruker innstillings-API. Men når du henger på lagre post det er nødvendig å utføre disse kontrollene.

Glad koding!