SNI阻断技术介绍(网络资料汇总)

SNI来源

早期的 SSLv2 根据经典的公钥基础设施 PKI(Public Key Infrastructure) 设计,它默认认为:一台服务器(或者说一个IP)只会提供一个服务,所以在 SSL 握手时,服务器端可以确信客户端申请的是哪张证书。

但是后来虚拟主机大力发展起来了,这就造成了一个 IP 会对应多个域名的情况。解决办法有一些,例如申请泛域名证书,对所有 *.yourdomain.com 的域名都可以认证,但如果你还有一个 yourdomain.net 的域名,那就不行了。

在 HTTP 协议中,请求的域名作为主机头(Host)放在 HTTP Header 中,所以服务器端知道应该把请求引向哪个域名,但是早期的 SSL 做不到这一点,因为在 SSL 握手的过程中,根本不会有 Host 的信息,所以服务器端通常返回的是配置中的第一个可用证书。因而一些较老的环境,可能会产生多域名分别配好了证书,但返回的始终是同一个。

既然问题的原因是在 SSL 握手时缺少主机头信息,那么补上就是了。

SNI(Server Name Indication,意为“服务器名称指示”) 定义在 RFC 4366,是一项用于改善 SSL/TLS 的技术,在 SSLv3/TLSv1 中被启用。它允许客户端在发起 SSL 握手请求时(具体说来,是客户端发出 SSL 请求中的 ClientHello 阶段),就提交请求的 Host 信息,使得服务器能够切换到正确的域并返回相应的证书。

要使用 SNI,需要客户端和服务器端同时满足条件,幸好对于现代浏览器来说,大部分都支持 SSLv3/TLSv1,所以都可以享受 SNI 带来的便利。

什么是 SNI? SNI 识别?

什么是 SNI

Server Name Indication (SNI) 是 TLS 协议(以前称为 SSL 协议)的扩展,该协议在 HTTPS 中使用。它包含在 TLS/SSL 握手流程中,以确保客户端设备能够看到他们尝试访问的网站的正确 SSL 证书。该扩展使得可以在 TLS 握手期间指定网站的主机名或域名 ,而不是在握手之后打开 HTTP 连接时指定。

Server Name Indication(SNI)是一种TLS扩展,用于在TLS握手过程中传递服务器的域名信息。在未使用SNI之前,客户端在建立TLS连接时只能发送单个IP地址,并且服务器无法知道客户端请求的具体域名。这导致服务器需要使用默认证书进行握手,无法正确选择合适的证书。

使用SNI扩展后,客户端在发送ClientHello消息时会包含所请求的服务器的域名。服务器根据该域名来选择对应的证书进行握手,从而实现了多个域名共享同一个IP地址并使用不同证书的能力。

SNI对于虚拟主机或者CDN等场景特别有用,因为这些场景下,多个网站可能共享同一个IP地址。通过使用SNI,服务器能够正确地选择与域名匹配的证书,提高了安全性和灵活性。

SNI产生解决什么问题

Server Name Indication(SNI)的产生背景与互联网发展和IPv4地址短缺有关。

在互联网发展初期,每个网站通常拥有自己独立的IP地址。但随着网站数量的迅速增长和IPv4地址资源的有限,IP地址短缺问题变得日益严重。

为了解决IPv4地址短缺问题,一种解决方案是引入虚拟主机技术。虚拟主机是指在同一台物理服务器上托管多个不同域名的网站。通过共享同一个IP地址,多个网站能够减少IP地址的使用,并降低运维成本。

然而,这种虚拟主机技术在使用TLS(Transport Layer Security)协议时遇到了问题。TLS协议是用于加密和保护网络通信的协议,它在握手过程中需要使用正确的证书来验证服务器身份。但在使用虚拟主机时,服务器无法根据传统的握手方式正确选择合适的证书,因为无法获知客户端请求的具体域名。

为了解决这个问题,SNI扩展被引入TLS协议中。它允许客户端在握手时发送所请求的服务器域名信息,使服务器能够正确选择并使用对应的证书,实现了多个域名共享同一个IP地址并使用不同证书的能力。SNI的引入为虚拟主机和CDN等场景提供了更好的支持,进一步促进了IPv4地址资源的有效利用。

SNI 的技术原理

SNI 通过让客户端发送虚拟域的名称作为 TLS 协商的 ClientHello 消息的一部分来解决此问题。这使服务器可以及早选择正确的虚拟域,并向浏览器提供包含正确名称的证书。

这样要说一下,服务器名称指示(SNI)有效负载未加密,因此客户端尝试连接的服务器的主机名对于被动的窃听者是可见的。

TLS 的 SNI 扩展有什么作用?

下面,我们就开始对 TLS 协议的 SNI 进行解析。

1、Web 服务器通常负责多个主机名–或域名。如果网站使用 HTTPS 则每个主机名将具有其自己的 SSL 证书。

2、在 HTTPS 中,先有 TLS 握手,然后才能开始 HTTP 对话。如果没有 SNI,客户端将无法向服务器指示正在与之通信的主机名。

3、如果服务器可能为错误的主机名生成 SSL 证书。那么 SSL 证书上的名称与客户端尝试访问的名称不匹配,则客户端浏览器将返回错误信息,并通常会终止连接。

4、通过 SNI,拥有多虚拟机主机和多域名的服务器就可以正常建立 TLS 连接了。

TLS 协议的 SNI 识别

这里给出的只是一部分代码实现。

static bool is_sslv3_or_tls(u_char *tls_data,int PayloadLen,int offset)

{

    uint8_t   content_type = 0;

    uint16_t  protocol_version = 0, record_length = 0;

    SslSession        *session;

    if (PayloadLen < 5) {

        return false;

    }

    content_type = tls_data[offset];

    offset += 1;

    protocol_version = ntohs(*(uint16_t*)(tls_data + offset));

    offset += 2;

    record_length = ntohs(*(uint16_t*)(tls_data + offset));

    //printf("record_length: %d\n",record_length);

    /* These are the common types. */

    if (content_type != SSL_ID_HANDSHAKE) {

        return false;

    }

    if (protocol_version != SSLV3_VERSION &&

        protocol_version != TLSV1_VERSION &&

        protocol_version != TLSV1DOT1_VERSION &&

        protocol_version != TLSV1DOT2_VERSION) {

        return false;

    }

    if (record_length == 0 || record_length >= TLS_MAX_RECORD_LENGTH + 2048) {

        return false;

    }

    return true;

}

static void dissect_ssl2_hnd_client_hello(u_char *tls_data,int PayloadLen, int offset)

{

    uint32_t offset_end = 0;

    uint16_t ext_type = 0;

    uint32_t ext_len = 0;

    uint32_t next_offset = 0;

    int    msg_length = 0;

    int    remaining_length = 0;

    uint32_t server_name_length = 0;

    uint16_t length = 0;

    uint16_t version = 0;

    int session_id_len = 0;

    uint32_t extensions_length = 0;

    uint8_t random[32];

    char s_name[256] = {0};

    int i = 0;  

    uint32_t     cipher_suite_length = 0;

    offset += 3;/*Length*/

    version = ntohs(*(uint16_t*)(tls_data + offset));

    printf("Version: 0x%.2X\n",version);

    offset += 2; /*Version*/

    if (offset > PayloadLen)

        return;

    memcpy(random,tls_data + offset,32);

    for (i = 0; i < 32;i++)

        printf("%x",random[i]);

    printf("\n");

    offset += 32; /*random*/

    session_id_len = tls_data[offset];

    printf("session_id_len: %d\n",session_id_len);

    offset += 1; /*Session ID Length*/

    if (session_id_len > PayloadLen)

        return;

    uint8_t session_id[session_id_len];

    memcpy(session_id,tls_data + offset,session_id_len);

    for (i = 0; i < session_id_len;i++)

        printf("%x",session_id[i]);

    printf("\n");

    offset += session_id_len; /*Session ID */

    cipher_suite_length = ntohs(*(uint16_t*)(tls_data + offset));   

    printf("cipher_suite_length: %d\n",cipher_suite_length);

    offset += 2;/*cipher_suite_length*/

    offset += cipher_suite_length; /*Session ID */

    offset += 1;/*compression_method length*/

    offset += 1;/*compression_method */

    extensions_length = ntohs(*(uint16_t*)(tls_data + offset));

    printf("Extensions Length: %d\n",extensions_length);

    printf("Extensions Length: 0x%.2X\n",extensions_length);

    offset += 2;

    //offset_end = PayloadLen - offset;

    //printf("offset_end: %d\n",offset_end);

    msg_length = PayloadLen;

    while (offset < msg_length)

    {

        /* Get the type and length */

        remaining_length = msg_length - offset;

        if (remaining_length < 3) {

            printf("Not enough data left for IE and length, %i bytes\n", remaining_length);

            return;

        }

        ext_type = ntohs(*(uint16_t*)(tls_data + offset));

        //printf("ext_type: 0x%.2X\n",ext_type);

        offset += 2;

        ext_len  = ntohs(*(uint16_t*)(tls_data + offset));

        //printf("ext_len: %d\n",ext_len);

        offset += 2;

        switch (ext_type)

        {

            case SSL_HND_HELLO_EXT_SERVER_NAME:

                 printf("==========SERVER_NAME======\n");

                 offset += 2; //Server Name list length

                 offset += 1; //Server Name Type

                 server_name_length = ntohs(*(uint16_t*)(tls_data + offset));

                 printf("server_name_length: %d\n",server_name_length);

                 offset += 2; //Server Name length

                 memset(s_name,0,256);

                 memcpy(s_name,tls_data + offset,server_name_length);

                 printf("Server Name: %s\n",s_name);

            break;

            default:

            break;

        }

       offset += ext_len;

    }

}

void dissect_tls(u_char *tls_data,int PayloadLen,int offset)

{

    SslSession   *session;

    uint16_t version = 0;

    uint16_t length = 0;

    uint8_t  msg_type = 0;

    printf("0x%.2X,0x%.2X,0x%.2X 0x%.2X,0x%.2X,0x%.2X \n",tls_data[offset],tls_data[offset+1],tls_data[offset+2],tls_data[offset+3],tls_data[offset+4],tls_data[offset+5]);

    if (!is_sslv3_or_tls(tls_data,PayloadLen,offset)) {

        return;

    }

    printf("====tls=========\n");

    offset += 1; /*Content Type*/

    version = ntohs(*(uint16_t*)(tls_data + offset));   

    printf("Version: 0x%.2X\n",version);    

    offset += 2; /*Version*/

    length = ntohs(*(uint16_t*)(tls_data + offset));

    printf("Length: %d\n",length);

    offset += 2; /*length*/

    /* fetch the msg_type */

    msg_type = tls_data[offset];

    offset += 1; /*handshake type*/

    printf("handshake type: 0x%.2X\n",msg_type);    

    switch (version)

    {

        case SSLV3_VERSION:

        case TLSV1_VERSION:

        case TLSV1DOT1_VERSION:

        case TLSV1DOT2_VERSION:

            if (PayloadLen < 5)

            {

                break;

            }

            ssl_looks_like_valid_v2_handshake(tls_data,PayloadLen,msg_type,offset);

        break;

        default:

        if (PayloadLen < 5)

        {

            break;

        }

    }

}

编译运行:

在客户端到服务端方向的 ClientHello 包中,提取 SNI 扩展字段中的 SNI_NAME 字段,通过数据包偏移量进行逐步匹配得到 SNI_NAME。对 SNI_NAME 进行规则匹配。

总结

这篇文章只是对 SSL/TLS 做一些简单的介绍,想通过更多认识,可以阅读官方的 RFC 文档,对于 SNI(Server Name Indication)是 TLS 的扩展,它主要是用来解决一个服务器拥有多个域名的情况。通过 SNI,拥有多虚拟机主机和多域名的服务器就可以正常建立 TLS 连接了。

nginx / apache 服务器端实现

nginx 同一个IP上配置多个HTTPS主机

如果在同一个IP上配置多个HTTPS主机遇到的问题:

server {

listen          443;

server_name     www.example.com;

ssl             on;

ssl_certificate www.example.com.crt;

...

}

server {

listen          443;

server_name     www.example.org;

ssl             on;

ssl_certificate www.example.org.crt;

...

}

使用上面的配置,不论浏览器请求哪个主机,都只会收到默认主机www.example.com的证书。这是由SSL协议本身的行为引起的——先建立SSL连接,再发送HTTP请求,所以nginx建立SSL连接时不知道所请求主机的名字,因此,它只会返回默认主机的证书。

最古老的也是最稳定的解决方法就是每个HTTPS主机使用不同的IP地址:

server {

listen          192.168.1.1:443;

server_name     www.example.com;

ssl             on;

ssl_certificate www.example.com.crt;

...

}

server {

listen          192.168.1.2:443;

server_name     www.example.org;

ssl             on;

ssl_certificate www.example.org.crt;

...

}

那么,在同一个IP上,如何配置多个HTTPS主机呢?

nginx支持TLS协议的SNI扩展(Server Name Indication,简单地说这个扩展使得在同一个IP上可以以不同的证书serv不同的域名)。不过,SNI扩展还必须有客户端的支持,另外本地的OpenSSL必须支持它。

如果启用了SSL支持,nginx便会自动识别OpenSSL并启用SNI。是否启用SNI支持,是在编译时由当时的 ssl.h 决定的(SSL_CTRL_SET_TLSEXT_HOSTNAME),如果编译时使用的OpenSSL库支持SNI,则目标系统的OpenSSL库只要支持它就可以正常使用SNI了。

nginx在默认情况下是TLS SNI support disabled。

启用方法:

需要重新编译nginx并启用TLS。步骤如下:

# wget http://www.openssl.org/source/openssl-1.0.1e.tar.gz

# tar zxvf openssl-1.0.1e.tar.gz

# ./configure --prefix=/usr/local/nginx --with-http_ssl_module \

--with-openssl=./openssl-1.0.1e \

--with-openssl-opt="enable-tlsext"

# make

# make install

查看是否启用:

# /usr/local/nginx/sbin/nginx -V

TLS SNI support enabled

这样就可以在 同一个IP上配置多个HTTPS主机了。

实例如下:

server  {

listen 443;

server_name   www.ttlsa.com;

index index.html index.htm index.php;

root  /data/wwwroot/www.ttlsa.com/webroot;

ssl on;

ssl_certificate"/usr/local/nginx/conf/ssl/www.ttlsa.com.public.cer";

ssl_certificate_key"/usr/local/nginx/conf/ssl/www.ttlsa.com.private.key";  

......

}      

server  {

listen 443;

server_name   www.heytool.com;

index index.html index.htm index.php;

root  /data/wwwroot/www.heytool.com/webroot;

ssl on;

ssl_certificate"/usr/local/nginx/conf/ssl/www.heytool.com.public.cer";

ssl_certificate_key"/usr/local/nginx/conf/ssl/www.heytool.com.private.key";  

......

}

这样访问每个虚拟主机都正常。

apache mod_gnutls实现多HTTPS虚拟主机

这里介绍在apache的环境下该如何配置多HTTPS虚拟主机。

利用的原理的都是同一个,也就是SNI。基于域名的虚拟主机,即共享同一个IP地址和端口的HTTPS虚拟主机。 SNI---服务器名称指示,是一个TLS的扩展,它使得启用SSL的基于域名的虚拟主机的配置成为可能。打破了每个HTTPS的虚拟主机需要一个IP地址的要求。因此,成本大大降低,因为所有的HTTPS虚拟主机可以共享相同的IP地址和端口,使HTTPS Web服务的更简单。 在apache环境下,需要使用mod_gnutls来实现同一个IP上配置多个HTTPS主机。下面来看看实现过程: mod_gnutls的网址参见:https://mod.gnutls.org 1. 安装mod_gnutls

# yum install httpd-devel gnutls-devel

# wget http://www.outoforder.cc/downloads/mod_gnutls/mod_gnutls-0.2.0.tar.bz2

# tar -xjvf mod_gnutls-0.2.0.tar.bz2

# cd mod_gnutls-0.2.0

# ./configure --prefix=/usr

# make

如果要安装高版本的gnutls的话,需要先安装相对应的依赖包libnettle gmplib。下载地址:http://www.gnutls.org/download.html  ftp://ftp.gnutls.org/gcrypt/gnutls 2.  apache加载mod_gnutls模块

# cp mod_gnutls-0.2.0/src/.libs/libmod_gnutls.so /usr/lib/httpd/modules/mod_gnutls.so

# cp mod_gnutls-0.2.0/data/{dh,rsa}file /etc/httpd/conf/

mod_gnutls模块依赖dhfile和rsafile文件. 3. 配置httpd.conf

Listen 10.1.1.22:443

LoadModule gnutls_module modules/mod_gnutls.so

AddType application/x-x509-ca-cert .crt

AddType application/x-pkcs7-crl    .crl

GnuTLSCache dbm "/var/cache/mod_gnutls_cache"

GnuTLSCacheTimeout 300

NameVirtualHost 10.1.1.22:443

创建回话缓存目录

# mkdir -m 0700 /var/cache/mod_gnutls_cache

# chown nobody.nobody /var/cache/mod_gnutls_cache

4. 配置虚拟主机

<VirtualHost 10.1.1.22:443>

    ServerName www.ttlsa.com:443

    GnuTLSEnable on

    GnuTLSCertificateFile ./ssl/www.ttlsa.com.public.cer

    GnuTLSKeyFile ./ssl/www.ttlsa.com.private.key

    DocumentRoot "/data/wwwroot/www.ttlsa.com/webroot"

</VirtualHost>

<VirtualHost 10.1.1.22:443>

    ServerName www.heytool.com:443

    GnuTLSEnable on

    GnuTLSCertificateFile ./ssl/www.heytool.com.public.cer

    GnuTLSKeyFile ./ssl/www.heytool.com.private.key

    DocumentRoot "/data/wwwroot/www.heytool.com/webroot"

</VirtualHost>

支持SNI的浏览器

Internet Explorer 7 及更高版本(Windows Vista 及更高版本操作系统上的),Windows XP 的 Internet Explorer 总不支持,哪怕是 Internet Explorer 8。

Mozilla Firefox 2.0 及更高版本

Opera 8.0 及更高版本 (必须开启 TLS 1.1 协议)

Opera Mobile 至少是 10.1 beta 的 Android 版本

Google Chrome (Vista 或更高版本;XP 上的话要求 Chrome 6 及更高版本;OS X 10.5.7 及更高版本要求 Chrome 5.0.342.1 及更高版本)

Safari 2.1 及更高版本 (Mac OS X 10.5.6 及更高版本或 Windows Vista 及更高版本)

Konqueror/KDE 4.7 及更高版本

MobileSafari (在 Apple iOS 4.0 及更高版本的环境下的)

Android 默认浏览器 (在 Honeycomb 及更高版本的)

Windows Phone 7

MicroB (在 Maemo 下的)

支持SNI的服务器

Apache 2.2.12 及更高版本 使用 mod_ssl (或用试验性的 mod_gnutls 代替)

Cherokee 如果编译了 TLS 支持

打了补丁的 lighttpd 1.4.x 或 1.5.x ,1.4.24+ 没打补丁就行

Nginx 在以 SNI 为支持的 OpenSSL 的陪同下

LiteSpeed 4.1 及更高版本

Pound 2.6 及更高版本

Apache Tomcat (Java 7) 及更高版本

Microsoft Internet Information Server (IIS) 8

支持SNI的库

Mozilla NSS 3.11.1 仅在客户端

OpenSSL

0.9.8f (2007年10月11日发布) - 不缺省编译,可用设置选项 ‘–enable-tlsext’ 编译

0.9.8j (2009年1月7日发布) 至 1.0.0 (2010年3月29日发布) – 缺省编译

GNU TLS

libcurl / cURL 至少 7.18.1 (2008年3月30日) 在 SNI 支持下编译一个 SSL/TLS 工具包

Python 3.2 (ssl, urllib 和 httplib 模块)

Qt 4.8

Oracle Java 7 JSSE

在共享 IP 主机上添加 SSL 支持

按照一般步骤安装 SSL 证书【生成证书请求文件(顺便生成私钥)→到 SSL 服务商生成证书→合并证书扔进去】,然后修改下网站资源链接即可。只要主机面板(如 cPanel)有相关选项,就应该没有内部技术问题(少数时候服务器不支持,此时应该联系主机商)。

cPanel 下“SSL/TLS 管理器”中的“管理 SSL 主机”会提示道:

注意:您没有一个独立 IP。因此,当用户访问您任何一个 SSL 网站时,不支持 SNI 的浏览器很可能会向用户发出安全警告。Windows XP 上的 Microsoft Internet Explorer 是最广泛使用而不支持 SNI 的浏览器。

经实际测试,虚拟主机共享 IP 上首个安装 SSL 证书的人看起来有特权——整个 IP 都“安装上”那个证书了。可以推得也可以观察到,不支持 SNI 的浏览器在需要 SSL 证书时也将请求到这首个安装的证书。这对有“特权”的那个站(即第一个安装 SSL 的站点)是好的,因为这非常好地兼容了不支持 SNI 的浏览器,使其总是不会发出警告,但主机上邻居们的 https 访问就悲催了。如果邻站安装了 SSL 证书,一个使用不支持 SNI 技术的浏览器去访问 https 站的访客将收到证书上域名不匹配的警告,即便忽略警告后访问的仍是目标站,但因证书不是预期的目标站的证书,此访客也存有中招中间人攻击的风险;更令人不安的是,若邻站没有安装 SSL 证书,使用不论支持 SNI 与否的浏览器去访问 https 站的访客将收到相同警告,忽略后会访问到有“特权”的那个站而非目标站(至少在 cPanel 主机如此),而此时“特权站长”除了能够发起中间人攻击外,更是可以十分方便省事地制作一个特别的网站,可以是各种跳转页,可以是恶作剧网站,更可以是……钓鱼网站之类的非法网站。

SNI阻断技术原理

基本原理

SNl(Server Name Indication)技术是一种能够根据网站域名,让服务器和客户端快速确定并切换SSL/TLS协议传输的技术。SNI原理是客户端在发出SSL/TLS握手之前,将期望访问的网站名称发送给服务器,服务器根据这个名称进行会话建立,以确定使用怎么的证书以及密钥,从而支持多个网站使用同一台服务器,而不会出现证书校验问题。

封锁原理

SNI封锁原理是,在客户端的通信过程中,如果检测到客户端发送的请求中含有被封锁域名,那么就会拦截这个请求,而把连接断开,从而实现SNI域名的封锁效果。

SNI封锁是通过防火墙设备实现,防火墙配置规则,当客户端发出请求,并保存在服务器上的请求的时候,防火墙会检测是否含有被封域名,发现含有被封域名将不会将请求透传到服务器,而是直接将连接断开,从而实现阻止访问的目的。

启用 TCP Timestamps 解决 SNI 阻断

1原因

前段时间研究泉州白名单的时候,查资料看到不少人报告河南全省也有出现“白名单”,进一步调查发现河南并不是白名单,而是比通用 GFW 黑名单更严格的省 GFW 黑名单,使用的是经典的 SNI 阻断。

经过测试,河南的 SNI 阻断似乎是两道筛:第一道直接过滤 TLD,.al这种国别域名、.xyz和.top这种被滥用严重的域名会直接阻断;第二道类似黑名单,即使是.com、.net等能通过第一道检测的域名,如果不能通过审查也会被阻断,这种审查可能是流量检测,是否符合某些协议的特征;也有可能是网站内容审查,判断是否出现敏感词。

2、解决方法

根据 GitHub 上的讨论1,启用 TCP Timestamps 可以绕过这种阻断。目前来看原理并不是这种方法不会被 GFW 检测到,而应该是 GFW 直接对启用了 Timestamps 的 Client Hello 直接Bypass(这么说似乎就是暂时对这种包没办法,笑)。

启用 TCP Timestamps:

1.1、Windows

在 PowerShell 中执行:

netsh interface tcp set global timestamps=enabled

检测是否开启:

netsh interface tcp show global

如第一行的值为enabled则开启成功。

1.2、Linux

执行:

sysctl -w net.ipv4.tcp_timestamps=1

虽然是指令是ipv4,但是对 IPv4、IPv6 网络同时生效。

1.3、Android

如果是安卓 13 或以上,系统已经默认开启。

在 Termux(已 Root)/ ADB Shell(未 Root)中执行:

sysctl net.ipv4.tcp_timestamps

检测是否开启,如输出值等于0说明未开启。

执行:

cat /proc/sys/net/ipv4/tcp_timestamps 1

sysctl -w net.ipv4.tcp_timestamps=1

即可启用。

3其他问题

这种方法并不是万能的,概括 GitHub 上相关讨论供参考:

TCP Timestamps 选项无法绕过位于北上广 GFW 的 SNI 阻断;

TCP Fast Open可以绕过位于北上广的 GFW 的 SNI 阻断(仅测试了广州出口的IPv4和IPv6),但是 Xray 的 TCP Fast Open 仅对 Linux 有效,在 Windows 启用无效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值