热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

破解知乎了!

本文由作者胡川港投稿,如果你在CSDN、博客园、掘金等平台有写技术博客的习惯,想让自己的原创博客被更多人看到,可以来Java后端投稿。最近

本文由作者 胡川港 投稿,如果你在 CSDN、博客园、掘金等平台有写技术博客的习惯,想让自己的原创博客被更多人看到,可以来 Java后端 投稿。

最近在新公司学习 Golang,心血来潮想写一个脚手架,脚手架的简单的功能就是 输入 关键词,然后工具帮我自动的去搜索比如 掘金 知乎 CSDN 的热门博客返回给我。

但是在调用知乎的接口发现 知乎的接口有签名 不能修改参数。修改了参数就让提示升级客户端了。这怎么能忍,岂能因为一个小小的知乎搜索阻挠我学习 Golang 的脚步呢「手动狗头」,今天就来逆向破解知乎。

dbc98cb5d785116514a165ab43ca3283.png

现象

先用 Chrome 将 知乎的搜索 API 导出为 cURL

1c0818a7277a2a19481858ebe882fa55.png

并将导出的 cURL 导入到 postman 中

0862bc2cdda45c291302486ff5a7a81e.png

在 postman 这里直接发送请求是可以的

5327f44a6cf90df1959c0ee9b9f362d7.png

但是如果我们一旦尝试修改查询参数的时候就会报错

9633d3f4c42a0ece96ed1ce9211f477a.png

分析原因

其实到这里很明显的知道肯定是因为参数加签导致的,但是我们需要知道具体是哪个参数导致,才好解决问题

bba0c12252c39cee8da6df97d60f21cb.png

咋一看 url 里面的这些参数 都很标准 应该没有动过什么手脚,我们把目标转向 header


7dde16adbfb54a3ca9ba73451ade0e2f.png

请求头里面这么多参数感觉是有问题的

测试方法也很简单,依次去除 单个字段,然后请求接口看是否可行即可,最后发现在没有修改请求参数 的情况下 去除 请求头里面的 x-zse-96 就会出现同样的问题,问题复现

39714625e0c9768ed73b5bc14278cfba.png

定位加密文件

在分析问题环节我们已经找到了加签后的验证字段 x-zse-96,最后其实我们值需要找到这个字段对应的加签规则即可。

基本的思路为:通过 Chrome 中的 JS 栈调用为入口

c90ec3efa713ad09ecf8879459a1d91f.png

如上图所示,在 Network 中 搜索 API 请求记录中有一项属性 Initiator [发起者],将鼠标移动上去就会显示 JS 的调用栈情况

JS call stack

f84a11b41bf0480a9b0297d62a5dcc76.png

有一点可以明确下来就是 针对参数 x-zse-96 这个参数的加签肯定是在这个调用栈中完成的,我的思路就是一次在这些调用过的 js 文件中搜索这个字段,先定位到在哪个 JS 文件中的,好巧不巧的事知乎 的js 应该是打包后的 所有的 js 文件都在一个代码文件中。

我直接在这个文件中进行搜索就行了

20d740c971c8bdcb46a7c165efdc45ad.png

代码位置已找到,开始表演😈

还原加密过程

接下来就是分析加密代码的时候了

c9f7b76d624e6d5b3174667fb689082c.png

从图中可以看出,x-zse-96 是由 2.0_ + signature 组成的

然后我们就需要查看组成 signature 代码的 a()(l()(s))的组成

0da5852cd0156caf6b6b04e5c37110c2.png

在 signature 上方打上一个断点, 再次点击搜索,让断点停在此处

f42acf3c8f55146367f20ea96fc09e8a.png

a() 直接在控制台中打印一下 a 方法,a 方法返回的一个方法为 __g._encrypt(encodeURIComponent(e))

dda7f0aa50bb7466968ee70bee1070ca.png

这里我们先将 a 方法放置在这里,先看里面的方法 l()(s),先看参数 s 基本上就是查询参数拼接成一个字符串, 暂时先可以不看

f7af6db8e16fbc6c11389eb5104639e7.png

l(), 我现在带入几个参数对这个加密算法进行了尝试,我实在感觉这个有点像 MD5

b13b87e9142349de4a9b30d5ce1de8a6.png

3680a93bfc70181a8a30d654419490a7.png

\

抱着试一试的心态找了一个在线的 Md5 加密网站测试了一下 1,卧槽 🐂🍺 还真的是,我直呼 666

回顾下加密算法

c47b426507dcea01c29ac68d4d46bc12.png

现在我们只需要展开 外层的 a()() 方法就 OK 了,在展开一点点__g._encrypt(encodeURIComponent(md5(s)))

82acc5e2b33ec52abf5683c5e3d04386.pngce35c9a790e29db994dc59cd251e1536.png

接下其实就去找这个 __g._encrypt 这个方法就可以了,打上断点在执行一下,尝试运行,看到这个玩意确实是没啥思路,而且在整个 JS 文件中 __g._encrypt 这个方法就只出现了这一次,就在没有出现过了, 所以通过猜测加密算法的方式就不太可行了

思路就是直接将加密算法模块的 JS 文件提取出来 然后 用 golang 的 otto 包来直接调用 JS 代码即可

提取出来的加密 JS 文件为:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`

Hello world

`);
window = dom.window;
document = window.document;
XMLHttpRequest = window.XMLHttpRequest;
function t(e) {return (t = "function" == typeof Symbol && "symbol" == typeof Symbol.A ? function(e) {return typeof e}: function(e) {return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e})(e)
}
Object.defineProperty(exports, "__esModule", {value: !0
});
var A = "2.0", __g = {};
function s() {}
function i(e) {this.t = (2048 & e) >> 11,this.s = (1536 & e) >> 9,this.i = 511 & e,this.h = 511 & e
}
function h(e) {this.s = (3072 & e) >> 10,this.h = 1023 & e
}
function a(e) {this.a = (3072 & e) >> 10,this.c = (768 & e) >> 8,this.n = (192 & e) >> 6,this.t = 63 & e
}
function c(e) {this.s = e >> 10 & 3,this.i = 1023 & e
}
function n() {}
function e(e) {this.a = (3072 & e) >> 10,this.c = (768 & e) >> 8,this.n = (192 & e) >> 6,this.t = 63 & e
}
function o(e) {this.h = (4095 & e) >> 2,this.t = 3 & e
}
function r(e) {this.s = e >> 10 & 3,this.i = e >> 2 & 255,this.t = 3 & e
}
s.prototype.e = function(e) {e.o = !1
},i.prototype.e &#61; function(e) {switch (this.t) {case 0:e.r[this.s] &#61; this.i;break;case 1:e.r[this.s] &#61; e.k[this.h]}},h.prototype.e &#61; function(e) {e.k[this.h] &#61; e.r[this.s]},a.prototype.e &#61; function(e) {switch (this.t) {case 0:e.r[this.a] &#61; e.r[this.c] &#43; e.r[this.n];break;case 1:e.r[this.a] &#61; e.r[this.c] - e.r[this.n];break;case 2:e.r[this.a] &#61; e.r[this.c] * e.r[this.n];break;case 3:e.r[this.a] &#61; e.r[this.c] / e.r[this.n];break;case 4:e.r[this.a] &#61; e.r[this.c] % e.r[this.n];break;case 5:e.r[this.a] &#61; e.r[this.c] &#61;&#61; e.r[this.n];break;case 6:e.r[this.a] &#61; e.r[this.c] >&#61; e.r[this.n];break;case 7:e.r[this.a] &#61; e.r[this.c] || e.r[this.n];break;case 8:e.r[this.a] &#61; e.r[this.c] && e.r[this.n];break;case 9:e.r[this.a] &#61; e.r[this.c] !&#61;&#61; e.r[this.n];break;case 10:e.r[this.a] &#61; t(e.r[this.c]);break;case 11:e.r[this.a] &#61; e.r[this.c]in e.r[this.n];break;case 12:e.r[this.a] &#61; e.r[this.c] > e.r[this.n];break;case 13:e.r[this.a] &#61; -e.r[this.c];break;case 14:e.r[this.a] &#61; e.r[this.c] >> e.r[this.n];break;case 19:e.r[this.a] &#61; e.r[this.c] | e.r[this.n];break;case 20:e.r[this.a] &#61; !e.r[this.c]}},c.prototype.e &#61; function(e) {e.Q.push(e.C),e.B.push(e.k),e.C &#61; e.r[this.s],e.k &#61; [];for (var t &#61; 0; t &#61; e.r[this.c];break;case 1:e.u &#61; e.r[this.a] <&#61; e.r[this.c];break;case 2:e.u &#61; e.r[this.a] > e.r[this.c];break;case 3:e.u &#61; e.r[this.a] ;
var k &#61; function(e) {for (var t &#61; 66, n &#61; [], r &#61; 0; r };
function Q(e) {this.t &#61; (4095 & e) >> 10,this.s &#61; (1023 & e) >> 8,this.i &#61; 1023 & e,this.h &#61; 63 & e
}
function C(e) {this.t &#61; (4095 & e) >> 10,this.a &#61; (1023 & e) >> 8,this.c &#61; (255 & e) >> 6
}
function B(e) {this.s &#61; (3072 & e) >> 10,this.h &#61; 1023 & e
}
function f(e) {this.h &#61; 4095 & e
}
function g(e) {this.s &#61; (3072 & e) >> 10
}
function u(e) {this.h &#61; 4095 & e
}
function w(e) {this.t &#61; (3840 & e) >> 8,this.s &#61; (192 & e) >> 6,this.i &#61; 63 & e
}
function G() {this.r &#61; [0, 0, 0, 0],this.C &#61; 0,this.Q &#61; [],this.k &#61; [],this.B &#61; [],this.f &#61; [],this.g &#61; [],this.u &#61; !1,this.G &#61; [],this.b &#61; [],this.o &#61; !1,this.w &#61; null,this.U &#61; null,this.F &#61; [],this.R &#61; 0,this.J &#61; {0: s,1: i,2: h,3: a,4: c,5: n,6: e,7: o,8: r,9: Q,10: C,11: B,12: f,13: g,14: u,15: w}
}
Q.prototype.e &#61; function(e) {switch (this.t) {case 0:e.f.push(e.r[this.s]);break;case 1:e.f.push(this.i);break;case 2:e.f.push(e.k[this.h]);break;case 3:e.f.push(k(e.b[this.h]))}
},C.prototype.e &#61; function(A) {switch (this.t) {case 0:var t &#61; A.f.pop();A.r[this.a] &#61; A.r[this.c][t];break;case 1:var s &#61; A.f.pop(), i &#61; A.f.pop();A.r[this.c][s] &#61; i;break;case 2:var h &#61; A.f.pop();A.r[this.a] &#61; eval(h)}},B.prototype.e &#61; function(e) {e.r[this.s] &#61; k(e.b[this.h])},f.prototype.e &#61; function(e) {e.w &#61; this.h},g.prototype.e &#61; function(e) {throw e.r[this.s]},u.prototype.e &#61; function(e) {var t &#61; this, n &#61; [0];e.k.forEach((function(e) {n.push(e)}));var r &#61; function(r) {var i &#61; new G;return i.k &#61; n,i.k[0] &#61; r,i.v(e.G, t.h, e.b, e.F),i.r[3]};r.toString &#61; function() {return "() { [native code] }"},e.r[3] &#61; r},w.prototype.e &#61; function(e) {switch (this.t) {case 0:for (var t &#61; {}, n &#61; 0; n > 12;new this.J[t](e).e(this)},
1  && (new G).v("AxjgB5MAnACoAJwBpAAAABAAIAKcAqgAMAq0AzRJZAZwUpwCqACQACACGAKcBKAAIAOcBagAIAQYAjAUGgKcBqFAuAc5hTSHZAZwqrAIGgA0QJEAJAAYAzAUGgOcCaFANRQ0R2QGcOKwChoANECRACQAsAuQABgDnAmgAJwMgAGcDYwFEAAzBmAGcSqwDhoANECRACQAGAKcD6AAGgKcEKFANEcYApwRoAAxB2AGcXKwEhoANECRACQAGAKcE6AAGgKcFKFANEdkBnGqsBUaADRAkQAkABgCnBagAGAGcdKwFxoANECRACQAGAKcGKAAYAZx&#43;rAZGgA0QJEAJAAYA5waoABgBnIisBsaADRAkQAkABgCnBygABoCnB2hQDRHZAZyWrAeGgA0QJEAJAAYBJwfoAAwFGAGcoawIBoANECRACQAGAOQALAJkAAYBJwfgAlsBnK&#43;sCEaADRAkQAkABgDkACwGpAAGAScH4AJbAZy9rAiGgA0QJEAJACwI5AAGAScH6AAkACcJKgAnCWgAJwmoACcJ4AFnA2MBRAAMw5gBnNasCgaADRAkQAkABgBEio0R5EAJAGwKSAFGACcKqAAEgM0RCQGGAYSATRFZAZzshgAtCs0QCQAGAYSAjRFZAZz1hgAtCw0QCQAEAAgB7AtIAgYAJwqoAASATRBJAkYCRIANEZkBnYqEAgaBxQBOYAoBxQEOYQ0giQKGAmQABgAnC6ABRgBGgo0UhD/MQ8zECALEAgaBxQBOYAoBxQEOYQ0gpEAJAoYARoKNFIQ/zEPkAAgChgLGgkUATmBkgAaAJwuhAUaCjdQFAg5kTSTJAsQCBoHFAE5gCgHFAQ5hDSCkQAkChgBGgo0UhD/MQ&#43;QACAKGAsaCRQCOYGSABoAnC6EBRoKN1AUEDmRNJMkCxgFGgsUPzmPkgAaCJwvhAU0wCQFGAUaCxQGOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQMOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQSOZISPzZPkQAaCJwvhAU0wCQFGAkSAzRBJAlz/B4FUAAAAwUYIAAIBSITFQkTERwABi0GHxITAAAJLwMSGRsXHxMZAAk0Fw8HFh4NAwUABhU1EBceDwAENBcUEAAGNBkTGRcBAAFKAAkvHg4PKz4aEwIAAUsACDIVHB0QEQ4YAAsuAzs7AAoPKToKDgAHMx8SGQUvMQABSAALORoVGCQgERcCAxoACAU3ABEXAgMaAAsFGDcAERcCAxoUCgABSQAGOA8LGBsPAAYYLwsYGw8AAU4ABD8QHAUAAU8ABSkbCQ4BAAFMAAktCh8eDgMHCw8AAU0ADT4TGjQsGQMaFA0FHhkAFz4TGjQsGQMaFA0FHhk1NBkCHgUbGBEPAAFCABg9GgkjIAEmOgUHDQ8eFSU5DggJAwEcAwUAAUMAAUAAAUEADQEtFw0FBwtdWxQTGSAACBwrAxUPBR4ZAAkqGgUDAwMVEQ0ACC4DJD8eAx8RAAQ5GhUYAAFGAAAABjYRExELBAACWhgAAVoAQAg/PTw0NxcQPCQ5C3JZEBs9fkcnDRcUAXZia0Q4EhQgXHojMBY3MWVCNT0uDhMXcGQ7AUFPHigkQUwQFkhaAkEACjkTEQspNBMZPC0ABjkTEQsrLQ&#61;&#61;");var b &#61; function(e) {return __g._encrypt(encodeURIComponent(e))
};module.exports &#61; {Q: b
}

712717ab36714c36d29b02eedc37bf5c.png1fbc535fc67594576fca3b8f63aadaa0.png

最后本地测试一下&#xff0c;对上了 对上了

cdd1bd83e3ba332f7be75d419065e3b9.png

b658b8a0887bf11311129f3b625dd601.png

在看一下 s 的值 s&#61;r&#43;c&#43;i&#xff0c; 101_3_3.0&#43;/api/v4/search/suggest?qv&#61;vim&#43;AHCY5gT4iBKPTsBQivYRPUIbDLBctuaZZzs&#61;|1611209467 其他都是固定的 只需要改一下请求的 url query 就可以了

76ea6410a5b98455e1e8fe3ef957b09f.png

在经过测试后 成功 修改参数 起飞

接入 Go otto 调用

在接入 Go 调用过程中发现一个问题&#xff0c;不知道是 golang otto 包的问题还是怎样&#xff0c;就是在 js 文件里面 如果有 require() 第三包的情况下 就会提示语法错误, 因为这个加密 js 文件中会调用 windows.atob 方法 所以需要借助第三方包 jsdom 来实现

e3f11aa960b458faba071f9d0e01a467.png

错误表现

33a3af41daa4bc3812d251d7c9a737a3.png

在经过冥思苦想不知道怎么解决的时候&#xff0c;去问了下团队里的爬虫老师傅&#xff0c;他说一般遇到这种情况就直接起一个 Node 的服务专门来处理这种解密的事就 OK 了&#xff0c;恍然大悟 原来如此

ServerLess

有点偷懒不想自己搞个服务来玩这个了&#xff0c;想直接用云厂商的试用版&#xff0c;看了一下 以前 用过的 LeanCloud 的 感觉不太行&#xff0c;后面选择了腾讯的 ServerLess 直接部署了一个服务器

https://service-denf06ck-1253616191.gz.apigw.tencentcs.com/release/secret/:content

替换掉上面的 content 就可以了&#xff0c;最后 Go 脚手架项目也完成了&#xff0c;项目地址&#xff1a;https://github.com/xiaoxiunique/go-cli/tree/master

投稿作者&#xff1a;胡川港

知乎主页&#xff1a;zhihu.com/people/hu-chuan-gang-58

GitHub主页&#xff1a;https://github.com/xiaoxiunique

#投 稿 通 道#

 让你的博客被更多人看到 

如果你在 CSDN、博客园、掘金等平台有写技术博客的习惯&#xff0c;想让自己的原创博客被更多人看到&#xff0c;可以来 Java后端 投稿。

Java后端 鼓励读者投稿个人技术博客、面试经验、教程。不管是入门的图文教程、还是热门技术讲解&#xff0c;只要你喜欢写东西&#xff0c;我们欢迎你来投稿。

&#x1f4dd; 稿件基本要求&#xff1a;

• 文章确系个人原创作品&#xff0c;如果在其他非公众号渠道有过发表也可以&#xff0c;只要是个人原创即可。

• 稿件建议以 markdown 格式撰写&#xff0c;文中配图以附件形式发送&#xff0c;要求图片清晰、语句通顺。

• 如果被采纳的原创稿件&#xff0c;我们将提供稿费以及个人影响力曝光&#xff0c;具体依据文章阅读量和质量结算稿费。

&#x1f4ec; 投稿通道&#xff1a;

• 投稿请联系下方微信&#xff0c;备注&#xff1a;原创投稿

feaf7466a1bbf51ee8166f8df42cedd1.png

△长按添加 Java后端 小编


推荐阅读
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 本文介绍了安全性要求高的真正密码随机数生成器的概念和原理。首先解释了统计学意义上的伪随机数和真随机数的区别,以及伪随机数在密码学安全中的应用。然后讨论了真随机数的定义和产生方法,并指出了实际情况下真随机数的不可预测性和复杂性。最后介绍了随机数生成器的概念和方法。 ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • 本文详细介绍了解决全栈跨域问题的方法及步骤,包括添加权限、设置Access-Control-Allow-Origin、白名单等。通过这些操作,可以实现在不同服务器上的数据访问,并解决后台报错问题。同时,还提供了解决second页面访问数据的方法。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
author-avatar
张露-Luna_309
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有