APIの脆弱性を学んでみる①

APIの利活用は業界によっては当たり前のように、別のところではまだこれから、というところもあります。
これからAPI開発を行うに際し、気をつけたいセキュリティについて記載します。
APIセキュリティはまだ馴染みが薄いと思いますが、OWASPではAPIに特化したOWASP API TOP10というものを出しています。こちらを参照くださいー＞https://owasp.org/www-project-api-security/

APIの脆弱性って、要するにプログラムに外部からアクセスし動作不良なってしまう欠陥があるようなセキュリティホールのことか？というと、必ずしもそうではありません。設計の不備や設定の不備と言ったらい良いでしょうか？具体的にどうのようなものかをデモ用プログラムを使い説明したいと思います。

デモ用プログラムはOWASPにて紹介されているcrAPIを使います。参照先：https://owasp.org/www-project-crapi/
ソースコードはこちらになりますー＞https://github.com/OWASP/crAPI
最近はコンテナイメージでの提供が増えたこともあり導入手順は簡単になっています。
環境はUbuntu22.04LTSを使います。
Podmanと、podman-composeをインストールします。pip3はpodman-composeをインストールするときに使用しますのでこちらもインストールする必要があります。

```
$sudo apt -y install podman 
```

動作確認をします。
```
$ podman pull ubuntu

Resolved "ubuntu" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull docker.io/library/ubuntu:latest...
Getting image source signatures
Copying blob de44b265507a done  
Copying config b1d9df8ab8 done  
Writing manifest to image destination
Storing signatures
b1d9df8ab81559494794e522b380878cf9ba82d4c1fb67293bcf931c3aa69ae4
```

```
$ podman ps -a

CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES
```

```
$ podman run ubuntu /bin/echo "Welcome to the Podman World" 
Welcome to the Podman World
```

コンテナのUbuntuを通して、正常に表示されました。

次はコンテナのUbuntuにアクセスします。

```
$ podman run -it ubuntu /bin/bash 
```

OSを確認します。

```
root@902174651a7d:/#  uname -a 

Linux 902174651a7d 6.8.0-49-generic #49~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Nov  6 17:42:15 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
```

コンテナから抜けます。
```
root@902174651a7d:/# exit
exit
$
```

これでPodmanは正常に動作していることが確認できました。

次にpodman-composerをインストールしますがその前にpip3をインストールします。

```
$ sudo apt install python3-pip
```

途中「Do you want to continue? [Y/n]」と聞いてきますので「y」を押し進みます。

エラーなく終了した場合は、podman-composeのインストールに進みます。

```
$ pip3 install podman-compose

  Downloading podman_compose-1.2.0-py2.py3-none-any.whl (39 kB)
Requirement already satisfied: pyyaml in /usr/lib/python3/dist-packages (from podman-compose) (5.4.1)
Collecting python-dotenv
  Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)
Installing collected packages: python-dotenv, podman-compose
  WARNING: The script dotenv is installed in '/home/tsano/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The script podman-compose is installed in '/home/tsano/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed podman-compose-1.2.0 python-dotenv-1.0.1
```

「Successfully installed podman-compose〜」が表示されていればインストールは完了です。


---
研修素材の「crAPI」をダウンロードします。

ブラウザでhttps://github.com/OWASP/crAPI を確認します。

Quickstart Guideを参照し、以下のように入力します。
```
$ curl -o docker-compose.yml https://raw.githubusercontent.com/OWASP/crAPI/main/deploy/docker/docker-compose.yml

$ podman-compose pull
```

```
$ podman-compose -f docker-compose.yml --compatibility up -d
```

DockerでなくPodmanを使うことから、実行前に修正する必要があります。以下の一文をファイル最後に追記します。
```
$ sudo vi /etc/containers/registries.conf

unqualified-search-registries=["docker.io"]
```

```
$podman-compose -f ./docker-compose.yml up -d
```

```
$ vi docker-compose.yml 
```


```PHP:postgresdb修正前：
  postgresdb:
    container_name: postgresdb
    image: 'postgres:14'
```


```PHP:postgresdb修正後：
  postgresdb:
    container_name: postgresdb
    image: 'docker.io/library/postgres:14'
```

 
```PHP:mongodb修正前：
  mongodb:
    container_name: mongodb
    image: 'mongo:4.4'
```


```PHP:mongodb修正後：
  mongodb:
    container_name: mongodb
    image: 'docker.io/library/mongo:4.4'
```

    

```
$ podman ps -a

CONTAINER ID  IMAGE                                   COMMAND               CREATED         STATUS                        PORTS                                            NAMES
c876ee409338  docker.io/library/postgres:14           postgres -c max_c...  29 seconds ago  Up 29 seconds ago (healthy)                                                    postgresdb
15b93f417bab  docker.io/library/mongo:4.4             mongod                28 seconds ago  Up 28 seconds ago (healthy)                                                    mongodb
1338afdd1111  docker.io/crapi/mailhog:latest                                27 seconds ago  Up 27 seconds ago (healthy)   127.0.0.1:8025->8025/tcp                         mailhog
7d67c2fe6742  docker.io/crapi/gateway-service:latest  /app/server           26 seconds ago  Up 26 seconds ago (starting)                                                   api.mypremiumdealership.com
8b6d4291aca2  docker.io/crapi/crapi-identity:latest   /entrypoint.sh        26 seconds ago  Up 26 seconds ago (healthy)                                                    crapi-identity
1de5661837ca  docker.io/crapi/crapi-community:latest  /bin/sh -c /app/m...  25 seconds ago  Up 25 seconds ago (healthy)                                                    crapi-community
40c8b17bf1e4  docker.io/crapi/crapi-workshop:latest   /bin/sh -c /app/r...  24 seconds ago  Up 23 seconds ago (healthy)                                                    crapi-workshop
05127e642cee  docker.io/crapi/crapi-web:latest        /bin/sh -c /etc/n...  22 seconds ago  Up 22 seconds ago (healthy)   127.0.0.1:8888->80/tcp, 127.0.0.1:8443->443/tcp  crapi-web
```


docker-compose.yamlファイルを修正します。
ダウンロードしたままでは、同じサーバー上でのアクセスは許容されていますが、ネットワーク越しのアクセスができません。そこで以下の２つのサーバー設定中の３箇所を修正します。

```PHP:修正前：
  crapi-web:
    container_name: crapi-web
    image: crapi/crapi-web:${VERSION:-latest}
    ports:
      - "${LISTEN_IP:-127.0.0.1}:8888:80"
      - "${LISTEN_IP:-127.0.0.1}:8443:443"
```

```PHP:修正後：
  crapi-web:
    container_name: crapi-web
    image: crapi/crapi-web:${VERSION:-latest}
    ports:
      - "${LISTEN_IP:-0.0.0.0}:8888:80"
      - "${LISTEN_IP:-0.0.0.0}:8443:443"
```

```PHP:修正前：
  mailhog:
    user: root
    container_name: mailhog
    image: crapi/mailhog:${VERSION:-latest}
    environment:
      MH_MONGO_URI: admin:crapisecretpassword@mongodb:27017
      MH_STORAGE: mongodb
    ports:
      #  - "127.0.0.1:1025:1025" # smtp server
      - "${LISTEN_IP:-0.0.0.0}:8025:8025" # Mail ui
```

```PHP:修正後：
  mailhog:
    user: root
    container_name: mailhog
    image: crapi/mailhog:${VERSION:-latest}
    environment:
      MH_MONGO_URI: admin:crapisecretpassword@mongodb:27017
      MH_STORAGE: mongodb
    ports:
      #  - "127.0.0.1:1025:1025" # smtp server
      - "${LISTEN_IP:-0.0.0.0}:8025:8025" # Mail ui
```

起動させます。
```
$ podman-compose -f ./docker-compose.yml up -d
```

ブラウザでアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/bd0c1703-c8c0-2dc0-0cbb-56d330696aca.png)
この画面がでればひとまず動作しています。
Dont have an Account? Sign Upをクリックします。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/403056e7-81cc-d298-a186-e9e576ee6c19.png)
必要な情報を入力します。
メールアドレスは適当なもので大丈夫です。
そういえばメールサーバーの設定していないなーと思われますか？
設定する箇所はありますが、すでにローカルで動くダミーメールサーバー(Mailhog)への設定がされています。もちろん、実在するメールサーバーに設定を変更することもできます。
Signupを押すことで登録が完了します。

メールを確認する必要があります。
メールサーバーへの接続は、
```
http://192.168.200.150:8025/
```
となります。
実在しないドメインのメールでも受信します。
該当するメールを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/59f7db87-2e6f-f570-b241-513dea2774c4.png)


メールを開くと以下のような文面になっています。
このうち、VINとPincodeをメモしておきます。
ログイン後の設定追加で使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/3a38d1bf-422b-b704-8ba5-a0e8fe5bae75.png)


```PHP:メモする内容
VIN: 4UERX64ZFXK054388
Pincode: 4813
```

いよいよログインします。ブラウザにて以下を使いアクセスします。
```
http://192.168.200.150:8888/
```

ログイン直後はこのような画面になります。
右上の「Add a Vehicle」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/1df70d15-39c4-34b3-0cd0-b94e9c8e6852.png)

右上の「Add a Vehicle」をクリックします。
すると以下のような入力画面となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/2a55e12d-8134-b63a-0263-33d84d6e29c8.png)

先ほどメモに控えた情報を入力します。
無事登録されると以下のようなダッシュボードが構成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/63be612c-331c-373e-7025-d32a05894582.png)

ここからはブラウザの開発者モードを開いておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/6260117d-27dd-142c-ec0c-5beb3b37bde1.png)


ここで確認できるのは、緯度経度の情報です。車の位置を表していると考えられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/de8439a4-642f-16e4-c643-5b1ce1698827.png)



```PHP:この部分
"vehicleLocation"{"id":3,"latitude":"37.746880","longitude":"-84.301460"},"fullName":"Test Test","email":"test01@ttt.com"}
```
自分はログインしているのでこの情報が取得できるのは当たり前、疑う余地はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/94691190-c846-5fee-9969-5c967772abd3.png)

上の図ではURLを目にすることができます。

```PHP:
payload: Response { type: "basic", url: "http://192.168.200.150:8888/identity/api/v2/vehicle/608ff343-b47d-4e6e-84e8-244688c9a7db/location", redirected: false, … }
```
このURLの中には「608ff343-b47d-4e6e-84e8-244688c9a7db」というどこかで目にした文字列があります。車を登録したあたりでUUIDとして目にすることができました。


気分を変えるべく、掲示板を見てみます。
Robotさんという人の書き込みを見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399809/4ff5455a-fe1e-3176-71b7-340aeddf2bb7.png)
開発者ツールの画面では、名前以外にビークルIDやメールアドレスが目に入ります。
ここでビークルIDを使って以下のような問い合わせをしたらどうなるか試してみます。



```
$ curl -H "Authorization: Bearer (省略)" http://192.168.200.150:8888/identity/api/v2/vehicle/4bae9968-ec7f-4de3-a3a0-ba1b2ab5e5e5/location

{"carId":"4bae9968-ec7f-4de3-a3a0-ba1b2ab5e5e5","vehicleLocation":{"id":3,"latitude":"37.746880","longitude":"-84.301460"},"fullName":"Robot","email":"robot001@example.com"}
```

応答を見ると”latitude","longitude"より緯度経度の情報が、"email"よりメールアドレスとわかります。つまり、Robotさんのメールアドレス、車の所在地を知ることができました。

個人情報は自身だけが見えれば良いのですが他人の情報も見えてしまいました。車の所在地が漏れると何が悪いか？そこにいけば写真に掲載されている車があるので、夜な夜な現地にいって盗難、というリスクが高まります。メールアドレスはウイルス付き、不正サイト誘導などの害のあるSPAMの送信先に加えられてしまいます。

```
もし、ここれがクレジットカード番号やマイナカード番号だったらどうでしょうか？
```

ここでOWASP API TOP10を見てみます。
オブジェクトの一部のプロパティについて認証されない不備がありますので、OWASP APIの3「Broken Object Property Level Authorization」に該当します。
よく目にする脆弱性というよりは、設計不備というべきなんでしょうね。セキュリティ全般で使う脆弱性とは全く違います。APIを使った開発では、フロントエンド側とバックエンド側の齟齬があった場合にこのような漏れが起きてしまうと考えられます。APIを使い回している場合は、設定不備がさらに拡大していきますので注意が必要です。
次回は、どのような方法で検知するかを考えています。