Goal:
Remove <?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>
from the beginning of every PHP file's first line using the stream editor, sed.
Discussion:
The stream editor has implicit and explicit line addressing. If you omit line addresses (numeric, regular expressions, or a combination of both), then the entire file is processed.
Point 1:
If you only want to target the first line, then you should specify it explicitly.
sed -i '1s/<pattern>/<substitution>/' <filename>
However, since you are trying to rid your files of "evil", you probably want to remove "evil" anywhere (globally) it is found on the first line.
sed -i '1s/<pattern>/<substitution>/g' <filename>
Point 2:
The "evil" you are dealing with uses non-alpha numeric characters, so you must be wary of using it as input in various contexts. In order to use a regular expression to search for regular expression meta-characters (?, +, *, [, ], ., et al), you must either:
Escape the meta-characters with backslashes to avoid pattern
collisions (Example: \?
), or
Change the regular expression pattern delimiter to avoid a pattern collision, or
Both (This is what you should do in this case).
In sed, you can change the regex pattern delimiter by escaping a character before your pattern begins.
Example:
sed -i '1s\#<pattern>#<substitution>#g' <filename>
Point 3:
You can search for strings as a <pattern>
with regular expressions in sed! By definition, the most basic pattern is a sequence of characters. However, you must adhere to point number two above and escape any regex meta-characters, or the default pattern delimiter, /, if necessary.
Solution 1:
Your evil, I mean regex pattern, has regex meta-characters and the default pattern delimiter embedded in it!
<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>
I would prescribe the following. Notice that I am now using double quotes because I want the shell to do variable interpolation before executing sed
. Also, because I changed the regex pattern delimiter to #
, I did not need to escape the two forward slashes associated with that micro block quote. :-)
#!/bin/bash
function evilRemover ()
{
pattern='\<\?php /\*\*/ eval\(base64_decode\("aWYoZnVuY3Rpb25"\)\);\?\>'
local IFS="\n"
for filename in "$@"; do
sed -i "1s\#${pattern}##g" "$filename"
done
}
evilRemover $(find ~/Sites/www.domain.com -name '*.php' -print)
Note: I will go out on a limb and say that anyone that puts white spaces in their file names should consider using the underscore, _
, instead.
Mr. @Ed Morton above is trying to warn against the possibility of word splitting, but "$@"
should prevent it if you pass your list into a function like above.
Hidden, non-printing characters in file names can be hard to deal with, but this specific solution should work for your problem to a high degree of certainty (99.9999%).
Solution 2:
More generically:
#!/bin/bash
function deleteWordsFromLine ()
{
lineNumber=$1
pattern=$2
local IFS="\n"
shift 2
for filename in "$@"; do
sed -i "${lineNumber}s\#${pattern}##g" "$filename"
done
}
targetLine=1
word='\<\?php /\*\*/ eval\(base64_decode\("aWYoZnVuY3Rpb25"\)\);\?\>'
filenames=$(find ~/Sites/www.domain.com -name '*.php' -print)
deleteWordsFromLine $targetLine $word $filenames
Solution 3:
In the event that it would be better to delete the first line of all the files ...
#!/bin/bash
function deleteLine ()
{
lineNumber=$1
local IFS="\n"
shift 1
for filename in "$@"; do
sed -i "${lineNumber}d" "$filename"
done
}
targetLine=1
filenames=$(find ~/Sites/www.domain.com -name '*.php' -print)
deleteLine $targetLine $filenames
Final Note:
Be sure to execute this solution with enough permissions, or else the find
command will return messages to stderr
in the following format.
find: '/some/dir/file.php': Permission denied