Skip to content

Useful Functions for Plugin and Theme Security

Erwan edited this page Jun 3, 2021 · 7 revisions

WordPress has some sanitisation functions which should always be used, and correctly used. See https://developer.wordpress.org/themes/theme-security/data-sanitization-escaping/

However, when reporting issues to vendor, we noticed that we were always seeing the same mistake/s (for example, checking an archive for malicious files after being extracted etc) for which there were no WordPress function for.

The snippets below are a collection of PHP functions to help WordPress Plugin/Theme developers secure their code.

is_url_local()

When using an arbitrary URL in functions such as wp_remote_get, curl etc (which is not really recommended but sometimes there is no other way), in addition to ensure that the URL is indeed an URL, it should be also be checked to make sure the URL is not a local one, to avoid issues such as SSRF (https://portswigger.net/web-security/ssrf, https://en.wikipedia.org/wiki/Server-side_request_forgery)

<?php

function is_url_local($url) {
    $host = parse_url($url, PHP_URL_HOST);

    // Case of an url passed w/o protocol
    if ($host === NULL)
        $host = $url;

    $ip = gethostbyname($host);

    return ! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
}

// Testing things out

$urls = [
    'doesntexistblabla.com', 'https://wpscan.com', 'wpscan.com', '192.168.1.32', '127.0.0.1:9090', 'https://youtube.com:23', 'localhost',
    'http://127.0.0.1', '127.1', 'https://0x7f.0x0.0x0.0x1', '0177.0.0.01', '2130706433', 'https://%6c%6f%63%61%6c%68%6f%73%74', 'http://0177.0.0.0x1:9090/aa.txt', '::1', 'https://[::1]:8080', 'http://[::ffff:127.0.0.1]:19983/'
];

foreach ($urls as $url) {
    if (is_url_local($url))
        $type = 'local';
    else
        $type = 'remote';

    echo "{$url} => {$type}\n";
}

zip_only_contains_allowed_extensions()

Before extracting a zip file uploaded by any user (including admin), its content should be checked to ensure that the archive only contains expected files (such as png) and no other ones (such as php etc) which could lead to severe security issues.

<?php

function zip_only_contains_allowed_extensions($zip_path, array $allowed_extensions) {
    $zip = new ZipArchive;
    $zip->open($zip_path);

    for ($i = 0; $i < $zip->numFiles; $i++) {
        $stat = $zip->statIndex( $i );
        $ext = pathinfo($stat['name'], PATHINFO_EXTENSION);
    
    	// Skip folders name (but their content will be checked)
    	if ($ext === '' && substr($stat['name'], -1) === '/')
            continue;

        //print_r( "{$stat['name']} => {$ext}" . PHP_EOL . "<br/>");

        if (!in_array(strtolower($ext), $allowed_extensions))
            return false;
    }
    return true;
}

// Testing things out
var_dump(zip_only_contains_allowed_extensions('Archive.zip', ['png']));