【问题标题】:sed causing bash script to hangsed 导致 bash 脚本挂起
【发布时间】:2014-07-05 13:06:24
【问题描述】:

我正在清理一个运行基于 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 上。

【问题讨论】:

  • 一般来说,撤消您知道的更改并不足以确保您重新获得对服务器的控制权。攻击者可能已经安装了一个(或多个)后门,在您“清理”系统后允许他们重新进入。服务器受损后的最佳方法是从头开始重建它,或者从入侵发生之前的备份中恢复。见this ServerFault question and answers
  • 当心for i in $(command) ...你应该避免这种语法。请参阅此答案以获取更多详细信息:stackoverflow.com/questions/19606864/ffmpeg-in-a-bash-pipe/…
  • @GordonDavisson 谢谢,这对阅读此问题的其他人很有帮助。在我的具体情况下,无论如何,www.domain.com 都应该下线了,我已经这样做了。我将此视为基本 bash 脚本的学习练习,而不是被黑的服务器恢复。

标签: php regex bash awk sed


【解决方案1】:

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

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

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

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

  1. 您的模式包含许多 sed 特有的字符。你必须逃离他们。看看你能不能改用fgrep -v
  2. $evil 不会用单引号展开。使用双引号。

【讨论】:

  • fgrep -v 将删除匹配的整行,而不仅仅是该行的匹配部分。
  • 您能解释一下bak 在该命令的上下文中的作用吗?一般来说,我是 sed 和 bash 脚本的新手。
  • 来自man sed:-i extension 就地编辑文件,使用指定的扩展名保存备份。如果给出零长度扩展名,则不会保存备份。不建议在就地编辑文件时提供零长度扩展名,因为在磁盘空间耗尽等情况下,您可能会面临损坏或部分内容的风险。
【解决方案2】:

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:我不确定您是否需要在“$evil”上转义其他内容。

【讨论】:

  • 当心for i in $(command) ...你应该避免这种语法。请参阅此答案以获取更多详细信息:stackoverflow.com/questions/19606864/ffmpeg-in-a-bash-pipe/…
  • 永远不要像那样使用 for i in,当你不想在搜索空间中使用 RE 时不要尝试使用 sed(你错过了转义 ?s 甚至更多),并且总是引用你的变量 ("$i") 以避免分词和通配问题。
【解决方案3】:

正如其他人指出的那样,您缺少 sed 命令的文件名,但不要尝试为此使用 sed,因为 sed 无法操作字符串,只能操作 RE。与其把时间浪费在 sed 的装饰性 -i 选项上,如果 GNU 家伙可以提供一个标志来告诉 sed 将其搜索模式视为字符串而不是正则表达式,他们会做得更好。

无论如何 - 试试这个:

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 标志。

【讨论】:

    【解决方案4】:

    目标:

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

    讨论:

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

    第 1 点:

    如果您只想定位第一行,那么您应该明确指定它。

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

    但是,由于您正在尝试删除文件中的“邪恶”,因此您可能希望在第一行发现的任何地方(全局)删除“邪恶”。

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

    第 2 点:

    您正在处理的“邪恶”使用非字母数字字符,因此您必须小心在各种上下文中使用它作为输入。要使用正则表达式搜索正则表达式元字符(?、+、*、[、]、. 等),您必须:

    1. 使用反斜杠转义元字符以避免出现模式 碰撞(例如:\?),或

    2. 更改正则表达式模式分隔符以避免模式冲突,或

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

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

    例子:

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

    第 3 点:

    您可以在 sed 中使用正则表达式搜索 &lt;pattern&gt; 形式的字符串!根据定义,最基本的模式是字符序列。但是,如果需要,您必须遵守上述第二点并转义任何正则表达式元字符或默认模式分隔符 /。

    解决方案 1:

    你的邪恶,我的意思是正则表达式模式,有正则表达式元字符和嵌入其中的默认模式分隔符!

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

    我会开以下处方。请注意,我现在使用双引号,因为我希望 shell 在执行 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
    

    【讨论】:

      猜你喜欢
      • 2022-11-10
      • 1970-01-01
      • 2013-02-08
      • 2014-04-07
      • 2018-04-21
      • 1970-01-01
      • 1970-01-01
      • 2011-03-02
      • 2011-09-04
      相关资源
      最近更新 更多