Inspired by Anubis last week, I came up with an idea to detect if a client is a browser using CSS animations and image lazy loading. I spent a few hours writing a simple PoC, and it works!
CSSWAF places random hidden empty.gif images in CSS animation progress, allowing the browser to play these images one by one in order.
...
<style>
@keyframes csswaf-load {
` + func(expectedSequence []string) string {
lines := []string{}
for i, img := range expectedSequence {
f := float64(i) / float64(len(expectedSequence))
lines = append(lines, strconv.Itoa(int(f*100))+`% { content: url('/_csswaf/img/`+img+`?sid=`+sessionID+`'); }`)
}
lines = shuffle(lines)
return strings.Join(lines, "\n")
}(expectedSequence) + `
}
.csswaf-hidden {
width: 1px;
height: 1px;
position: absolute;
top: 0px;
left: 0px;
animation: csswaf-load ` + strconv.FormatFloat(cssAnimationTS, 'f', -1, 64) + `s linear infinite;
}
</style>
...
<div class="csswaf-hidden"></div>The backend measures the loading order. If the loading order is correct, it passes the request to the target server. Otherwise, 🙅.
...
// Check if sequence matches expected sequence
expectedSeqTTL := st.expected.Get(sessionID)
var expectedSeq []string
if expectedSeqTTL != nil {
expectedSeq = expectedSeqTTL.Value()
}
if expectedSeq != nil && len(sequence) == len(expectedSeq) {
match := true
for i := range sequence {
if (sequence)[i] != (expectedSeq)[i] {
match = false
break
}
}
st.validated.Set(sessionID, match, ttlcache.DefaultTTL)
...CSSWAF places some honeypot empty.gif files in HTML <img> tags but instructs the browser not to load them. If someone loads the honeypot GIFs, 🙅.
lines = append(lines, `<img src="/_csswaf/img/`+img+`?sid=`+sessionID+`" style="width: 0px; height: 0px; position: absolute; top: -9999px; left: -9999px;" loading="lazy">`)CSSWAF also places some unvisible <a> tags in HTML, if someone clicks the honeypot links, 🙅.
.honeya {
display: none;
width: 0px;
height: 0px;
position: absolute;
top: -9898px;
left: -9898px;
}
lines = append(lines, "<a href='/_csswaf/img/"+img+"?sid="+sessionID+"' class='honeya'>View Content</a>")Inspired by Anubis last week, I came up with an idea to detect if a client is a browser using CSS animations and image lazy loading. I spent a few hours writing a simple PoC, and it works!
CSSWAF places random hidden empty.gif images in CSS animation progress, allowing the browser to play these images one by one in order.
...
<style>
@keyframes csswaf-load {
` + func(expectedSequence []string) string {
lines := []string{}
for i, img := range expectedSequence {
f := float64(i) / float64(len(expectedSequence))
lines = append(lines, strconv.Itoa(int(f*100))+`% { content: url('/_csswaf/img/`+img+`?sid=`+sessionID+`'); }`)
}
lines = shuffle(lines)
return strings.Join(lines, "\n")
}(expectedSequence) + `
}
.csswaf-hidden {
width: 1px;
height: 1px;
position: absolute;
top: 0px;
left: 0px;
animation: csswaf-load ` + strconv.FormatFloat(cssAnimationTS, 'f', -1, 64) + `s linear infinite;
}
</style>
...
<div class="csswaf-hidden"></div>The backend measures the loading order. If the loading order is correct, it passes the request to the target server. Otherwise, 🙅.
...
// Check if sequence matches expected sequence
expectedSeqTTL := st.expected.Get(sessionID)
var expectedSeq []string
if expectedSeqTTL != nil {
expectedSeq = expectedSeqTTL.Value()
}
if expectedSeq != nil && len(sequence) == len(expectedSeq) {
match := true
for i := range sequence {
if (sequence)[i] != (expectedSeq)[i] {
match = false
break
}
}
st.validated.Set(sessionID, match, ttlcache.DefaultTTL)
...CSSWAF places some honeypot empty.gif files in HTML <img> tags but instructs the browser not to load them. If someone loads the honeypot GIFs, 🙅.
lines = append(lines, `<img src="/_csswaf/img/`+img+`?sid=`+sessionID+`" style="width: 0px; height: 0px; position: absolute; top: -9999px; left: -9999px;" loading="lazy">`)CSSWAF also places some unvisible <a> tags in HTML, if someone clicks the honeypot links, 🙅.
.honeya {
display: none;
width: 0px;
height: 0px;
position: absolute;
top: -9898px;
left: -9898px;
}
lines = append(lines, "<a href='/_csswaf/img/"+img+"?sid="+sessionID+"' class='honeya'>View Content</a>")