Segurança & Performance
WordPress
VIP.WORDPRESS.COM
Henrique Mouta
twitter.com/vaurdan
henrique@automattic.com
henrique.blog
560
TOTAL DE AUTOMATTICIANS
69
LINGUAS FALADAS
51
PAISES REPRESENTADOS
Automattic foi fundada em
2005 por Matt Mullenweg,
co-criador do WordPress, para
construir uma nova geração de
ferramentas para publicar
conteúdo online e para apoiar
a comunidade open-source.
VIP.WORDPRESS.COM
Alguns dos nossos clientes
VIP.WORDPRESS.COM
Segurança WordPress
VIP.WORDPRESS.COM
Validação de Dados
Escaping
VIP.WORDPRESS.COM
Porquê & Importância
VIP.WORDPRESS.COM
Late Escaping - Sempre!
VIP.WORDPRESS.COM
CORRECTO
ERRADO
$value = some_attribute();
echo "<div class='" . esc_attr( $value ) . "'> Hello! </div>";
$value = esc_attr( $some_attribute );
echo "<div class='$value'> Hello! </div>";
VIP.WORDPRESS.COM
ERRADO PORQUÊ?
// (...) muito codigo novo aqui
// Mudámos o value para ir buscar um valor à API, que não está escaped
$value = get_attribute_from_api();
// (...) muito código novo aqui
// Esta linha acaba esquecida e intocada, sem ter sido feito o escaping devido.
echo "<div class='$value'> Hello! </div>";
$value = esc_attr( $some_attribute );
echo "<div class='$value'> Hello! </div>";
após vários commits…
VIP.WORDPRESS.COM
Funções de Escaping
Attributes Nodes
esc_attr( $text )
esc_attr__( $text )
esc_attr_e ( $text )
Text Nodes
esc_html( $text )
esc_html__ ( $text )
esc_html_e ( $text )
esc_textarea( $text )
JavaScript
esc_js( $text )
wp_json_encode ( $var )
https://codex.wordpress.org/Data_Validation#Output_Sanitation
URL’s
esc_url( $url, (array) $protocols = null )
esc_url_raw( $url, (array) $protocols = null )
rawurlencode( $string )
Fragmentos de HTML & XHTML
wp_kses( (string) $fragment, (array) $allowed_html, (array) $protocols = null )
wp_kses_post ( $data )
Inteiros
intval( $int )
(int) $int
absint ( $int )
VIP.WORDPRESS.COM
esc_html ( $text )
$html = esc_html( ' <a href="http://example.com/">Exemplo</a>' );
echo $html;
A string é codificada para entidades HTML
&lt;a href=&quot;http://example.com/&quot;&gt;Exemplo&lt;/a&gt;
<a href="http://example.com/">Exemplo</a>
VIP.WORDPRESS.COM
esc_html__ ( $text )
Faz escape a uma string traduzida. Equivalente a esc_html( __( $text ) )
esc_html_e ( $text )
O mesmo que o anterior, mas para _e( $text ). Imprime e não necessita de echo
esc_textarea( $text )
Codifica o texto para ser utilizado dentro de uma <textarea>
VIP.WORDPRESS.COM
esc_attr ( $text )
$caption = "'><img src='jadsusaidaijd' onerror='javascript:alert('Hacked!')";
echo "<img src='https://exemplo.com/img.png' alt='" . esc_attr( $caption ) . "'>";
<img src='https://exemplo.com/img.png' alt='&#039;&gt;&lt;img src=&#039;jadsusaidaijd&#039;
onerror=&#039;javascript:alert(&#039;Hacked!&#039;)'>
'><img src='jadsusaidaijd' onerror='javascript:alert('Hacked!')
VIP.WORDPRESS.COM
esc_attr__ ( $text )
Faz escape a uma string traduzida. Equivalente a esc_attr( __( $text ) )
esc_attr_e ( $text )
O mesmo que o anterior, mas para _e( $text ). Imprime e não necessita de echo
VIP.WORDPRESS.COM
esc_url( $url, (array) $protocols = null )
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">Home</a>
Home
VIP.WORDPRESS.COM
$url = 'http://wordpress.org';
$response = wp_remote_get( esc_url_raw( $url ) );
esc_url_raw ( $url, $protocols )
● Semelhante ao esc_url, valida o URL
● Não faz substituição de caracteres
● Não é seguro para utilizar no output
<img src='<?php echo esc_url_raw( $url ); ?>' />
VIP.WORDPRESS.COM
rawurlencode ( $string )
<script src="https://sometracker.com/client_id=<?php echo rawurlencode(
$client_id ); ?>">
<script src="https://sometracker.com/client_id= 1%26client_id%3D123456">
$client_id = " 1&client_id=123456";
VIP.WORDPRESS.COM
Porquê usar o rawurlencode e não o esc_url?
$client_id = " 1&client_id=123456";
<script src="https://sometracker.com/client_id=<?php echoesc_url( $client_id ); ?>">
<script src="https://sometracker.com/client_id= 1&client_id=123456">
O parametro client_id no servidor sometracker.com será 123456, visto que houve sobreposição.
VIP.WORDPRESS.COM
esc_js ( $text )
Só deve ser utilizado para JS inline (num atributo, como por exemplo no onclick=”...”)
<input type="text" onfocus="if ( this.value == ' <?php echo esc_js(
$instance['input_text'] ); ?>') { this.value = ''; }" name="email" />
Para fazer escape de JavaScript que não seja inline, deverá ser utilizado o wp_json_encode
VIP.WORDPRESS.COM
Errado!
<script>
var title = ' <?php echo esc_js( $title ); ?>';
var content = ' <?php echo esc_js( $content ); ?>';
var comment_count = ' <?php echo esc_js( $comment_count ); ?>';
</script>
Deve ser usado o wp_json_encode!
VIP.WORDPRESS.COM
<script>
var title = <?php echo wp_json_encode( $title ); ?>;
var content = <?php echo wp_json_encode( $content ); ?>;
var comment_count = <?php echo wp_json_encode( $comment_count ); ?>;
</script>
wp_json_encode ( $data )
<script>
var title = "Olá Mundo";
var content = "Isto é um exemplo.";
var comment_count = 42;
</script>
VIP.WORDPRESS.COM
wp_kses( $fragment, $allowed_html, $protocols = null )
$allowed_html = array(
'a' => array(
'href' => array(),
'title' => array()
),
'br' => array(),
'em' => array(),
'strong' => array(),
);
$content = mytheme_get_some_html_content();
echo wp_kses( $content, $allowed_html );
Limpa o HTML gerado pelo mytheme_get_some_html_content de modo a apenas permitir os
elementos e atributos listados em $allowed_html
Nota!
Não se deve permitir iframe ou script no $allowed_html porque não é seguro.
VIP.WORDPRESS.COM
wp_kses_post ( $text )
$content = mytheme_get_some_html_content();
echo wp_kses_post( $content );
● Utilizado no get_content()
● Evita definir um array $allowed_html
VIP.WORDPRESS.COM
intval ( $int )
ou (int) $int
$post_id = get_the_ID();
echo 'Post #' . intval( $post_id );
VIP.WORDPRESS.COM
absint ( $int )
$var = -1;
echo absint( $var ); // returns "1"
$posts_per_page = get_option( 'posts_per_page' );
$query = WP_Query( array(
// Makes sure $posts_per_page is never -1
'posts_per_page' => absint( $posts_per_page ),
// ...
) );
VIP.WORDPRESS.COM
Validação de Dados
Sanitation
VIP.WORDPRESS.COM
Porquê & Importância
VIP.WORDPRESS.COM
HTTP GET request para http://example.com/?name=Henrique
$name = $_GET['name'];
// Store the option
wp_update_option( 'saved_name', $name );
E se for para http://example.com/?name= <img src=”somethinginvalid.png” onerror=”(.
..)”> ?
Vai ser guardado um valor potencialmente perigoso na base de dados. A solução é sanitizar (limpar) o input o
mais cedo possível, ou seja, assim que o obtemos.
$name = sanitize_text_field( $_GET['name'] );
// Store the sanitized option
wp_update_option( 'saved_name', $name );
VIP.WORDPRESS.COM
Funções de Sanitização
sanitize_email( $data )
sanitize_file_name( $data )
sanitize_html_class( $data )
sanitize_key( $data )
sanitize_meta( $data )
sanitize_mime_type( $data )
sanitize_option( $data )
sanitize_sql_orderby( $data )
sanitize_text_field( $data )
sanitize_title( $data )
sanitize_title_for_query( $data )
sanitize_title_with_dashes( $data )
sanitize_user( $data )
esc_url_raw( $data )
wp_filter_post_kses( $data )
wp_filter_nohtml_kses( $data )
Sanitiza emails
Sanitiza nomes de ficheiros
Sanitiza classes HTML
Sanitiza chaves
Sanitiza meta (para post meta)
Sanitiza Mime Types
Sanitiza opções (para Options API)
Sanitiza o ORDER BY do SQL
Sanitiza campos de texto
Sanitiza titulos
Sanitiza titulos para utilização em queries
Sanitiza títulos com traços
Sanitiza usuários
Sanitiza URL’s (conforme já vimos)
Sanitiza conteudo HTML (à semelhança do wp_kses)
Remove todo o HTML da string
VIP.WORDPRESS.COM
Sanitizar a Base de Dados
VIP.WORDPRESS.COM
$wpdb->query( "INSERT INTO product_review (review) VALUES '" . strip_tags(
$_GET['review'], '<a><b><i><img>') . "'" );
http://example.com/?review='; DROP DATABASE;
Pode ser utilizado não só para destruir a base de dados, mas também para selecionar e devolver
usernames e passwords encriptadas.
INSERT INTO product_review (review) VALUES ''; DROP DATABASE;
VIP.WORDPRESS.COM
$wpdb->query(
$wpdb->prepare( " INSERT INTO product_review (review) VALUES %s ",
$_GET['review']
)
);
Seguro mas insuficiente
Deve ser sempre feita a sanitização antes de inserir na base de dados!
$wpdb->query(
$wpdb->prepare( " INSERT INTO product_review (review) VALUES %s ",
wp_kses_post( $_GET['review'] )
)
);
VIP.WORDPRESS.COM
$wpdb->query(
"SELECT FROM product_review WHERE review LIKE ' ". esc_like(
sanitize_text_field( $_GET['review'] ) ) . " ' "
);
Utilizar o esc_like()?
Errado :(
O esc_like() funciona como esperado, apenas para as operações LIKE do SQL.
Apenas faz o escape de % / e  que são caracteres com significado nos argumentos LIKE.
Continua a ser necessário passar os valores pelo $wpdb->prepare().
VIP.WORDPRESS.COM
Escaping & Sanitization
Nunca confiar no input dos usuários.
O escaping deve ser o mais tarde possível
Fazer escape a tudo com origem em fontes desconhecidas ou terceiras
Nunca assumir nada
Nunca confiar no input dos usuários
Sanitização é bom, mas validação/rejeição é melhor
Nunca confiar no input dos usuários
1.
2.
3.
4.
5.
6.
7.
https://vip.wordpress.com/documentation/vip/best-practices/security/validating-sanitizing-escaping/
Regras Principais
VIP.WORDPRESS.COM
Capabilities & Roles
VIP.WORDPRESS.COM
Exemplo
Queremos criar um role para Revisor de Artigos (Reviewer).
function add_reviewer_role() {
add_role( 'reviewer', 'Reviewer', array(
'read' => true,
// (...) All the necessary capabilities (...)
'edit_posts' => true,
'edit_others_posts' => true,
'edit_private_posts' => true,
) );
}
register_activation_hook( __FILE__, 'add_reviewer_role' );
Os Roles são guardados na base de dados, pelo que devem ser criados/removidos nos hooks de activação do
tema/plugin.
VIP.WORDPRESS.COM
Funções disponíveis
add_role( $role, $name, $capabilities )
remove_role( $role )
get_role( $role )
$wp_roles->add_cap( $role, $cap )
$wp_roles->remove_cap( $role, $cap )
https://vip-svn.wordpress.com/plugins/vip-do-not-include-on-wpcom/vip-roles.php
VIP.WORDPRESS.COM
add_role ( $role, $name, $capabilities )
Aviso!
function mytheme_add_foo_role() {
add_role( 'foo', 'Foo', array(
'read' => true,
) );
}
register_activation_hook( __FILE__, 'mytheme_add_foo_role' );
Os novos roles deverão conter sempre a capability read, caso
contrário o usuários não conseguirá navegar no site.
VIP.WORDPRESS.COM
get_role ( $role )
function mytheme_add_foo_caps() {
$role = get_role( 'foo' ); // Devolve um objecto WP_Role
$role->add_cap( 'edit_posts' );
}
add_action( 'admin_init', 'mytheme_add_foo_caps');
VIP.WORDPRESS.COM
$wp_roles->add_cap ( $role, $cap )
ou $role->add_cap( $cap )
function mytheme_add_foo_caps() {
$role = get_role( 'foo' );
$role->add_cap( 'edit_posts' );
}
add_action( 'admin_init', 'mytheme_add_foo_caps');
function mytheme_add_foo_caps() {
global $wp_roles;
$wp_roles->add_cap( 'foo', 'edit_posts' );
}
add_action( 'admin_init', 'mytheme_add_foo_caps');
ou
VIP.WORDPRESS.COM
$wp_roles->remove_cap ( $role, $cap )
ou $role->remove_cap( $cap )
function mytheme_remove_author_caps() {
$role = get_role( 'author' );
$role->remove_cap( 'edit_posts' );
}
add_action( 'admin_init', mytheme_remove_author_caps);
function mytheme_remove_author_caps() {
global $wp_roles;
$wp_roles->remove_cap( 'author', 'edit_posts' );
}
add_action( 'admin_init', 'mytheme_remove_author_caps');
ou
VIP.WORDPRESS.COM
Como utilizar os Roles & Capabilities
para validar permissões?
VIP.WORDPRESS.COM
current_user_can ( $capability , $object_id );
add_action( 'wp_ajax_update_foo', 'update_foo' );
function update_foo() {
// Se o usuário não pode gerir opções, retorna falso
if ( ! current_user_can('manage_options') ) {
return false;
}
update_option( 'foo', sanitize_text_field( $_POST['bar'] )) ;
}
VIP.WORDPRESS.COM
current_user_can ( $capability , $object_id );
E caso queira verificar se o usuário tem permissão para um determinado objecto?
add_action( 'wp_ajax_update_foo', 'update_foo' );
function update_foo() {
$post_id = intval( $_GET['post_id'] );
// Passa-se o $post_id no segundo argumento
if ( ! current_user_can( 'edit_post', $post_id ) ){
return false;
}
update_post_meta( $post_id, 'foo', sanitize_text_field( $_GET['bar'] )
);
}
VIP.WORDPRESS.COM
Como se pode validar a intenção do
usuário?
VIP.WORDPRESS.COM
Nonces
VIP.WORDPRESS.COM
<img src=”http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Olá
Mundo”>
wp_create_nonce ( $action )
http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Olá Mundo
&_wpnonce=ba623fa
VIP.WORDPRESS.COM
wp_create_nonce ( $action )
$nonce = wp_create_nonce( 'update_foo_' . $post_id );
echo "<a href='" . esc_url(
"http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Ol
á Mundo&_wpnonce=" . $nonce ) . "'>Update Foo!</a>";
update_foo_$post_id é o action name
VIP.WORDPRESS.COM
wp_verify_nonce ( $nonce, $action )
add_action( 'wp_ajax_update_foo', 'update_foo' );
function update_foo() {
$post_id = intval( $_GET['post_id'] );
// Valida se o nonce está presente e se é válido
if ( ! wp_verify_nonce( $_GET[ '_wpnonce' ], 'update_foo_' . $post_id ) ) {
return false;
}
// (...) o resto das validações de capabilities (...)
}
update_foo_$post_id é o action name
VIP.WORDPRESS.COM
wp_nonce_url ( $actionurl, $action, $name )
$nonce = wp_create_nonce( 'update_foo_' . $post_id );
echo "<a href='" . wp_nonce_url( admin_url(
'admin-ajax.php?action=update_foo&post_id=123&bar=Olá Mundo'), "update_foo_" .
$post_id ) . "'>Update Foo!</a>";
VIP.WORDPRESS.COM
wp_nonce_field ( $action, $name, $referer, $echo )
// Cria e imprime um campo escondido com o nonce
wp_nonce_field( "update_foo_" . $post_id );
Vai imprimir algo como:
<input type="hidden" id="_wpnonce" name="_wpnonce" value=" 796c7766b1" />
VIP.WORDPRESS.COM
check_admin_referer ( $action, $query_arg )
add_action( 'wp_ajax_update_foo', 'update_foo' );
function update_foo() {
$post_id = intval( $_GET['post_id'] );
check_admin_referer( 'update_foo_' . $post_id );
// (...) o resto das validações de capabilities (...)
}
VIP.WORDPRESS.COM
Manipulação de tipos de dados
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
'hello' == 0
Qual o valor de
retorno?
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
'hello' == 0
É uma comparação com um int.
Converte 'hello' para 0
(int) "hello"; // devolve 0
(int) "1hello"; // devolve 1
Portanto
0 == 0
Retorna true.
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
$string = 'Olá Mundo';
if ( $string == 0 ) {
echo "$string é um inteiro de valor 0";
} else {
echo "$string não é um inteiro de valor 0";
}
Olá Mundo é um inteiro de valor 0
$string == 0 avalia como true
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
$string = 'Olá Mundo';
if ( $string === 0 ) {
echo "$string é um inteiro de valor 0";
} else {
echo "$string não é um inteiro de valor 0";
}
Olá Mundo não é um inteiro de valor 0
$string === 0 avalia como false
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
$array = array(
"foo" => "bar",
"word" => "press",
"php" => 0
);
$string = "hello";
if ( in_array( $string, $array ) ) {
echo 'Encontrado!';
} else {
echo 'Não Encontrado!';
}
Encontrado!
Mas "hello" não está no array!
"hello" == 0 avalia como true
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
$array = array(
"foo" => "bar",
"word" => "press",
"php" => 0
);
$string = "hello";
if ( in_array( $string, $array, true ) ) {
echo 'Encontrado!';
} else {
echo 'Não Encontrado!';
}
Não Encontrado!
Utilizando o parâmetro strict, o resultado já é o esperado.
"hello" === 0 avalia como false
VIP.WORDPRESS.COM
Utilizar sempre comparações estritas
Sempre que for feita uma comparação, deverá ser feita de forma estrita. Desta
forma, o interpretador não tenta adivinhar qual o tipo de dados (Type Juggling)
e não comete estes erros inesperados.
Tentar utilizar sempre o parâmetro strict no in_array(), e === nas
comparações.
VIP.WORDPRESS.COM
Performance WordPress
VIP.WORDPRESS.COM
Performance WordPress
Full Page Caching
VIP.WORDPRESS.COM
Full Page Cache
● Fazer sempre o full page cache de páginas com todos os parâmetros GET, ou
ignorar alguns como o 'fb*', 'utm_*', etc
● Inclui também requests ao WP-API
● Para gerir o cache pode ser utilizado Varnish, Memcache(d) (juntamente com o
plugin Batcache), ou outras soluções com os respectivos plugins (W3 Total
Cache, WP Super Cache, …)
VIP.WORDPRESS.COM
Full Page Cache e AJAX
● O Full Page Cache deverá ser configurado para fazer o cache de certos
parametros para os pedidos AJAX.
● Para aproveitar o Full Page Cache, utilizar a API do WP_Rewrite para converter os
URLs AJAX:
https://example.com/admin-ajax.php?category_id=123&page=2
com o WP_Rewrite pode-se transformar em
https://example.com/ajax/category/123/2/
VIP.WORDPRESS.COM
Performance WordPress
Queries SQL Lentas
VIP.WORDPRESS.COM
Como identificar?
VIP.WORDPRESS.COM
Como identificar?
● Plugins de apoio ao desenvolvimento:
Query Monitor - https://wordpress.org/plugins/query-monitor/
Debug Bar - https://wordpress.org/plugins/debug-bar/
● New Relic
http://newrelic.com/
● Bom conhecimento do DB Schema do WordPress
http://codex.wordpress.org/Database_Description
VIP.WORDPRESS.COM
O que evitar?
VIP.WORDPRESS.COM
● Queries que pesquisem por meta value
○ WP_Query( 'meta_key' => 'city', 'meta_value' =>
'São Paulo' );
● Utilização de NOT IN
○ WP_Query( 'category__not_in' => 'featured' );
● Muitos JOINs
○ WP_Query( 'category_in' => [ 1,3,4,6,8,9 ],
'meta_key' => 'is_starred', 'post_tag_in' => ...
);
VIP.WORDPRESS.COM
Post Meta
● Utilizar um termo em vez de um post meta
'meta_key' => 'city', 'meta_value' => 'São Paulo' torna-se
'taxonomy' => 'city', 'term' => 'São Paulo'
1.
● Situações Binárias ( is_featured = true ou is_featured = false )
Procura pela existencia da meta_key
Apagar a meta_key se falso
● Situações Não Binárias ( meta_key => primary_category, meta_value => sports )
Procurar pela meta_key primary_category_sports e aplicar a mesma lógica binária
● Utilizar Elastic Search (https://github.com/alleyinteractive/es-wp-query ou outro)
VIP.WORDPRESS.COM
NOT IN em Taxonomias
● Remover todos os posts dessa categoria e coloca-los num novo Post Type.
● Saltar posts no PHP
Em vez de pedir apenas 10 posts, pedir 20 e saltar os posts que não queremos no PHP. (não é
garantido obter os 10 posts que queremos)
// Antes
get_posts( 'category__not_in' => 'featured');
// Depois
get_posts( 'post_type' => 'featured' );
VIP.WORDPRESS.COM
Alavancar a Cache de Base de Dados
● Garantir que todas as chamadas para get_posts() incluem
suppress_filters => false para permitir o caching
● Generalizar as queries o mais possível
Em vez de utilizar posts__not_in,fazer o query com num_posts + xe saltar
posts condicionalmente no PHP
VIP.WORDPRESS.COM
Performance WordPress
Funções sem Cache
VIP.WORDPRESS.COM
Funções que precisam de suppress_filters =>
false
● get_posts()
● wp_get_recent_posts()
● get_children()
(é também necessário um limite na query)
VIP.WORDPRESS.COM
Funções sem Cache - Core
● Funções que fazem chamadas directas à base de dados sempre
● wp_get_post_terms()
● wp_get_post_categories()
● wp_get_post_tags()
● wp_get_object_terms()
● Solução: Utilizar get_the_terms() em vez destas funções
https://vip.wordpress.com/documentation/uncached-functions/
● term_exists()
● get_page_by_title()
● get_page_by_path()
● url_to_post_id()
● count_users_posts()
● Solução: Utilizar as funções de substituição que tenham cache
VIP.WORDPRESS.COM
E se a query não poder ser mais
otimizada?
VIP.WORDPRESS.COM
Faz-se o cache dos resultados
wp_cache_set() e wp_cache_get() (ou transientes)
$something = wp_cache_get( 'cache_key' );
if ( ! $something ) {
// Obter os dados necessarios
$something = new WP_Query( ... );
wp_cache_set( 'cache_key', $something );
}
return $something;
Não fazer o cache para usuários autenticados ou no wp-admin (excepto AJAX)
VIP.WORDPRESS.COM
Cache de Pedidos Remotos
VIP.WORDPRESS.COM
● wp_remote_get()
● WP_HTTP
● wp_oembed_get()
Solução: Utilizar o Object Caching (wp_cache_set e wp_cache_get) para fazer o
cache do resultado.
Não esquecer de programar o comportamento quando o servidor remoto está em
baixo.
https://vip.wordpress.com/documentation/best-practices/fetching-remote-data/
https://vip-svn.wordpress.com/vip-helper.php
Estamos contratando!
https://vip.wordpress.com/jobs/
Perguntas?

Segurança e Performance WordPress

  • 1.
  • 2.
  • 3.
    560 TOTAL DE AUTOMATTICIANS 69 LINGUASFALADAS 51 PAISES REPRESENTADOS Automattic foi fundada em 2005 por Matt Mullenweg, co-criador do WordPress, para construir uma nova geração de ferramentas para publicar conteúdo online e para apoiar a comunidade open-source.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
    VIP.WORDPRESS.COM CORRECTO ERRADO $value = some_attribute(); echo"<div class='" . esc_attr( $value ) . "'> Hello! </div>"; $value = esc_attr( $some_attribute ); echo "<div class='$value'> Hello! </div>";
  • 11.
    VIP.WORDPRESS.COM ERRADO PORQUÊ? // (...)muito codigo novo aqui // Mudámos o value para ir buscar um valor à API, que não está escaped $value = get_attribute_from_api(); // (...) muito código novo aqui // Esta linha acaba esquecida e intocada, sem ter sido feito o escaping devido. echo "<div class='$value'> Hello! </div>"; $value = esc_attr( $some_attribute ); echo "<div class='$value'> Hello! </div>"; após vários commits…
  • 12.
  • 13.
    Attributes Nodes esc_attr( $text) esc_attr__( $text ) esc_attr_e ( $text ) Text Nodes esc_html( $text ) esc_html__ ( $text ) esc_html_e ( $text ) esc_textarea( $text ) JavaScript esc_js( $text ) wp_json_encode ( $var ) https://codex.wordpress.org/Data_Validation#Output_Sanitation URL’s esc_url( $url, (array) $protocols = null ) esc_url_raw( $url, (array) $protocols = null ) rawurlencode( $string ) Fragmentos de HTML & XHTML wp_kses( (string) $fragment, (array) $allowed_html, (array) $protocols = null ) wp_kses_post ( $data ) Inteiros intval( $int ) (int) $int absint ( $int )
  • 14.
    VIP.WORDPRESS.COM esc_html ( $text) $html = esc_html( ' <a href="http://example.com/">Exemplo</a>' ); echo $html; A string é codificada para entidades HTML &lt;a href=&quot;http://example.com/&quot;&gt;Exemplo&lt;/a&gt; <a href="http://example.com/">Exemplo</a>
  • 15.
    VIP.WORDPRESS.COM esc_html__ ( $text) Faz escape a uma string traduzida. Equivalente a esc_html( __( $text ) ) esc_html_e ( $text ) O mesmo que o anterior, mas para _e( $text ). Imprime e não necessita de echo esc_textarea( $text ) Codifica o texto para ser utilizado dentro de uma <textarea>
  • 16.
    VIP.WORDPRESS.COM esc_attr ( $text) $caption = "'><img src='jadsusaidaijd' onerror='javascript:alert('Hacked!')"; echo "<img src='https://exemplo.com/img.png' alt='" . esc_attr( $caption ) . "'>"; <img src='https://exemplo.com/img.png' alt='&#039;&gt;&lt;img src=&#039;jadsusaidaijd&#039; onerror=&#039;javascript:alert(&#039;Hacked!&#039;)'> '><img src='jadsusaidaijd' onerror='javascript:alert('Hacked!')
  • 17.
    VIP.WORDPRESS.COM esc_attr__ ( $text) Faz escape a uma string traduzida. Equivalente a esc_attr( __( $text ) ) esc_attr_e ( $text ) O mesmo que o anterior, mas para _e( $text ). Imprime e não necessita de echo
  • 18.
    VIP.WORDPRESS.COM esc_url( $url, (array)$protocols = null ) <a href="<?php echo esc_url( home_url( '/' ) ); ?>">Home</a> Home
  • 19.
    VIP.WORDPRESS.COM $url = 'http://wordpress.org'; $response= wp_remote_get( esc_url_raw( $url ) ); esc_url_raw ( $url, $protocols ) ● Semelhante ao esc_url, valida o URL ● Não faz substituição de caracteres ● Não é seguro para utilizar no output <img src='<?php echo esc_url_raw( $url ); ?>' />
  • 20.
    VIP.WORDPRESS.COM rawurlencode ( $string) <script src="https://sometracker.com/client_id=<?php echo rawurlencode( $client_id ); ?>"> <script src="https://sometracker.com/client_id= 1%26client_id%3D123456"> $client_id = " 1&client_id=123456";
  • 21.
    VIP.WORDPRESS.COM Porquê usar orawurlencode e não o esc_url? $client_id = " 1&client_id=123456"; <script src="https://sometracker.com/client_id=<?php echoesc_url( $client_id ); ?>"> <script src="https://sometracker.com/client_id= 1&client_id=123456"> O parametro client_id no servidor sometracker.com será 123456, visto que houve sobreposição.
  • 22.
    VIP.WORDPRESS.COM esc_js ( $text) Só deve ser utilizado para JS inline (num atributo, como por exemplo no onclick=”...”) <input type="text" onfocus="if ( this.value == ' <?php echo esc_js( $instance['input_text'] ); ?>') { this.value = ''; }" name="email" /> Para fazer escape de JavaScript que não seja inline, deverá ser utilizado o wp_json_encode
  • 23.
    VIP.WORDPRESS.COM Errado! <script> var title =' <?php echo esc_js( $title ); ?>'; var content = ' <?php echo esc_js( $content ); ?>'; var comment_count = ' <?php echo esc_js( $comment_count ); ?>'; </script> Deve ser usado o wp_json_encode!
  • 24.
    VIP.WORDPRESS.COM <script> var title =<?php echo wp_json_encode( $title ); ?>; var content = <?php echo wp_json_encode( $content ); ?>; var comment_count = <?php echo wp_json_encode( $comment_count ); ?>; </script> wp_json_encode ( $data ) <script> var title = "Olá Mundo"; var content = "Isto é um exemplo."; var comment_count = 42; </script>
  • 25.
    VIP.WORDPRESS.COM wp_kses( $fragment, $allowed_html,$protocols = null ) $allowed_html = array( 'a' => array( 'href' => array(), 'title' => array() ), 'br' => array(), 'em' => array(), 'strong' => array(), ); $content = mytheme_get_some_html_content(); echo wp_kses( $content, $allowed_html ); Limpa o HTML gerado pelo mytheme_get_some_html_content de modo a apenas permitir os elementos e atributos listados em $allowed_html Nota! Não se deve permitir iframe ou script no $allowed_html porque não é seguro.
  • 26.
    VIP.WORDPRESS.COM wp_kses_post ( $text) $content = mytheme_get_some_html_content(); echo wp_kses_post( $content ); ● Utilizado no get_content() ● Evita definir um array $allowed_html
  • 27.
    VIP.WORDPRESS.COM intval ( $int) ou (int) $int $post_id = get_the_ID(); echo 'Post #' . intval( $post_id );
  • 28.
    VIP.WORDPRESS.COM absint ( $int) $var = -1; echo absint( $var ); // returns "1" $posts_per_page = get_option( 'posts_per_page' ); $query = WP_Query( array( // Makes sure $posts_per_page is never -1 'posts_per_page' => absint( $posts_per_page ), // ... ) );
  • 29.
  • 30.
  • 31.
    VIP.WORDPRESS.COM HTTP GET requestpara http://example.com/?name=Henrique $name = $_GET['name']; // Store the option wp_update_option( 'saved_name', $name ); E se for para http://example.com/?name= <img src=”somethinginvalid.png” onerror=”(. ..)”> ? Vai ser guardado um valor potencialmente perigoso na base de dados. A solução é sanitizar (limpar) o input o mais cedo possível, ou seja, assim que o obtemos. $name = sanitize_text_field( $_GET['name'] ); // Store the sanitized option wp_update_option( 'saved_name', $name );
  • 32.
  • 33.
    sanitize_email( $data ) sanitize_file_name($data ) sanitize_html_class( $data ) sanitize_key( $data ) sanitize_meta( $data ) sanitize_mime_type( $data ) sanitize_option( $data ) sanitize_sql_orderby( $data ) sanitize_text_field( $data ) sanitize_title( $data ) sanitize_title_for_query( $data ) sanitize_title_with_dashes( $data ) sanitize_user( $data ) esc_url_raw( $data ) wp_filter_post_kses( $data ) wp_filter_nohtml_kses( $data ) Sanitiza emails Sanitiza nomes de ficheiros Sanitiza classes HTML Sanitiza chaves Sanitiza meta (para post meta) Sanitiza Mime Types Sanitiza opções (para Options API) Sanitiza o ORDER BY do SQL Sanitiza campos de texto Sanitiza titulos Sanitiza titulos para utilização em queries Sanitiza títulos com traços Sanitiza usuários Sanitiza URL’s (conforme já vimos) Sanitiza conteudo HTML (à semelhança do wp_kses) Remove todo o HTML da string
  • 34.
  • 35.
    VIP.WORDPRESS.COM $wpdb->query( "INSERT INTOproduct_review (review) VALUES '" . strip_tags( $_GET['review'], '<a><b><i><img>') . "'" ); http://example.com/?review='; DROP DATABASE; Pode ser utilizado não só para destruir a base de dados, mas também para selecionar e devolver usernames e passwords encriptadas. INSERT INTO product_review (review) VALUES ''; DROP DATABASE;
  • 36.
    VIP.WORDPRESS.COM $wpdb->query( $wpdb->prepare( " INSERTINTO product_review (review) VALUES %s ", $_GET['review'] ) ); Seguro mas insuficiente Deve ser sempre feita a sanitização antes de inserir na base de dados! $wpdb->query( $wpdb->prepare( " INSERT INTO product_review (review) VALUES %s ", wp_kses_post( $_GET['review'] ) ) );
  • 37.
    VIP.WORDPRESS.COM $wpdb->query( "SELECT FROM product_reviewWHERE review LIKE ' ". esc_like( sanitize_text_field( $_GET['review'] ) ) . " ' " ); Utilizar o esc_like()? Errado :( O esc_like() funciona como esperado, apenas para as operações LIKE do SQL. Apenas faz o escape de % / e que são caracteres com significado nos argumentos LIKE. Continua a ser necessário passar os valores pelo $wpdb->prepare().
  • 38.
    VIP.WORDPRESS.COM Escaping & Sanitization Nuncaconfiar no input dos usuários. O escaping deve ser o mais tarde possível Fazer escape a tudo com origem em fontes desconhecidas ou terceiras Nunca assumir nada Nunca confiar no input dos usuários Sanitização é bom, mas validação/rejeição é melhor Nunca confiar no input dos usuários 1. 2. 3. 4. 5. 6. 7. https://vip.wordpress.com/documentation/vip/best-practices/security/validating-sanitizing-escaping/ Regras Principais
  • 39.
  • 40.
    VIP.WORDPRESS.COM Exemplo Queremos criar umrole para Revisor de Artigos (Reviewer). function add_reviewer_role() { add_role( 'reviewer', 'Reviewer', array( 'read' => true, // (...) All the necessary capabilities (...) 'edit_posts' => true, 'edit_others_posts' => true, 'edit_private_posts' => true, ) ); } register_activation_hook( __FILE__, 'add_reviewer_role' ); Os Roles são guardados na base de dados, pelo que devem ser criados/removidos nos hooks de activação do tema/plugin.
  • 41.
  • 42.
    add_role( $role, $name,$capabilities ) remove_role( $role ) get_role( $role ) $wp_roles->add_cap( $role, $cap ) $wp_roles->remove_cap( $role, $cap ) https://vip-svn.wordpress.com/plugins/vip-do-not-include-on-wpcom/vip-roles.php
  • 43.
    VIP.WORDPRESS.COM add_role ( $role,$name, $capabilities ) Aviso! function mytheme_add_foo_role() { add_role( 'foo', 'Foo', array( 'read' => true, ) ); } register_activation_hook( __FILE__, 'mytheme_add_foo_role' ); Os novos roles deverão conter sempre a capability read, caso contrário o usuários não conseguirá navegar no site.
  • 44.
    VIP.WORDPRESS.COM get_role ( $role) function mytheme_add_foo_caps() { $role = get_role( 'foo' ); // Devolve um objecto WP_Role $role->add_cap( 'edit_posts' ); } add_action( 'admin_init', 'mytheme_add_foo_caps');
  • 45.
    VIP.WORDPRESS.COM $wp_roles->add_cap ( $role,$cap ) ou $role->add_cap( $cap ) function mytheme_add_foo_caps() { $role = get_role( 'foo' ); $role->add_cap( 'edit_posts' ); } add_action( 'admin_init', 'mytheme_add_foo_caps'); function mytheme_add_foo_caps() { global $wp_roles; $wp_roles->add_cap( 'foo', 'edit_posts' ); } add_action( 'admin_init', 'mytheme_add_foo_caps'); ou
  • 46.
    VIP.WORDPRESS.COM $wp_roles->remove_cap ( $role,$cap ) ou $role->remove_cap( $cap ) function mytheme_remove_author_caps() { $role = get_role( 'author' ); $role->remove_cap( 'edit_posts' ); } add_action( 'admin_init', mytheme_remove_author_caps); function mytheme_remove_author_caps() { global $wp_roles; $wp_roles->remove_cap( 'author', 'edit_posts' ); } add_action( 'admin_init', 'mytheme_remove_author_caps'); ou
  • 47.
    VIP.WORDPRESS.COM Como utilizar osRoles & Capabilities para validar permissões?
  • 48.
    VIP.WORDPRESS.COM current_user_can ( $capability, $object_id ); add_action( 'wp_ajax_update_foo', 'update_foo' ); function update_foo() { // Se o usuário não pode gerir opções, retorna falso if ( ! current_user_can('manage_options') ) { return false; } update_option( 'foo', sanitize_text_field( $_POST['bar'] )) ; }
  • 49.
    VIP.WORDPRESS.COM current_user_can ( $capability, $object_id ); E caso queira verificar se o usuário tem permissão para um determinado objecto? add_action( 'wp_ajax_update_foo', 'update_foo' ); function update_foo() { $post_id = intval( $_GET['post_id'] ); // Passa-se o $post_id no segundo argumento if ( ! current_user_can( 'edit_post', $post_id ) ){ return false; } update_post_meta( $post_id, 'foo', sanitize_text_field( $_GET['bar'] ) ); }
  • 50.
    VIP.WORDPRESS.COM Como se podevalidar a intenção do usuário?
  • 51.
  • 52.
    VIP.WORDPRESS.COM <img src=”http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Olá Mundo”> wp_create_nonce ($action ) http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Olá Mundo &_wpnonce=ba623fa
  • 53.
    VIP.WORDPRESS.COM wp_create_nonce ( $action) $nonce = wp_create_nonce( 'update_foo_' . $post_id ); echo "<a href='" . esc_url( "http://example.com/admin-ajax.php?action=update_foo&post_id=123&bar=Ol á Mundo&_wpnonce=" . $nonce ) . "'>Update Foo!</a>"; update_foo_$post_id é o action name
  • 54.
    VIP.WORDPRESS.COM wp_verify_nonce ( $nonce,$action ) add_action( 'wp_ajax_update_foo', 'update_foo' ); function update_foo() { $post_id = intval( $_GET['post_id'] ); // Valida se o nonce está presente e se é válido if ( ! wp_verify_nonce( $_GET[ '_wpnonce' ], 'update_foo_' . $post_id ) ) { return false; } // (...) o resto das validações de capabilities (...) } update_foo_$post_id é o action name
  • 55.
    VIP.WORDPRESS.COM wp_nonce_url ( $actionurl,$action, $name ) $nonce = wp_create_nonce( 'update_foo_' . $post_id ); echo "<a href='" . wp_nonce_url( admin_url( 'admin-ajax.php?action=update_foo&post_id=123&bar=Olá Mundo'), "update_foo_" . $post_id ) . "'>Update Foo!</a>";
  • 56.
    VIP.WORDPRESS.COM wp_nonce_field ( $action,$name, $referer, $echo ) // Cria e imprime um campo escondido com o nonce wp_nonce_field( "update_foo_" . $post_id ); Vai imprimir algo como: <input type="hidden" id="_wpnonce" name="_wpnonce" value=" 796c7766b1" />
  • 57.
    VIP.WORDPRESS.COM check_admin_referer ( $action,$query_arg ) add_action( 'wp_ajax_update_foo', 'update_foo' ); function update_foo() { $post_id = intval( $_GET['post_id'] ); check_admin_referer( 'update_foo_' . $post_id ); // (...) o resto das validações de capabilities (...) }
  • 58.
  • 59.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas 'hello' == 0 Qual o valor de retorno?
  • 60.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas 'hello' == 0 É uma comparação com um int. Converte 'hello' para 0 (int) "hello"; // devolve 0 (int) "1hello"; // devolve 1 Portanto 0 == 0 Retorna true.
  • 61.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas $string = 'Olá Mundo'; if ( $string == 0 ) { echo "$string é um inteiro de valor 0"; } else { echo "$string não é um inteiro de valor 0"; } Olá Mundo é um inteiro de valor 0 $string == 0 avalia como true
  • 62.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas $string = 'Olá Mundo'; if ( $string === 0 ) { echo "$string é um inteiro de valor 0"; } else { echo "$string não é um inteiro de valor 0"; } Olá Mundo não é um inteiro de valor 0 $string === 0 avalia como false
  • 63.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas $array = array( "foo" => "bar", "word" => "press", "php" => 0 ); $string = "hello"; if ( in_array( $string, $array ) ) { echo 'Encontrado!'; } else { echo 'Não Encontrado!'; } Encontrado! Mas "hello" não está no array! "hello" == 0 avalia como true
  • 64.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas $array = array( "foo" => "bar", "word" => "press", "php" => 0 ); $string = "hello"; if ( in_array( $string, $array, true ) ) { echo 'Encontrado!'; } else { echo 'Não Encontrado!'; } Não Encontrado! Utilizando o parâmetro strict, o resultado já é o esperado. "hello" === 0 avalia como false
  • 65.
    VIP.WORDPRESS.COM Utilizar sempre comparaçõesestritas Sempre que for feita uma comparação, deverá ser feita de forma estrita. Desta forma, o interpretador não tenta adivinhar qual o tipo de dados (Type Juggling) e não comete estes erros inesperados. Tentar utilizar sempre o parâmetro strict no in_array(), e === nas comparações.
  • 66.
  • 67.
  • 68.
    VIP.WORDPRESS.COM Full Page Cache ●Fazer sempre o full page cache de páginas com todos os parâmetros GET, ou ignorar alguns como o 'fb*', 'utm_*', etc ● Inclui também requests ao WP-API ● Para gerir o cache pode ser utilizado Varnish, Memcache(d) (juntamente com o plugin Batcache), ou outras soluções com os respectivos plugins (W3 Total Cache, WP Super Cache, …)
  • 69.
    VIP.WORDPRESS.COM Full Page Cachee AJAX ● O Full Page Cache deverá ser configurado para fazer o cache de certos parametros para os pedidos AJAX. ● Para aproveitar o Full Page Cache, utilizar a API do WP_Rewrite para converter os URLs AJAX: https://example.com/admin-ajax.php?category_id=123&page=2 com o WP_Rewrite pode-se transformar em https://example.com/ajax/category/123/2/
  • 70.
  • 71.
  • 72.
    VIP.WORDPRESS.COM Como identificar? ● Pluginsde apoio ao desenvolvimento: Query Monitor - https://wordpress.org/plugins/query-monitor/ Debug Bar - https://wordpress.org/plugins/debug-bar/ ● New Relic http://newrelic.com/ ● Bom conhecimento do DB Schema do WordPress http://codex.wordpress.org/Database_Description
  • 73.
  • 74.
    VIP.WORDPRESS.COM ● Queries quepesquisem por meta value ○ WP_Query( 'meta_key' => 'city', 'meta_value' => 'São Paulo' ); ● Utilização de NOT IN ○ WP_Query( 'category__not_in' => 'featured' ); ● Muitos JOINs ○ WP_Query( 'category_in' => [ 1,3,4,6,8,9 ], 'meta_key' => 'is_starred', 'post_tag_in' => ... );
  • 75.
    VIP.WORDPRESS.COM Post Meta ● Utilizarum termo em vez de um post meta 'meta_key' => 'city', 'meta_value' => 'São Paulo' torna-se 'taxonomy' => 'city', 'term' => 'São Paulo' 1. ● Situações Binárias ( is_featured = true ou is_featured = false ) Procura pela existencia da meta_key Apagar a meta_key se falso ● Situações Não Binárias ( meta_key => primary_category, meta_value => sports ) Procurar pela meta_key primary_category_sports e aplicar a mesma lógica binária ● Utilizar Elastic Search (https://github.com/alleyinteractive/es-wp-query ou outro)
  • 76.
    VIP.WORDPRESS.COM NOT IN emTaxonomias ● Remover todos os posts dessa categoria e coloca-los num novo Post Type. ● Saltar posts no PHP Em vez de pedir apenas 10 posts, pedir 20 e saltar os posts que não queremos no PHP. (não é garantido obter os 10 posts que queremos) // Antes get_posts( 'category__not_in' => 'featured'); // Depois get_posts( 'post_type' => 'featured' );
  • 77.
    VIP.WORDPRESS.COM Alavancar a Cachede Base de Dados ● Garantir que todas as chamadas para get_posts() incluem suppress_filters => false para permitir o caching ● Generalizar as queries o mais possível Em vez de utilizar posts__not_in,fazer o query com num_posts + xe saltar posts condicionalmente no PHP
  • 78.
  • 79.
    VIP.WORDPRESS.COM Funções que precisamde suppress_filters => false ● get_posts() ● wp_get_recent_posts() ● get_children() (é também necessário um limite na query)
  • 80.
    VIP.WORDPRESS.COM Funções sem Cache- Core ● Funções que fazem chamadas directas à base de dados sempre ● wp_get_post_terms() ● wp_get_post_categories() ● wp_get_post_tags() ● wp_get_object_terms() ● Solução: Utilizar get_the_terms() em vez destas funções https://vip.wordpress.com/documentation/uncached-functions/ ● term_exists() ● get_page_by_title() ● get_page_by_path() ● url_to_post_id() ● count_users_posts() ● Solução: Utilizar as funções de substituição que tenham cache
  • 81.
    VIP.WORDPRESS.COM E se aquery não poder ser mais otimizada?
  • 82.
    VIP.WORDPRESS.COM Faz-se o cachedos resultados wp_cache_set() e wp_cache_get() (ou transientes) $something = wp_cache_get( 'cache_key' ); if ( ! $something ) { // Obter os dados necessarios $something = new WP_Query( ... ); wp_cache_set( 'cache_key', $something ); } return $something; Não fazer o cache para usuários autenticados ou no wp-admin (excepto AJAX)
  • 83.
  • 84.
    VIP.WORDPRESS.COM ● wp_remote_get() ● WP_HTTP ●wp_oembed_get() Solução: Utilizar o Object Caching (wp_cache_set e wp_cache_get) para fazer o cache do resultado. Não esquecer de programar o comportamento quando o servidor remoto está em baixo. https://vip.wordpress.com/documentation/best-practices/fetching-remote-data/ https://vip-svn.wordpress.com/vip-helper.php
  • 85.
  • 86.