0

0

PHP的安全实例分享

小云云

小云云

发布时间:2018-03-13 09:45:15

|

2052人浏览过

|

来源于php中文网

原创

一个有趣的请求已开通针对php来作出bin2hex()一定的时间。这导致了一些关于邮件列表的有趣讨论(甚至让我回复:-x)。php在远程计时攻击方面的报道非常好,但他们谈论了字符串比较。我想谈谈其他类型的定时攻击。

什么是远程定时攻击?

好的,让我们假设你有以下代码:

function containsTheLetterC($string) {
    for ($i = 0; $i < strlen($string); $i++) {
        if ($string[$i] == "c") {
            return true;
        }
        sleep(1);
    }
    return false;}var_dump(containsTheLetterC($_GET['query']));

说出它的作用应该很容易。它接受query来自URL 的参数,然后逐字逐句通过它,检查它是否是小写字母c。如果是,它会返回。如果不是,它睡一秒钟。

所以让我们想象我们通过了字符串?query=abcdef。我们预计检查将花费2秒钟。

现在,让我们想象一下,我们不知道要查找哪封信。让我们想象一下,这"c"是一个我们不知道的不同价值。你能想出如何弄清楚那封信是什么吗?

立即学习PHP免费学习笔记(深入)”;

这很简单。我们构造一个字符串"abcdefghijklmnopqrstuvwxyzABCDEFGHIJ....",并将其传入。然后我们可以计算返回需要多长时间。然后我们知道哪个角色不同!

这是定时攻击的基础。

但是我们没有在现实世界中看起来像那样的代码。我们来看一个真实的例子:

$secret = "thisismykey";if ($_GET['secret'] !== $secret) {
    die("Not Allowed!");}

为了理解发生了什么,我们需要is_identical_function从PHP的源代码中看到。如果你看看这个函数,你会发现结果是由以下情况定义的:

case IS_STRING:
    if (Z_STR_P(op1) == Z_STR_P(op2)) {
        ZVAL_BOOL(result, 1);
    } else {
        ZVAL_BOOL(result, (Z_STRLEN_P(op1) == Z_STRLEN_P(op2))
        && (!memcmp(Z_STRVAL_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op1))));
    }
    break;

该if如果两个变量都相同的变量(如主要是要求$secret === $secret)。在我们的情况下,这是不可能的,所以我们只需要看else块。

Z_STRLEN_P(op1) == Z_STRLEN_P(op2)

所以如果字符串长度不匹配,我们立即返回。

这意味着如果字符串的长度相同,就可以完成更多的工作!

如果我们花费多长时间来执行不同的长度,我们会看到类似这样的内容:

长度 时间运行1 时间跑2 时间运行3 平均时间
7 0.01241241 0.01152 0.01191 0.01194
8 0.01151 0.01212 0.01189 0.01184
9 0.01114 0.01251 0.01175 0.01180
10 0.01212 0.01171 0.01120 0.01197
11 0.01210 0.01231 0.01216 0.01219
12 0.01121 0.01211 0.01194 0.01175
13 0.01142 0.01174 0.01251 0.01189
14 0.01251 0.01121 0.01141 0.01171

如果您忽略平均列,您会注意到似乎没有多少模式。这些数字都在彼此的原因之内。

但是,如果您平均进行多次跑步,您就会注意到一种模式。你会注意到长度11需要更长的时间(略),然后是其他长度。

这个例子非常夸张。但它说明了这一点。它已经显示了可以使用约49000(所以49000次尝试,而不是在上述实施例3)的样品大小远程检测的差异在时间缩短到约15纳秒。

但是,我们发现了这个长度。那不会给我们太多的收入......但第二部分呢?那怎么样memcmp(...)?

如果我们看的执行memcmp()::

int memcmp(const void *s1, const void *s2, size_t n){
    unsigned char u1, u2;
    for ( ; n-- ; s1++, s2++) {
        u1 = * (unsigned char *) s1;
        u2 = * (unsigned char *) s2;
        if ( u1 != u2) {
            return (u1-u2);
        }
    }
    return 0;}

等一下!这返回两个字符串之间的第一个区别!

所以一旦我们确定了字符串的长度,我们可以尝试不同的字符串开始检测差异:

axxxxxxxxxxbxxxxxxxxxxcxxxxxxxxxxdxxxxxxxxxx...yxxxxxxxxxxzxxxxxxxxxx

并通过相同的技术,发现与“txxxxxxxxxx”的差异比其他时间略长。

为什么?

让我们看看在memcmp中一步一步发生的事情。

  1. 首先,它查看每个字符串的第一个字符。

    如果第一个字符不同,请立即返回。

  2. 接下来,看看每个字符串的第二个字符。

    如果它们不同,立即返回。

  3. 等等。

因此"axxxxxxxxxx",它只执行第一步(因为我们正在比较的字符串"thisismykey")。但是"txxxxxxxxxx",第一步和第二步相匹配。所以它做更多的工作,因此需要更长的时间。

所以一旦你看到了,你知道t是第一个字符。

那么这只是一个重复这个过程的问题:

taxxxxxxxxxtbxxxxxxxxxtcxxxxxxxxxtdxxxxxxxxx...tyxxxxxxxxxtzxxxxxxxxx

为每个角色做到这一点,你就完成了。你已经成功推断出一个秘密!

防止比较攻击

所以这是一个基本的比较攻击。==并且===在PHP中都容易受到攻击。

有两种基本的防御方法。

首先是手动比较两个字符串,并且总是比较每个字符(这是我以前的博客文章中的函数:

/**
 * A timing safe equals comparison
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */function timingSafeEquals($safe, $user) {
    $safeLen = strlen($safe);
    $userLen = strlen($user);
    if ($userLen != $safeLen) {
        return false;
    }
    $result = 0;
    for ($i = 0; $i < $userLen; $i++) {
        $result |= (ord($safe[$i]) ^ ord($user[$i]));
    }
    // They are only identical strings if $result is exactly 0...
    return $result === 0;}

第二个是使用内置的PHP hash_equals() function。这是在5.6中添加的,与上面的代码做同样的事情。

注:一般情况下,它是不是能够防止长度泄漏。所以可以泄漏这个长度。重要的部分是它不会泄漏关于两个字符串的差异的信息。

其他类型的计时攻击 - 索引查找

那就是比较。这是相当好的覆盖。但是让我们来谈谈索引查找:

如果您有一个数组(或字符串),并且使用秘密信息作为索引(键),则可能会泄漏有关该键的信息。

为了理解为什么,我们需要了解一下CPU如何处理内存。

通常,CPU具有固定宽度的寄存器。把这些想象成小变量。在现代处理器上,这些寄存器可能是64位(8字节)宽。这意味着CPU可以在一次处理的最大变量是8个字节。(注意:这是不正确的,因为大多数处理器都有基于向量的操作,例如SIMD,它允许它与更多的数据交互。对于这个讨论来说,尽管这并不重要)。

那么当你想读一个长度为16字节的字符串时会发生什么呢?

那么,CPU需要加载它块。根据操作的不同,它可能一次加载8个字节的字符串,并且一次对它操作8个字节。或者更常见的是,它一次处理一个字节。

所以这意味着它需要从某处获取字符串的其余部分。这个“某处”是主存(RAM)。但记忆非常缓慢。像真的很慢。大约100ns。这是我们的15纳秒阈值。

而且由于主内存非常慢,所以CPU在CPU本身上只有很少的内存空间来充当缓存。实际上,它们通常有两种类型的缓存。它们具有特定于每个核心(每个核心都有自己的L1高速缓存)的L1高速缓存,也是特定于核心的L2高速缓存,以及经常在单个芯片上的所有核心之间共享的L3高速缓存。为什么3层?由于速度:

内存类型 尺寸 潜伏
L1缓存 32KB 0.5纳秒
L2高速缓存 256KB 2.5 ns
L3缓存 4-16MB 10-20纳秒
内存 地段 60 - 100纳秒

所以我们来看看在string[index]C字符串(char \*字符数组)上做了什么。想象一下你有这样的代码:

char character_at_offset(const char *string, size_t offset) {
    return string[offset]}

编译器会将其编译为:

character_at_offset:
    pushq   %rbp    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -16(%rbp), %rax
    movq    -8(%rbp), %rdx
    addq    %rdx, %rax    movzbl  (%rax), %eax
    popq    %rbp    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

虽然有很多噪音。让我们把它缩小到一个非功能性但更合适的尺寸:

character_at_offset:
    addq    %rdx, %rax    movzbl  (%rax), %eax
    popq    %rbp
    ret

该函数有两个参数,其中一个是指针(字符串的第一个元素),第二个是整数偏移量。它将两者相加以获得我们想要的字符的内存地址。然后,movzbl从该地址移动一个字节并将其存储%eax(其余零也为零)。

那么,CPU如何知道在哪里可以找到那个内存地址呢?

那么,它会遍历高速缓存链,直到找到它。

因此,如果它在L1缓存中,整体操作大约需要0.5 ns。如果它在L2,2.5ns。等等。因此,通过仔细计算信息的时间,我们可以推断出该项目被缓存的位置(或者它是否被缓存)。

值得注意的是,CPU不会缓存单个字节。他们缓存称为线的内存块。现代处理器通常具有64字节宽的高速缓存行。这意味着缓存中的每个条目都是连续的64字节的内存块。

所以,当你进行内存提取时,CPU会将一个64字节的内存块写入缓存行。因此,如果您的movzbl调用需要打到主内存,整个块将被复制到较低的缓存行中。(请注意,这是一个非常简单的事情,但它是为了演示下一步会发生什么)。

现在,这里是真正有趣的地方。

假设我们正在处理一个大字符串。一个不适合二级缓存。所以1MB。

现在,让我们设想一下,我们从基于数字的秘密序列的字符串中提取字节。

通过观察获取字节需要多长时间,我们实际上可以确定关于秘密的信息!

让我们想象我们获取以下偏移量:

  • offset 10

  • offset 1

第一次提取会导致缓存未命中,将从主内存加载到缓存中。

但是第二个fetch(offset 1)将从L1缓存中获取,因为它可能与原来的缓存行(内存块)相同offset 10。所以它很可能是缓存命中。

如果我们然后提取offset 2048,它很可能不在缓存中。

因此,通过仔细观察延迟模式,您可以确定有关偏移序列关系的一些信息。通过多次使用正确的信息来做到这一点,你可以推断出这个秘密。

这被称为缓存时间攻击。

现在看起来真的很牵强,对吧?我的意思是,你有多频繁地获取完美的信息?这怎么可能是实际的。那么,这是100%的实际,并发生在现实世界中。

针对缓存时间攻击的防御:

只有一种防御这种风格攻击的实用方法:

  1. 不要通过秘密索引数组(或字符串)。

这真的很简单。

基于分支的计时攻击

你看过几次类似下面的代码?

$query = "SELECT * FROM users WHERE id = ?";$stmt = $pdo->prepare($query);$stmt->execute([$_POST['id']]);$user = $stmt->fetchObject();if ($user && password_verify($_POST['password'], $user->password)) {
    return true;}return false;

当然,这是安全的?

那里有信息泄漏。

如果您尝试使用不同的用户名,则根据用户名是否存在需要不同的时间。如果password_verify需要0.1秒,您可以简单地测量该差异来确定用户名是否有效。平均而言,对使用用户名的请求将花费比可用用户名更长的时间。

BJXSHOP网上开店专家
BJXSHOP网上开店专家

BJXShop网上购物系统是一个高效、稳定、安全的电子商店销售平台,经过近三年市场的考验,在中国网购系统中属领先水平;完善的订单管理、销售统计系统;网站模版可DIY、亦可导入导出;会员、商品种类和价格均实现无限等级;管理员权限可细分;整合了多种在线支付接口;强有力搜索引擎支持... 程序更新:此版本是伴江行官方商业版程序,已经终止销售,现于免费给大家使用。比其以前的免费版功能增加了:1,整合了论坛

下载

现在,这是一个问题吗?我不知道,这取决于你的要求。许多网站希望保留用户名的秘密,并尽量不公开他们的信息(例如:不说用户名或密码在登录表单中是否无效)。

如果你想保持用户名的秘密,你就不会。

防御基于分支的计时攻击

做到这一点的唯一方法是不分支。但那里有问题。如果你不分支,你如何获得像上面那样的功能?

那么,一个想法是执行以下操作:

$query = "SELECT * FROM users WHERE id = ?";$stmt = $pdo->prepare($query);$stmt->execute([$_POST['id']]);$user = $stmt->fetchObject();if ($user) {
    return password_verify($_POST['password'], $user->password);} else {
    password_verify("", DUMMY_HASH);}return false;

这意味着你password_verify在两种情况下运行。这削减了0.1第二个区别。

但核心计时攻击依然存在。原因在于数据库将返回查询的时间稍微有点不同,以查找找到该用户的查询,以及查找不到的查询。这是因为它在内部执行大量分支和条件逻辑,最终需要通过线路将数据传输回程序。

所以防御这种风格攻击的唯一方法就是不要将您的用户名视为秘密!

关于“随机延迟”的一个注记

许多人在听到定时攻击时,都会想:“呃,我只是随意添加一个延迟!这将工作!“。而事实并非如此。

要理解为什么,让我们来谈谈添加随机延迟时实际发生的情况:

整体执行时间是work + sleep(rand(1, 10))。如果兰德行为良好(这是随机的),那么随着时间的推移,我们可以将其平均。

让我们说这是rand(1, 10)。那么,这意味着当我们平均运行时,平均延迟约为5.相同的平均值加到所有情况下。所以我们需要做的就是每运行一次运行一次以平均噪音。我们运行的次数越多,随机值越倾向于平均。所以我们的信号仍然存在,它只需要稍微更多的数据来对抗噪声。

因此,如果我们需要运行49,000次测试以获得15ns的准确度,那么我们需要大概100,000或1,000,000次测试来获得相同的准确度和随机延迟。或者可能达到100,000,000。但数据仍然存在。

修复漏洞,不要仅仅在它周围增加噪音。

有效的实际延迟

随机延迟不起作用。但我们可以通过两种方式有效地使用延迟。第一个是更有效的,也是我唯一“依靠”的一个。

  1. 延迟取决于用户输入。

    因此,在这种情况下,您可以使用本地密钥对用户输入进行哈希处理,以确定要使用的延迟:

    function delay($input, $secret_key) {
        $hash = crc32(serialize($secret_key . $input . $secret_key));
        // make it take a maximum of 0.1 milliseconds
        time_nanosleep(0, abs($hash % 100000));}

    然后只需将用户输入用于延迟功能。这样,随着用户改变他们的输入,延迟也会改变。但它会以同样的方式改变,使他们无法用统计技术来平均它。

    请注意,我使用过crc32()。这不需要是加密散列函数。由于我们只是派生一个整数,所以我们不需要担心碰撞。如果您希望更安全,您可以用SHA-2功能替换它,但我不确定这是否值得速度损失。

  2. 使操作花费最少时间(夹紧)

    因此,许多人浮现的想法是将操作“夹”到特定的运行时(或者更准确地说,使其至少需要一定的运行时间)。

    function clamp(callable $op, array $args, $time = 100) {
        $start = microtime(true);
        $return = call_user_func_array($op, $args);
        $end = microtime(true);
        // convert float seconds to integer nanoseconds
        $diff = floor((($end - $start) * 1000000000) % 1000000000);
        $sleep = $diff - $time;
        if ($sleep > 0) {
            time_nanosleep(0, $sleep);
        }
        return $return;}

    所以你可以说比较必须花费最少的时间。因此,不要试图比较一直持续的时间,你只需要花时间。

    所以,你可以钳住等于100纳秒(clamp("strcmp", [$secret, $user], 100))。

    这样做,你保护了字符串的第一部分。如果前20个字符花费了100纳秒,那么通过钳位到100纳秒,可以防止那些泄漏的差异。

    但是有一些问题:

  • 它非常脆弱。如果你时间太短,你会失去所有的保护。如果时间过长,可能会在应用程序中增加不必要的延迟(如果不小心,可能会暴露DOS风险)。

  • 它实际上并没有保护任何东西。它只是掩盖了这个问题。我认为这是一种通过默默无闻的安全形式。这并不意味着它没有用或无效。这只是意味着风险。很难知道它是否确实有效地让你更安全,或者让你在夜晚更好地睡觉。当在图层中使用时,它可能是好的。

  • 它不能防止本地攻击者。如果攻击者可以在服务器上获得代码(甚至是未经授权的,在不同的用户帐户上,如共享服务器上),则他们可以查看CPU使用情况,从而可以看到过去的睡眠状况。这是一个延伸,在这种情况下可能会有更有效的攻击,但至少值得注意。

防御DOS攻击

所有这些技术都需要很多请求。它们基于依靠大量数据有效“平均”噪声的统计技术。

这意味着要获得足够的数据来实际执行攻击,攻击者可能需要制造数千,数十万甚至数百万的请求。

如果你正在练习好的DOS保护技术(基于IP的速率限制等),那么你将能够绕过很多这些风格的攻击。

但是DDOS保护难以防范。通过分配流量,防范难度更大。但对攻击者来说也更难,因为他们有更多的噪音需要处理(而不仅仅是本地网段)。所以这并不太实际。

但是就像安全的任何事情一样,纵深防御。即使我们认为这次攻击是不可能的,但如果我们原来的保护失败,仍然值得保护它。深度使用防御,我们可以让自己在各种规模的攻击中更具弹性。

回到点

目前有关PHP内部的一个关于是否使某些核心功能的时序安全与否的线索。正在讨论的具体功能是:

  • bin2hex

  • hex2bin

  • base64_encode

  • base64_decode

  • mcrypt_encrypt

  • mcrypt_decrypt

现在,为什么这些功能?井,bin2hex和base64_encode编码输出到浏览器(编码会话参数例如)当经常使用。然而,更重要的是hex2bin和base64_decode,因为它们可以用于解密秘密信息(就像在将密钥用于加密之前的密钥)。

到目前为止,大多数受访者的共识是,为了获得更多的安全,不值得让它们变得更慢。我同意这一点。

但是,我不同意的是,它会让它们“变慢”。更改比较(从)==到hash_equals较慢是因为它将函数的复杂性(最佳,平均,最差)从O(1, n/2, n)更改为O(n, n, n)。这意味着它将对平均情况下的性能产生重大影响。

但改变编码功能不会影响复杂性。他们将继续O(n)。所以问题是,速度差是多少?那么,我用PHP算法和一个时间安全的标准对bin2hex和hex2bin进行了基准测试,差异不是太显着。编码(bin2hex)大致相同(误差范围),并且解码的差异(hex2bin)大约为0.5μs。对于大约40个字符的字符串,这是5e-10秒多。

对我而言,这足够小,根本不用担心。平均应用程序调用其中一个受影响的函数多少次?也许每一次执行可能?但有什么潜在的好处?这可能是一个漏洞被阻止?

也许吧。我认为没有充足的理由去做这件事,一般而言,这些漏洞在用PHP编写的应用程序类型中将非常困难。但有了这个说法,如果实施过程足够快速(对我来说,0.5μs足够快),那么我认为没有一个重要的理由不去做这个改变。即使它有助于防止所有数百万PHP用户的单一攻击,这是否值得?是。它会阻止单一攻击吗?我不知道(可能不)。

但是,我认为有几项功能必须不断进行时间安全审计:

  • mcrypt_\*

  • hash_\*

  • password_\*

  • openssl_\*

  • md5()

  • sha1()

  • strlen()

  • substr()

基本上,我们所知道的任何东西都会与敏感信息一起使用,或者将在敏感操作中用作原语。

至于字符串函数的其余部分,或者没有必要让它们的时间安全(像lcfirst或strpos),或者它不可能(像trim)或已经完成(像strlen),或者它没有任何业务在PHP(如hebrev)...

跟进

因此,HackerNews和Reddit发布了这篇文章。评论有几个共同的主题,所以我会在这里跟进。我还编辑了帖子内联来解决这些问题。

不可能使代码保持恒定时间

那么,我应该澄清“不变”的含义。在绝对意义上,我并不是指不变的。我的意思是不变的相对于秘密。这意味着时间不会依赖于我们试图保护的数据而改变。因此总体而言,绝对时间可能由于许多原因而波动。但我们不希望我们试图保护的价值影响它。

这是区别:

for ($i = 0; $i < strlen($_GET['input']); $i++) {
    $input .= $_GET['input'][$i];}

这是可变的时间,但泄漏什么是秘密,

$time = 0;for ($i = 0; $i < strlen($_GET['input']); $i++) {
    $time += abs(ord($_GET['input'][$i]) - ord($secret[$i]));}sleep($time);

现在,这是一个荒谬的例子。但它表明,两者都会根据投入改变时间,但也会因我们试图保护的秘密而有所不同。这就是我们说“恒定时间”时的意思,而不是基于秘密的价值而变化。

怎样钳制一个特定的运行时间?

我已经在帖子的主体中解决了上述问题。

不保护DOS的工作?

是。我已经将其添加到防御列表中。但考虑到它对DDOS不起作用(虽然时间差异很难识别),但我不会因为这个原因而忽略它。

这不实用

那么,事实并非如此。有视频和文件和工具以及更多工具和更多论文以及更多视频。

所以如果攻击者一直在谈论这件事情,那肯定是有好处的。

但是,成功利用计时攻击需要很多工作。因此,攻击者通常会寻找更容易和更常见的攻击,例如SQLi,XSS,远程代码执行等,但这实际上取决于更多因素。如果您正在保护博客网站的会话标识符,那么您可能不必担心它。但是,如果您保护用于加密信用卡号码的加密密钥......

从实际的角度来看,我不会担心定时攻击,除非我确信其他潜在的媒介是安全的。就这样说,我认为这很有趣,值得了解。但是像安全和编程中的其他一切一样,这都是关于权衡的。

相关推荐:

对php一些服务器端特性配置,加强php的安全

php安全实例详解

整理了一些关于PHP安全性的知识

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

178

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

35

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

79

2026.01.28

php怎么写接口教程
php怎么写接口教程

本合集涵盖PHP接口开发基础、RESTful API设计、数据交互与安全处理等实用教程,助你快速掌握PHP接口编写技巧。阅读专题下面的文章了解更多详细内容。

2

2026.01.28

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

4

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

8

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

24

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

122

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

72

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 9.9万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.2万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号