Funzionalità e Nonces

In questo precedente articolo ho cercato di mantenere sicuro il tema o il plug-in attraverso la corretta sanificazione e convalida dei dati. In questo articolo, vedremo un altro aspetto importante della sicurezza di WordPress: capability e nonces.

Quando sviluppi un plug-in (e in misura minore, temi), troverai spesso che desideri consentire a un utente di eseguire varie azioni: eliminare, modificare o aggiornare post, categorie, opzioni o anche altri utenti. Molto spesso, si desidera che determinati utenti autorizzati eseguano queste azioni. Per questo, WordPress utilizza due concetti: ruoli e capacità.


Link Elimina front-end: un semplice esempio

Supponiamo di volere un pulsante di eliminazione front-end per rimuovere rapidamente i post. Quanto segue crea un collegamento ovunque utilizziamo wptuts_frontend_delete_link () all'interno del ciclo.

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

Quindi per elaborare l'azione di cancellazione:

 if (isset ($ _ REQUEST ['action']) && $ _REQUEST ['action'] == 'wptuts_frontend_delete') add_action ('init', 'wptuts_frontend_delete_post');  function wptuts_frontend_delete_post () // Ottieni l'ID del post. $ post_id = (isset ($ _ REQUEST ['post'])? (int) $ _REQUEST ['post']: 0); // nessun post? Oh bene ... se (vuoto ($ post_id)) ritorno; // Elimina post wp_trash_post ($ post_id); // Reindirizzamento alla pagina di amministrazione $ redirect = admin_url ('edit.php'); wp_redirect ($ reindirizzamento); Uscita; 

Quindi, quando un utente fa clic sul collegamento "Elimina", il post viene spostato nel cestino e l'utente viene reindirizzato alla schermata di amministrazione post.

Il problema con il codice precedente è che non esegue controlli di autorizzazione: chiunque può visitare il link e cancellare un post - non solo quello, ma cambiando il inviare variabile di query che possono eliminare qualsiasi post. Prima di tutto, vogliamo essere sicuri solo le persone che vogliamo essere in grado di eliminare i post sono in grado di eliminare i post.


Autorizzazioni, ruoli e capacità

Quando un utente è registrato sul tuo sito WordPress, gli viene assegnato un ruolo: questo potrebbe essere Admin, editore o abbonato. Ad ogni ruolo vengono assegnate delle funzionalità, ad esempio edit_posts, edit_others_posts, delete_posts o manage_options. A prescindere dal ruolo assegnato a un utente, ereditano quelle funzionalità: le funzionalità sono assegnate ai ruoli, non agli utenti.

Queste funzionalità determinano quali parti della schermata di amministrazione possono accedere e cosa possono e non possono fare mentre sono lì. È importante notare che quando si controllano le autorizzazioni si controlla la capacità e non il ruolo. Le funzionalità possono essere aggiunte o rimosse ai ruoli e quindi non si può presumere che un utente 'admin' debba essere in grado di gestire le opzioni del sito - è necessario verificare in modo specifico se l'utente corrente abbia effettivamente il manage_options capacità.

Ad esempio, generalmente dovresti evitare:

 if (current_user_can ('admin')) // Fai qualcosa che solo gli utenti che possono gestire le opzioni dovrebbero essere in grado di fare. 

Anziché, controllare la capacità (o capacità):

 if (current_user_can ('manage_options')) // Fai qualcosa che solo gli utenti che possono gestire le opzioni dovrebbero essere in grado di fare. 

Aggiungi / Rimuovi funzionalità

Aggiungere e rimuovere funzionalità è molto semplice. WordPress fornisce il add_cap e remove_cap metodi per il WP_Role oggetto. Ad esempio per aggiungere la capacità 'perform_xyz' al ruolo dell'editor:

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

Allo stesso modo per rimuovere una capacità:

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

Le funzionalità di un ruolo sono memorizzate nel database, quindi è necessario eseguire questa operazione una sola volta (ad esempio quando il plug-in è attivato o disinstallato).

Se si desidera che il plug-in fornisca le impostazioni per consentire agli utenti di modificare le funzionalità di altri (funzionalità relative alla funzionalità del plug-in), è utile utilizzare una funzione utile get_editable_roles () che restituisce un array filtrato di ruoli. Questo non dovrebbe essere usato al posto dei controlli di autorizzazione, ma consente all'utente del plugin di limitare i ruoli che possono essere modificati da un determinato ruolo. Ad esempio, gli editor possono essere autorizzati a modificare gli utenti, ma solo gli autori.

Meta Funzionalità

Le capacità che abbiamo visto finora hanno chiamato capacità "primitive" - ​​e queste sono assegnate a vari ruoli. Poi ci sono meta-funzionalità, che non sono assegnati ai ruoli, ma invece mappano a capacità primitive che sono richieste al ruolo dell'utente corrente. Ad esempio, dato un post ID, potremmo voler chiedere che un utente abbia la possibilità di modificare Questo inviare?

 if (current_user_can ('edit_post', 61)) // Fai qualcosa che solo gli utenti che possono modificare post 61 dovrebbero essere in grado di fare. 

Il modifica post la capacità non è assegnata a nessun ruolo (la capacità primitiva, edit_posts, tuttavia, è) - invece, WordPress controlla quali ruoli primitivi questo utente richiede per concedere loro il permesso di modificare questo post. Ad esempio, se l'utente corrente è l'autore del post, richiede edit_posts capacità. Se non lo sono, richiedono il edit_others_posts capacità. In entrambi i casi, se il post è pubblicato, richiederanno anche il edit_published_posts capacità. In questo modo, le funzionalità meta vengono mappate su una o più funzionalità primitive.

Quando registri un tipo di messaggio, per impostazione predefinita le funzionalità selezionate sono le stesse di quelle per i post. Tuttavia, puoi specificare le tue capacità:

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

Quindi per verificare se l'utente corrente ha il permesso di modificare i post:

 if (current_user_can ('edit_events')) // Fai qualcosa che solo gli utenti che possono modificare gli eventi dovrebbero essere in grado di fare. 

e per verificare se l'utente corrente può modificare un particolare evento:

 if (current_user_can ('edit_event', $ post_id)) // Fai qualcosa che solo gli utenti che possono modificare $ post_id dovrebbero essere in grado di fare. 

però - edit_event (piace read_event e delete_event) è una meta capacità, e quindi abbiamo bisogno di mappare le relative capacità primitive. Per fare ciò usiamo il map_meta_cap filtro.

La logica è spiegata nei commenti, ma in sostanza per prima cosa controlliamo che la meta capacità si riferisca al nostro tipo di messaggio dell'evento e che l'ID del post passato si riferisca a un evento. Successivamente, usiamo a interruttore dichiarazione per gestire ogni meta capacità e aggiungere ruoli al $ primitive_caps array. Sono queste funzionalità che l'utente corrente avrà bisogno se gli verrà concessa l'autorizzazione e esattamente ciò che dipende dal contesto.

 add_filter ('map_meta_cap', 'wptuts_event_meta_cap', 10,4); function wptuts_event_meta_cap ($ primitive_caps, $ meta_cap, $ user_id, $ args) // Se la meta-funzionalità non è basata su eventi, non fare nulla. if (! in_array ($ meta_cap, array ('edit_event', 'delete_event', 'read_event'))) return $ primitive_caps;  // Il post di controllo è di tipo 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'utente è post autore if ('publish' == $ post-> post_status) // L'evento è pubblicato: richiede la funzionalità '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', vero)) // Event è un post pubblicato nel cestino richiede 'edit_published_events' capacità $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  else $ primitive_caps [] = $ post_type-> cap-> edit_posts;  else // L'utente sta provando a modificare un post che appartiene a qualcun altro. $ primitive_caps [] = $ post_type-> cap-> edit_others_posts; // Se il post è pubblicato o privato, sono necessari ulteriori limiti. 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;   rompere; caso 'read_event': if ('private'! = $ post-> post_status) // Se il post non è privato, richiede solo capacità di lettura $ primitive_caps [] = $ post_type-> cap-> read;  elseif ($ post-> post_author == $ user_id) // Il post è privato, ma l'utente corrente è autore $ primitive_caps [] = $ post_type-> cap-> read;  else // Il post è privato e l'utente corrente non è l'autore $ primitive_caps [] = $ post_type-> cap-> read_private_post;  rompere; caso 'delete_event': if ($ post-> post_author == $ user_id) // L'utente corrente è autore, richiede capacità delete_events $ primitive_caps [] = $ post_type-> cap-> delete_posts;  else // L'utente corrente non è autore, richiede capacità delete_others_events $ primitive_caps [] = $ post_type-> cap-> delete_others_posts;  // Se il post è pubblicato, richiede anche la funzione delete_published_posts se ('publish' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> delete_published_posts;  rompere; endswitch; return $ primitive_caps; 

Tornando al nostro link di eliminazione front-end, vogliamo aggiungere il seguente controllo di capacità. Aggiungi questo appena sopra il wp_trash_post chiamare wptuts_frontend_delete_post.

 se (! current_user_can ('delete_post', $ post_id)) restituisce;

nonces

Il controllo della capacità di cui sopra garantisce che solo gli utenti che hanno il permesso di eliminare quello post, sono in grado di cancellare quel post. Ma supponiamo che qualcuno ti inganni a visitare quel link. Hai le capacità necessarie, quindi cancelli involontariamente il post. Chiaramente dobbiamo verificare che l'utente corrente intenda eseguire l'azione. Lo facciamo attraverso nonces.

L'analogia è che un attaccante vuole dare alcune istruzioni a qualcuno. I controlli di capacità sono il ricevitore che richiede di vedere prima alcuni ID. Ma cosa succede se l'attaccante fa scivolare le istruzioni nella tua mano? Il destinatario li eseguirà volentieri (dopotutto, hai il permesso di fare tali istruzioni).

Un nonce è come un sigillo su una busta che verifica che tu fossi il mittente effettivo. Il sigillo è unico per ciascun utente, quindi se l'attaccante ha fatto scivolare queste istruzioni nella tua mano, il ricevitore potrebbe ispezionare il sigillo e vedere che non è tuo. Tuttavia, i sigilli possono essere falsificati, quindi un nonce cambia ogni volta che si consegnano le istruzioni. Questo sigillo è 'per il nonce'(da cui il nome) o in altre parole, temporaneo.

Quindi se qualcuno ti invia il link di eliminazione, conterrà il loro nonce e quindi fallirà il controllo di nonce. Di solito nonce sono solo un uso, ma l'implementazione di nonces di WordPress è leggermente diversa: il nonce infatti cambia ogni 12 ore, e ogni nonce è valido per 24 ore. Puoi cambiare questo con il nonce_life filtro che filtra la vita di un nonce in secondi (quindi normalmente 86400)

 add_filter ('nonce_life', 'wptuts_change_nonce_hourly'); function wptuts_change_nonce_hourly ($ nonce_life) // Cambia la durata del nonce a 1 ora di ritorno 60 * 60; 

(ma 24 ore dovrebbero essere sufficientemente sicure). Ancora più importante, i nonces devono essere unici per le istruzioni stesse e per gli oggetti a cui si riferiscono (ad esempio, l'eliminazione di un post e l'ID del post).

Come sono generati i nonces

WordPress prende una chiave segreta (la puoi trovare nel tuo file di configurazione) e la blocca insieme alle seguenti parti:

  • azione - questo identifica in modo univoco l'azione. Ciò include l'azione e, se applicabile, l'ID oggetto a cui si sta applicando l'azione: ad es. per eliminare il post con ID 61 potremmo impostare l'azione nonce wptuts_frontend_delete_61
  • ID utente - ID che identifica l'ID utente. Questo rende il nonce univoco per ogni utente.
  • zecca - Il segno di spunta segna un progresso nel tempo. Aumenta ogni 12 ore (o metà di ciò che è la vita nonce). Questo fa sì che il nonce cambi ogni 12 ore.

Per creare un nonce, puoi usare wp_create_nonce ($ action) dove $ azione è spiegato sopra. WordPress aggiunge quindi il segno di spunta e l'ID utente e lo blocca con la chiave segreta.

Quindi invii questo nonce insieme all'azione e a tutti gli altri dati necessari per eseguire quell'azione. Controllare il nonce è molto semplice.

 // $ nonce è il valore nonce ricevuto con l'azione. $ action è ciò che abbiamo usato per generare il nonce wp_verify_nonce ($ nonce, $ action); // Restituisce vero o falso

dove $ nonce è il valore nonce ricevuto e $ azione è l'azione richiesta come sopra. WordPress quindi genera il nonce usando il $ azione e controlla se corrisponde al dato $ nonce variabile. Se qualcuno ti ha inviato il link, il loro nonce sarà stato generato con il loro ID e quindi sarà diverso dal tuo.

In alternativa, se il nonce è stato pubblicato o aggiunto come variabile di query, con nome nome $:

 check_admin_referer ($ action, $ name);

Se il nonce non è valido, interromperà ogni ulteriore azione e visualizzerà un 'Sei sicuro?' Messaggio.

WordPress lo rende particolarmente facile da usare nonce: per i moduli che è possibile utilizzare wp_nonce_field ($ azione, $ name). Questo genera un campo nascosto con il nome nome $ e la forma nonce generata $ azione come il suo valore.

Per gli URL che puoi utilizzare wp_nonce_url ($ url, $ azione). Questo prende il dato $ url e lo restituisce con la variabile di query _wpnonce aggiunto, con il nonce generato come valore.

Nel nostro esempio:

 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 (); eco "Elimina"; 

Quale (per il post con ID 61) genera un nonce con l'azione wptuts_frontend_delete_61. Quindi appena sopra il spazzatura chiamare wptuts_frontend_delete_post, possiamo controllare il nonce:

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

Utilizzo di Nonces nelle richieste AJAX

L'utilizzo di nonces nelle richieste AJAX è leggermente più complicato. Nonces sono generati lato server, quindi il valore nonce deve essere stampato come una variabile javascript da inviare insieme alla richiesta AJAX. Per fare questo puoi usare wp_localize_script. Supponiamo che tu abbia registrato uno script chiamato wptuts_myjs che contiene una richiesta AJAX.

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

Quindi all'interno del nostro script "wptuts_myjs":

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

Infine, all'interno del tuo callback AJAX:

 check_ajax_referer ( 'my_nonce_action');

Usando più di un nonce

Normalmente un nonce per modulo (o per richiesta) è sufficiente. Tuttavia, con WordPress il contesto è leggermente complicato. Ad esempio, quando aggiorni un post, WordPress eseguirà i permessi e i controlli di nonce, quindi presumibilmente non hai bisogno di controllare un nonce per la tua funzione agganciata save_post che si occupa della tua meta-box personalizzata? Non così: save_post può essere attivato in altre istanze, in vari contesti o eventi manualmente, infatti ogni volta wp_update_post è chiamato infatti. Per essere sicuro che i dati che stai ricevendo provengano il tuo metabox dovresti usare il tuo nonce.

>

Ovviamente se si utilizza più di un nonce in un modulo è importante assegnare al proprio nonce un nome univoco, ovvero un nome univoco per il campo nascosto contenente il valore nonce. Se più noces in un modulo hanno lo stesso nome, uno supererà l'altro e da qualche parte un assegno che dovrebbe passare, fallirà.

Quindi quando crei un nonce per il tuo metabox, assicurati di dargli un nome univoco:

 function my_metabox_callback ($ post) $ name = 'my_nonce_name'; // Assicurati che sia univoco, anteponilo al nome del tuo plug-in / tema $ action = 'my_action_xyz _'. $ Post-> ID; // Questa è l'azione nonce wp_nonce_field ($ action, $ name); // La tua casella di meta ...

Allora per il tuo save_post meta-box:

 add_action ( 'save_post', 'my_metabox_save_post'); function my_metabox_save_post ($ post_id) // Verifica che non sia un auto save se (definito ('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; // Controlla che i tuoi dati siano stati inviati - questo aiuta a verificare che intendiamo elaborare il nostro metabox se (! Isset ($ _ POST ['my_nonce_name'])) restituisce; // Controlla i permessi se (! Current_user_can ('edit_post', $ post_id)) restituisce; // Infine controlla il nonce check_admin_referer ('my_action_xyz _'. $ Post_id, 'my_nonce_name'); // Esegui azioni

Se hai a che fare con dati provenienti da più di un metabox, dovresti, idealmente, avere un nonce per ognuno. Le caselle Meta possono essere rimosse e se la meta-box contenente il nonce viene rimossa, allora non passeranno nemmeno le richieste valide. Di conseguenza, non si verificherà alcuna elaborazione di altri metabox che si basano su tale nonce.


Estetica

Ora solo gli utenti con privilegi possono eliminare i post e disponiamo di metodi per impedire agli aggressori di indurli a visitare il link. Attualmente, tuttavia, il collegamento viene visualizzato per tutti: dovremmo riordinarlo in modo che appaia solo per gli utenti a cui è consentito utilizzarlo! Ho lasciato questo ultimo passaggio perché nascondere il collegamento da utenti non privilegiati non ha alcun vantaggio in termini di sicurezza. L'oscurità non è sicurezza.

Dobbiamo supporre che l'autore dell'attacco sia in grado di ispezionare completamente il codice sorgente (che si tratti di WordPress, tema o plug-in) e, in tal caso, nascondere semplicemente il collegamento non fa nulla: possono semplicemente costruire l'URL stesso. I controlli di capacità impediscono questo, perché, anche con l'URL, non hanno i cookie che danno loro il permesso. Inoltre, nonces impedisce loro di indurti a visitare l'URL richiedendo un nonce valido.

Quindi, come esercizio completamente estetico, includiamo un controllo di capacità all'interno di wptuts_frontend_delete_link () funzione:

 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 "Elimina"; 

Sommario

È importante ricordare che le capacità indicano l'autorizzazione e l'intenzione di non verificare. Entrambi sono necessari per mantenere il plug-in sicuro, ma uno non implica l'altro. A volte WordPress gestisce questi controlli per te, ad esempio quando utilizzi l'API delle impostazioni. Tuttavia, quando ci si aggancia save_post è necessario eseguire questi controlli.

Buona programmazione!