Closed masatokinugawa closed 11 hours ago
First of all, truly amazing (hats off @securityMB).
Can you help me understand something - is the fetching of a font required to be external AFAYU? Or would this work similarly by forming the font on the client side without having to reach out to external servers?
Understanding this will help me determine the criticality level of this attack and what requirements must be met for attackers to succeed (a need to communicate with a server and load fonts is more likely to meet limitations such as network-CSP than a local-only attack)
Thanks again for toying around with LavaDome @masatokinugawa!
Drive-by comment: I think it theoretically should be possible to create the font client-side.
Masato used https://www.npmjs.com/package/svg2ttf in the proof-of-concept and if this library can also work in the client-side JS, then this attack should work without having to contact any server.
That said, I think you can still contain the attack with CSP since with local attacks you would probably still need either data:
or blob:
URLs to be able to load the font.
Exactly what I was thinking, just wanted to make sure my intuition was correct. Thank you.
So with proper font-src CSP configuration it should be very straight forward to mitigate this vector - would you agree?
Because honestly, I think setting up a well-thought font-src
directive is a very fair requirement from products that are willing to adopt LavaDome into their apps. If adding this requirement into the README under usage
will be the mitigating force of LavaDome, this might be the course of action we will chose to take.
Unlike other modern browsers, Safari supports SVG fonts. This allows using Michał's trick with SVG only, without converting fonts to TTF, WOFF, etc.: https://mksben.l0.cm/2021/11/css-exfiltration-svg-font.html
Or would this work similarly by forming the font on the client side without having to reach out to external servers?
In the case of SVG fonts, all font components can be defined by putting them to HTML directly, so there is no need to fetch external URLs. Therefore, using Safari + SVG fonts, it still should work even if the strict CSP font-src
is configured.
UPDATE: never mind, this seems to still work...
Say @masatokinugawa , any chance Safari dropped support for SVG font-face?
Am I missing something?
Because if they did, this will save me a lot of trouble
One more question, if I may @masatokinugawa,
Trying to study your research - is it true to assume, that while forming fonts in Safari does not require external connection (thanks to SVG), this isn't the case for the part where the secret is being leaked, in which external communication is necessary - right?
In other words - using this SVG technique in Safari will only work by leaking each char to a remote server, and there's no way to access this leakage in the client without needing a remote server, if I understand correctly.
And if so, if I manage to implement strict network CSP to my app which successfully enforces a whitelist of domains it can communicate with, this should mitigate your SVG attack - is this right in your opinion?
I'm saying this because it seems from your research that the leakage of the secret happens using background: url
, correct?
So for example, would you agree that Content-Security-Policy: default-src: "self"
is enough to block this attack?
In my blog article, I assume that an attacker can only inject CSS and can't execute JS. That is the reason why I used image requests for the leak.
In situations where an attacker can execute JS, they don't need to use images, just look at the scrollWidth
property as I did in the PoC above. Therefore, even if strict CSP is used, the leakage is still possible.
Although I haven't been able to create a working SVG font PoC for LavaDome yet because Safari does not apply the ::first-line
properly, at least I confirmed that the ligature is actually created and the scrollWidth
is changed, so all we need to do should be adjust the PoC for Safari.
I think I understand your PoC and general approach better now @masatokinugawa.
I think I made some progress on your idea with SVG instead of font against Safari, where I manage to successfully capture only a single char but not the rest.
I can see why first-line
would've completed the bypass for you, but I too can't seem to make it work in Safari.
Any ideas on how to continue this? Couldn't find any other pseudo elements/classes to use here instead.
setTimeout(() => {
const container = document.body.appendChild(document.createElement('div'));
const defaultWidth = document.body.scrollWidth;
const secretChars = "0123456789abcdef";
let index = 0;
let foundChars = "";
const style = document.createElement('style');
document.body.appendChild(style);
style.innerHTML = `#PRIVATE {
font-size:0;
width:0;
word-wrap: break-word
}
#PRIVATE:is(*) {
font-family:hack;
font-size:100px
}`;
const xxx = () => {
if (defaultWidth < document.body.scrollWidth) {
foundChars += secretChars[index];
console.log(`Found: ${foundChars}`);
index = 0;
} else {
index++;
}
if (foundChars.length === 32) {
alert(foundChars);
} else {
loadFont(`${foundChars}${secretChars[index]}`);
}
};
const loadFont = target => {
setTimeout(xxx, 1000);
console.log('load font:', target);
container.innerHTML = `
<svg>
<defs>
<font horiz-adv-x="0">
<font-face font-family="hack" units-per-em="1000" />
<glyph unicode="${escape(target)}" horiz-adv-x="99999" d="M1 0z"/>;
</font>
</defs>
</svg>`;
};
loadFont(secretChars[index]);
}, 3000);
Since #47 is merged, the responsibility to mitigate this attack surface officially shifts to the developer since it requires CSP.
That leaves us with Safari only, where CSP can be bypassed using SVG fonts instead (theoretically).
(Removing chrome
& firefox
labels)
The reason why the SVG font leak doesn't work well seems to be because Safari doesn't create ligatures when elements are separated. e.g.:
<!-- Safari can create "AB" ligature -->
<span>AB</span>
<!-- In this case, can't -->
<span>A</span><span>B</span>
I haven't found a way to leak all characters well yet. The browser's behavior with regards to ligatures seems quite inconsistent :(
I salute your effort and willingness to help either way @masatokinugawa, thanks for your research, you helped significantly making LavaDome safer 🫡
I'm closing this for now, not because this vector is impossible, but because I prefer to only leave issues open when they indicate a practical way to perform a bypass, as opposed to theoretical only.
By creating ligature fonts having large width, applying it to the secret and detecting the change of the
scrollWidth
property, the secret can be leaked. The basic idea comes from https://research.securitum.com/stealing-data-in-great-style-how-to-use-css-to-attack-web-application/ by @securityMB.npm install
andnode index.js