DVWAをk8s上の任意のURLに配置できるよう改造してみた

# はじめに

DVWAは脆弱なWebアプリケーションで、典型的な攻撃手法を学習するためのプラットフォームです。

:::note alert
DVWAは任意のコマンド実行が可能であるなどの脆弱性を持つWebアプリケーションです。
インターネットに直接接続しているサーバー(Internet-Facing Server)へ導入しないでください。
:::

残念ながらサイトトップでのみの動作を想定しているため http\://localhost/dvwa/ のようなURLでアクセスすることができません。

Relocatableでないことでいろいろと個人的に不都合があるので、改造した顛末をまとめておきます。

オリジナルは https://github.com/digininja/DVWA で、これをベースに変更を行った結果は以下のURLで公開しています。またコンテナ化については https://github.com/cytopia/docker-dvwa を参考にしています。

最終的な成果物はGitHubで公開しています。

* https://github.com/YasuhiroABE/dvwa

# 参考資料

DVWAは解法を含めた資料を参考にすることでいろいろ学びがあると思います。

* https://github.com/keewenaw/dvwa-guide-2019

Security Levelに応じた解法と取り組み方法のガイドが掲載されているので英文を全て読まなくてもコマンドを拾っていくだけでも参考になると思います。

# コンテナ化

公開されているコンテナはdocker composeを前提にしていたり、内部にMySQLを含んでものがあります。

このオリジナルのDVWAの構成やデフォルト設定を引き継いでいることで、MySQLへの接続情報をコンテナの実行時に変更できないものも多いです。

なによりもコンテキスト・ルート(context-root)を変更することができないため、サイト上の任意の場所に配置することができません。

:::note warn
繰り返しますが、DVWAは脆弱性を多数持つWebアプリケーションなので自分以外がアクセスできるような場所には公開しないでください。
:::

## コンテキスト・ルート (context-root) について

世の中のWebアプリケーションはJ2EEアプリケーションのWARを除いて任意のcontext-rootに配置することを意識しているものは少ないと思います。

(とはいっても単純なTomcatなどのWebコンテナはEARに対応していないので、EARに設定を格納することを考慮していないWARファイルはやはりrelocatableでないことが多いです)

このため当然のようにアプリケーションの動作にユニークなFQDNとTLS証明書を要求するWebアプリケーションは多くあります。

## Dockerfile

こういった課題を解決するために、とりあえずオリジナルのコードに手を加えてMySQLの接続情報とcontext-rootを変更できるようにしてみました。

オリジナルのDockerfileはphp:8-apacheコンテナをベースにしていてENTRYPOINTなどの記述はありませんでした。EXPOSEも親のphpコンテナで設定済みのため明記する必要はなかったのですが、分かりやすいだろうと思い追加しています。

```dockerfile:Dockerfileに追加した設定
COPY --chown=www-data:www-data docker/config.inc.php.dist config/config.inc.php
                                  
COPY --chmod=755 docker/run.sh /

EXPOSE 80 

ENV DVWA_DB_SERVER localhost
ENV DVWA_DBNAME mysql
ENV DVWA_DBUSERNAME dvwa
ENV DVWA_DBPASSWORD f3538c7cc848
ENV DVWA_DBPORT 3306

ENV DVWA_WEB_CONTEXTROOT ""
ENV DVWA_ADMIN_PASSWORD password

ENTRYPOINT ["/run.sh"]
```

## run.sh

ENTRYPOINTに指定してるrun.shでは設定ファイルの上書きとapache2の起動をしているだけです。

```bash:run.sh
#!/bin/bash

sed -e "s!_DVWA_DB_SERVER_!${DVWA_DB_SERVER}!" \
    -e "s!_DVWA_DBNAME_!${DVWA_DBNAME}!" \
    -e "s!_DVWA_DBUSERNAME_!${DVWA_DBUSERNAME}!" \
    -e "s!_DVWA_DBPASSWORD_!${DVWA_DBPASSWORD}!" \
    -e "s!_DVWA_DBPORT_!${DVWA_DBPORT}!" \
    -e "s!_DVWA_WEB_CONTEXTROOT_!${DVWA_WEB_CONTEXTROOT}!" \
    -i config/config.inc.php

sed -e "s/_DVWA_ADMIN_PASSWORD_/${DVWA_ADMIN_PASSWORD}/" \
    -i dvwa/includes/DBMS/MySQL.php

exec apache2-foreground
```

## DVWA_WEB_CONTEXTROOT環境変数を反映させる方法

コードを追っていくと少し変なところがあって、PHPコード内でリソースを参照するところと出力のHTML内でリソースを参照するところの両方で共通のPrefix(DVWA_WEB_PAGE_TO_ROOT)を利用しています。

```diff:imgタグ内にDVWA_WEB_PAGE_TO_ROOTが埋め込まれているため変更した箇所の例
diff --git a/setup.php b/setup.php
index 13d4f03..ade9351 100644
--- a/setup.php
+++ b/setup.php
@@ -53,7 +53,7 @@ if (version_compare(phpversion(), '6', '<')) {
 
 $page[ 'body' ] .= "
 <div class=\"body_padded\">
-       <h1>Database Setup <img src=\"" . DVWA_WEB_PAGE_TO_ROOT . "dvwa/images/spanner.png\" /></h1>
+       <h1>Database Setup <img src=\"" . $_DVWA[ 'web_contextroot' ] . "dvwa/images/spanner.png\" /></h1>^M
 
        <p>Click on the 'Create / Reset Database' button below to create or reset your database.<br />
        If you get an error make sure you have the correct user credentials in: <em>" . realpath(  getcwd() . 
DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "config.inc.php" ) . "</em></p>
```

修正する必要はありませんが、下記のようにPHPが別ファイルを参照するための相対パスとしても使われています。

```php:ファイルの読み込みpathの指定にDVWA_WEB_PAGE_TO_ROOTが使われている例
define( 'DVWA_WEB_PAGE_TO_ROOT', '' );
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';
```

この変数の目的としては相対パスでアクセスするためのPrefixとしての用途を想定しているようですが完全ではなく、だいたいうまく動作していますが、setup.phpやlogin.phpでは完全には動作しない場合があります。

またオリジナルと思われるvulnerables/web-dvwaではDVWA_WEB_PAGE_TO_ROOTの利用が徹底されておらず、必要なJavaScriptが読み込めないために"View Source"ボタンが動作しないバグなどがありました。

# コンテナの起動

最新版のコンテナイメージは [docker.io/yasuhiroabe/sccp-dvwa:latest](https://hub.docker.com/repository/docker/yasuhiroabe/sccp-dvwa/general) に配置しています。

これを起動しようとする場合にはMySQLのインスタンスを別に動作させる必要があり、環境変数の指定なども必要です。

テストのためにMySQLの起動とlocalhost/sccp-dvwa:latestを実行するために次のようなMakefileを準備しています。

```Makefile:Makefile
DOCKER_CMD = podman

.PHONY: docker-run
docker-run:
	$(DOCKER_CMD) run -it --rm  -d \
		--env DVWA_DB_SERVER=192.168.1.1 \
		--env DVWA_DBNAME=dvwa \
		--env DVWA_DBUSERNAME=dvwa \
		--env DVWA_DBPASSWORD=f3538c7cc848 \
		-p 8080:80 \
		--name dvwa \
		sccp-dvwa:latest

.PHONY: docker-mysql-run
docker-mysql-run:
	$(DOCKER_CMD) run -it --rm -d \
		--env MYSQL_ROOT_PASSWORD=$(shell openssl rand -hex 8) \
		--env MYSQL_USER=dvwa \
		--env MYSQL_PASSWORD=f3538c7cc848 \
		--env MYSQL_DATABASE=dvwa \
		-p 3306:3306 \
		--name dvwa-mysql \
		docker.io/library/mysql:9
```

docker composeを使っていないのでDVWAからMySQLに接続するためにloopbackではないIPアドレスをDVWA_DB_SERVERに指定してください。

完全なMakefileはGitHubに配置しています。

* https://github.com/YasuhiroABE/dvwa

とはいえこの目的で使う場合にはdigininja/dvwaを使った方が便利でしょう。

## Kubernetes上で動作させる

必要なYAMLファイルをkubernetes/ディレクトリに配置しています。

03.cm-dvwa.yamlファイルは将来の拡張のために https://github.com/cytopia/docker-dvwa からコピーして配置していますが、まだ対応していません。

## Secretsの準備

YAMLファイルにはMySQLに接続するためのユーザー名やパスワードを記述していないので、あらかじめSecretsオブジェクトを作成しておく必要があります。

Makefile中にはランダムなユーザー名・パスワードを指定するためのタスクや、内容を確認するために簡単に出力させるタスクを記述しています。

```Makefile:Makefile
K8S_NAMESPACE = ${K8S_NAMESPACE:-dvwa}

.PHONY: setup-secrets
setup-secrets:
        kubectl -n $(K8S_NAMESPACE) create secret generic mysql-secret \
                --from-literal=admin-password=$(shell openssl rand -hex 16) \
                --from-literal=username=$(shell openssl rand -hex 8) \
                --from-literal=password=$(shell openssl rand -hex 16) \
                --from-literal=dvwa-admin-password=$(shell openssl rand -hex 16)

.PHONY: extract-secrets
extract-secrets:
        @echo -n "root-password:\t"
        @kubectl -n $(K8S_NAMESPACE) get secret mysql-secret -o jsonpath={.data.admin-password} | base64 -d && echo ""
        @echo -n "username:\t"
        @kubectl -n $(K8S_NAMESPACE) get secret mysql-secret -o jsonpath={.data.username} | base64 -d && echo ""
        @echo -n "password:\t"
        @kubectl -n $(K8S_NAMESPACE) get secret mysql-secret -o jsonpath={.data.password} | base64 -d && echo ""
        @echo -n "dvwa admin password:\t"
        @kubectl -n $(K8S_NAMESPACE) get secret mysql-secret -o jsonpath={.data.dvwa-admin-password} | base64 -d && echo ""

.PHONY: delete-secrets
delete-secrets:
        kubectl -n $(K8S_NAMESPACE) delete secret mysql-secret
```

もちろんYAMLファイルでSecretオブジェクトを生成することもできますが、運用ポリシーによってはランダムな文字列での初期化と確認方法を準備しておく方が便利だと思います。

## YAMLファイル群

* https://github.com/YasuhiroABE/dvwa/tree/master/kubernetes

00.pvc-mysql.yamlファイルではStorageClassにrook-ceph-blockを指定していましたが、最新のコードからは削除しています。

```yaml:00.pvc-mysql.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dvwa-mysql-pvc
spec:
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 1Gi
```

04.deploy-dvwa.yamlファイルではDVWA_WEB_CONTEXTROOTが/dvwa/に指定されています。この部分は適宜変更してください。サイトトップで動作させる場合には指定そのものが不要です。


## ブラウザからのアクセスについて

それぞれの環境に合わせて設定を追加してください。LoadBalancer(LB)が使える環境であれば次の要領でservice/dvwaオブジェクトの設定を変更することができます。

```bash:
## "-n dvwa"はnamespaceに合わせて変更してください
$ kubectl -n dvwa patch svc dvwa  --patch '{"spec": {"type": "LoadBalancer"}}'
```

LBではなくIngressを利用している環境もあるでしょう。もし自身で自由にIngressの設定が変更できないのであれば、自前でnginxなどでReverse Proxyサーバーを準備するといった対応もできると思います。

## Databaseの初期化への対応

setup.phpには認証なしにアクセスが可能です。初期化されて困ることはあまりないと思いますが、もし自分以外がアクセスする可能性があるのであれば、このページに適切なプロテクトをかけたほうが良いかもしれません。

他のページは dvwa/includes/dvwaPage.inc.php を読み込んでいるはずなので、phpinfo.phpも含めてログインしていない場合には login.php にredirectされます。

認証なしにアクセスできるのは about.php と instructions.php, setup.php, logout.php だけのはずです。

# さいごに

DVWAを自習用に常に自分だけがアクセスするサイトの1アプリとして登録しておきたいと思っても、自由に配置ができないのは不便だったので変更してみました。いまのところはうまく動作していて満足しています。

商用のJ2EEアプリケーションサーバーを管理していた経験から、自習用の教材がほぼないのでJavaでServletやJSPなどが扱えるエンジニアでもWARを作成できて、EARを適切に構成できる人は限られるのが実情だと思います。

最近はそういったJ2EE的な意味でのコンテナを使うことは少いと思いますが、フレームワークの中でコンテキスト・ルートを意識したアプリケーション開発を推進した点はすばらしいと思います。

Webアプリケーションを開発することは手軽にできる時代ですが、普及しているアプリケーションでもこの点には対応できていないアプリケーションが多くあるのは残念なことだと思います。

以上