Perhaps you could use a basic CSRF approach and only load the font once per request, creating session keys that expire after one use and checking the referrer (which could possibly break if user is emptying there's on subsequent requests).
But for something todo ;p I've put together an example of what I mean, its not 100%, but it will stop the general public from trying to directly access the font. download the source example
Using this method, to get the font you would need to:
- Make a request to the page, without loading the css font. (fgc,curl,ect)
- Parse the pages source and get the keys from the CSS.
Then make a custom request to the font loader, setting the referrer
and using the key value pairs.
- Perhaps it will pass there expectations of protection.
The CSS, what you can do is instead of pointing the path to the font, use PHP to load the font, this way you can add some nonce's to the query.
@font-face {
font-family: 'TheFontName';
src: local('TheFontName'), url('<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?>') format('woff');
font-weight: normal;
font-style: normal;
}
Example (index.php)
<?php
/**
* @name ProtectFont
* @link https://www.dropbox.com/s/xsbpw4g3xn4fzai/ProtectFont.zip
*/
session_start();
//Define paths
define('BASE_FOLDER', dirname($_SERVER['SCRIPT_NAME']));
define('SITE_ROOT', pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME));
define('SITE_URL', rtrim( 'http://'.$_SERVER['HTTP_HOST'].BASE_FOLDER, '/'));
//Site host will be checked against as the referrer host
define('SITE_HOST', parse_url(SITE_URL, PHP_URL_HOST));
/* Now for the font protection, we create 2 CSRF keys,
one for the $_GET key and the other as the value.
*/
$_SESSION['font_csrf_key'] = sha1(uniqid());
$_SESSION['font_csrf_token'] = sha1(uniqid());
?>
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
@font-face {
font-family: 'TheFontName';
src: local('TheFontName'), url('<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?>') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-size: 110%;
}
h1{
font-family: 'TheFontName',serif;
}
</style>
<body>
<h1>Your Font, try and nick me...</h1>
<p>This is the CSS thats on this page, the font is only accessible once for this request, if you try to link it will fail:<br>
<pre>
@font-face {
font-family: 'TheFontName';
src: local('TheFontName'), url(<span style="color:green">'<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?></span>') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-size: 110%;
}
h1{
font-family: 'TheFontName',serif;
}
</pre>
<p>Using this method, to get the font you would need to:</p>
<ol>
<li>Make a request to this page, without loading the css font. Can be done with file_get_content, curl
ect.</li>
<li>Parse the pages source and get the <?php echo $_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?> keys.</li>
<li>Make a request to the font loader using the above key value pairs </li>
<li>And set the referrer in that request to: <?php echo SITE_URL; ?></li>
</ol>
<p>Perhaps its enough to stop a few people but not everyone.</p>
<p>Where there's a will, there's a way... Anything
that your browser sees can be downloaded/saved. I don't know how they can expect
you todo this, they don't offer any reliable solution because then there not
liable for loss. 99.9% of your users wont think about taking the font. Others
will find a way...</p>
<p>Good luck</p>
</body>
</html>
Now we move onto the fonts.php font loader, this file will only load the font if curtain values match (CSRF tokens and referrer), else it sends a blank 404.
Example (fonts.php)
<?php
/**
* Font loader, this file will only load the font if curtain values match
*/
session_start();
//Define script our paths
define('BASE_FOLDER', dirname($_SERVER['SCRIPT_NAME']));
define('SITE_ROOT', pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME));
define('SITE_URL', rtrim( 'http://'.$_SERVER['HTTP_HOST'].BASE_FOLDER, '/'));
//This will be checked against as the passed referer
define('SITE_HOST', parse_url(SITE_URL, PHP_URL_HOST));
if(
//Check required variables are set
// -The tokens for the request
isset($_SESSION['font_csrf_key']) &&
isset($_SESSION['font_csrf_token']) &&
// -The font you want to load
isset($_GET['font']) &&
// -The $_GET request key
isset($_GET[$_SESSION['font_csrf_key']]) &&
// -The referer
isset($_SERVER["HTTP_REFERER"])&&
// - Validate session keys with the passed token
$_GET[$_SESSION['font_csrf_key']] == $_SESSION['font_csrf_token'] &&
// - Validate the Referer
parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST) == SITE_HOST
){
//check font exists
if(file_exists(SITE_ROOT.'/_fonts/'.basename($_GET['font']))){
//no cache
header("Cache-control: no-store, no-cache, must-revalidate");
header("Expires: Mon, 26 Jun 1997 05:00:00 GMT");
header("Pragma: no-cache");
//I think this is the right header
header("Content-Type: application/octet-stream");
set_time_limit(0);
//ok nows lets load and send the font
$font = null;
$h = fopen(SITE_ROOT.'/_fonts/'.basename($_GET['font']), 'rb');
if($h){
while ($line = fgets($h, 4096)){
$font .= $line;
}
}else{
header("HTTP/1.0 404 Not Found");
}
fclose($h);
header('Content-Length: '.strlen($font));
echo $font;
}else{
header("HTTP/1.0 404 Not Found");
}
}
else{
header("HTTP/1.0 404 Not Found");
}
//Unset to stop second access
unset($_SESSION['font_csrf_key'], $_SESSION['font_csrf_token']);
?>
Hope it helps, download the example source here. You could also add cookies to the protection to add that extra layer of protection. Or even change the filename TheFontName.woff
to a session key and store the real value of the filename in session, or mix it up and add decoys in the query string, you can set which is right value or order in session. Be creative, But in the end its not bullet proof. Good Luck