REC
首页
文章分类
源码资源
技术教程
程序软件
文创娱乐
玄学修炼
关于我们
其他页面
网站统计
友情链接
用户留言
高清壁纸
关于易航
热门文章
Joe再续前缘主题 - 搭建本站同款网站
易航网址导航系统 – 功能强大,轻量易用
JsonDb-PHP轻量级文件数据库系统
Typecho一键调整网站为冬天情景插件
V免签全开源免签约码支付系统(支持:支付宝 微信 QQ)
标签搜索
PHP
Web前端
网站源码
PHP源码
Typecho
Typecho插件
课程资料
Windows程序
Android软件
武术内功
HTML源码
Web
Joe主题
Python
Windows
国漫
网络协议
MySQL
NodeJs
小说
发布
登录
注册
找到
246
篇与
易航
相关的结果
- 第 5 页
2025-01-10
PHP 文件上传漏洞总结
文件上传漏洞 文件上传漏洞是指后端服务器允许前端用户上传文件,但是对文件的名称、后缀、内容、大小等信息没有做过滤和控制,导致攻击者可以上传任意文件到服务器。 影响 1、上传恶意文件:比如上传后缀为.php、.jsp的文件,服务器收到后,对其没有进行校验。访问文件时,直接调用PHP解释器或JSP引擎执行,如果文件内容是可以执行系统命令的代码,攻击者就可以通过该文件获取服务器权限。 2、覆盖关键文件:服务器允许上传相同文件名的文件,如果可以通过目录遍历更改上传路径和位置,则可以覆盖关键文件。 3、DDoS攻击:如果服务器对上传文件的大小没有限制,则可以上传大量超大文件,占用服务器存储空间,消耗服务器带宽,造成DDoS攻击。 漏洞利用 当服务器存在文件上传漏洞时,需要根据具体情况采用合适的利用办法和绕过方式等,如下。 无限制上传WebShell 服务器不管是在前端还是后端,都没有对用户上传的文件做过滤和校验,导致攻击者可以在上传点直接上传webshell到服务器,从而获取服务器控制权。 绕过Centent-Type验证上传WebShell MIME:定义数据类型,告诉客户端或服务器,数据是什么类型,应该用什么方式处理。 Centent-Type:HTTP协议的一个字段,是MIME在HTTP协议中的应用。 Centent-Type验证属于后端的一个防护,服务器收到请求后,会验证Centent-Type类型是否被允许,如果不允许,则拒绝请求。如下图: 图片 假设服务器只允许Centent-Type类型为image/jpeg,上传test.php,Content-Type类型是application/octet-stream,服务器收到请求后,对Centent-Type进行对比检查,如果不符,拒绝访问。 在实战利用中,可以修改Centent-Type为服务器允许的类型进行绕过,方法有二。 一、前端上传.php文件,对上传请求进行拦截,在请求包中修改Centent-Type。 二、前端上传服务器允许的文件,如.jpg,对上传请求进行拦截,在请求包中修改文件文件后缀及内容。 通过目录遍历上传WebShell 为了让攻击者无法通过上传WebShell获取服务器权限,一般会将上传目录设置为只允许上传静态文件,且没有执行权,文件不允许被当作脚本执行,就算绕过层层防护,把WebShell上传到了服务器,也无法利用。 上传成功后,访问时,会把WebShell内容直接输出,或者是将WebShell下载,而不是调用解释器执行。 图片 遇到这种情况时,就是上传目录没有执行权,要绕过防护执行WebShell,可以通过中间件解析漏洞,或者通过目录遍历,将文件上传到可执行目录中,解析漏洞后面会详细讲,这里主要说一下目录遍历绕过。如下图: 图片 使用../将WebShell上传到上层目录,服务器如果对目录遍历有检测,禁止使用../进行目录遍历,也可以尝试目录遍历绕过,比如将/进行URL编码或其他什么方式。 绕过黑名单上传WebShell 情况一: 为了不让攻击者上传.php、.jsp等后缀的恶意文件,服务器直接将这些后缀列入了黑名单,但是百密一疏,黑名单这种情况总会漏掉一些其他可执行的文件后缀,如下: php > php3、php5、phtml jsp > jspx asp > asa、aspx如果服务器采用黑名单防护,可以尝试其他后缀代替。 情况二: 关于覆盖服务器配置的情况,服务器怎么去处理文件,以什么方式处理,都是根据配置文件中的配置去执行的,拿Apache服务器举例,Apache的配置文件是/etc/apache2/apache2.conf,如下配置: LoadModule php_module /usr/lib/apache2/modules/libphp.so AddType application/x-httpd-php .php这段配置是告诉服务器,加载php模块,将.php文件交给php解释器处理,那假如要配置上传目录没有执行权限,是不是也要跑到/etc/apache2/apache2.conf下配置呢,其实不用,该配置文件应用于全局,也就是整个服务器,为了一个目录的配置而修改整个服务器的配置是非常不方便的,这时候就用到了.htaccess文件,这是一个只针对于目录的配置文件,不影响全局配置,怎么使用呢,只要在需要单独配置的目录下,创建.htaccess文件即可,立即生效,不需要重启服务器。 如果服务器使用黑名单防护漏掉了.htaccess文件,则可以通过上传该文件修改目录配置或权限,假如上传目录upload没有执行权,那么可以上传以下内容的.htaccess。 LoadModule php_module /usr/lib/apache2/modules/libphp.so AddType application/x-httpd-php .php .jpg .txt简单理解就是后缀为.php、.jpg、.txt的文件,都交给php解释器执行,不管是图片马还是内容为木马的文本文件,都可以进行利用从而获取服务器权限。 混淆后缀上传WebShell 双写:上传WebShell到服务器后,服务器会把黑名单中的后缀替换为空,也就是去除,顺序是从左到右,且只进行一次操作,通过双写后缀进行绕过,如test.pphphp,经过替换后的文件名为test.php。 大小写:服务器验证文件后缀时,设置为区分大小写,那么就可以对文件后缀进行大小写处理,如.pHp、PhP等,因为只过滤.php,所以经过大小写处理的后缀可以被上传,又因为配置文件处理.php文件时不区分大小写,所以.pHp可以被执行。 多后缀:服务器处理上传文件时,对后缀进行检查,顺序是从后往前,具体来说就是检查从后往前的第一个点(.)第一个点(.)后面的内容就会被识别为后缀,那么webshell文件test.php则可以在后面添加允许的后缀绕过检查,test.php.jpg。也可以只加点(.),test.php.,这样的话后缀实际上就是空,空后缀没有在黑名单中,就可以绕过。 尾部字符:文件上传到服务器后,如果文件名中带空格,服务器会自动去除空格,如果上传时服务器对空格没有过滤,且加空格的后缀也没有在黑名单,就可以通过后缀加空格绕过了。上传到服务器后空格被去除,后缀被还原。 尾部符号绕过还有分号(;)和空字节的URL编码(%00),即代表结束、截断的意思,test.php;.jpg、test.php%00.jpg,上传时服务器检查后缀是.jpg,可以上传没问题,但是在执行时,分号(;)和空字节的URL编码(%00)后面的部分会被忽略,最后识别到的文件就是test.php。但只针对GET请求,如果是POST,则需要拦截请求包,在数据包中修改了,test.php0x00.jpg,0x00是空字节的十六进制编码,实际利用中需要将其替换,而不是直接输入0x00。 URL编码:如果服务器在处理上传文件时,没有解码操作,则可以将点(.)进行URL编码(%2e)绕过,test%2ephp,服务器执行该文件时会进行解码,这时文件名被还原test.php。 这只是混淆文件后缀众多方法中的一小部分。其中双写、大小写、末尾加点(.)加空格和URL编码属于是黑名单绕过范畴,而多后缀、00截断则针对的是白名单防护。 绕过文件内容检查上传WebShell 为了防止攻击者上传包含恶意代码的图片文件,服务器会对上传文件的内容做检查,检查是否具有图片的特征,如文件头的前两个字节是不是图片,内容中有没有图片的尺寸属性等等。 可以制作一个图片马进行绕过,方式如下: copy test.jpg/b+shell.php shell.jpgcopy:windows系统复制命令。 test.jpg:合法图片文件。 /b:二进制模式,表示复制过程中,合法图片的内容不被修改,原封不动地复制。 +:附加,这里表示把shell.php文件内容附加到test.jpg文件中去。 shell.php:webshell。 shell.jpg:最后生成的图片马文件。 制作方法有很多,这里用的是copy方法,这样制作的图片马可以以图片形式正常打开,只是把webshell文件内容隐藏到了图片内容的末尾。 如果只检查文件头,则可以只修改文件头为合法的即可,如下: JPG:0xFF 0xD8 PNG:0x89 0x50 GIF:0x47 0x49修改文件头有很多工具和插件都可以完成,也可以使用Burp拦截上传请求后,在数据包中修改,现在代码前添加两个占位符,选中后修改其十六进制为图片即可,如下图: 图片 条件竞争上传WebShell 现代框架处理上传文件,一般是先放到沙盒目录,比如/upload\_sandbox,用于临时存放文件和隔离上传文件,且没有执行权,文件上传到沙盒目录时还会进行随机改名处理,防止覆盖文件。接下来会检查文件的文件名、文件内容、后缀、魔数等等,如果符合要求判定为安全,才会将文件上传到真实上传目录,比如/upload,如果识别文件是危险的,则会直接删除。 但是,如果没有使用任何框架,而是自定义处理上传的文件,则会出现一种情况,就是上传文件时,没有经过任何过滤就直接存放到了服务器真实上传目录,然后才对其是否合法进行检查,合法保留,不合法删掉,检查时间可能就只有几毫秒,虽然时间很短,但仍然可以通过条件竞争方式上传WebShell。 操作只需要两步: 一、拦截上传请求,对其进行并发,文件后缀为php,内容如下: <?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[123])?>'); ?>fopen('shell.php','w') :以写的模式打开shell.php,如果文件不存在就新建一个。 fputs :将 <?php @eval($_POST[123])?> 写入shell.php。 如下图: 图片 二、拦截访问请求,对其进行并发,访问的是我们上传的test.php,上传路径可以先上传一张合法图片查看,并发过程和第一步一样,也可以修改线程,提高成功率。 上传test.php到服务器后,服务器并没有及时删除(几毫秒时间),且幸运的被访问到,则执行命令,创建WebShell。 上传其他恶意文件 除了上传WebShell获取服务器权限外,还可以上传其他恶意文件进行攻击,如下情况: 一、服务器允许上传html、svg后缀的文件,则可以构造恶意JS脚本,进行XSS攻击。 二、服务器允许上传docx、xlsx后缀的文件,则可以引入XML外部实体,进行XXE攻击。 docx和xlsx文件都是一个ZIP压缩包,其中包含多个XML文件,可以向XML文件中注入XXEPayload,方法如下: docx 1、新建docx文件并解压。 2、解压后的文件结构如下图。 图片 word文件夹下有多个XML文件,均可以注入,但推荐修改document.xml。 图片 3、记事本打开文件document.xml进行修改。 4、document.xml原内容如下: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <w:document> <w:body> <w:p w14:paraId="367FED8D" w14:textId="1728AEF6" w:rsidR="007A2F41" w:rsidRDefault="00653F26"> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t></w:t> </w:r> </w:p> <w:sectPr w:rsidR="007A2F41"> <w:pgSz w:w="11906" w:h="16838"/> <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/> <w:cols w:space="425"/> <w:docGrid w:type="lines" w:linePitch="312"/> </w:sectPr> </w:body> </w:document>修改后的内容如下: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE test [ <!ENTITY test SYSTEM 'file:///etc/passwd'>]> <w:document> <w:body> <w:p w14:paraId="367FED8D" w14:textId="1728AEF6" w:rsidR="007A2F41" w:rsidRDefault="00653F26"> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>&test;</w:t> </w:r> </w:p> <w:sectPr w:rsidR="007A2F41"> <w:pgSz w:w="11906" w:h="16838"/> <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/> <w:cols w:space="425"/> <w:docGrid w:type="lines" w:linePitch="312"/> </w:sectPr> </w:body> </w:document>共对两处进行了修改: a.定义外部实体test,内容为读取服务器/etc/passwd文件。 b.在<w:t>标签中引用test实体。 5、修改后,将文件夹重新压缩成ZIP文件,再改后缀为docx。 6、打开文件时会显示文件部分内容有问题,是否恢复,点否即可。 xlsx: 大致过程和具体细节和docx相似,但涉及的XML文件不同。 1、新建xlsx文件并解压。 2、解压后的文件结构如下图。 图片 3、记事本打开文件sheet1.xml进行修改,位置:xl > worksheets > sheet1.xml。 4、sheet1.xml原内容如下: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <worksheet> <dimension ref="A1"/> <sheetViews> <sheetView tabSelected="1" workbookViewId="0"/> </sheetViews> <sheetFormatPr defaultRowHeight="13.8" x14ac:dyDescent="0.25"/> <sheetData> <row r="1" spans="1:1" x14ac:dyDescent="0.25"> <c r="A1" t="s"> <v>0</v> </c> </row> </sheetData> <phoneticPr fontId="1" type="noConversion"/> <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/> <pageSetup paperSize="9" orientation="portrait" r:id="rId1"/> </worksheet>修改后的内容如下: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE test [ <!ENTITY test SYSTEM 'file:///etc/passwd'>]> <worksheet> <dimension ref="A1"/> <sheetViews> <sheetView tabSelected="1" workbookViewId="0"/> </sheetViews> <sheetFormatPr defaultRowHeight="13.8" x14ac:dyDescent="0.25"/> <sheetData> <row r="1" spans="1:1" x14ac:dyDescent="0.25"> <c r="A1" t="s"> <v>&test;</v> </c> </row> </sheetData> <phoneticPr fontId="1" type="noConversion"/> <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/> <pageSetup paperSize="9" orientation="portrait" r:id="rId1"/> </worksheet>还是定义了外部实体test并且引用,在<v>标签中。 5、修改后,将文件夹重新压缩成ZIP文件,再改后缀为xlsx。 6、打开文件时会显示文件部分内容有问题,是否恢复,点否即可。 如果服务器允许解析XML外部实体,上传后观察响应,是否返回内容,也不排除盲的情况,可以将定义的外部实体内容修改为自己的dnslog地址进行测试。 使用PUT上传文件 如果服务器允许PUT方法,即使没有上传点,也可以进行文件上传,如下请求: PUT /images/test.php HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-httpd-php Content-Length: 49<?php @eval($_POST[123]); ?>可以通过OPTIONS方法向服务器发送预检请求,判断是否允许PUT方法。 文件包含 文件包含漏洞主要出现在PHP开发的Web应用中,在开发的时候,有很多地方会用到相同的代码,每次都重复写一遍太麻烦,为了方便,会使用文件包含函数直接包含代码文件,如果文件包含函数对包含的文件没有进行过滤和校验,就会造成文件包含漏洞。 include():包含文件不存在或出错,程序继续运行。 require():包含文件不存在或出错,程序终止运行。 include_once():如果相同的文件已被包含,不再进行二次包含操作。 require_once():如果相同的文件已被包含,不再进行二次包含操作。文件包含又分为本地包含(LFI)和远程包含(RFI),包含路径用相对路径或绝对路径都可以,RFI需要PHP配置allow\_url\_include=on。 LFI:https://example.com/index.php?file=../../upload/shell.php RFI:https://example.com/index.php?file=https://test.com/upload/shell.php利用文件包含漏洞执行WebShell 在防御文件上传攻击时,服务器使用了白名单校验,但是,如果服务器存在文件包含漏洞的话,攻击者依然可以执行WebShell获取服务器权限。 制作图片马,或其他包含恶意代码的合法文件上传至服务器绕过白名单校验,通过对该文件进行包含,让服务器去执行,PHP在执行include()或require()时,不会验证后缀,只会读取内容,如果是PHP代码,则会使用PHP解释器执行。 通过写入日志执行WebShell 服务器使用了更强大的防护机制,不仅检查文件后缀,还对文件内容进行了严格校验,只要包含恶意代码,一律拒绝上传,这样的话就无法通过上传方式执行WebShell获取服务器权限了,但是,如果服务器存在文件包含漏洞,可以直接包含恶意代码,将代码写入日志文件,再包含日志文件执行WebShell。 https://example.com/index.php?file=<?php @eval($_POST[123]); ?>如果在浏览器包含执行,浏览器会把特殊符号进行URL编码,可以通过Burp发送。 图片 400的原因是包含的文件不存在,查看日志文件,木马成功写入。 图片 再包含日志文件就可以执行WebShell了,默认访问日志文件路径参考: nginx: /var/log/nginx/access.log apache: /var/log/apache2/access.log /var/log/httpd/access_log 解析漏洞 从客户端发送请求,到数据库收到请求,这中间经过的程序都叫中间件,而解析漏洞主要说的是Web服务应用程序,也就是IIS、Apache、Nginx等等,这里就先简单讲一下关于这三个中间件的解析漏洞。 IIS IIS分低版本(IIS 5.x-IIS 6.x)和高版本(IIS 7.x及以上),低版本只能解析asp的WebShell。方法如下: 目录解析: 服务器会把后缀为.asp文件夹下的文件,都按asp文件解析执行,如下: https://example.com/upload/test.asp/test.jpg如果上传路径可控,可以添加一个以.asp为后缀的文件夹,把图片马放到该目录下上传,访问执行WebShell。 文件解析: 之前将混淆后缀的时候讲到过,就是在后缀后面加个分号(;)在跟一个合法后缀,如下: https://example.com/upload/test.asp;.jpg解析的时候分号(;)代表结束,所以后面的内容不会解析,解析文件就成了test.asp。 格式解析: 有些后缀服务器也会当作asp解析,比如.asa、cer、cdx等等,具体可以查看ISAPI扩展。 https://example.com/upload/test.cerIIS7.x版本也存在解析漏洞,跟Nginx的解析漏洞一样, 这就不讲了,统一在Nginx解析漏洞中讲。 Apache 解析顺序: Apache服务器解释的时候顺序是从右往左,如果添加服务器无法识别的后缀会发生什么呢,如下: https://example.com/upload/test.php.aaa.bbb假如往服务器上传了一个test.php.aaa.bbb文件,正常情况下肯定是访问失败,但是Apache低版本(Apache2.2及以下)是可以正常解析的,访问执行时,服务器从右往左开始解析后缀,如果无法解析,就会继续向左解析,直到碰到可以解析的后缀,那么test.php.aaa.bbb文件最后就会被解析成test.php。 配置文件: 上面也提到过,就是AddType application/x-httpd-php配置,该配置的意思是什么后缀会被当成php代码解析,如果配置不当,将一些合法后缀添加到该配置项,就会被攻击者利用,造成安全问题,如下: AddType application/x-httpd-php .php .jpg .txtNginx 跟php和nginx的配置有关,先看一下正常情况,向服务器上传图片马绕过白名单校验。 https://example.com/upload/test.jpg在图片马后添加一个不存在的php文件,如下: https://example.com/upload/test.jpg/123.php正常肯定返回404页面不存在,但是如果nginx错误配置try\_files,收到请求后,不会去检查文件是否存在,而是看是什么类型的文件,如果是php,直接交给php解释器去执行,php配置cgi.fix\_pathinfo,默认值为1,表示开启,0是关闭,正常情况,php解释器会检查123.php是否存在,如果不存则响应404,但是使用cgi.fix\_pathinfo配置后,如果123.php不存在,则会检查上一层目录是否存在,以此类推,检查到test.jpg时,发现test.jpg存在,php解释器就会把test.jpg当作php解析执行。 所以测试的时候,在图片马后加一个不存在的php文件判断就行了。 绕过 文件上传漏洞绕过总结,如下图: 图片 预防攻击 可以借鉴以下方式对文件上传漏洞进行防御和修复。 白名单校验 对文件后缀进行白名单校验,只允许上传白名单中被允许后缀的文件,例如只允许上传.jpg、.png类型文件。同时对MIME类型进行检查。 检查文件内容 对上传文件的内容进行严格检查,如果存在恶意代码,拒绝上传,检查文件名,防止覆盖文件,或者对上传到服务器的文件进行修改随即名处理。 上传目录权限 设置上传目录为只允许上传静态文件,避免上传目录拥有执行权,防止攻击者绕过防护上传WebShell到服务器执行。 使用安全框架 尽量使用安全的框架处理文件上传,如果使用自定义,请做好安全防护。 安全的中间件 使用最新版本的中间件,及时更新,正确配置,避免攻击者利用中间件解析漏洞进行文件上传漏洞利用。 总结 如果你对安全感兴趣,别忘了关注我们,持续为你带来最新的安全动态与技术分享!
技术教程
# PHP
# 网络安全
易航
1月10日
0
40
0
2025-01-10
PHP 内置对象是什么
PHP 内置对象是什么? 这个问题看似简单,实则暗藏玄机。简单来说,它们是 PHP 语言自带的、无需你额外声明就能直接使用的对象。但 “直接使用” 背后,是 PHP 运行时环境为你默默构建的一整套机制,理解它,能让你写出更优雅、更高效的 PHP 代码。 首先,咱们得明确一点,这些对象并非凭空出现。它们是 PHP 处理各种请求、管理资源、执行操作的基石。例如,你访问一个数据库,背后是 PDO 对象在默默工作;你处理用户上传的文件,$_FILES 数组(虽然是数组,但其底层依赖对象机制)帮你收集信息;甚至你写的每一个函数,都在 Closure 对象的庇护下运行。 深入挖掘,你会发现 PHP 内置对象大致可以分为几类: 一、与请求相关的对象 $_GET、$_POST、$_REQUEST、$_SERVER、$_COOKIE、$_SESSION,这些家伙们是处理 HTTP 请求的得力干将,它们分别从不同的渠道收集信息,将用户的请求转化为 PHP 能理解的数据结构。 别小看它们,安全问题往往就藏在对这些对象的处理中。例如,直接使用 $_GET 或 $_POST 的值而不进行任何过滤,很容易造成 SQL 注入或 XSS 攻击。 记住,永远不要相信用户输入,这是程序员的金科玉律。 二、与文件操作相关的对象 SplFileInfo、SplFileObject 等,它们是文件系统操作的利器,用它们可以更方便地处理文件和目录。与直接使用 fopen、fread 等函数相比,面向对象的方式更易于维护和扩展。 一个典型的例子:你需要遍历一个目录下的所有文件,SplFileInfo 可以让你轻松实现,避免了繁琐的循环和错误处理。 三、与数据库操作相关的对象 PDO (PHP Data Objects) 是连接数据库的标准接口。虽然它并非像 $_GET 那样可以直接使用,但它提供了访问各种数据库的统一方式。 使用 PDO 的好处在于,它帮你屏蔽了不同数据库之间的差异,让你只需关注 SQL 语句本身,而不用担心数据库连接细节。 但 PDO 的使用也有一些坑,比如参数绑定一定要做好,否则很容易造成 SQL 注入。 四、与异常处理相关的对象 Exception 及其子类,是处理程序错误的基石。抛出异常,优雅地处理错误,是写出健壮程序的关键。 别总是用 die() 或 exit() 来结束程序,学会使用异常处理,能让你的程序更易于调试和维护。 一个小例子,感受一下 SplFileInfo 的魅力: <?php $directory = new \RecursiveDirectoryIterator('./my_directory'); $iterator = new \RecursiveIteratorIterator($directory); foreach ($iterator as $file) { if ($file->isDir()) { echo "Directory: " . $file->getPathname() . PHP_EOL; } elseif ($file->isFile()) { echo "File: " . $file->getPathname() . " (" . $file->getSize() . " bytes)" . PHP_EOL; } } ?>这段代码简洁地遍历了一个目录及其子目录下的所有文件和目录,并打印出文件名和大小。 如果没有 SplFileInfo 和 RecursiveIteratorIterator,你得写一大堆代码来实现同样的功能。 最后,我想说,深入理解 PHP 内置对象,不仅能让你写出更优雅的代码,还能提升你的编程水平。它们是 PHP 运行时的核心组成部分,理解它们的工作机制,才能真正驾驭这门语言。 别只是停留在表面,去探索它们的内部细节,你会发现更多惊喜。
技术教程
# PHP
易航
1月10日
0
21
0
2025-01-10
JavaScript逆向时,常用的11个hook
在逆向分析JavaScript代码时,开发者经常使用一些用于hook(钩子)的技术来监视或修改程序的行为。以下是一些常用的hook技术及其示例代码。 01、dom操作 在JS逆向油猴脚本中,DOM操作是最常用的一种Hook方式。通过修改DOM元素的属性和样式,我们可以实现对网页的控制和修改。 // 修改DOM元素的属性 document.getElementById('elementId').setAttribute('attrName', 'attrValue'); // 修改DOM元素的样式 document.getElementById('elementId').style.property = 'value';02、Cookie操作 Cookie Hook 用于定位 Cookie 中关键参数生成位置,以下代码演示了当 Cookie 中匹配到了 \_\_dfp 关键字, 则插入断点: (function () { 'use strict'; var cookieTemp = ''; Object.defineProperty(document, 'cookie', { set: function (val) { if (val.indexOf('__dfp') != -1) { debugger; } console.log('Hook捕获到cookie设置->', val); cookieTemp = val; return val; }, get: function () { return cookieTemp; }, }); })(); (function () { 'use strict'; var org = document.cookie.__lookupSetter__('cookie'); document.__defineSetter__('cookie', function (cookie) { if (cookie.indexOf('__dfp') != -1) { debugger; } org = cookie; }); document.__defineGetter__('cookie', function () { return org; }); })();03、事件监听操作 事件监听也是JS逆向油猴脚本中常用的一种Hook方式。通过监听网页上的事件,我们可以触发自定义的操作和行为。 // 监听按钮点击事件 document.getElementById('buttonId').addEventListener('click', function() { // 自定义操作和行为 });04、AJAX拦截操作 AJAX拦截也是JS逆向油猴脚本中常用的一种Hook方式。通过拦截网页上的AJAX请求,我们可以实现对数据的控制和修改。 // 拦截AJAX请求 XMLHttpRequest.prototype._send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { // 自定义操作和行为 this._send.apply(this, arguments); };05、函数替换操作 函数替换也是JS逆向油猴脚本中常用的一种Hook方式。通过替换网页上的函数,我们可以实现对函数的控制和修改。 // 替换原有函数 var originalFunction = window.functionName; window.functionName = function() { // 自定义操作和行为 originalFunction.apply(this, arguments); };06、Header操作 Header Hook 用于定位 Header 中关键参数生成位置,以下代码演示了当 Header 中包含 Authorization 关键字时,则插入断点: (function () { var org = window.XMLHttpRequest.prototype.setRequestHeader; window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) { if (key == 'Authorization') { debugger; } return org.apply(this, arguments); }; })()07、URL操作 URL Hook 用于定位请求 URL 中关键参数生成位置,以下代码演示了当请求的 URL 里包含 login 关键字时,则插入断点: (function () { var open = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (method, url, async) { if (url.indexOf("login") != 1) { debugger; } return open.apply(this, arguments); }; })();08、JSON.stringify操作 JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.stringify() 时,则插入断点: (function() { var stringify = JSON.stringify; JSON.stringify = function(params) { console.log("Hook JSON.stringify ——> ", params); debugger; return stringify(params); } })();09、JSON.parse操作 JSON.parse() 方法用于将一个 JSON 字符串转换为对象,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.parse() 时,则插入断点: (function() { var parse = JSON.parse; JSON.parse = function(params) { console.log("Hook JSON.parse ——> ", params); debugger; return parse(params); } })();10、eval操作 JavaScript eval() 函数的作用是计算 JavaScript 字符串,并把它作为 脚本代码来执行。如果参数是一个表达式,eval() 函数将执行表达式。如果参数是 Javascript 语句,eval() 将执行 Javascript 语句,经常被用来动态执行 JS。以下代码执行后,之后所有的 eval() 操作都会在控制台打印输出将要执行的 JS 源码: (function() { // 保存原始方法 window.__cr_eval = window.eval; // 重写 eval var myeval = function(src) { console.log(src); console.log("=============== eval end ==============="); debugger; return window.__cr_eval(src); } // 屏蔽 JS 中对原生函数 native 属性的检测 var _myeval = myeval.bind(null); _myeval.toString = window.__cr_eval.toString; Object.defineProperty(window, 'eval', { value: _myeval }); })();11、Function操作 以下代码执行后,所有的函数操作都会在控制台打印输出将要执行的 JS 源码: (function() { // 保存原始方法 window.__cr_fun = window.Function; // 重写 function var myfun = function() { var args = Array.prototype.slice.call(arguments, 0, -1).join(","), src = arguments[arguments.length - 1]; console.log(src); console.log("=============== Function end ==============="); debugger; return window.__cr_fun.apply(this, arguments); } // 屏蔽js中对原生函数native属性的检测 myfun.toString = function() { return window.__cr_fun + "" } Object.defineProperty(window, 'Function', { value: myfun }); })();本文到此结束,感谢您的阅读,祝编程愉快!
技术教程
# Web前端
易航
1月10日
0
31
0
2025-01-10
我们的世界是虚拟的吗
很多人都认为我们生活的世界是虚拟的,它是由更高智慧的物种,用1跟0编程出来的,虚拟世界。 甚至有人提出,我们的世界就是高等智慧生物,设计出来的电子游戏。 乍一听这种理论感觉像是一种阴谋论,不过随着人类虚拟现实,与人工智能技术的发展,越来越多的人开始相信这种理论了。 也就是说,我们的世界是否是虚拟的,现在看来不仅仅是一种可能性,更是一种可能。 图片 根据埃隆.马斯克的说法,人类没有生活在虚拟世界的可能性,只有十亿分之一。如此小的概率,意味着我们的世界极,有可能不是真实存在的。 本期内容我们就剖析一下,我们的世界是否存在于一个巨大的模拟空间里面?并且,究竟存在什么样的理论? 其实在古代,世界是否虚拟这个问题就困扰了人类数千年,遍观全球,无论是多神论还是一神论的宗教,无一例外的都会宣称,有一位至高无上的神,从无到有的创造了我们所在的世界。 虽然我们在任何一本宗教典籍里,都无法检索到“模拟”或者“虚拟”两个词,但是,其概念几乎是一样的,就是一个伟大的实体从零开始设计了一个新的世界。 图片 可悲的是,这个所谓的伟大实体,极有可能是在欺骗我们。 17世纪法国哲学家勒内笛卡尔,在他的著作《第一哲学沉思录》当中,就假设了一个充满欺骗与邪恶的造物之神。它的终极目的就是为了困住人类,为此它专门设计了一个虚假的现实。 后来勒内的这个假设,被人们逐渐的演化成一个哲学思想实验,这就是著名的缸中大脑实验。 这个实验就是把人类的大脑从身体中取出,放置于一个盛满液体的容器当中。 图片 在这个容器里,会有一台电脑链接到大脑上,然后这台电脑会向大脑发送一定的模拟信号,使大脑可以感受到真实。 那么这里就有一个问题,从大脑的角度看它,如何分辨自己所感受到的是真的,还是模拟出来的呢? 如果我们把缸中大脑实验代入到我们的现实生活,想象一下,假如我们进入到一个虚拟现实的游戏里,戴上VR眼镜之后,我们发现面前呈现出来一座令人毛骨悚然的古老豪宅。 图片 随即,我们走入尘土飞扬的阴森走廊,我们可以听到腐朽的地板,发出的吱吱呀呀的响声。 空旷的空间,偶尔传来的声音,让我们脊背发凉,这是一种极度压迫神经的恐惧感,它看起来很真实,听起来也很真实,甚至感觉起来也是如此真实。 那么,我们会不会认为这个阴森的古老豪宅是真实的呢? 这个问题对我们来说很容易回答,我们所有人几乎都会认为它就是个游戏,由于我们提前明白了这个东西的原理,就绝对不会在主观上把这个阴森古老的豪宅当成真实的存在。 图片 换句话说,无论它如何真实,它在我们的主观意识当中都被认为是模拟出来的。 科技发展至今,人类已经可以模拟很多3D空间来欺骗我们的感官了,只不过也仅仅就是欺骗而已。因为人类自身知道的越多,就越不能将真实世界与虚拟世界混淆。 如我们如果知道,可以随时自由的离开虚拟世界,那么这种想法就一定会影响到我们的行为。 哪怕我们在虚拟世界里拥有各种情绪,也不能让大脑相信我们所处的虚拟世界是真实的。 图片 不过还是有一群科学家持反对意见,他们认为在幻觉足够强大的情形下,就算我们知道如何离开虚拟世界,也不会影响到我们的行为。 让我们重新回到那个阴森的豪宅,此时我们突然看到一个怪物从阴影当中出现。 它让我们极度惊悚,然后迅速摘掉VR,此刻,我们已经离开了虚拟世界,但是我们的心脏却由于惊吓会加速跳动。 呼吸的频率,也会越来越高,即使我们回到了现实世界,我们的大脑也需要一定的时间,才能准确的理解刚刚发生了什么事情。 图片 因为我们的大脑被那个虚拟世界中发生的事情给欺骗了,这种欺骗不是永久性的,仅仅会持续几分钟甚至更短的时间。 分不清的现实与虚拟,那么,人类是否可以创造一种永久性的欺骗? 这种欺骗可以让我们的大脑分不清现实与虚拟,也就是说,我们的大脑不会将一个空间视为真实,另一个空间视为虚假在大脑看来,这两种不同的环境都是平等的现实。 基于这个理论,我们设想下500年后的世界,这个年代中,虚拟与现实才是生活的日常。 两个世界几乎没有边界的融合在一起,VR眼镜也许已经进化成时尚的墨镜,通过它人类可以轻松的改变周围的世界。 图片 也许,只需要一个眼神,人类就可以快速的切换所处的世界。可能刚开始的时候,人类只是为了寻求感官的刺激。 但是随着感官刺激充斥在生活的各个角落里,可以断定人类一定会为了这些感官刺激,而纷纷逃避所谓的真实世界,从而放弃了真实的生活。 我们的大脑,的确是个高效且神奇的东西,只不过它不太完美,它很容易被愚弄,或者说被控制。 图片 500年后的虚拟世界与现实世界的融合,可能仅仅是欺骗大脑,使之完全不会区分现实世界与虚拟世界而已。 这种对五百年后虚拟世界的预测,看似很科幻,但它对研究我们现在的世界是否是虚拟的,提供了一定的理论支持,究竟有没有人或者某种高智慧生物在欺骗我们的大脑呢? 刚刚谈到的500年后的世界,人类毕竟还是使用工具进出不同的世界,只不过将VR变成了墨镜而已。 图片 墨镜可以让人类随意的穿梭在不同的环境里,真实还是虚拟,人类自由选择。 现在回到我们的世界,这个世界的我们是无法进行选择的,如果我们的世界的确是个虚拟的存在。 那在我们不知情的状况下,这个虚拟的世界必须达到什么条件才可以完全欺骗我们的大脑呢? 为了达成这个目的,它一定要干一件事情,那就是它可以通过某种方式,吸引到人类的感官,创造出来的环境,在任何一点上都完全可以欺骗大脑而没有BUG。 只要大脑发现了BUG,那么这个虚拟的世界立刻会受到严重的影响。 图片 现在的理论表示,至少有两种方式可以完全欺骗大脑,并且不会让大脑发现BUG。 第一种就是缸中大脑,我们每个人都只有大脑被放置在一个容器里。通过网络及相关信号,让我们所有人的意识,沉浸在一个虚假的世界当中。 真正属于我们的肉体,其实不在这个世界,只不过意识在不知不觉中被转移到了虚拟世界。 图片 第二种完全欺骗大脑的方式是,我们所有的存在,包括肉体与意识都是被模拟出来的。 这句话也就是在说世间的万事万物,精细到每一个原子,每一种力量,每一条自然法则都不是真实存在的。 也许,我们每个人都是某种意义上的NPC而已。请思考下这两种理论,我们的世界是否是虚拟的,也许跟这两种理论真的有关系。 图片 这代表着人类出生的时候就进入到模拟模式,一旦走到生命的终点,人类就会在某个神奇的地方醒来。此时可能还戴着VR,然后慢慢回忆起来真正的生活是什么样子。 这两个理论看起来貌似有点道理,但关于我们的世界,是否是虚拟的还有更合理的解释。 英国哲学家尼克博斯特罗姆,提出一种设想,他认为如果世界是虚拟的,现在的世界极有可能是后人类物种创造的一种祖先模拟。 图片 在他著名的文章《模拟假设》中,博斯特罗姆提出了三种可能的结论,根据他的说法,这三个结论中有一个且只有一个结论,会在遥远的未来被证明是正确的。 第一个结论,人类可能根本没有进入到后人类时代就灭绝了,换句话说就是,人类发展到一定阶段就已经不在了,科技并不那么发达。 那么没有后人类时代,就意味着没有人造的虚拟世界,这就代表着GAME OVER。 这个结论就代表着,我们没有生活在虚拟世界里。 图片 假如人类没有灭绝,并且顺利进入到科技发达的后人类时代,那么就会诞生第二个与第三个结论,第二个结论,是后人类对运行祖先模拟这种东西没有兴趣。 或者,后人类们根本就无视他们自身的过往,那么就根本不会创造虚拟世界。这就代表着,我们生活的世界是真实的而不是模拟出来。 但是,如果后人类对祖先模拟有兴趣呢?这就是博斯特罗姆的第三个结论,这代表着我们现在的世界就是背后人类模拟出来的。 图片 重点是后人类不仅仅模拟了我们现在生活的世界,可能同时模拟了成千上万个世界。 这极有可能就是平行宇宙的来源,也许,只有在我们这个世界里恐龙才是被小行星灭绝的,又或是只有我们这的火山爆发,才淹没了庞贝城。 如果博斯特罗姆的第三个结论是正确的,那么我们所认为的现实世界,极有可能是后人类实验当中的一个不起眼的尘埃而已。 图片 其实人类生活的世界是否是虚拟的,这一点对我们人类来说真的重要么?也许有更高等的生物的确把我们的大脑给欺骗了。 但是,我们的确感受到了真实,这种真实的感觉不就是我们所需要的么。 源于网络,侵删。
文创娱乐
易航
1月10日
0
22
0
2025-01-10
绝了!图片可以直接转成代码!开发是越来越简单了~~~
平时我们浏览网站的时候,经常会遇到令人眼前一亮的网页设计,如果能够将这些设计元素或者整个页面的布局应用到自己的项目中,那该多好? 今天就就给大家介绍一个能够将截图转为代码的开源项目 screenshot-to-code。 screenshot-to-code 项目通过结合先进的 AI 技术,为开发者和设计师提供了一个从视觉设计到代码实现的高效工具,极大地简化了开发流程。 screenshot-to-code 可以把界面设计截图直接转化为 HTML、CSS 或 JavaScript 代码,这样能帮助开发者快速生成网页前端代码。无论是一个按钮、一个导航栏,还是整个页面布局都能从截图转换成实际的前端代码。 图片 screenshot-to-code支持将截图转换成 HTML、Tailwind CSS、React 和 Vue 等现代技术栈的代码 。 用户还可以输入 URL 来克隆实时网站 。 图片 现在支持 Claude Sonnet 3.5 和 GPT-4o! {iframe src="https://www.lequxiang.com.cn/view.php/5bdd13d974c3bcdf09c0f55de4c58840.mp4" height="40vh"/} {iframe src="https://www.lequxiang.com.cn/view.php/d5ec12c1432e8e9fef6d375a2f452b99.mp4" height="40vh"/} 开源地址:隐藏内容,请前往内页查看详情 官方网站:隐藏内容,请前往内页查看详情 支持的技术栈: HTML + Tailwind HTML + CSS React + Tailwind Vue + Tailwind Bootstrap Ionic + Tailwind SVG 支持的人工智能模型: Claude Sonnet 3.5 GPT-4o DALL-E 3 或 Flux Schnell(使用 Replicate)用于图像生成 此外,该项目还可以将网站的视频/录屏转换成网页,演示如下: {iframe src="https://www.lequxiang.com.cn/view.php/603a2aed15a31b1962e64eee146f299e.mp4" height="50vh"/} 安装使用 该项目使用 React/Vite 作为前端, FastAPI 作为后端。 需要 GPT-4 的 OpenAI API 密钥或 Anthropic 密钥(可选), 推荐两者都使用,以便你可以比较 Claude 和 GPT4o 的结果。 运行后端(使用 Poetry 进行包管理 - 如果你没有它,请安装 pip install poetry): cd backend echo "OPENAI_API_KEY=sk-your-key" > .env echo "ANTHROPIC_API_KEY=your-key" > .env poetry install poetry shell poetry run uvicorn main:app --reload --port 7001OpenAI API 密钥也可以通过前端的对话框设置密钥(加载前端后点击齿轮图标)。 图片 运行前端: cd frontend yarn yarn dev然后打开 http\://localhost:5173 就可以开始使用了。 图片 如果要使用其他端口,请更新 frontend/.env.local 中的 VITE\_WS\_BACKEND\_URL 配置选项。 如果你不想浪费 GPT4-Vision,你可以在模拟模式下运行后端: MOCK=true poetry run uvicorn main:app --reload --port 7001Docker 上安装 如果你已经安装了 Docker,可以使用 docker-compose 命令启动该项目: echo "OPENAI_API_KEY=sk-your-key" > .env docker-compose up -d --build接下来就可以在浏览器中打开 http://localhost:5173 使用了。 图片
源码资源
技术教程
# Web前端
# Python
# TypeScript
易航
1月10日
0
112
3
2025-01-08
getHTML() - 替代 innerHTML 的最佳方法
随着所有主流浏览器现已支持 getHTML() 方法,前端开发者有了一个强大的新工具来操作DOM。本文主要探讨 getHTML()的独特优势,特别是在处理Shadow DOM时的卓越表现。 getHTML()与innerHTML的异同 getHTML()和 innerHTML 的 getter 在基本功能上相似,都返回元素内部DOM树的HTML表示。但getHTML()的真正优势在于它能够包含Shadow DOM的HTML,而innerHTML则完全忽略Shadow DOM。 getHTML()的高级用法 getHTML()接受一个可选的options对象参数,通过适当的选项可以获取完整的HTML,包括Shadow DOM: const container = document.body; const host = createDiv(123); const root = attachShadowDOM(host); container.append(host); console.log(container.getHTML({ shadowRoots: [root] }));图片 这段代码会返回包含声明式Shadow Root的完整HTML: <div> <template shadowrootmode="open"> <p>Paragraph <slot>default</slot></p> </template> 123 </div>如果在浏览器中将返回的 上面的 HTML 作为新页面打开,则会再现原始 DOM 树: 图片 通常,shadow trees和slots是在自定义元素的构造函数中创建的,但为了保持上面和下面示例页面中的代码简单,这里没有创建任何自定义元素。相反,使用了两个辅助函数: // shared.js export function attach(host) { const shadowRoot = host.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<p>Paragraph <slot>default</slot></p>'; return shadowRoot; } export function div(n) { const el = document.createElement('div'); if (n) el.innerHTML = n; return el; }div(n)创建一个新的div元素,里面包含数字n,例如<div>123</div>,而attach(host)将HTML为<p>Paragraph <slot>default</slot></p>的shadow树附加到host元素上。为了用常见情况挑战getHTML(),div中的数字123被分配到shadow DOM的slot中。 处理嵌套的Shadow DOM 在上面的页面中,getHTML()被调用时使用了所有两个可能的选项: <script type="module"> import { attach, div } from './shared.js'; const container=document.body; const host=div(123); const root=attach(host); container.append(host); console.log('>innerHTML',container.innerHTML); console.log('>getHTML',container.getHTML()); console.log('>getHTML2',container.getHTML({ serializableShadowRoots: true })); console.log('>getHTML3',container.getHTML({ shadowRoots: [root] })); </script>options对象可以有两个属性:serializableShadowRoots和shadowRoots。 当getHTML()在没有options的情况下被调用时,Shadow DOM会被忽略,就像在innerHTML中一样。 如果serializableShadowRoots为true,HTML将包括具有serializable属性设置为true的shadow roots。这样的roots通常不应该存在,因为serializable是与getHTML()一起引入的,默认情况下它是false。 要获取shadow roots的HTML,需要在shadowRoots属性中提供要序列化的shadow roots。当shadow roots是open的时候,可以很容易地递归检索网页中的所有shadow roots。在网页上下文中无法检索closed shadow roots,但可以在浏览器扩展注入的内容脚本中检索。 提供的shadow roots不一定会被序列化。在下一个示例页面中,创建了两个shadow trees。第二个shadow DOM嵌套在第一个中: <script type="module"> import { attach, div } from './shared.js'; const container=document.body; const host=div(123); const root=attach(host); container.append(host); const host2=div(456); const root2=attach(host2); container.append(host); root.append(host2); console.log('>innerHTML',container.innerHTML); console.log('>getHTML',container.getHTML()); console.log('>getHTML2',container.getHTML({ serializableShadowRoots: true })); console.log('>getHTML3',container.getHTML({ shadowRoots: [root] })); console.log('>getHTML4',container.getHTML({ shadowRoots: [root2] })); console.log('>getHTML5',container.getHTML({ shadowRoots: [root,root2] })); </script>如果第一个shadow DOM不包含在options中,getHTML()不会返回第二个shadow DOM的HTML: 要被序列化,shadow roots需要直接连接到要被序列化的DOM。如果省略了父shadow root,嵌套的shadow root也不会被序列化。 getHTML 局限性 缺少outerHTML等价物:目前还没有获取包含元素自身在内的HTML的方法。 单根元素限制:getHTML()返回的HTML如果没有单一根元素,浏览器可能无法正确解析为声明式Shadow DOM。 封闭的Shadow DOM:在网页上下文中无法获取封闭的Shadow DOM,但可以通过浏览器扩展的内容脚本来实现。 结语 getHTML()为开发者提供了一种强大的方法来处理包含Shadow DOM的复杂DOM结构。虽然它有一些限制,但在处理现代Web组件和复杂UI时,getHTML()的优势是显而易见的。随着Web组件的普及,掌握getHTML()将成为前端开发者的重要技能。 在实际开发中,getHTML()可以用于创建更精确的DOM快照、调试复杂的组件结构,以及在需要保留Shadow DOM结构的情况下序列化页面内容。随着Web标准的不断发展,我们可以期待看到更多类似getHTML()这样的强大API,进一步增强前端开发的能力和灵活性。
技术教程
# Web前端
易航
1月8日
0
24
0
2025-01-08
超高级的CSS印章效果!附源码!!
效果预览 图片 源码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>印章</title> </head> <body> <canvas id="canvas" width="250" height="250"></canvas> </body> <script> function createSeal(id, company, name) { var canvas = document.getElementById(id); var context = canvas.getContext('2d'); // 绘制印章边框 var width = canvas.width / 2; var height = canvas.height / 2; context.lineWidth = 7; context.strokeStyle = "#f00"; context.beginPath(); context.ellipse(width, height, 110, 80, 0, 0, Math.PI * 2); // 修改为椭圆形 context.stroke(); // 绘制印章中间的文字 context.font = '16px Helvetica'; context.textBaseline = 'middle';//设置文本的垂直对齐方式 context.textAlign = 'center'; //设置文本的水平对对齐方式 context.lineWidth = 1; context.fillStyle = '#f00'; context.fillText('全国二次元聚集地', width, height + 5); // 绘制印章名称 context.font = '20px Helvetica'; context.textBaseline = 'middle';//设置文本的垂直对齐方式 context.textAlign = 'center'; //设置文本的水平对对齐方式 context.lineWidth = 1; context.fillStyle = '#f00'; context.fillText(name, width, height + 55); // 绘制印章单位 context.translate(width, height); // 平移到此位置 context.font = '18px Helvetica'; var count = company.length; // 字数 var angle = 2 * Math.PI / 15; // 修改为围绕椭圆的角度 var radiusX = 86; // 椭圆的X半径 var radiusY = 60; // 椭圆的Y半径 var chars = company.split(""); var c; var startAngle = Math.PI / 1; // 设置开始文字的角度 for (var i = 0; i < count; i++) { c = chars[i]; // 需要绘制的字符 var currentAngle = startAngle + angle * i; // 当前字符的角度 var x = radiusX * Math.cos(currentAngle); // X坐标 var y = radiusY * Math.sin(currentAngle); // Y坐标 context.save(); context.translate(x, y); // 平移到字符位置 context.rotate(currentAngle + Math.PI / 2); // 旋转字符 context.fillText(c, 0, 0); // 绘制字符 context.restore(); } } createSeal('canvas', '全国统一发票监制章', '749局'); </script> </html>
技术教程
# Web前端
易航
1月8日
0
76
0
2025-01-04
用 iframe 必定遇到过这六种“坑”之一(以 vue 为示例)
前言 如果你是做web前端,那么不可避免早晚都会用到iframe的。其实博主很久前用过,但最近又要有项目用了,由于年代久远对iframe的注意事项都有点忘记了,然后想着总结一下比较需要注意的几个重点事项,除了便于高效工作还能分享给有需要用到iframe的小伙伴。 iframe基于父窗口大小自适应宽高 「简述:」 这是iframe最常见的需求了,有时候我们用iframe嵌入一个页面时,不想固定宽高想跟随父系统屏幕大小动态变化,从而大大提高适配性。 「实现思路:」 iframe标签绑定:style 来动态设置宽高,监听父窗口宽高变化时动态获取并且绑定到:style,但监听变化需要考虑到初始化和窗口缩放的情况,并且记得移除事件监听器防止内存泄漏。 「完整实现代码如下所示」 <template> <div class="box" @resize="iframeResize"> <iframe :src="iframeSrc" :style="{ width: '100%', height: frameHeight + 'px' }" ref="myRef"></iframe> </div> </template> <script setup> import { onMounted, onUnmounted, ref, watchEffect } from 'vue'; const myRef = ref(null); const iframeSrc = 'https:******.com'; const frameHeight = ref(0); // 调整iframe的高度的方法 function initHeight() { if (myRef.value) { frameHeight.value = window.innerHeight; } } // 窗口大小变化触发 function iframeResize() { initHeight(); } // 移除事件监听器, 防止内存泄漏 onUnmounted(() => { window.removeEventListener('resize', iframeResize); }); // 在组件挂载时先获取一次iframe高度 onMounted(() => { initHeight(); window.addEventListener('resize', iframeResize); }); // 时刻监听变化,防止iframeRef没有赋值 watchEffect(() => { if (myRef.value) { initHeight(); } }); </script> <style scoped> .box { position: relative; width: 100%; height: 100vh; } </style>iframe基于内容动态宽高 「简述:」 例如我们业务需求嵌入的是一个表格而不是一个页面,并且表格高度并不确定时我们不能固定iframe的高度,否则只有一条内容或者没有内容的时候会不好看,这里要根据内容的数量去决定ifram嵌入窗口的高度。 「实现思路:」 思路是子窗口通信告诉父窗口具体高度,然后父窗口再动态设置高度即可。具体实现是子窗口利用 window.postMessage 来发送具体高度,然后父窗口用window.addEventListener('message', 方法)接收内容,从而根据接收到的内容动态调整iframe的高度。 「子窗口(被嵌入页面)代码」 <script setup> window.onload = function () { let height = '想要告诉父窗口的高度' if (window.parent && window.parent.postMessage) { window.parent.postMessage({ height: height }, '*'); } }; </script>「父窗口代码」 <template> <div> <iframe :src="iframeSrc" ref="myRef"></iframe> </div> </template> <script setup> import { onMounted, ref } from 'vue'; const iframeSrc = 'https:******.com'; const myRef = ref(null); onMounted(() => { window.addEventListener('message', iframeMessage); }); const iframeMessage = (event) => { // 验证消息来源,确保安全性 if (event.origin !== 'https:******.com') return; const newHeight = event.data.height; if (newHeight && myRef.value) { myRef.value.style.height = `${newHeight}px`; } }; </script>「注意:」 if (event.origin !== 'https:******.com') return 这行代码记得加上。 iframe嵌入页面免登录处理 这个小弟有单独写过相关详细文章,并且分析了几种情况的处理方式,移步:iframe嵌入页面实现免登录 http无法嵌入https 「简述:」 例如开发环境是HTTP,嵌入的环境是生产环境的HTTPS,这时就会发现这个问题了。其实是现代浏览器的安全机制,会认为是跨域不同源而禁止。因为HTTP协议传输的数据未加密的会有安全风险。 「解决思路:」 网上五花八门的方法,但其实真正靠谱的就两种,(1)将 HTTP 转换成 HTTPS (2)使用代理服务。 方法一:将 HTTP 转换成 HTTPS(推荐) 1、获取 SSL 证书并安装 方法不止一种,这里就细说啦。 2、配置服务器(这里以nginx为例) 下面我把核心部分解释放在注释说明,这是简单版本用于说明HTTP转HTTPS,如果真的上生产其实还有不少其它配置。 server { listen 80; // 监听http默认的80端口 return 301 https://$host$request_uri; // 把所有http永久重定向到https server_name ****.com www.****.com; // 指定域名,这里视真实情况而定 } server { listen 443 ssl; // 监听https默认的443端口。 server_name ****.com www.****.com; // 指定域名,这里视真实情况而定 // 指定 SSL 证书文件路径。 ssl_certificate /etc/lets/live/****.com/fullchain.pem; // 指定 SSL 私钥文件路径 ssl_certificate_key /etc/lets/live/****.com/privkey.pem; location / { index index.html index.htm; root /var/w/html; } }方法二:使用代理服务 用 nodejs 搭建个简单的代理服务器(这里是用 nodejs 举例,真实业务场景可能是后端那边搞) 要先安装对应的依赖,例如 npm init -y npm install express http-proxy然后再配置对应的代理服务器,主要核心是下面四个模块 express:作用是构建 Web 应用。 http:作用是处理 HTTP 请求。 https:作用是处理 HTTPS 请求。 httpProxy:作用是创建代理服务器。 const express = require('express'); const fs = require('fs'); const https = require('https'); const http = require('http'); const httpProxy = require('http-proxy'); const app = express(); const proxy = httpProxy.createProxyServer(); const port = 3000; // 读取对应的SSL证书文件 const options = { key: fs.readFileSync('/etc/lets/live/proxy.****.com/privkey.pem'), cert: fs.readFileSync('/etc/lets/live/proxy.****.com/fullchain.pem') }; // 设置路由信息 app.all('/proxy/*', (req, res) => { const targetUrl = `http://${req.params[0]}`; proxy.web(req, res, { target: targetUrl }, (error) => { res.status(500).send('Proxy request failed'); }); }); // 创建HTTPS服务器 const server = https.createServer(options, app); // 启动代理服务器 server.listen(port, () => { console.log(`HTTPS`); });跨域问题 「简述:」 iframe 页面的跨域问题是因为涉及到浏览器的安全策略,即同源策略。同源策略限制了一个网页脚本不能读写不同源页面的 DOM 与 Cookie之类的信息。即如果 iframe 中的内容与包含它的页面不在同一个源上,那么这两个页面之间会受到跨域限制。 「解决思路:」 1、使用 window.postMessage 实现跨域通信 父页面代码: 主要用window.addEventListener监听消息用postMessage发送消息。 「注意」: @load加载完成后再监听和window.removeEventListener取消监听这两个细节。 <template> <div> <iframe :src="iframeSrc" ref="iframeRef" @load="onIframeLoad" style="width: 100%; height: 400px;"></iframe> <button @click="sendMessage">发送消息</button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; onMounted(() => { window.addEventListener('message', handleMessage); // 在组件卸载时移除事件监听器 return () => { window.removeEventListener('message', handleMessage); }; }); const iframeSrc = 'http://***.com'; const iframeRef = ref(null); // 当 iframe 加载完成后,再设置监听器 const onIframeLoad = () => { window.addEventListener('message', handleMessage); }; const sendMessage = () => { const iframe = iframeRef.value; if (iframe.contentWindow) { iframe.contentWindow.postMessage('Hello!', 'http://***.com'); } }; const handleMessage = (event) => { // 确保来自想要的源才处理消息 if (event.origin !== 'http://***.com') return; console.log(event.data); }; </script>子页面代码:和父页面一样,用window.addEventListener监听消息用postMessage发送消息。 <template> <div> <button @click="sendMessage">发送消息到父页面</button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; onMounted(() => { window.addEventListener('message', handleMessage); // 在组件卸载时移除事件监听器 return () => { window.removeEventListener('message', handleMessage); }; }); const sendMessage = () => { const parentWindow = window.parent; if (parentWindow) { parentWindow.postMessage('Hello!', 'http://****.com'); } }; const handleMessage = (event) => { if (event.origin !== 'http://****.com') return; console.log(event.data); }; </script>2、使用 document.domain document.domain用于解决二级域名之间跨域问题的方法,例如:a.tty.com 和 b.tty.com,它们都属于同一个顶级域名 tty.com,这时就适合用document.domain来让这两个页面能够相互访问。用法相当于简单,就是分别设置两个页面的document.domain。 核心代码在第10与19行。 <template> <div> <iframe :src="iframeSrc" @load="onIframeLoad" ref="iframeRef"></iframe> </div> </template> <script setup> import { ref } from 'vue'; document.domain = 'tty.com'; // 设置顶级域名 const iframeRef = ref(null); const iframeSrc = 'http://b.tty.com'; const onIframeLoad = () => { const iframe = iframeRef.value; if (iframe.contentWindow) { // 设置iframe的 document.domain iframe.contentWindow.document.domain = 'tty.com'; } }; </script>3、使用 CORS 这里主要是后端的配置了,通过调整服务器响应头中的 Access-Control-Allow-Origin 来控制哪些源是可以安全访问资源。 以为nginx为例,*设置为所有。 http { server { listen 80; server_name yourdomain.com; # 替换为你的域名 # 代理 iframe 请求并添加 CORS 头部 location /iframe-proxy/ { # 添加CORS头部 add_header Access-Control-Allow-Origin *; # 其他配置... } } }4、nginx配置代理 算是常见解决方案了,思路是通过 Nginx 反向代理,将请求重定向到想要请求的目标服务器。 核心就是第10行代码,具体可以特意去看看nginx。 http { server { listen 80; server_name yourdomain.com; # 替换为你的域名 # 代理 iframe 请求并添加 CORS 头部 location /iframe-proxy/ { # 将请求代理到目标 proxy_pass http://tty.com/; # 其他配置... } } }iframe嵌入后报拒绝连接请求 不知道你用iframe有没有见过这个页面,这通常是目标页面设置了 X-Frame-Options 响应头来限制内容被嵌入到其他站点的 iframe 中。这个可以找后端看看 X-Frame-Options 。 图片 小结 都是把遇到的场景总结了一下,感觉都是比较常见的情况。 如果大佬们有什么 iframe 的 “坑” 也可以分享一下我同步学习一下,还有那里写的不好也可以指出更正鸭
技术教程
# Web前端
# Vue
易航
1月4日
0
117
0
2025-01-04
掌握 PHP 静态成员:self::, parent::, static:: 详解
图片 在 PHP 中,静态成员(包括方法和属性)直接隶属于类,而非类的实例对象。这意味着我们无需实例化对象,就能直接访问类的静态成员。这一特性在需要跨对象共享数据或功能时尤为有用。 PHP 提供了 self::,parent:: 和 static:: 三个关键字来访问静态成员,它们各自拥有不同的工作机制,尤其是在继承关系中。本文将深入解析这三个关键字的运作原理,并结合实例阐明它们之间的区别。 何时使用静态成员 模拟全局变量: 将静态属性视为类内部的全局变量,所有实例都能共享访问。 提供工具方法: 静态方法适用于提供独立于具体对象实例的实用功能。 定义类常量: 使用静态属性定义类级别常量,确保其值在整个应用生命周期内保持不变。 实现单例模式: 静态方法和属性是实现单例模式(确保一个类只有一个实例)的关键要素。 调用静态方法 要调用静态方法,请使用 :: 运算符,后跟方法名称。 以下是示例: class MyClass { public static function greet() { echo "Hello, world!"; } } MyClass::greet(); // 输出: Hello, world!调用静态属性 要访问静态属性,也可以使用 :: 运算符,后跟属性名称。 以下是示例: class MyClass { public static $count = 0; public static function incrementCount() { self::$count++; } } MyClass::incrementCount(); echo MyClass::$count; // 输出: 1 Use code with caution.三个关键词:self::、、parent::和static 1. self:: self:: 关键字始终指向 代码编写的类 本身,不考虑任何继承关系。这意味着即使子类重写了父类的静态方法或属性,self:: 仍然会引用父类中定义的版本。 2. parent:: parent:: 关键字用于从 直接父类 中调用静态方法或属性。它会绕过子类中任何重写的方法或属性,确保使用的是 父类 的版本。 3. static:: static:: 关键字与 self:: 类似,但它引入了 后期静态绑定 机制。这意味着 static:: 会根据运行时环境动态地绑定到最 派生类 中的静态方法或属性,即使调用代码位于父类中。 举例说明差异 让我们看看这些关键字在具有继承的 PHP 程序中是如何表现的。 示例 1:使用self class A { public static function sayHello() { return "Hello from A"; } public static function test() { return self::sayHello(); } } class B extends A { public static function sayHello() { return "Hello from B"; } } echo B::test(); // 输出: "Hello from A"在这个例子中,self::sayHello() 语句出现在类 A 的代码中,因此 self:: 指向的是类 A 本身。 尽管类 B 重写了 sayHello() 方法,但由于 self:: 的绑定机制,程序仍然会调用 父类 A 中的 sayHello() 方法,最终输出 "Hello from A"。 示例 2:使用parent class A { public static function sayHello() { return "Hello from A"; } } class B extends A { public static function sayHello() { return parent::sayHello() . " and B"; } } echo B::sayHello(); // 输出: "Hello from A and B"在这个例子中,类 B 中的 parent::sayHello() 语句明确指示调用 父类 A 的 sayHello() 方法。因此,程序会先输出父类 A 中的消息,然后拼接上类 B 自身的消息,最终输出 "来自 A 和 B 的问候"。 示例 3:使用static class A { public static function sayHello() { return "Hello from A"; } public static function test() { return static::sayHello(); } } class B extends A { public static function sayHello() { return "Hello from B"; } } echo B::test(); // 输出: "Hello from B"这段代码中,static::sayHello() 语句位于类 A 中,但由于 static:: 支持后期静态绑定,它会指向 运行时确定的最底层派生类,也就是类 B。最终,程序调用的是类 B 中的 sayHello() 方法,输出 "Hello from B"。 主要区别 self::: 引用当前代码所在的类,不考虑继承关系。 当我们希望子类重写方法时,父类中的调用不受影响,就可以使用 self::。 parent::: 专门用于调用父类中的方法或属性,即使子类进行了重写。 当我们需要在子类中扩展父类的功能,但仍然希望保留对父类原始方法的访问权限时,就可以使用 parent::。 static::: 实现后期静态绑定,根据运行时环境动态绑定到最派生类的方法或属性。 当我们希望方法的行为能够根据调用它的类动态调整时,就可以使用 static::。 深入理解 self::,parent:: 和 static:: 之间的区别,有助于我们编写更加健壮、易于维护的面向对象 PHP 代码,尤其是在处理复杂的继承关系时。
技术教程
# PHP
易航
1月4日
0
37
0
上一页
1
...
4
5
6
...
28
下一页