Skip to content
article

Meu cliente foi hackeado por um plugin 'nulled'. O que todo plugin WordPress precisa ter

nonce, capability, sanitize, escape. as 4 coisas que eu olho antes de instalar qualquer plugin

Matheus Barreto@matheus_wp

August 25, 2026 · 2 min read

oxente, que semana. cliente meu aqui em salvador, uma pousada, comprou um plugin "premium" de reservas num site desses de plugin pirata. de graça. massa né? três semanas depois o google search console mandou alerta e o site tava cheio de página de cassino em russo e japonês. barril.

código PHP de um tema WordPress na tela

como eu achei

primeira coisa, ver quem é admin:

wp user list --role=administrator

tinha um usuário "wp_support" que ninguém criou. clássico. depois:

wp core verify-checksums
wp plugin verify-checksums --all
grep -rn "base64_decode\|eval(" wp-content/plugins wp-content/uploads

o verify-checksums do core compara os arquivos com os oficiais do wordpress.org. o dos plugins só funciona pra plugin que existe no repositório oficial, o pirata não tem checksum, óbvio. e o grep achou um eval(base64_decode(...)) enterrado num arquivo chamado class-license-check.php. "license check" kkkk a ironia.

limpeza: removi o plugin, apaguei o usuário, troquei TODAS as senhas e os salts do wp-config.php, reinstalei o core, procurei .php dentro de uploads (não pode ter nenhum), e pedi reindexação pro google. dois dias de trabalho. cobrei, claro.

o que um plugin decente faz (e o seu precisa fazer)

como eu acabei reescrevendo a parte de reservas do zero, aqui o esqueleto que eu uso. quatro regras:

// no formulário
wp_nonce_field('mb_salvar_reserva');

// no handler
add_action('admin_post_mb_salvar_reserva', 'mb_salvar_reserva');

function mb_salvar_reserva() {
    // 1. permissão primeiro
    if (! current_user_can('edit_posts')) {
        wp_die('sem permissão', 403);
    }

    // 2. nonce (protege contra CSRF, NÃO é permissão)
    check_admin_referer('mb_salvar_reserva');

    // 3. sanitize na entrada
    $nome   = sanitize_text_field(wp_unslash($_POST['nome'] ?? ''));
    $email  = sanitize_email(wp_unslash($_POST['email'] ?? ''));
    $noites = absint($_POST['noites'] ?? 0);

    // SQL sempre com prepare
    global $wpdb;
    $wpdb->query($wpdb->prepare(
        "INSERT INTO {$wpdb->prefix}mb_reservas (nome, email, noites) VALUES (%s, %s, %d)",
        $nome,
        $email,
        $noites
    ));

    wp_safe_redirect(admin_url('admin.php?page=mb-reservas&ok=1'));
    exit;
}
// 4. escape na saída, sempre, mesmo que "venha do banco"
echo '<td>' . esc_html($reserva->nome) . '</td>';
echo '<a href="' . esc_url($link) . '">' . esc_html__('Ver', 'mb') . '</a>';
  • capability: quem pode fazer isso? current_user_can antes de tudo.
  • nonce: o pedido veio mesmo do seu formulário? check_admin_referer.
  • sanitize: limpa o que entra. sanitize_text_field, absint, sanitize_email.
  • escape: limpa o que sai. esc_html, esc_attr, esc_url.

e se tiver endpoint REST, o permission_callback é obrigatório. nada de __return_true em rota que grava coisa.

resumo da ópera: plugin pirata é backdoor com frete grátis. o cliente economizou 59 dólares e pagou 400 pra mim limpar. PHP paga meu aluguel, mas eu preferia que fosse por outro motivo kkkk 🌴

Written by

Matheus Barreto

freela de wordpress e php desde 2016, salvador bahia 🌴 cliente que pede "só um sitezinho" agora eu cobro em dólar kkkk

Get the best of #php_still_pays weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

10

Sign in to join the conversation.

Sign in to comment
Julien Lefèvre@julien_wp#7 in PHP Still Pays
Je ne parle pas portugais mais le code parle français 😄 nonce + capability + escape, c'est exactement ma checklist. Et les plugins nulled, j'ai nettoyé trois sites de boulangerie l'année dernière à cause de ça.
2 net votes
Matheus Barreto@matheus_wp#5 in PHP Still Pays
kkkk o código é universal véi. merci julien 🌴
0 net votes
Caio Nascimento@caio_backend#9 in PHP Still Pays
Oxe, PHP com eval(base64_decode) no meio, nenhuma surpresa kkkkk. Brincadeira, o post tá muito bom. Mas confessa que WordPress é o paraíso do backdoor.
0 net votes
Matheus Barreto@matheus_wp#5 in PHP Still Pays
véi, o backdoor tava no plugin pirata, não no wordpress. log4shell era java, lembra? 😎 PHP paga meu aluguel, respeita
0 net votes
Caio Nascimento@caio_backend#9 in PHP Still Pays
touché kkkkkkk
0 net votes
Fatima Al-Sayed@fatima_appsec2 rep in PHP Still Pays
Great checklist. One thing to stress: check_admin_referer is not authorization, only CSRF protection. You did both and in the right order. Many plugins only check the nonce and think they are safe. Capability check first, always.
1 net votes
Matheus Barreto@matheus_wp#5 in PHP Still Pays
yes!! thank you, i will put this in bold. nonce is not permission. many plugin from marketplaces make this mistake
1 net votes
Nadia Benali@nullbyte_nadia2 rep in PHP Still Pays
the wp_support admin user is a classic, I have seen it in dozens of compromised sites. and yes, uploads should never execute php. add a rule in the server config so even if a .php lands there, it does not run. voilà
1 net votes
Ayesha Siddiqui@laravel_ayesha#1 in PHP Still Pays
used translate, worth it. sending to our WordPress team, every year somebody has a 'just this once' nulled theme story
1 net votes
José Gregorio Marcano@chamo_coder2 rep in PHP Still Pays
Pana, a mí me pasó igual con un cliente en Valencia, plugin 'premium' gratis y al mes la web vendía pastillas en ruso jajaja. Lo de wp core verify-checksums no lo conocía, burda de útil.
2 net votes