Bezpieczny kod Trzy nieco trudniejsze kawałki

Krzysiek Dróżdż
krzysiek@wpmagus.pl
WPmagus.pl

Dlaczego?

I to nic nie zmienia :(

Błędy są częste

Znacznie częstsze niż myślisz!

Większe wpadki

  • WordPress (XXS, SSRF)
  • Twenty Fifteen (XSS)
  • Gravity Forms (Upload, SQL Injection, XSS)
  • FreshMail Newsletter (SQL Injection)
  • TineMCE Advanced (CSRF)
  • WooCommerce (SQL Injection, XSS)
  • Pods (XSS, CSRF, SQL Injection)
  • RevSlider (Upload, LFD)
  • All In One WP Security (SQL Injection, XSS)
  • WordFence (XSS)
  • WordPress SEO by Yoast (SQL Injection, XSS)
  • itd.

W poprzedniej części

Dowiedzieliśmy się jak:

  • bezpiecznie ukrywać pliki na serwerze,
  • zabezpieczyć się przed SQL Injection,
  • poprawnie sprawdzać uprawnienia użytkownika.

https://wpmagus.pl/pliki/prezentacje/bezpieczny-kod-trzy-latwe-kawalki/

Kod wyjęty spod kontroli

Problem

Tworząc zabezpieczenia kodu musimy zadbać, żeby nie dało się ich ominąć.

W szczególności niebezpieczny/niezabezpieczony kod NIGDY nie powinien być samodzielny.

Co z tym zrobić?

Nie twórz bocznych drzwi

Jeśli aplikacja ma tylko jedno wejście, to łatwiej jest go pilnować.

Zamiast

<form action="<?php echo plugins_url( 'process-form.php', __FILE__ ); ?>" method="POST">

Użyj

<form action="<?php echo site_url('/'); ?>" method="POST">

Ani okien

Zamiast

if ( $action == 'save_file' ) {
    include "process-form.php";
}

użyj

function process_form() {...}
if ( $action == 'save_file' ) {
    process_form();
}

I nie odkrywaj koła!

Jest admin-ajax.php, ale także admin-post.php, czyli

<form action="<?php echo admin_url('admin-post.php'); ?>" method="POST">

a potem w zależności od przeznaczenia:

add_action( 'wp_post_my_process_form', 'my_process_form_callback' );

lub:

add_action( 'wp_post_nopriv_my_process_form', 'my_process_form_callback' );

Zagadka: O czym zapomniałeś, zabezpieczając dostęp do katalogu wp-admin?

Pułapki na użytkownika

Problem

A gdyby tak wcale się nie włamywać…

… tylko sprawić, żeby użytkownik zrobił coś za nas?

Co z tym zrobić?

Weryfikować pochodzenie requestów

Tylko jak?

A może…:

  • każdemu użytkownikowi dać losowy, trudny do zgadnięcia numer,
  • przy obsługiwaniu requestu od użytkownika pytać go o ten numer.

A nazwiemy to… number used once lub krócej… nonce

Jak używać nonce?

w linkach:

$complete_url = wp_nonce_url( $bare_url, 'my-action_'.$post->ID );

w formularzach:

wp_nonce_field( 'my-action_'.$post->ID );

gdzie indziej:

$nonce = wp_create_nonce( 'my-action_'.$post->ID );

Jak sprawdzać nonce?

w panelu admina:

check_admin_referer( 'my-action_'.$post->ID, 'query_arg' );

w requestach AJAX:

check_ajax_referer( 'my-action_'.$post->ID, 'query_arg' );

gdzie indziej:

wp_verify_nonce( $_REQUEST['query_arg'], 'my-action_'.$post->ID );

Więcej: https://codex.wordpress.org/WordPress_Nonces

Uważaj kogo gościsz

Problem

Każdy plik wgrany na serwer to potencjalne zagrożenie.

Co z tym zrobić?

Uważaj na to, co pozwalasz wgrywać.

Zabezpiecz się przed tym, co zostało wgrane.

Nie wgrywaj bezmyślnie

Co tu jest nie tak?

if ( isset( $_FILES['mal_file'] ) ) {
    $fname = $_FILES['mal_file']['name'];
    $upload_dir = wp_upload_dir();

    move_uploaded_file(
        $_FILES['mal_file']['tmp_name'],
        $upload_dir['path'] .'/'. $fname
    );
    $file = $upload_dir['url'] .'/'. $fname;
}

Tak będzie lepiej…

$uploaded_file_data = $_FILES['file'];
$upload_overrides = array( 'test_form' => false );
$uploaded_file = wp_handle_upload( $uploaded_file_data, $upload_overrides );

I trochę szczegółów

Co znajdzie się w $uploaded_file?

  • file(string) ścieżka do lokalnego pliku.
  • url (string) publiczny adres url pliku.
  • type (string) typ MIME pliku.

… lub informacja o błędzie.

A jeśli już wgrywasz…

Jeśli mają być niedostępne dla wszystkich, to losuj nazwy.

Zablokuj dostęp do .php

<FilesMatch "\.(?i:php)$">
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Deny from all
    </IfModule>
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
</FilesMatch>

i zablokuj hotlinkowanie

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourwebsite\.com/ [NC]
RewriteRule .*$ http://yourwebsite.com/ [NC]

Zagadka: A co, jeśli wgramy kod PHP, jako .jpg, a potem go zainkludujemy?

Dzięki za uwagę!

Krzysiek Dróżdż
krzysiek@wpmagus.pl
WPmagus.pl