So I wanted a nifty “Now Playing” box on the side of my blog, but I wanted to do it securely.

I found a WinAmp plug-in called Do-Something which looked promising, at the start of any new song it would do any (or any chain) of several actions. Most notable to me was submit a URL. The idea was simple enough, submit a url with the artist and song name (these it gets from the file tag), and a shared password, to a CGI script on the web server. The CGI would then update some file used in a SSI by the page, and poof, “Now Playing” would be up and running. Unfortunately, this simple method is not very secure…


Anyone able to sniff network traffic between the server and my computer could see the password in plain text and then submit anything they like to the CGI. Not only could this be used as method to post arbitrary code into my blog (XSS for instance), they could also do something really serious like post that I’m listening to John Tesh. In light of that, I set out to solve this old problem in the usual way: transmit a hash instead of the password. There is one additional step we will need to add for this method to be truly secure however, which I will discuss after I introduce the basics:

Take the ARTIST, the song TITLE, and your PASSWORD, and combine them together as follows:

Ex:
ARTIST: Weezer
TITLE: Freak Me Out
PASSWORD: ReplaceWithSecurePassword

OUTPUT: WeezerFreak Me OutReplaceWithSecurePassword

Now we take that OUTPUT, hash it with a strong hash function (say SHA1), and get:

HASHOUTPUT: 5c87ac46e7714c579c9e6401a049eb7fb635ad19

Now we send the TITLE, ARTIST, and HASHOUTPUT. The receiving CGI already knows the password, so it takes the ARTIST and TITLE you sent, and PASSWORD it knows, and hashes them and verifies that the HASHOUTPUT it calculates matches the HASHOUTPUT you sent. If it does, then all is good and it updates the “Now Playing” file, otherwise it ignores the request.

A few important things to note about this method:

  • It does not reveal the password in plaintext as the original method did - Well that was the point wasn’t it?
  • It uses a hash of the artist, song, and password, instead of just a hash of the password - this is important. If you don’t use the artist and title in the hash then the attacker doesn’t need the password anyway! The hash of the password alone won’t change. He can simply submit whatever artist, whatever title, and then use the same hash he captured from you earlier as it will not have changed. Concatennating the password with the other info serves to hide the password and ensure that a captured hash does no good to a would-be attackers.
  • If you follow the second point carefully, it leads to a weakness in the above method. We can be (almost totally) certain that no two songs are going to give us the same hash output, but by the above method we are absolutely guaranteed that the same song will always give the same hash. This is a problem. This means that although an attacker can’t submit random songs/artists, he can resend previous submissions and they will be accepted. Essentially, any valid submission we send will ALWAYS be valid, and therefore always honored. This leads to what is called a “replay” attack: the attacker can resend any submission we’ve made and it will be successful.

The solution to a replay attack is an expiry (experation date), or in this case a timestamp. We make a slight modification to our previous method and include the current time:

Ex:
ARTIST: Weezer
TITLE: Freak Me Out
TIME: 1150106145 (in readable form 6/12/2006 02:55:45)
PASSWORD: ReplaceWithSecurePassword

OUTPUT: WeezerFreak Me Out1150106145ReplaceWithSecurePassword

Now we hash that output as before:
HASHOUTPUT: 269022a985c8df4dc343e007d4ae8c0340ee71e9

Because this new hash is time dependent, if we do this calculation one second later we will get a completely different result. By sending the timestamp along in the submission, the CGI can use it to check that its hash output matches and that the time is reasonably close to the current time (say within a few seconds to a few minutes depending on your time synchronization). After that time is up the captured hash does no good to the attacker, any replay he makes will be rejected as its valid time window has expired.

We now have a fully functional cryptographic method, but implementing it is now harder. The “Do Something!” plug-in does not support hashing, timestamps, etc… so we’re going to have to do that ourselves. What is does support is outputting a file with the ARTIST and TITLE at the start of each song, and it supports executing commands, so we’re going to have it output this file, then execute a program we write which will get the ARTIST and TITLE from that file, then do the required hashing and submit the information to our CGI.

We first set up the “Generate HTML Playlist” action in “Do Something!” This action asks for a template in and an output file. We first create a template containing simply %%CURRENTARTIST%%::%%CURRENTSONGTITLE%% and set the template in to that file. You can then choose any output file you want, just make sure you know where it is, you’ll need to put its location in the submission file so it knows where to get its info from. Be sure to “Add” this action when you finish setting it up.
Next we add a “Run A Command” action, containing simply the path to our submission program (submitsong.pl) posted below. Add this and hit “OK.” Set up the files below and you’re done!
The files:

submitsong.pl: - Client side perl script to submit info

use strict;
use warnings;

use Digest::SHA1 "sha1_hex";
use HTTP::Request;
use LWP::UserAgent;

my $file = 'c:\temp\songlist';

my $url = "http://www.yourblog.com/nowplaying/song.php?";

my $PSK = "ReplaceWithSecurePassword";

open(FILE, "<$file") or die "Unable to open input file!";

my $line = ;
chomp $line;

my ($artist, $song) = split(/::/, $line,2);

close FILE;

my $timestamp = time();

my $sha1sum = sha1_hex($artist . $song . $timestamp . $PSK);

&clean(\$artist);
&clean(\$song);

my $request = HTTP::Request->new(GET => $url . "a=$artist" . "&" . "s=$song" . "&" . "t=$timestamp" . "&" . "h=$sha1sum");
my $ua = LWP::UserAgent->new;
my $response = $ua->request($request);

if ($response->is_success) {
  print $response->content . "\n";
}
else {
	print $response->status_line . "\n";
}

exit();

sub clean {
	my $sr = shift;
	$$sr =~ s/#/%23/g;
	$$sr =~ s/\$/%24/g;
	$$sr =~ s/&/%26/g;
	$$sr =~ s/\+/%2B/g;
	$$sr =~ s/\,/%2C/g;
	$$sr =~ s/\//%2F/g;
	$$sr =~ s/:/%3A/g;
	$$sr =~ s/;/%3B/g;
	$$sr =~ s/=/%3D/g;
	$$sr =~ s/\?/%3F/g;
	$$sr =~ s/@/%40/g;
}

song.php: - Server side CGI to process and verify song submission

 $tolerance ) {
   print "Timestamp is outside tolerance\n";
   exit;
}

if (sha1($artist . $song . $timestamp . $PSK) != $sha1sum) {
   print "SHA1 sum did not match\n";
   exit;
}

if ( $logfp = @fopen("song.txt", "w") ) {
    fwrite($logfp, $artist . " - " . $song . "\n");
    fclose($logfp);
}

if ( $logfp = @fopen("artist.txt", "w") ) {
    fwrite($logfp, $artist);
    fclose($logfp);
}

if ( $logfp = @fopen("title.txt", "w") ) {
    fwrite($logfp, $song);
    fclose($logfp);
}

?>

If all goes well then song.php will make several files:

song.txt - Contains “ARTIST - TITLE”, useful if you want this common format.
artist.txt - Contains ARTIST
title.txt - Contains TITLE

These last two can be used to present them individually however you’d like.

Now all we need is to display them in the page and we are done. This can be done as:

SSI style:
;

Script style:
“; print file_get_contents(”title.txt”);?>

You now have your own secure “Now Playing” section!

A few notes:

  • SHA1 is not truly secure anymore, but attacks on it are still hugely computationally intensive - much more work than breaking into your ‘Now Playing’ list would be worth. In the future, improvements and new attack vectors may make this a more realistic threat, but ostensibly by then you could simply use a different hash function in the files above (it would only be a line or two of code to change in each).
  • DoSomething is not great. It only works with mp3s, only looks at version 1 ID3 tags, opens an annoying system console window, and is generally ill-suited to this application. If anyone knows of a better plug-in please let me know, and if I ever get really fed up with it I may just write my own.
  • Should go without saying, but be sure to replace the password “ReplaceWithSecurePassword” with a secure password, and one roughly the same length (or longer).