web-dev-qa-db-ja.com

URLパラメータ暗号化よりも優れた手法

私は、URLパラメータ値に対称暗号化を実装することが唯一の選択肢/対/期限であるアプリケーションに取り組んでいるプログラマです。データは本質的に機密ではありませんが、販売代理店がお互いのリードを覗き見しないようにする必要がありました。 (鍵はセッションの作成時に生成され、暗号的に強力です。)セッションは頻繁に終了することが予想されます。

役割階層はManager--> Supervisor--> Agentsでした。現在、データ構造はこれらの役割を考慮しておらず、だれが何を参照できるかを厳密に実施しています。データベースからこの情報を取得することは、簡単なことではありませんでした。 (再帰データベース。)

この手法は、パラメーター操作に対する防御策として、リストのかなり下にあることを知っています。より良いテクニックは何でしょうか?

制約:
ロールベースのチェックはオプションではありません。

[追加情報]変更を加える前に作成されてクライアントに送信されたURLは次のようになります。

https://www.example.com/agent/?producerId=12345

ここでの特定の脅威の表面は、?agentId=12345に対するパラメータ操作です。エージェントIDは、各エージェントに一意に割り当てられます。したがって、エージェントAがエージェントBの統計を確認したい場合は、エージェントの見積もりと現在の販売統計を確認するために、agentId = 22222を入力することができます。

繰り返しますが、ロールベースのチェックは私にとって選択肢ではありませんでした。データベースに変更を加えることができませんでしたOR永続層。

私の解決策は、セッションで作成された暗号化キー(JavaのKeyGeneratorクラスを使用)を使用し、クライアントに送信される送信URLを暗号化することでした。したがって、URLは次のようになります。

https://www.example.com/agent/?producerId=<ciphertext>

ここで、誰かがagentId = 22222を試行すると、サーバーはそれを復号化しますthinksは暗号文であり、最終的に無効な文字シーケンスを作成します。

(これにより、既存のagentId couldが見つかる可能性がありますが、攻撃を実行する人物に関連する可能性はほとんどありません。

この質問は、最適なセキュリティ(リソースアクセスを保証するためのロールベースのチェック)に関するものではなく、灰色の領域でセキュリティを絞り込もうとすることに関するものではないことを強調しておきます。

ここのパラメーター暗号化ソリューションは、私たちのセキュリティ担当者から勧められました。私はこのソリューションで考慮していなかった1つのポイントを取得しました-壊れたURL-そしてthatを使用し、アクセスルールを適用する時間について議論するためにこのソリューションによって作成されたメンテナンスの問題を使用します一時的ではない方法で。

21
avgvstvs

良い質問!防御しようとしている脅威について詳しく説明していただきありがとうございます。私はそれに応じて私の答えを編集しました。

要約。あなたの主要な防御はアクセス制御でなければなりません。どのユーザーがどのページを表示できるかを制限する必要があります。詳細は以下。

Webアプリケーションでのアクセス制御。ページに表示するデータにアクセスする権限がユーザーにあることを確認する必要があります。彼らがそのデータを見ることを許可する前に。これは基本的にはアクセス制御に帰着します。何らかの承認ポリシーに基づいて、どのユーザーがどのデータを表示できるかを制限する制御が必要です。

各エージェントに1つずつ、一連​​のページがあるようです。

http://www.example.com/agent/?producerId=12345
http://www.example.com/agent/?producerId=12346
http://www.example.com/agent/?producerId=12347
...

ここで、producerIds(agentIds)は、推測または予測できる可能性があります。エージェント12345がhttp://www.example.com/agent/?producerId=12345を表示できるが、他のページは表示できないようにする必要があります。 OK。

これは沼地標準の状況であり、沼地標準の防御はアクセス制御です。

アクセス制御を実装するには、ユーザーがそのページを表示することを許可する前に、ユーザーがそのページを表示することを許可されているかどうかを各ページがチェックするようにWebアプリケーションをコーディングします。たとえば、上記のページの場合、そのページを実装するロジックは、現在ログインしているユーザーのIDをチェックします。ログインしているユーザーのIDがページパラメータのproducerIdと一致する場合は、それらのユーザーに情報を表示します。 IDが一致しない場合は、情報を表示しません。他のユーザーの場合は、エラーページ(アクセス方法に関する情報)を表示するか、ユーザーがまだログインしていない場合はリダイレクトしますログインページに移動します。

これはブックマークを壊しません。データベースへの変更、永続化レイヤーへの変更、またはロールベースのアクセス制御は必要ありません。現在ログインしているユーザーのIDを検索してプロバイダーIDに関連付ける方法が必要です。また、マネージャとスーパーバイザが他のすべてのエージェントのデータを表示できるようにする場合は、現在ログインしているユーザーを検索して、それらがマネージャかスーパーバイザかを判別する方法が必要です。エージェントのマネージャー/スーパーバイザーのみ(他のすべてのマネージャー/スーパーバイザーではない)のページの表示を許可する場合は、各エージェントのマネージャー/スーパーバイザーを決定する方法が必要です。これらはかなり基本的な最小要件です。それらをどのように回避できるかを理解するのは困難です。

@symbcbeanが正しく指摘しているように、これはWebアプリケーションで頻繁に見られる非常に一般的なエラーです。典型的な例としては、推測可能なパラメーター値を使用してリソースを識別し、ユーザーを適切に認証しないサイトが考えられます。たとえば、注文に連続した注文番号が割り当てられているとします。

https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...

また、URLを知っている人なら誰でも注文を表示できると仮定します。これは、注文番号を知っている(または推測する)誰もが許可されていなくても、注文を表示できることを意味するため、悪いことです。これは OWASPのトップ1 Webアプリケーションのセキュリティリスクの1つです: 安全でない直接オブジェクト参照 。詳細については、OWASPで利用可能なリソースを読むことをお勧めします。 OWASPには、Webアプリケーションのセキュリティに関する優れたリソースがたくさんあります。

その他のコメント。他の人はSSLの使用を提案しています。これはパラメータの改ざんを防止するものではありませんが、他の種類の問題を防ぐのは、一般的に優れたセキュリティ対策です。 SSLの使用は簡単です。httpではなくhttpsを使用するようにWebサイトを構成するだけです(理想的には、HSTSを有効にし、すべてのCookieにsecureビットを設定します)。

また、機密情報をURLパラメータに格納しないことをお勧めします。機密情報は、セッション状態またはデータベースに保存できます。

16
D.W.

要するに: RLパラメータを暗号化しないでください、別のルックアップを使用してください

また、Webアプリケーションのセキュリティ対策が必要な場合は、HTTPSの使用は基本的に交渉不可能です。 2015年には必須です。TLS1.1以降に慣れてください。


開発者がしたいこと

What developers want to do

代わりに開発者がすべきこと

enter image description here

7

しかし、販売代理店がお互いのリードを覗き見しないようにする必要がありました。

これはむしろクライアントがブラウザであることを意味します-ある時点でキーをクリアテキストとして送信していますか?

多項式は正しく、SSLを使用する必要があります。これは、次のようなURLに隣接する値を入力するユーザーの問題を解決するものではありません。

_https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...
_

リクエストを検証するために提示する必要があるパラメーターに基づいて、サーバー側で認証トークンを生成することは十分に可能です。これにはメッセージ認証コード(MAC)を使用するのが理想的ですが、注意すればハッシュも機能します。例えばPHPで...

_ print "<a href='show_order.php?id=" . $id . "&valid=" . md5($id . crypto_key()) . "'>...
_

これは、以下によって簡単に検証されます。

_if ($_GET['valid'] != md5($_GET['id'] . crypto_key()) {
   die('not authorized');
}
_

ここでcrypto_key()は静的な暗号化キーを返します(たとえば、_/dev/urandom_から128ビットをプルしてデータベースに格納することで生成します)。

ただし、URLを生成するコードへのアクセスを制御する必要があります。

3
symcbean

これが私の解決策です

$id=1234;
$en_id = encrypString( $id);

と私はのようなURLを作成します

https://www.example.com/show_order.php?id=$en_id

uRLは次のようになります

https://www.example.com/show_order.php?id=9muEYh4lShFDeCnXqoNpxucs42Fuz5Nexq1IUGWYEffffe88yRbJu

そして反対側で私は解読します

$en_id= decryptString($_GET['id']);

cryptとdecryptの機能は

function encrypString($plaintext) {
         # --- ENCRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";


        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        # creates a cipher text compatible with AES (Rijndael block size = 128)
        # to keep the text confidential 
        # only suitable for encoded input that never ends with value 00h
        # (because of default zero padding)
        $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,
                                     $plaintext, MCRYPT_MODE_CBC, $iv);

        # prepend the IV for it to be available for decryption
        $ciphertext = $iv . $ciphertext;

        # encode the resulting cipher text so it can be represented by a string
        $ciphertext_base64 = base64_encode($ciphertext);

        return  rawurlencode($ciphertext_base64);//important rawurlencode for + symbol in url

    }


decryptString($ciphertext_base64) {
        # --- DECRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";

        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        $ciphertext_dec = base64_decode($ciphertext_base64);

        # retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
        $iv_dec = substr($ciphertext_dec, 0, $iv_size);

        # retrieves the cipher text (everything except the $iv_size in the front)
        $ciphertext_dec = substr($ciphertext_dec, $iv_size);

        # may remove 00h valued characters from end of plain text
        $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,
                                    $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

        return rawurldecode($plaintext_dec);
    }
2

パラメータの改ざんを防ぐために、私は常にプレーンテキストの値を含むハッシュを送信しています。

例えばあなたが保護したいこのURLを取る

https://www.mysite.com/somepage?param1=abc&param2=xyz

サーバーでは、モデルはすべてのURL値を秘密のソルトでハッシュします

string salt = "A1B2C3D4...";
string param1 = "abc";
string param2 = "xyz";
string hash = YourFavoriteHashingAlgorithm(param1 + param2 + salt);
// result hash = "Y83YMB38DX83YUHFIEIGKDHSEUG"

次に、このハッシュを他のURL値と一緒に送信します

https://www.mysite.com/somepage?param1=abc&param2=xyz&hash=Y83YMB38DX83YUHFIEIGKDHSEUG

ここで、このURLへのリクエストを受信すると、提示されたパラメーターを再度受け取り、同じアルゴリズムを介してハッシュします。生成したハッシュは、表示されているハッシュと一致する必要があります。それ以外の場合は、400 "Bad Request"の結果を送信してください。

良い点は、パラメーターは依然として人間が読み取れることであり、既存のすべての検証ロジックは同じままでかまいません。

1
raterus

ユーザー入力を使用しない

(あなたはcamnotしないため trust it)

この答えは accepted を拡張したものであり、私にはかなり単純化されているように見えます。

さて、あなたの説明と私のbestの理解から、Sales Agent A(つまり12345)がSales Agent B(つまり54321)のデータを覗き見しないようにする必要があると述べました。

単に、クエリ文字列からagentIdパラメータを削除して、セッションから取得します

URLはhttps://example.org/show_order.phpになります

内部的には、アプリケーションmustは、セッション内に格納されているプリンシパルから販売代理店IDを抽出します。 PHPで過度に錆びているので、疑似コードを使用します

SELECT * FROM sales where salesman_id = ?1;
[1 = getPrincipalSalesId()]

このクエリは、クライアントからのすべてを単に無視します。永続化レイヤーを変更する必要はありません。 RBAC(ロールベースのアクセス制御)を実装する必要さえありませんが、すべてはその関数getPrincipalSalesIdに関するものです。

基本的にはgenerate URLに使用するのと同じコードですが、今回はその値をクエリにハンマーで入れてimplicitにします。