sed导致bash脚本挂起

时间:2014-05-16 23:46:15

标签: php regex bash awk sed

我正在清理一个运行基于PHP的CMS的被黑网站。网站上的每个PHP文件都在文件第一行的开头插入了以下字符串:

<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>

(为清楚起见,我已截断了base64字符串。)

我的目标是通过bash脚本删除此字符串。我首先确保我可以遍历所有文件。

#!/bin/bash
# de-malware-ifier

for i in $(find ~/Sites/www.domain.com -name '*.php'); do
  echo "file $i"
done

这可以正常工作,打印出数百个受感染文件的文件名。

然后我尝试修改bash脚本以替换每个这些文件的邪恶字符串:

#!/bin/bash
# de-malware-ifier

for i in $(find ~/Sites/www.domain.com -name '*.php'); do
  echo "file $i"
  evil='<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>'
  sed 's/$evil//'
done

但是,运行此脚本会挂起第一个文件。为什么这个脚本挂起,我应该如何修改这个脚本来给我想要的结果呢?

我在Mac OSX上。

4 个答案:

答案 0 :(得分:1)

它挂起的原因是因为你没有给sed一个文件名,所以它正在等待stdin的输入。

要编辑文件,您应该使用:

sed -i bak 's/foo/bar/' "$i"

请注意,这还不足以修复您的脚本。其他问题包括:

  1. 您的模式包含许多特殊于sed的字符。你必须逃脱它们。看看您是否可以使用fgrep -v
  2. $evil不会在单引号中展开。使用双引号。

答案 1 :(得分:0)

Sed缺少输入。

试试这个:

#!/bin/bash
# de-malware-ifier

for i in $(find ~/Sites/www.domain.com -name '*.php'); do
   echo "file $i"
   evil='<?php \/\*\*\/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>'
   sed  -i "s/$evil//" $i
done

PS:我不确定你是否需要逃避其他事情&#34; $ evil&#34;。

答案 2 :(得分:0)

正如其他人指出的那样,你错过了sed命令的文件名,但是不要尝试使用sed,因为sed不能对字符串进行操作,只能对RE进行操作。如果他们能够提供一个标志告诉sed将其视为搜索模式,那么GNU的家伙们不会浪费时间在sed的化妆品-i选项上,而是会做得更好。一个字符串而不是一个正则表达式。

无论如何 - 试试这个:

tmp="/usr/tmp/tmp$$"
trap 'rm -f "$tmp"; exit' 0
find ~/Sites/www.domain.com -name '*.php' |
while IFS= read -r i; do
  echo "file $i"
  evil='<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>'
  awk -v evil="$evil" 's=index($0,evil){$0 = substr($0,1,s-1) substr($0,s+length(evil)} 1' "$i" > "$tmp" $$ mv "$tmp" "$i"
done

我还修复了文件名的循环。永远不要使用for i in $(...)因为包含任何空格的文件名都会失败。如果您的文件名包含换行符,我发布的循环将失败。

如果你想避免手动指定tmp文件,GNU awk会有一个-i inplace标志。

答案 3 :(得分:0)

目标:

使用sed流编辑器从每个PHP文件第一行的开头删除<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>

讨论:

流编辑器具有隐式和显式的行寻址。如果您省略行地址(数字,正则表达式或两者的组合),则将处理整个文件。

要点1:

如果只想定位第一行,则应明确指定它。

sed -i '1s/<pattern>/<substitution>/' <filename>

但是,由于您试图清除文件中的“ evil”,因此您可能希望在第一行中的任何位置(全局)删除“ evil”。

sed -i '1s/<pattern>/<substitution>/g' <filename>

要点2:

您要处理的“邪恶”使用非字母数字字符,因此必须警惕在各种情况下将其用作输入。为了使用正则表达式搜索正则表达式元字符(?,+,*,[,] 、.等),您必须:

  1. 用反斜杠转义元字符以避免模式 冲突(例如:\?)或

  2. 更改正则表达式模式定界符以避免模式冲突,或者

  3. 两者(在这种情况下,您应该这样做)。

在sed中,您可以通过在模式开始前转义字符来更改正则表达式模式定界符。

示例:

sed -i '1s\#<pattern>#<substitution>#g' <filename>

要点3:

您可以将sed中的正则表达式作为<pattern>来搜索字符串!根据定义,最基本的模式是字符序列。但是,必须遵守上述第二点,并在必要时转义任何正则表达式元字符或默认的模式定界符/。

解决方案1:

您的邪恶,我是说正则表达式模式,其中包含正则表达式元字符和默认的模式定界符!

<?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25"));?>

我将规定以下内容。注意,我现在使用双引号,因为我希望外壳程序在执行sed之前进行变量插值。另外,由于我将正则表达式模式定界符更改为#,因此不需要转义与该微块引号相关联的两个正斜杠。 :-)

#!/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)

注意:我会弯腰,说任何在文件名中添加空格的人都应该考虑使用下划线_

先生。上面的@Ed Morton尝试警告单词拆分的可能性,但是如果您将列表传递给上述函数,"$@"应该阻止它。

文件名中隐藏的非打印字符可能很难处理,但是此特定解决方案应该可以高度确定地解决您的问题(99.9999%)。

解决方案2:

更一般地:

#!/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

解决方案3:

如果最好删除所有文件的第一行...

#!/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

最终通知

请务必以足够的权限执行该解决方案,否则find命令将以以下格式将消息返回到stderr

find: '/some/dir/file.php': Permission denied