阐述 WordPress `wp_get_referer_if_valid()` 函数源码:如何判断 `Referer` 的有效性。

各位观众老爷,晚上好!今天咱们来聊聊 WordPress 里的一个“幕后英雄”:wp_get_referer_if_valid()。别看它名字长,其实它干的活儿挺实在,专门负责验证 Referer(来路)信息的真伪。这玩意儿在安全方面可有点作用,能帮咱们稍微挡一挡 CSRF 攻击啥的。

好了,废话不多说,咱们这就开始解剖一下这个函数的源码,看看它到底是怎么判断 Referer 有效性的。

一、Referer 是个啥?为啥要验证它?

在深入代码之前,先得弄清楚 Referer 是个什么东西。简单来说,Referer 是 HTTP 请求头中的一个字段,它告诉服务器,你是从哪个页面链接到当前页面的。

举个例子,你正在浏览 www.example.com/page1.html,然后点击了一个链接,跳转到了 www.example.com/page2.html。那么,当你访问 page2.html 时,你的浏览器会在 HTTP 请求头中带上 Referer: www.example.com/page1.html 这么一行信息。

那么,为啥要验证 Referer 呢?因为这玩意儿可以被伪造!恶意用户可以修改 HTTP 请求头,把 Referer 改成任何他们想要的值。这样一来,服务器就可能被欺骗,执行一些不应该执行的操作。

CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击就经常利用这一点。攻击者会诱骗用户点击一个恶意链接,这个链接会向用户的网站发送一个请求,并且伪造 Referer,让服务器误以为这个请求是用户自己发起的。

所以,验证 Referer,虽然不能完全杜绝 CSRF 攻击,但至少可以提高安全性,增加攻击的难度。

二、wp_get_referer_if_valid() 源码解析

咱们直接上代码,然后逐行解释:

function wp_get_referer_if_valid() {
    $ref = wp_get_raw_referer();

    if ( ! $ref ) {
        return false;
    }

    $val = wp_validate_redirect( $ref, false );

    if ( ! $val ) {
        return false;
    }

    return $val;
}

这段代码非常简洁,主要做了三件事:

  1. 获取原始 Referer 信息: 使用 wp_get_raw_referer() 获取未经处理的 Referer 值。
  2. 检查 Referer 是否为空: 如果 Referer 为空,直接返回 false
  3. 验证 Referer 的有效性: 使用 wp_validate_redirect() 验证 Referer 是否是合法的 URL。如果验证失败,返回 false
  4. 返回验证后的 Referer 如果验证通过,返回验证后的 Referer 值。

看起来很简单,是吧?但魔鬼藏在细节里。咱们一个个函数拆开来看。

2.1 wp_get_raw_referer():获取原始 Referer

这个函数负责从 $_SERVER 超全局变量中获取 HTTP_REFERER 的值。代码如下:

function wp_get_raw_referer() {
    if ( ! isset( $_SERVER['HTTP_REFERER'] ) ) {
        return '';
    }

    return trim( $_SERVER['HTTP_REFERER'] );
}

非常简单,就是从 $_SERVER 中取 HTTP_REFERER 字段,然后用 trim() 函数去除首尾的空白字符。 如果 HTTP_REFERER 不存在,则返回空字符串。

2.2 wp_validate_redirect():验证 Referer 的有效性

这是整个验证过程中最核心的函数,它负责判断 Referer 是否是一个合法的 URL,并且是否符合一些安全规则。

wp_validate_redirect() 函数的原型如下:

function wp_validate_redirect( $location, $default = '' ) {
    $location = wp_sanitize_redirect( $location );

    if ( ! $location ) {
        return $default;
    }

    $location = wp_kses_bad_protocol( $location, array( 'http', 'https' ) );

    if ( false !== strpos( $location, ';' ) ) {
        return $default;
    }

    $parsed_url = wp_parse_url( $location );

    if ( ! is_array( $parsed_url ) ) {
        return $default;
    }

    // Absolute URL.
    if ( isset( $parsed_url['host'] ) ) {
        $allowed_hosts = apply_filters( 'allowed_redirect_hosts', array( wp_parse_url( home_url(), PHP_URL_HOST ) ) );

        if ( ! in_array( strtolower( $parsed_url['host'] ), array_map( 'strtolower', $allowed_hosts ), true ) ) {
            return $default;
        }
    } else {
        $content_url = strtolower( str_replace( array( 'http://', 'https://' ), '', content_url() ) );
        $site_url    = strtolower( str_replace( array( 'http://', 'https://' ), '', site_url() ) );

        $location_no_scheme = strtolower( str_replace( array( 'http://', 'https://' ), '', $location ) );

        // If $location_no_scheme contains content_url or site_url, consider it safe.
        if ( false !== strpos( $location_no_scheme, $content_url ) || false !== strpos( $location_no_scheme, $site_url ) ) {
            return $location;
        }

        if ( preg_match( '#^//#', $location ) ) {
            return $location;
        }
    }

    return $location;
}

这段代码略微复杂,咱们一步一步来分析:

  1. wp_sanitize_redirect():清理 URL

    首先,使用 wp_sanitize_redirect() 函数对 URL 进行清理。这个函数主要负责移除 URL 中的一些非法字符,例如换行符、回车符等等。

    function wp_sanitize_redirect( $location ) {
        $location = preg_replace( '|[^a-z0-9-%/_.~+?=&:[]@!$*'(),;]|i', '', $location );
        $location = str_replace( ' ', '%20', $location );
        return $location;
    }

    这段代码使用正则表达式移除了 URL 中除了字母、数字、一些特殊字符之外的所有字符,然后将空格替换为 %20

  2. 检查 URL 是否为空:

    如果清理后的 URL 为空,直接返回默认值 $default。在 wp_get_referer_if_valid() 中,$default 的值为 false

  3. wp_kses_bad_protocol():移除危险协议

    使用 wp_kses_bad_protocol() 函数移除 URL 中的危险协议,例如 javascript:vbscript: 等等。这个函数可以防止 XSS 攻击。

    function wp_kses_bad_protocol( $string, $allowed_protocols ) {
        $string = wp_kses_no_null( $string );
        wp_kses_allowed_protocols( $allowed_protocols );
    
        $string = preg_replace_callback( '#^([srnt]*)([^:]+):#i', '_wp_kses_bad_protocol_once2', $string );
    
        return $string;
    }

    这个函数的核心是 _wp_kses_bad_protocol_once2(),它使用正则表达式来查找 URL 中的协议,然后判断该协议是否在 $allowed_protocols 列表中。如果不在列表中,就移除该协议。 在 wp_validate_redirect() 中,$allowed_protocols 的值为 array( 'http', 'https' ),也就是说,只允许 httphttps 协议。

  4. 检查 URL 中是否包含分号:

    如果 URL 中包含分号 ;,直接返回默认值 $default。分号在 URL 中可能会导致安全问题,所以需要禁止。

  5. wp_parse_url():解析 URL

    使用 wp_parse_url() 函数解析 URL,将 URL 分解成不同的部分,例如协议、主机名、路径等等。

    $parsed_url = wp_parse_url( $location );

    如果解析失败,wp_parse_url() 会返回 false。 如果返回值不是数组,直接返回默认值 $default

  6. 检查主机名是否合法:

    如果 URL 中包含主机名,需要检查主机名是否在允许的列表中。

    if ( isset( $parsed_url['host'] ) ) {
        $allowed_hosts = apply_filters( 'allowed_redirect_hosts', array( wp_parse_url( home_url(), PHP_URL_HOST ) ) );
    
        if ( ! in_array( strtolower( $parsed_url['host'] ), array_map( 'strtolower', $allowed_hosts ), true ) ) {
            return $default;
        }
    }

    这段代码首先使用 apply_filters( 'allowed_redirect_hosts', ... ) 获取允许的主机名列表。默认情况下,只允许当前网站的主机名。 allowed_redirect_hosts 是一个过滤器,允许开发者自定义允许的主机名列表。

    然后,使用 in_array() 函数检查 URL 中的主机名是否在允许的列表中。如果不在列表中,直接返回默认值 $default

  7. 处理相对URL:

    如果没有设置host,表示是一个站内的相对URL,也需要验证

     else {
        $content_url = strtolower( str_replace( array( 'http://', 'https://' ), '', content_url() ) );
        $site_url    = strtolower( str_replace( array( 'http://', 'https://' ), '', site_url() ) );
    
        $location_no_scheme = strtolower( str_replace( array( 'http://', 'https://' ), '', $location ) );
    
        // If $location_no_scheme contains content_url or site_url, consider it safe.
        if ( false !== strpos( $location_no_scheme, $content_url ) || false !== strpos( $location_no_scheme, $site_url ) ) {
            return $location;
        }
    
        if ( preg_match( '#^//#', $location ) ) {
            return $location;
        }
    }

    首先获取 content_urlsite_url 去除协议头后的值,然后判断location是否包含这些字符串,如果包含,则认为是安全的,直接返回。

    另外,如果 location// 开头,也认为是安全的,直接返回。

  8. 返回验证后的 URL:

    如果所有的验证都通过了,说明 URL 是合法的,返回验证后的 URL。

三、wp_get_referer_if_valid() 的使用场景

wp_get_referer_if_valid() 函数主要用于验证 Referer 的有效性,防止 CSRF 攻击。它通常用于以下场景:

  • 处理敏感操作: 例如修改用户资料、删除文章等等。在执行这些操作之前,可以先验证 Referer,确保请求是从合法的页面发起的。
  • 防止表单重复提交: 可以使用 Referer 来判断用户是否是从同一个表单页面提交的请求。

四、wp_get_referer_if_valid() 的局限性

虽然 wp_get_referer_if_valid() 可以提高安全性,但它并不能完全杜绝 CSRF 攻击。因为:

  • Referer 可以被伪造: 恶意用户可以修改 HTTP 请求头,伪造 Referer
  • Referer 可能会被浏览器禁用: 有些浏览器出于隐私保护的考虑,可能会禁用 Referer

因此,不能完全依赖 wp_get_referer_if_valid() 来防止 CSRF 攻击,还需要结合其他的安全措施,例如:

  • 使用 Nonce: 为每个表单生成一个唯一的 Nonce(一次性令牌),并在提交表单时验证 Nonce 的有效性。
  • 使用 CAPTCHA: 验证用户是否是真人。
  • 实施 SameSite Cookie 策略: 限制 Cookie 的跨站访问。

五、代码示例

下面是一个使用 wp_get_referer_if_valid() 函数的示例:

<?php
// 验证 Referer
$referer = wp_get_referer_if_valid();

if ( ! $referer ) {
    // Referer 无效,拒绝请求
    wp_die( 'Invalid Referer!' );
}

// Referer 有效,继续处理请求
// ...
?>

这段代码首先使用 wp_get_referer_if_valid() 函数验证 Referer 的有效性。如果 Referer 无效,使用 wp_die() 函数显示错误信息,并终止程序的执行。如果 Referer 有效,继续处理请求。

六、总结

wp_get_referer_if_valid() 函数是 WordPress 中一个用于验证 Referer 的函数。它可以帮助我们提高安全性,防止 CSRF 攻击。但它并不能完全杜绝 CSRF 攻击,还需要结合其他的安全措施。

总的来说,wp_get_referer_if_valid() 函数的验证流程可以总结为以下几点:

  1. 获取原始 Referer 信息。
  2. 清理 URL,移除非法字符。
  3. 移除危险协议。
  4. 检查 URL 中是否包含分号。
  5. 解析 URL。
  6. 检查主机名是否合法。
  7. 返回验证后的 URL。

希望今天的讲解对大家有所帮助。 记住,安全无小事,多一份防范,少一份风险! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注