阐述 WordPress `wp_get_referer_if_valid()` 函数的源码:如何验证 `HTTP_REFERER` 的有效性。

各位程序猿、程序媛们,晚上好!今天咱们来聊聊 WordPress 里一个挺有意思的小家伙:wp_get_referer_if_valid()。别看它名字长,其实干的活儿挺实在,就是负责验证 HTTP_REFERER 这个东西靠不靠谱。

好,废话不多说,直接开讲!

啥是 HTTP_REFERER

在Web世界里,HTTP_REFERER 是一个HTTP请求头,它告诉服务器,用户是从哪个页面链接过来的。 简单来说,就是“我是谁,我从哪儿来”。

举个栗子:你正在浏览“我的博客”,然后点击了一个链接跳转到“你的博客”,这时,浏览器发给“你的博客”服务器的HTTP请求里,HTTP_REFERER 的值就是“我的博客”的URL。

为什么要验证 HTTP_REFERER

HTTP_REFERER 虽然方便,但有个致命的问题:它很容易被伪造! 坏人可以轻松地修改这个值,冒充是从一个“安全”的页面跳转过来的,以此来绕过一些安全检查,或者进行一些恶意操作,比如跨站请求伪造 (CSRF) 攻击。

所以,验证 HTTP_REFERER 就显得很重要了,它可以帮助我们判断请求的来源是否可信,从而提高网站的安全性。

wp_get_referer_if_valid() 是干啥的?

wp_get_referer_if_valid() 函数就是 WordPress 用来验证 HTTP_REFERER 的。 它会检查 HTTP_REFERER 是否存在,是否符合一定的规则,如果验证通过,就返回 HTTP_REFERER 的值;否则,就返回 false

wp_get_referer_if_valid() 源码剖析

让我们深入 wp-includes/pluggable.php (通常在这个文件里) 看看 wp_get_referer_if_valid() 的真面目:

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

    if ( $ref && wp_validate_redirect( $ref, false ) ) {
        return $ref;
    }

    return false;
}

是不是很简单? 只有三行代码!

  1. $ref = wp_get_raw_referer();: 首先,它调用了 wp_get_raw_referer() 函数来获取原始的 HTTP_REFERER 值。
  2. if ( $ref && wp_validate_redirect( $ref, false ) ) { ... }: 然后,它判断 $ref 是否存在,并且使用 wp_validate_redirect() 函数来验证 $ref 是否是一个有效的 URL。
  3. return $ref;: 如果验证通过,就返回 $ref 的值。
  4. return false;: 否则,就返回 false

可以看到,wp_get_referer_if_valid() 函数的关键在于 wp_get_raw_referer()wp_validate_redirect() 这两个函数。 接下来,咱们就分别分析一下它们。

wp_get_raw_referer():获取原始的 HTTP_REFERER

wp_get_raw_referer() 函数的作用很简单,就是从 $_SERVER 超全局变量中获取 HTTP_REFERER 的值。 源码如下:

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

    return '';
}
  1. if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) { ... }: 首先,它判断 $_SERVER['HTTP_REFERER'] 是否存在且不为空。
  2. return wp_unslash( $_SERVER['HTTP_REFERER'] );: 如果存在,就使用 wp_unslash() 函数来移除反斜杠,并返回 HTTP_REFERER 的值。
  3. return '';: 否则,返回空字符串。

wp_unslash() 函数的作用是移除字符串中的反斜杠。 为什么要移除反斜杠呢? 因为有些服务器会自动在 HTTP_REFERER 的值中添加反斜杠,为了保证 HTTP_REFERER 的值是原始的,我们需要移除这些反斜杠。

wp_validate_redirect():验证 URL 是否有效

wp_validate_redirect() 函数才是真正验证 HTTP_REFERER 是否有效的关键。 它的作用是检查一个 URL 是否是一个有效的重定向目标。换句话说,就是判断这个 URL 是否可以安全地用于重定向。

wp_validate_redirect() 函数的源码比较长,咱们一点一点来分析:

function wp_validate_redirect( $url, $allowed_hosts = '' ) {
    $original_url = $url;
    $url          = wp_sanitize_redirect( $url );

    if ( ! $url ) {
        return false;
    }

    if ( wp_is_url_in_content( $url ) ) {
        return $url;
    }

    $home_url = wp_parse_url( home_url() );

    if ( ! $home_url ) {
        return false;
    }

    // If the URL is a relative path, then it's fine.
    if ( strpos( $url, '://' ) === false ) {
        return $url;
    }

    $parsed_url = wp_parse_url( $url );

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

    // Scheme check.
    if ( ! isset( $parsed_url['scheme'] ) || ! in_array( $parsed_url['scheme'], array( 'http', 'https', 'feed' ), true ) ) {
        return false;
    }

    // Host check.
    if ( isset( $parsed_url['host'] ) ) {
        $home_host = $home_url['host'];
        $is_safe   = strtolower( $parsed_url['host'] ) === strtolower( $home_host );

        if ( ! $is_safe && ! empty( $allowed_hosts ) ) {
            $allowed_hosts = array_map( 'strtolower', (array) $allowed_hosts );
            $is_safe       = in_array( strtolower( $parsed_url['host'] ), $allowed_hosts, true );
        }

        if ( ! $is_safe ) {
            return false;
        }
    }

    /**
     * Filters a validated redirect URL.
     *
     * @since 4.5.0
     *
     * @param string $url            The validated redirect URL.
     * @param string $original_url   The URL prior to validation.
     */
    return apply_filters( 'wp_safe_redirect_url', $url, $original_url );
}

咱们把这段代码分成几个部分来分析:

  1. $original_url = $url;: 保存原始的 URL,以便在过滤器中使用。
  2. $url = wp_sanitize_redirect( $url );: 使用 wp_sanitize_redirect() 函数对 URL 进行清理。
  3. if ( ! $url ) { return false; }: 如果清理后的 URL 为空,说明 URL 无效,返回 false
  4. if ( wp_is_url_in_content( $url ) ) { return $url; }: 检查 URL 是否在内容中,如果是,则直接返回 URL。 这个函数主要是为了兼容一些特殊情况,比如 URL 包含特殊字符等。
  5. $home_url = wp_parse_url( home_url() );: 获取网站的 URL,并使用 wp_parse_url() 函数解析 URL。
  6. if ( ! $home_url ) { return false; }: 如果解析网站 URL 失败,返回 false
  7. if ( strpos( $url, '://' ) === false ) { return $url; }: 如果 URL 是一个相对路径,说明 URL 是站内链接,直接返回 URL。
  8. $parsed_url = wp_parse_url( $url );: 使用 wp_parse_url() 函数解析 URL。
  9. if ( ! is_array( $parsed_url ) ) { return false; }: 如果解析 URL 失败,返回 false
  10. if ( ! isset( $parsed_url['scheme'] ) || ! in_array( $parsed_url['scheme'], array( 'http', 'https', 'feed' ), true ) ) { return false; }: 检查 URL 的协议是否是 httphttpsfeed,如果不是,返回 false
  11. if ( isset( $parsed_url['host'] ) ) { ... }: 检查 URL 的主机名是否是网站的主机名,或者是允许的主机名。
  12. return apply_filters( 'wp_safe_redirect_url', $url, $original_url );: 使用 wp_safe_redirect_url 过滤器,允许开发者自定义验证规则。

可以看到,wp_validate_redirect() 函数的验证逻辑还是比较复杂的,它主要做了以下几件事情:

  • 清理 URL: 使用 wp_sanitize_redirect() 函数对 URL 进行清理,移除一些不安全的字符。
  • 检查协议: 检查 URL 的协议是否是 httphttpsfeed
  • 检查主机名: 检查 URL 的主机名是否是网站的主机名,或者是允许的主机名。
  • 允许自定义验证规则: 使用 wp_safe_redirect_url 过滤器,允许开发者自定义验证规则。

wp_sanitize_redirect():清理 URL

wp_sanitize_redirect() 函数的作用是清理 URL,移除一些不安全的字符。 源码如下:

function wp_sanitize_redirect( $location ) {
    $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%@$|*'"![]{}`´()\x80-xff]|i', '', $location );
    $location = wp_kses_no_null( $location );

    // Remove remaining invalid lower ASCII chars.
    $location = preg_replace( '/[x00-x1f]/', '', $location );
    $location = str_replace( '%0d', '', $location );

    return $location;
}

这个函数主要做了以下几件事情:

  1. *`$location = pregreplace( ‘|[^a-z0-9-~+.?#=&;,/:%@$|‘"![]{}´()\x80-xff]|i', '', $location );**: 使用正则表达式移除 URL 中不合法的字符。
  2. $location = wp_kses_no_null( $location );: 移除 URL 中的 NULL 字符。
  3. $location = preg_replace( '/[x00-x1f]/', '', $location );: 移除 URL 中剩余的无效的 ASCII 字符。
  4. $location = str_replace( '%0d', '', $location );: 移除 URL 中的 %0d 字符。

wp_is_url_in_content(): 检查 URL 是否在内容中

这个函数相对简单,主要检测是否是 data: URI 或者包含特殊字符。

function wp_is_url_in_content( $url ) {
    $url = strtolower( $url );

    if ( strpos( $url, 'data:' ) === 0 ) {
        return true;
    }

    // Check for unicode chars.
    if ( preg_match( '/%u[0-9a-f]{2,4}|&#x[0-9a-f]{1,6};|&#[0-9]{1,7};/', $url ) ) {
        return true;
    }

    return false;
}

允许的主机名 ($allowed_hosts)

wp_validate_redirect() 函数中,还有一个 $allowed_hosts 参数,用于指定允许的主机名。 如果 $allowed_hosts 参数为空,那么只允许网站的主机名。 如果 $allowed_hosts 参数不为空,那么允许网站的主机名和 $allowed_hosts 参数指定的主机名。

wp_get_referer_if_valid() 的应用场景

wp_get_referer_if_valid() 函数在 WordPress 中有很多应用场景,比如:

  • 防止 CSRF 攻击: 在处理一些敏感操作时,可以使用 wp_get_referer_if_valid() 函数来验证请求的来源是否可信,从而防止 CSRF 攻击。
  • 统计页面访问量: 可以使用 wp_get_referer_if_valid() 函数来获取用户的来源页面,从而统计页面的访问量。
  • 个性化用户体验: 可以使用 wp_get_referer_if_valid() 函数来获取用户的来源页面,从而为用户提供个性化的用户体验。

使用示例

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

$referer = wp_get_referer_if_valid();

if ( $referer ) {
    // Referer 有效
    echo 'Referer: ' . esc_url( $referer );
} else {
    // Referer 无效
    echo 'Referer 无效';
}

在这个示例中,我们首先调用 wp_get_referer_if_valid() 函数来获取 HTTP_REFERER 的值。 如果 HTTP_REFERER 的值有效,就输出 HTTP_REFERER 的值;否则,就输出 "Referer 无效"。 注意使用了 esc_url() 函数来对输出的 URL 进行转义,防止 XSS 攻击。

总结

wp_get_referer_if_valid() 函数是 WordPress 中一个用于验证 HTTP_REFERER 的函数。 它通过 wp_get_raw_referer() 函数获取原始的 HTTP_REFERER 值,然后使用 wp_validate_redirect() 函数来验证 HTTP_REFERER 是否是一个有效的 URL。wp_validate_redirect() 还会调用 wp_sanitize_redirect() 来清理 URL。

wp_get_referer_if_valid() 函数在 WordPress 中有很多应用场景,可以用于防止 CSRF 攻击、统计页面访问量、个性化用户体验等。

总的来说,wp_get_referer_if_valid() 函数是一个很有用的函数,可以帮助我们提高 WordPress 网站的安全性。

深入思考

  • HTTP_REFERER 始终是不可靠的,即使通过了 wp_get_referer_if_valid() 的验证。 攻击者仍然可以通过多种方式伪造 HTTP_REFERER。 因此,不应该仅仅依赖 HTTP_REFERER 来做安全验证。
  • CSRF Token 是更可靠的 CSRF 防御机制。 应该尽可能使用 CSRF Token 来保护敏感操作。
  • wp_validate_redirect() 函数不仅仅用于验证 HTTP_REFERER,还可以用于验证其他的 URL。

好啦,今天的讲座就到这里。 希望大家对 wp_get_referer_if_valid() 函数有了更深入的了解。 如果有什么问题,欢迎随时提问! 祝大家编程愉快!

发表回复

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