网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 其他技术区 > 网络安全 >

全版本Shiro反序列化漏洞原理详解

时间:2025-07-15 06:05

人气:

作者:admin

标签:

导读:前言 本文为是作者耗时两个通宵写出的Shiro反序列化漏洞原理详解,主要涉及Shiro认证的一些基础概念以及为什么能够这样进行攻击。文章削弱了具体代码以及反序列化部分,主要强调很...

本文为是作者耗时两个通宵写出的Shiro反序列化漏洞原理详解,主要涉及Shiro认证的一些基础概念以及为什么能够这样进行攻击。文章削弱了具体代码以及反序列化部分,主要强调很多新手宝宝看不懂(面试常考)的漏洞版本划分即利用区别等内容。因为反序列化部分其实是一个攻击大类,在利用中也只是把对应攻击链Poc进行加密,Shiro反序列化的根本我认为还是在如何能让服务器开始反序列化这一步,即伪造明文这一步。反序列化相关的Sink、找链什么的其实专门去看Java反序列化漏洞比本文好。

用户登录时如果点击“记住密码”,会发送请求包类似下面:

POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=123456&rememberMe=true          //大部分网站rememberMe都在请求体

登陆成功:响应包的响应头会有 Set-Cookie: rememberMe=值 ,里面保存加密的用户登录信息

HTTP/1.1 302 Found
Location: /home
Set-Cookie: rememberMe=YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=; Path=/; HttpOnly
Set-Cookie: JSESSIONID=xxx; Path=/; HttpOnly

     之后的会话中请求头会携带该加密的登录信息的Cookie

GET /home HTTP/1.1
Host: example.com
Cookie: rememberMe=YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=; JSESSIONID=xxx

登陆失败:

HTTP/1.1 302 Found
Location: /home
Set-Cookie: rememberMe=deleteMe; Path=/; HttpOnly
Set-Cookie: JSESSIONID=xxx; Path=/; HttpOnly

rememberMe值的可能情况:

情况 典型值示例 出现场景
rememberMe=登录信息 rememberMe=YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo= 用户登录成功并勾选“记住我”
deleteMe rememberMe=deleteMe 登录失败、Cookie 无效(密钥错误、填充校验失败)、注销、主动清除
空值 rememberMe= 某些框架或浏览器的特殊处理
无效值 rememberMe=invalid 或乱码 攻击者伪造失败、Cookie 损坏
无此 Cookie (响应头中不出现 rememberMe 用户未勾选“记住我”,或者解出的明文不是用户信息

rememberMe登录信息格式

该值为Base64编码值,解开后的格式与版本有关

情况 Shiro 版本 IV 类型 密钥类型 Cookie 结构
固定IV + Ciphertext <=1.2.4 固定(全 0) 固定 IV + Ciphertext
随机IV + Ciphertext 1.2.5-1.4.1 随机 随机 IV + Ciphertext
随机IV + Ciphertext + Tag >=1.4.2 随机 随机 IV + Ciphertext + Tag

反序列化点

当服务器Base64解开Cookie的rememberMe值,将利用Cookie里的IV服务器内存中Key(启动网站进程生成)解密Ciphertext后,会得到用户登录信息的序列化值,为了读取里面的登录信息具体值,服务器会进行反序列化。
如果攻击者可以自定义序列化值,让服务器最终能反序列化它,就能触发反序列化攻击
注意:因为客户端Cookie正常是由服务器的Set-Cookie发来的,所以正确的用户Cookie里的IV就是服务器内存的IV(这里是为后续爆破密钥储备知识点)

加密方式

Shiro<=1.2.4

登录信息用AES-128-CBC(分组链接)加密
固定IV(16个0x00)作为初始向量,固定AES密钥(kPH+bIxk5D2deZiIxcaaaA==)。
Cookie格式:Cookie:rememberMe= base64_enc(IV + Ciphertext)
原理:

  • 密钥Key:128 位(16 字节)
  • 初始向量IV:128位(16 字节)
  • 加密模式:CBC(Cipher Block Chaining)
  • 填充方式:PKCS5Padding
项目 长度(位) 是否固定 说明
分组大小 128 ✅ 固定 AES 标准定义,永远 16 字节
密钥长度 128/192/256 ❌ 可变 分别对应 AES-128、AES-192、AES-256
IV 长度 128 ✅ 固定 与分组长度一致,永远是 16 字节

补充:Shiro支持长度分为128、196、256三种,考虑计算量效率等问题默认为128

PKCS5Padding填充

以16字节为一个分组为例,不足部分差几个字节,就全部填充为该数字16进制
例如最后一个分组块user,4个字节,那么后面全部填充12的16进制0x0c,即120x0c
最终结果为:75 73 65 72 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C
Pasted image 20250713063611

加密过程

1、将明文按16字节分组,最后一组不足16字节按PKCS5Padding规则填充
2、提取Cookie中的初始向量,将初始向量(正常用户为16个0x00)与第一块明文进行异或得到一个值我们称为中间值
3、将中间值利用内存中的密钥(该版本为kPH+bIxk5D2deZiIxcaaaA==)进行AES加密,得到密文1
4、将密文1明文2进行异或,得到中间值2
5、将中间值2进行AES加密,得到密文2
6、重复
Pasted image 20250715040652

1.2.4 < Shiro < 1.4.2

登录信息用AES-128-CBC(分组链接)加密,随机IV作为初始向量,随机AES密钥
Cookie格式:Cookie:rememberMe= base64_enc(IV + Ciphertext)

注意:这里的随机是指程序启动随机生成一次存储在内存中,多用户时值都一样。

加密过程

加密过程与Shiro<=1.2.4相同,只是初始向量密钥随机值
1、将明文按16字节分组,最后一组不足16字节按PKCS5Padding规则填充
2、提取Cookie中的初始向量,将初始向量(该版本为随机值)与第一块明文进行异或得到一个值我们称为中间值
3、将中间值利用内存中的密钥(正常用户为服务器生成的固定随机值)进行AES加密,得到密文1
4、将密文1明文2进行异或,得到中间值2
5、将中间值2进行AES加密,得到密文2
6、重复
Pasted image 20250715040431

Shiro >=1.4.2

登录信息用AES-128-GCM加密,随机IV作为初始向量,随机AES密钥。
Cookie格式:Cookie:rememberMe= base64_enc(IV + Ciphertext+Tag)
注意:Shiro官方将AES-GCM作为加密算法名称,但为兼容历史系统未遵循NIST的GCM标准。

标准GCM:

  • IV : 12字节随机值
  • key: 16字节随机值
  • 计数器块 :IV (12B) + 0x00000001 (4B) (0x00000001开始每次自增+1)
  • 密钥流:由计数器块生成的一串密钥,保证每次加密的使用的密钥都不同
    (作用:保证密钥流唯一性、防止重放攻击)
  • Tag :16 字节计算值 (AES加密 + 伽罗瓦域乘法)
    (作用:数据完整性和来源认证)
生成密文
  1. 密钥流生成
    • 初始化计数器:CTR₀ = IV + 0x00000001(16字节)
    • 生成密钥流块:
        KS₁ = AES_Encrypt(Key, CTR₀)
        KS₂ = AES_Encrypt(Key, CTR₀ + 1)
        KS₃ = AES_Encrypt(Key, CTR₀ + 2)
  1. 加密过程
		C₁ = P₁ ⊕ KS₁        // 明文与密钥流异或
		C₂ = P₂ ⊕ KS₂
		C₃ = P₃ ⊕ KS₃
  1. 最终输出
		密文 = IV + C₁ + C₂ + ... + Cₙ
生成Tag(简化表示)

纯密码学计算,比较复杂,以下简写

  • key: 加密密钥
  • iv: 初始化向量(12字节)
  • aad: 附加认证数据(在Shiro中通常为空)
  • ciphertext: 生成的密文
tag = generate_gcm_tag(key, iv, aad, ciphertext)            # 生成16字节认证标签
最终输出
        输出 = IV(12字节) + C₁ + C₂ + ... + Cₙ + Tag

Shiro_GCM:

  • IV : 16字节随机值          (不同处:为了兼容历史版本-CBC模式)
  • key: 16字节随机值
  • 密钥流:由JDK内部方法生成的一串密钥,保证每次加密的使用的密钥都不同
    (作用:保证密钥流唯一性、防止重放攻击)
  • Tag :16 字节计算值 (AES加密 + 伽罗瓦域乘法)
    (作用:数据完整性和来源认证)
生成密文
  1. 密钥流生成
    • 初始化计数器:J₀ = GHASH(IV)(16字节,JDK内部转换)
    • 生成密钥流块:
        KS₁ = AES_Encrypt(Key, J₀)
        KS₂ = AES_Encrypt(Key, J₀ + 1)
        KS₃ = AES_Encrypt(Key, J₀ + 2)
  1. 加密过程(与标准GCM相同)
    注意: 若明文不足 16 字节,不会填充 0,而是直接截取密钥流的对应长度进行异或。
        C₁ = P₁ ⊕ KS₁             例:8 字节明文 `P` → 仅取 `KS[0:8]` 异或生成 8 字节密文
        C₂ = P₂ ⊕ KS₂
        C₃ = P₃ ⊕ KS₃
生成Tag(简化表示)

纯密码学计算,比较复杂,以下简写

  • key: 加密密钥
  • iv: 初始化向量(12字节)
  • aad: 附加认证数据(在Shiro中通常为空)
  • ciphertext: 生成的密文
    tag = generate_gcm_tag(key, iv, aad, ciphertext)             // 注意iv是16字节
最终输出
    输出 = IV(16字节) + C₁ + C₂ + ... + Cₙ + Tag

标准GCM vs Shiro_GCM对比

特性 标准GCM Shiro实现
IV长度 12字节 (96位) 16字节 (128位)
IV结构 8字节固定 + 4字节计数器 完全随机
计数器管理 会话内严格递增 无状态
初始计数器(J₀) IV + 0x00000001 GHASH(IV)计算
填充 无填充,最后块可不足 16 字节 无填充,最后块可不足 16 字节
重放防护 强制计数器验证
输出结构 12B IV + 密文 + 16B Tag 16B IV + 密文 + 16B Tag
性能 高效(直接计数器) 较低(GHASH计算)

Shiro_GCM缺陷

  • 无限次执行同一恶意操作(如反复创建管理员账户)
  • 绕过单次执行防护机制
  • 触发分布式拒绝服务(重复执行高消耗操作)

版本:Shiro <=1.2.4
条件:1、硬编码密钥
原理:

  • Shiro 默认使用硬编码的 AES 密钥kPH+bIxk5D2deZiIxcaaaA==)和IV(16个0x00)来加密 rememberMe Cookie。
  • 攻击者可以直接使用这个默认密钥和任意IV,根据AES-128-CBC加密方式构造恶意的序列化数据,加密后发送给服务器。
  • 服务器利用Cookie里的任意IV服务器内存中Key解密后触发反序列化,导致远程代码执行(RCE)
    Pasted image 20250713083256

注意:这里是任意IV,虽然服务器用的IV(16个0x00)来加密,但服务器解密是提取的请求包Cookie中的IV,而我们不是破解原Cookie,所以随便输入一个IV都可以,只是反序列化后因为不是合法用户信息,服务器不会走判断账号逻辑,而是返回空


版本:Shiro <=1.4.1
条件:1、需要一个正确的rememberMe值(无需知道密钥)

缺陷:可以伪造任意长度(shiro上限)明文,但爆破时间会增加n

原理:
Padding Oracle Attack:利用服务器对填充校验结果的返回值不同,爆破出最后一组密文块的中间值,从而直接伪造出明文。该方式算是对AES-128-CBC加密算法的明文伪造(非破解)

Shiro的服务器验证会校验两个东西:①账号密码是否正确②填充是否正确
  明文分块 = 明文1+明文2+明文3
  密文分块 = 密文1+密文2+密文3
Pasted image 20250713045327
那么:
  密文1 = AES_enc(明文1 ^ IV)
  密文2 = AES_enc(明文2 ^ 密文1)
  密文3 = AES_enc(明文3 ^ 密文2)
Pasted image 20250713044926

 因为异或等式是任意两个相互异或都能得到第三个
   AES_dec(密文3) = 密文2 ^ 明文3

 解密时,我们把AES_dec(密文3) 称为密文3中间值

image

阶段一:得到中间值

① 在服务器视角:
 解密流程:
   用密文3解出的中间值3密文2异或,得到明文3,然后校验明文3填充是否正确
 那么:
   明文3 = 密文2 异或 中间值3
 而异或一位对应一位的操作,可以得到:
   明文3[16] = 密文2[16] 异或 中间值3[16]
Pasted image 20250713052051
②同时可以利用填充校验是否正确
  服务器让密文2[16]中间值3[16]异或,得到明文3[16],这时服务器会校验明文3[16]是否符合PKCS5的填充规则。
爆破倒数第一位:

  • 因为我们只控制了一位,所以要让它异或出0x01。由于密文是随便写的,肯定密码不正确,但我们不关心。只在乎填充校验是否正确。
  • 所以只要当遍历密文2[16]为某个值,服务器判断为“密钥正确,填充正确,非登录信息,静默处理”——即无Set-Cookie或者deleteMe
  • 那么有且只有一个值满足公式:
        0x01 = 密文2[16] 异或 中间值3[16]
  • 然后已知0x01密文2[16] ,可以算出中间值3[16]的值
  • 整个过程最多需要256次爆破一个字节
# 填充正确响应
HTTP/1.1 200 OK
Content-Type: text/html

# 填充错误响应
HTTP/1.1 500 Internal Server Error
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT

Shiro的特定行为:
    - 填充错误时,Shiro会强制设置`rememberMe=deleteMe`来清除客户端Cookie
    - 这是Shiro框架的**标准错误处理机制**
场景
服务器响应特征 脚本判断逻辑
填充正确 无Set-Cookie头或内容不含deleteMe 返回成功
填充错误 Set-Cookie头包含deleteMe 抛出异常

爆破倒数第二位:
然后我们修改密文2[15]密文2[16], 让他们与中间值3[15]中间值3[16]进行两位的异或,当得到0x02 0x02时,服务器会返回“密钥正确,填充正确,非登录信息,静默处理”——即无Set-Cookie或者deleteMe,由于中间值3[16]是已知的,所以密文2[16] 是已知的,只需要遍历密文2[15],满足公式:
   0x02 0x02 = 密文2[15] 密文2[16] 异或 中间值3[15] 中间值3[16]

   然后已知0x02 0x02、密文2[15] 密文2[16]中间值3[16],可以算出中间值3[15]的值

image

  以此类推得到最后一个密文块的中间值的每一位

同理:
我们可以去掉密文3然后固定密文2的值,也就是让下一个要爆破的分组密文作为最后一个分组然后遍历密文1的每个字节,得到倒数第二个密文块的中间值的每一位
整个分组的第一个密文块用的异或的前一个密文块是初始向量,所以爆破中间值1就改变初始向量
整个过程需要最多需要256*密文分组次爆破所有分组的中间值

流程总结:
截断(最后一组不用)——>爆破中间值——>构造新的前一组密文,伪造明文——>截断

误判:

这种判断方式可能存在误判
  因为我们只知道服务器校验填充是否合法。假如在爆破中间值3[16]时,我只修改了密文2[16]为了找到符合以下公式的值:
    0x01 = 遍历的密文2[16] 异或 中间值3[16]
  但如果运气好,此时满足
    0x02 0x02 = 未遍历密文2[15] 遍历的密文2[16] 异或 中间值3[15] 中间值3[16]
  服务器也会返回填充正确,这样的误判直到0x0f 0x0f ... 0x0f 总共16种情况
  最终误判概率为:
          P(0x0n 误判)=(1/256​)^n
              等于
        P(总误判)≈1/(256×255)​=1/65280

阶段二:伪造明文

构造明文3:
  因为解密时的公式:
    明文3 = 密文2 异或 中间值3
 现在已经通过爆破获得 中间值3,只需要修改密文2,就能构造出想让服务器解析的明文3
构造明文2:
  因为解密公式:
    明文2 = 密文1 异或 中间值2

阶段三:扩容明文分组

上面描述的原理只能控制256(密文分组-1)个字符,对攻击来说远远不够。所以我们需要对分组进行扩容
我们伪造一个密文4无论他是否合理,服务器都会对这个分组密文进行解密,得到中间值4。由于密文4不合理,中间值4可能也不合理,但我们不关心是否合理,只需要修改密文3,通过填充校验爆破出中间值4就行
这样我们就从原本的:
  IV+C1+C2+C3 (可控值:明文2+明文3 16*2=32字节)
变为(扩容1组)
  IV+新C1+新C2+新C3+C4 (可控值:明文2+明文3+明文4 16*3=48字节)
同理(扩容2组)
IV+新C1+新C2+新C3+新C4+C5(可控值:明文2+明文3+明文4+明文5 16*4=64字节)
  理论可以扩容到shiro上限,但每扩容一个分组,增加16
256次爆破

举例:

假如我们原本登录成功得到一个正确Cookie:
Set-Cookie:rememberMe=Base64(IV+密文)
rememberMe=Base64(0x00 ... 0x00 0x01 ... 0x01 0x02 ... 0x02)
通过分组得到:
    IV = 0x00 0x00 ... 0x00
    C1 = 0x01 0x01 ... 0x01
    C2 = 0x02 0x02 ... 0x02
现在按扩容到C4构造:
    IV = 0x00 0x00 ... 0x00
    C1 = 0x01 0x01 ... 0x01
    C2 = 0x02 0x02 ... 0x02
    C3 = 0x03 0x03 ... 0x03 (写任意值AES都能解密)
    C4 = 0x04 0x04 ... 0x04 (写任意值AES都能解密)
将rememberMe=Base64(IV+C1+C2+C3+C4)作为请求头发送给服务器
服务器会先解密C4,假设得到C4的中间值4(攻击者未知):
  中间值4 = F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF

1、获取中间值

爆破倒数第一位
  保持C4不变,首先改变C3的最后一个字节C3[16],比如改为0xDD
  令rememberMe=Base64(00 ... 00|01 ... 01|02 ... 02|03 ... DD|04 ... 04)
  服务器视角:
    判断错误:
    让0xDD中间值4[16]0xFF异或(攻击者不可视),结果得到的不是0x01,所以返回 “密码不正确、校验不合格”的相关内容(不考虑误报),如状态码500 (攻击者只能看到响应信息
    判断正确:
    而当C3[16]遍历到 0xFE 时:
      0x01 = 中间值4[16] 异或 0xFE
    服务器返回“密码不正确、校验合格”的相关内容,如状态码301,那么就得到:
      中间值4[16] = 0x01 异或 0xFE
            = 0xFF
接着爆破倒数第二位
  由于上一步算出了中间值4[16]等于0xFF,可以得到
    新C3[16] = 0x02 异或 0xFF
         = 0xFD
  那么改变C3[15],比如改为0xEE
  令rememberMe=Base64(00 ... 00|01 ... 01|02 ... 02|03 ... EE FD|04 ... 04)
  服务器视角:
    判断错误:
    让0xEE 0xFD中间值4[15]中间值4[16]0xFE 0xFF异或(攻击者不可视),结果得到的不是0x02 0x02,所以返回 “密码不正确、校验不合格”的相关内容
    判断正确:
    当C3[15]遍历到 0xFC 时:
      0x02 0x02 = 中间值4[15] 中间值4[16] 异或 0xFC 0xFD
    服务器返回“密码不正确、校验合格”的相关内容,如状态码301,那么就得到:
      中间值4[15] = 0x02 异或 0xFC
            = 0xFE
    最终得到完整的中间值4
      中间值4 = F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF

2、伪造明文

因为公式:
    明文 = C3 异或 中间值4
  而中间值4是已知的,所以公式:
    新C3 = 伪造明文4 异或 中间值4
  我们伪造一个明文:helloword
  步骤 1:
    把“helloword”变成 16 字节明文
    伪造明文helloword(9 字节)
    PKCS5Padding:再补 7 个 0x07
      明文4 = 68 65 6C 6C 6F 77 6F 72 64 07 07 07 07 07 07 07
  步骤 2:
    计算新 C3(16 字节)
    已知:
      中间值4 = F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
    逐字节异或:
      新C3[i] = 明文4[i] 异或 中间值4[i
    得到(示例值,按位计算即可):
      新C3 = 97 93 9F 9F 9A 81 9A 8E 9B F8 F8 F8 F8 F8 F8 F8

3、截断

得到新的C3,现在要进行C3位置的爆破操作,改变C2固定新C3去掉C4
  该过程发送的Cookie值去掉C4
  原Cookie:
    IV + C1 + C2 + C3 + C4
    00 ... 00 01 ... 01 02 ... 02 03 ... 03 04 04 ... 04
  该阶段Cookie:
    IV + C1 + C2 + 新C3
    00 ... 00 01 ... 01 02 ... 02 97 93 9F 9F 9A 81 9A 8E 9B F8 F8 F8 F8 F8 F8 F8

4、重复获取中间值

该阶段固定伪造出C4位置明文的新C3,遍历C2的值,去获取新C3中间值,从而伪造C3位置的明文


版本:Shiro >=1.4.2
原理:GCM不存在填充机制,所以只能用全版本通用方式——爆破密钥,详情见下一章。

版本:全版本
条件:密钥在爆破字典中
原理:在讨论算法中其实很清楚知道,无论AES-CBC还是AES-GCM的加解密算法都是公开已知的。而解密时需要的两个变量IVKey,解密时服务器是使用Cookie中提取的IV,所以也就只存在一个变量Key,我们可以利用:
1、密钥错误,服务器解密失败,返回deleteMe
2、密钥正确,服务器解密成功,但明文不是登陆信息,静默处理,返回null
解密处理流程:

1. 请求进入 → `ShiroFilter` 读取所有 Cookie。
2. 发现 `rememberMe=...` → 调用 `AbstractRememberMeManager#getRememberedPrincipals()`。
3. 里面先做 `decrypt()`:  
    • 密钥错 → 抛异常 → catch 住 → 立即添加 `Set-Cookie: rememberMe=deleteMe` 并返回 null。  
    • 密钥对 → 解密成功 → 继续反序列化。
4. 反序列化后拿到的是一个“伪造的”或“空的” PrincipalCollection。  
    Shiro 判断“这不是合法用户” → 直接返回 null,既不再 set 任何 Cookie,也不再追加 deleteMe。
5. 请求继续走,但当前 Subject 是匿名,后续若访问受限资源会被重定向到登录页;我们做的只是普通 GET,没有受限资源,于是最终响应头里完全没有 Set-Cookie 字段。

所以关键就是我们根据对应版本加密算法,遍历密钥伪造密文,利用服务器是否返回Set-Cookie: rememberMe=deleteMe 字段,从而爆破出密钥。

这也是为什么工具中会出现对应的GCM选项,因为加密方式完全不同
Pasted image 20250715013955
比如以1.2.4版本漏洞来看,虽然是默认密钥,但本质也能走爆破流程。而1.2.4属于AES-CBC加密,就不能勾选AES-GCM。如果勾选了就爆破不出来

漏洞 影响版本 漏洞成因 利用
Shiro550 Shiro<=1.2.4 硬编码固定值Key和IV(16个0x00) 默认值
Shiro721 Shiro <=1.4.1 利用服务器对填充校验爆破末尾分组的中间值,从而伪造明文 爆破中间值
Shiro_GCM版本 >=1.4.2 如果爆破出密钥Key,可以根据算法伪造密文(rememberMe=) 爆破密钥
爆破密钥 全版本 如果爆破出密钥Key,可以根据版本所对应算法伪造密文(rememberMe=) 爆破密钥

1、如今高版本开发Cookie传递的参数是自定义的,而不是常见的rememberMe,需要注意。

本文基本改于作者自用笔记,耗费两天精力,发出来造福各位进步的师傅。因为本地挪过来格式有点问题,改了好久(累死我了~)可能有些丑。内容有个人见解的,欢迎评论!(一键三连!!!)

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信