1. O documento discute segurança e desempenho no WordPress VIP, mencionando validação de dados, escaping de strings e funções.
2. É explicado porque escapar dados é importante para prevenir exploits e ataques como SQL injection.
3. Diferentes funções de escaping e sanitização são explicadas, incluindo como escapar atributos, texto, URLs e JavaScript.
3. 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.
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…
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
<a href="http://example.com/">Exemplo</a>
<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>
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
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 ); ?>' />
21. 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.
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.
31. 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 );
35. 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;
36. 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'] )
)
);
37. 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().
38. 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
40. 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.
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'] )
);
}
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
60. 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.
61. 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
62. 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
63. 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
64. 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
65. 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.
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 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/
72. 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
74. 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' => ...
);
75. 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)
76. 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' );
77. 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
79. 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)
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
82. 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)
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