Capabilități și noncese

În acest articol precedent m-am uitat la ajutarea menținerii temei sau a plug-in-ului dvs. securizată prin dezinfectarea și validarea datelor adecvate. În acest articol, vom analiza un alt aspect important al securității WordPress: capabilități și noncese.

Atunci când dezvolți un plug-in (și într-o măsură mai mică, temele), veți găsi adesea că doriți să permiteți unui utilizator să efectueze diferite acțiuni: șterge, editează sau actualizează postări, categorii, opțiuni sau chiar alți utilizatori. De cele mai multe ori doriți doar anumiți utilizatori autorizați să efectueze aceste acțiuni. Pentru aceasta, WordPress folosește două concepte: roluri și capabilități.


Front link-ul de ștergere: un exemplu simplu

Să presupunem că vrem un buton de ștergere din front-end pentru a elimina rapid mesajele. Următoarele creează un link oriunde vom folosi wptuts_frontend_delete_link () în interiorul bucla.

 funcția wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); ecou "Șterge"; 

Apoi, pentru a procesa acțiunea de ștergere:

 dacă isset ($ _ REQUEST ['action']) && $ _REQUEST ['action'] == 'wptuts_frontend_delete') add_action ('init', 'wptuts_frontend_delete_post');  funcția wptuts_frontend_delete_post () // Obțineți ID-ul postului. $ post_id = (isset ($ _ REQUEST ['post'])? (int) $ _REQUEST ['post']: 0); // Nici o postare? Ei bine ... dacă (gol ($ post_id)) se întoarce; // Ștergeți postul wp_trash_post ($ post_id); // Redirecționați la pagina admin $ redirect = admin_url ('edit.php'); wp_redirect ($ redirect); Ieșire; 

Atunci când un utilizator dă clic pe linkul "șterge", postarea este trasată și utilizatorul este redirecționat către ecranul de administrare post.

Problema cu codul de mai sus este că nu efectuează controale de permisiune: oricine poate vizita linkul și poate șterge un post - nu numai acest lucru, ci schimbând post interogare pot șterge orice postare. Mai întâi de toate, vrem să ne asigurăm asta numai persoanele pe care dorim să le putem șterge posturile pot șterge postările.


Permisiuni, roluri și capacități

Când un utilizator este înregistrat pe site-ul dvs. WordPress, îi este atribuit un rol: acesta ar putea fi admin, editor sau abonat. Fiecărui rol îi sunt atribuite capabilități, de exemplu edit_posts, edit_others_posts, delete_posts sau manage_options. Indiferent de rolul pe care îl atribuie un utilizator, acestea moștenesc aceste capacități: capacitățile sunt atribuite rolurilor, nu utilizatorilor.

Aceste capacități dictează ce părți ale ecranului de administrare pot accesa și ce pot și ce nu pot face în timp ce există. Este important să rețineți că atunci când verificați permisiunile, verificați capacitatea și nu rolul. Capacitățile pot fi adăugate sau eliminate în roluri, astfel încât să nu puteți presupune că un utilizator "admin" ar trebui să poată gestiona opțiunile site-ului - trebuie să verificați în mod specific dacă utilizatorul curent are de fapt manage_options capacitate.

De exemplu, în general ar trebui evita:

 dacă (current_user_can ('admin')) // Efectuați ceva ce numai utilizatorii care pot gestiona opțiunile pot face. 

In schimb, verificați capacitatea (sau capabilitățile):

 dacă (current_user_can ('manage_options')) // Faceți ceva ce numai utilizatorii care pot gestiona opțiunile pot face. 

Adăugați / eliminați capabilități

Adăugarea și eliminarea capabilităților este foarte simplă. WordPress oferă add_cap și remove_cap metode pentru WP_Role obiect. De exemplu, pentru a adăuga funcția "perform_xyz" la rolul editorului:

 $ editor = get_role ("editor"); $ Redactor> add_cap ( 'perform_xzy');

În mod similar, pentru a elimina o capacitate:

 $ editor = get_role ("editor"); $ Redactor> remove_cap ( 'perform_xzy');

Funcțiile unuia dintre roluri sunt stocate în baza de date - deci nu trebuie decât să efectuați acest lucru o singură dată (de exemplu, când plug-in-ul este activat sau dezinstalat).

Dacă doriți ca pluginul dvs. să ofere setări pentru a permite utilizatorilor să editeze capabilitățile altora (capabilități care se referă la funcționalitatea plug-in-ului), atunci este util să folosiți funcția get_editable_roles () care returnează o matrice filtrată de roluri. Acest lucru nu ar trebui folosit în locul controalelor de permisiune, dar permite utilizatorului pluginului să restricționeze ce roluri pot fi editate de un anumit rol. De exemplu, editori ar putea avea dreptul să editeze utilizatori - dar numai autori.

Meta Capabilități

Capacitățile pe care le-am văzut până acum capabilități "primitive" - ​​și acestea sunt atribuite diferitelor roluri. Apoi sunt meta capabilități, care nu sunt atribuite rolurilor, ci în loc de a capta capabilități primitive care sunt necesare rolului utilizatorului curent. De exemplu, având în vedere un ID de postare - am putea dori să întrebăm dacă un utilizator are capacitatea de a edita acest post?

 dacă (current_user_can ('edit_post', 61)) // Faceți ceva ce numai utilizatorii care pot edita postul 61 ar trebui să poată face. 

editează postarea capacitatea nu este atribuită nici unui rol (capacitatea primitivă, edit_posts, cu toate acestea, este) - în schimb, WordPress verifică care roluri primitive le cere acest utilizator pentru a le acorda permisiunea de a edita această postare. De exemplu, dacă utilizatorul curent este autorul postului, acesta are nevoie de edit_posts capacitate. Dacă nu sunt, ei cer edit_others_posts capacitate. În ambele cazuri, dacă postul este publicat, aceștia vor cere de asemenea edit_published_posts capacitate. În acest fel, capabilitățile meta sunt mapate la una sau mai multe capabilități primitive.

Când înregistrați un tip de postare, în mod prestabilit, capabilitățile care sunt verificate sunt identice cu cele pentru postări. Cu toate acestea, puteți specifica propriile dvs. capabilități:

 ("metadata" => array (// meta capacities 'edit_post' => 'edit_event', 'read_post' => 'read_event', 'delete_post' capabilități 'edit_posts' => 'edit_events', 'edit_others_posts' => 'edit_others_events', 'publish_posts' => 'edit_others_events', 'read_private_posts' => 'read_private_events'), ...));

Apoi pentru a verifica dacă utilizatorul curent are permisiunea de a edita postările:

 dacă (current_user_can ('edit_events')) // Efectuați ceva ce numai utilizatorii care pot edita evenimente pot face. 

și pentru a verifica dacă utilizatorul curent poate edita un anumit eveniment:

 dacă (current_user_can ('edit_event', $ post_id)) // Faceți ceva ce numai utilizatorii care pot edita $ post_id pot face. 

in orice caz - edit_event (ca read_event și delete_event) este o meta capabilitate, așa că trebuie să cartografiem capabilitățile primitive relevante. Pentru a face acest lucru vom folosi map_meta_cap filtru.

Logica este explicată în comentarii, dar, în esență, verificăm în primul rând faptul că capacitatea meta se referă la tipul postului de eveniment și că ID-ul postului trecut se referă la un eveniment. Apoi, folosim a intrerupator declarație pentru a face față fiecărei capacități meta și a adăuga roluri către $ primitive_caps matrice. Este vorba de aceste capabilități pe care utilizatorul actual le va avea nevoie dacă li se va acorda permisiunea - și exact ceea ce depind de context.

 add_filter ('map_meta_cap', 'wptuts_event_meta_cap', 10,4); funcția wptuts_event_meta_cap ($ primitive_caps, $ meta_cap, $ user_id, $ args) // Dacă meta-capacitatea nu este bazată pe eveniment nu face nimic. dacă (! in_array ($ meta_cap, array ('edit_event', 'delete_event', 'read_event'))) return $ primitive_caps;  // Verificați că postul este de tip postare. $ post = get_post ($ args [0]); $ post_type = get_post_type_object ($ post-> post_type); dacă ('event'! = $ post_type) return $ primitive_caps;  $ primitive_caps = array (); ($ post_author == $ user_id) // Utilizatorul este autorul postului dacă ('publish' == $ post-> post_status) // Evenimentul este publicat: necesită funcția "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' capabilitate $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  altceva $ primitive_caps [] = $ post_type-> cap-> edit_posts;  altceva // Utilizatorul încearcă să editeze un post care aparține altcuiva. $ primitive_caps [] = $ post_type-> cap-> edit_others_posts; // Dacă postul este publicat sau privat, sunt necesare plafoane suplimentare. dacă ('publicați' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_published_posts;  elseif ('privat' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> edit_private_posts;   pauză; cazul "read_event": if ('privat'! = $ post-> post_status) // Dacă postarea nu este privată, trebuie doar citiți capacitatea $ primitive_caps [] = $ post_type-> cap-> read;  elseif ($ post-> post_author == $ user_id) // Post este privat, dar utilizatorul curent este autorul $ primitive_caps [] = $ post_type-> cap-> read;  altfel // Postul este privat și utilizatorul curent nu este autorul $ primitive_caps [] = $ post_type-> cap-> read_private_post;  pauză; caz "delete_event": dacă ($ post-> post_author == $ user_id) // Utilizatorul curent este autor, necesită funcția delete_events $ primitive_caps [] = $ post_type-> cap-> delete_posts;  altfel // Utilizatorul curent nu este autorul, necesită funcția delete_others_events $ primitive_caps [] = $ post_type-> cap-> delete_others_posts;  // Dacă postarea este publicată, este necesară și funcția delete_published_posts ('publish' == $ post-> post_status) $ primitive_caps [] = $ post_type-> cap-> delete_published_posts;  pauză; endswitch; returnați $ primitive_caps; 

Revenind la link-ul de ștergere de la front-end, vrem să adăugăm următoarea verificare a capacității. Adăugați acest lucru chiar deasupra wp_trash_post sună wptuts_frontend_delete_post.

 dacă (! current_user_can ('delete_post', $ post_id)) retur;

Nonces

Verificarea capacității de mai sus asigură că numai utilizatorii care au permisiunea de a șterge acea post, pot șterge postarea respectivă. Dar să presupunem că cineva vă înșeală să vizitați acea legătură. Aveți capabilitățile necesare, astfel încât să ștergeți involuntar postarea. Este evident că trebuie să verificăm dacă utilizatorul curent intenționează să efectueze acțiunea. Noi facem asta prin noncese.

Analogia este că un atacator dorește să dea niște instrucțiuni unei persoane. Capacitatea de verificare este ca receptorul să ceară să vadă mai întâi un ID. Dar dacă atacatorul aruncă instrucțiunile în mâna ta? Receptorul le va îndeplini cu plăcere (voi, la urma urmei, aveți permisiunea de a face astfel de instrucțiuni).

Un nonce este ca un sigiliu pe un plic care vă verifică că sunteți expeditorul real. Sigiliul este unic pentru fiecare utilizator, astfel încât, dacă atacatorul a alunecat aceste instrucțiuni în mâna dvs., receptorul putea inspecta sigiliul și a văzut că nu este al tău. Cu toate acestea, sigiliile pot fi falsificate - deci o schimbare se va întâmpla de fiecare dată când transmiteți instrucțiunile. Acest sigiliu este "pentru nonce"(de aici numele) sau cu alte cuvinte, temporar.

Deci, dacă cineva vă trimite linkul de ștergere, acesta va conține nonce și astfel va eșua verificarea nonce. De obicei, noncesurile sunt doar o utilizare, dar implementarea WordPress a noncesurilor este puțin diferită: nonce se schimbă de fapt la fiecare 12 ore, iar fiecare nonce este valabil 24 de ore. Puteți schimba acest lucru cu nonce_life filtru care filtrează durata de viață a unui nonce în câteva secunde (în mod normal, 86400)

 add_filter ("nonce_life", "wptuts_change_nonce_hourly"); funcția wptuts_change_nonce_hourly ($ nonce_life) // Schimbați durata de viață la 1 oră retur 60 * 60; 

(dar 24 de ore ar trebui să fie suficient de sigure). Mai important, noncesul ar trebui să fie unic pentru instrucțiunile propriu-zise și pentru orice obiecte la care se referă (de exemplu, ștergerea unei postări și a codului postului).

Cum sunt generate noncesele

WordPress ia o cheie secretă (îl puteți găsi în fișierul dvs. de configurare) și o compune împreună cu următoarele părți:

  • acțiune - acest lucru identifică în mod unic acțiunea. Aceasta include acțiunea și, dacă este cazul, ID-ul obiectului la care aplicați acțiunea la: de ex. pentru ștergerea postului cu ID 61 am putea seta acțiunea nonce să fie wptuts_frontend_delete_61
  • numele de utilizator - ID-ul care identifică ID-ul utilizatorului. Acest lucru face nonce unic pentru fiecare utilizator.
  • căpușă - "Bifați" marchează progresul în timp. Se incrementează la fiecare 12 ore (sau jumătate din ceea ce este viața nonce). Acest lucru face schimbarea nonce la fiecare 12 ore.

Pentru a crea un nonce, puteți folosi wp_create_nonce (acțiune $) Unde acțiune $ este explicat mai sus. WordPress adaugă apoi codul de bifare și ID-ul de utilizator și îl tastează cu cheia secretă.

Apoi trimiteți acest nonce împreună cu acțiunea și orice alte date de care aveți nevoie pentru a efectua acea acțiune. Verificarea nonce este foarte simplă.

 // $ nonce este valoarea nonce primită cu acțiunea. $ action este ceea ce am folosit pentru a genera nonce wp_verify_nonce ($ nonce, $ action); // Returnează true sau false

Unde $ nonce este valoarea nonce obținută și acțiune $ este acțiunea cerută ca mai sus. WordPress generează apoi nonce folosind acțiune $ și verifică dacă se potrivește cu cea dată $ nonce variabil. Dacă cineva v-ar fi trimis linkul, nonce-ul lor va fi generat cu ID-ul lor și astfel va fi diferit de al tău.

Alternativ, dacă nonce a fost postat sau adăugat ca variabilă de interogare, cu nume numele $:

 check_admin_referer ($ acțiune, $ nume);

Dacă nonce este nevalid, va opri orice acțiune suplimentară și va afișa un "Esti sigur?"mesaj.

WordPress îl face deosebit de ușor de utilizat nonces: pentru formularele pe care le puteți utiliza wp_nonce_field ($ acțiune, numele $). Aceasta generează un câmp ascuns cu nume numele $ și formularul generat nonce acțiune $ ca valoare.

Pentru adresele URL pe care le puteți utiliza wp_nonce_url ($ url-ul, $ acțiune). Acest lucru ia decizia dată $ url și îl returnează cu variabila de interogare _wpnonce adăugat, cu nonce ca valoare generată.

În exemplul nostru:

 funcția wptuts_frontend_delete_link () $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID ();)); $ nonce = 'wptuts_frontend_delete_'. get_the_ID (); ecou "Șterge"; 

Ceea ce (pentru postul cu ID 61) generează un nonce cu acțiune wptuts_frontend_delete_61. Apoi, chiar deasupra Gunoi sună wptuts_frontend_delete_post, putem verifica nonce:

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

Utilizarea nonces în cererile AJAX

Utilizarea nonces în cererile AJAX este puțin mai implicat. Noncesurile sunt generate de server, deci valoarea nonce trebuie să fie tipărită ca o variabilă javascript care trebuie trimisă împreună cu cererea AJAX. Pentru a face acest lucru puteți folosi wp_localize_script. Să presupunem că ați înregistrat un script numit wptuts_myjs care conține o solicitare AJAX.

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

Apoi, în scriptul nostru "wptuts_myjs":

 $ .ajax (url: wptuts_ajax.url, dataType: 'json', date: acțiune: 'my_ajax_action', _ajax_nonce: wptuts_ajax.nonce,, ...);

În cele din urmă, în interiorul apelului AJAX:

 check_ajax_referer ( 'my_nonce_action');

Folosind mai mult decât un nonce

În mod normal, este suficientă o singură dată pe formular (sau la cerere). Cu toate acestea, cu WordPress contextul este ușor complicat. De exemplu, atunci când actualizați o postare, WordPress va efectua verificări de permisiune și nonce - astfel încât probabil nu aveți nevoie să verificați un nonce pentru funcția pe care ați atașat save_post care se ocupă de meta cutie personalizată? Nu asa: save_post poate fi declanșată în alte cazuri, în diverse contexte sau eveniment manual - de fapt ori de câte ori wp_update_post este numit de fapt. Pentru a vă asigura că datele pe care le primiți au venit ta metabox ar trebui să utilizați propriul nonce.

>

Desigur, dacă utilizați mai mult de un nonce într-o formă, este important să vă dați unic un nume unic - acesta este un nume unic pentru câmpul ascuns care conține valoarea nonce. În cazul în care mai multe nonces într-o formă au același nume, unul va supra-ride celălalt și undeva un cec care ar trebui să treacă, nu reușește.

Deci, atunci când creați un nonce pentru metabox, asigurați-vă că îi dați un nume unic:

 funcția my_metabox_callback ($ post) $ name = 'my_nonce_name'; // Asigurați-vă că acest lucru este unic, prefixați-l cu numele dvs. de plug-in / temă $ action = 'my_action_xyz _'. $ Post-> ID; // Aceasta este acțiunea nonce wp_nonce_field ($ action, $ name); // caseta de meta ...

Atunci pentru tine save_post meta-box:

 ADD_ACTION ( 'save_post', 'my_metabox_save_post'); funcția my_metabox_save_post ($ post_id) // Verificați că nu este o salvare automată dacă (definit ('DOING_AUTOSAVE') && DOING_AUTOSAVE) retur; // Verificați dacă datele dvs. au fost trimise - aceasta confirmă faptul că intenționăm să procesăm metaboxul nostru dacă (! Isset ($ _ POST ['my_nonce_name'])) retur; // Verificați permisiunile dacă (! Current_user_can ('edit_post', $ post_id)) returnează; // În cele din urmă, verificați check_admin_referer nonce ('my_action_xyz _'. $ Post_id, 'my_nonce_name'); // Efectuați acțiuni

Dacă aveți de-a face cu date de la mai mult de un metabox - ar trebui, în mod ideal, să aveți un nonce pentru fiecare. Meta-urile pot fi șterse, iar în cazul în care caseta meta care conține nonce este eliminată, atunci nu vor trece nici cereri valide. În consecință, nu se va produce nici o prelucrare a altor metaboxi care se bazează pe acest nonce.


Estetică

Acum, numai utilizatorii privilegiați pot șterge postările - și avem metode pentru a împiedica atacatorii să le pacalească în vizitarea link-ului. În prezent, totuși, link-ul apare pentru toată lumea - ar trebui să ordonăm acest lucru astfel încât să apară numai pentru utilizatorii cărora li se permite să-l folosească! Am lăsat ultimul pas pentru că ascunzând link-ul de la utilizatorii neavizați nu are nici un beneficiu de securitate. Obscuritatea nu este securitate.

Trebuie să presupunem că atacatorul este capabil să inspecteze complet codul sursă (fie WordPress, temă sau plug-in) - și dacă este așa, ascunzând pur și simplu link-ul nu face nimic: ei pot construi pur și simplu adresa URL. Capabilitățile de verificare împiedică acest lucru, deoarece, chiar și cu adresa URL, nu au cookie-urile care le dau permisiunea. De asemenea, nu le împiedica să vă păcălească în vizitarea adresei URL prin solicitarea unui nonce valid.

Deci, ca un exercițiu complet estetic, includem un control al capacității în interiorul wptuts_frontend_delete_link () funcţie:

 funcția wptuts_frontend_delete_link () if (actual_user_can ('delete_post', get_the_ID ())) $ url = add_query_arg (array ('action' => 'wptuts_frontend_delete', 'post' => get_the_ID; ecou "Șterge"; 

rezumat

Este important să rețineți că capabilitățile indică permisiunea și verificarea intenției. Ambele sunt necesare pentru a vă menține plug-in-ul securizat, dar nu implică celălalt. Uneori, WordPress se ocupă de aceste verificări pentru dvs. - de exemplu, când utilizați setările API. Cu toate acestea, atunci când se cuplează save_post este necesar să se efectueze aceste verificări.

Codificare fericită!

Cod