比 url 参数加密更好的技术

信息安全 加密 Web应用程序 访问控制
2021-08-19 12:46:36

我是一名程序员,正在开发一个应用程序,其中唯一的选择/vs/deadline 是对 url 参数值实施对称加密。这些数据本质上是不敏感的,但我们需要防止销售代理偷看对方的线索。(密钥是在会话创建时生成的,并且具有很强的加密能力。)会话预计会频繁结束。

角色层次结构是Manager--> Supervisor--> Agents. 数据结构目前没有以严格强制谁可以看到什么的方式来解释这些角色。从数据库中获取这些信息并非易事。(递归数据库。)

我知道这种技术作为对参数操纵的防御在列表中排名靠后。什么会是更好的技术?

约束:
基于角色的检查不是一种选择。

[附加信息] 在我进行任何更改之前构建并发送到客户端的 url 如下所示:

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

这里的具体威胁面是针对?agentId=12345. 代理 ID 被唯一分配给每个代理。因此,如果代理 A 想要查看代理 B 的统计数据,他可以输入 agentId=22222 以查看该代理的报价和当前销售统计。

同样,基于角色的检查对我来说不是一个选项:我无法对数据库或持久层进行更改。

我的解决方案是使用会话创建的加密密钥(使用 Java 的 KeyGenerator 类)并加密发送到客户端的出站 URL。所以现在,网址看起来像:

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

现在,如果有人尝试 agentId=22222,服务器将解密它认为是密文的内容,并最终创建一个无效的字符序列。

(这留下了可以找到现有 agentId 的可能性,但不太可能与执行攻击的人相关。

我要强调的是,这个问题不是关于最佳安全性(这将是基于角色的检查以确保资源访问)以及试图在灰色区域中挤压一些安全性。

我们的一位安全人员向我推荐了这里的参数加密解决方案。我得到了一个我没有考虑过这个解决方案的外卖 - 损坏的网址 - 并且将使用以及这个解决方案产生的维护问题来争取时间以一种更少的权宜之计方式强制执行访问规则。

4个回答

好问题!感谢您详细说明您试图防御的威胁。我已经相应地编辑了我的答案。

概括。您的主要防御应该是访问控制您需要限制哪些用户可以查看哪些页面。详情如下。

Web 应用程序中的访问控制。您需要做的是检查用户是否有权访问您将在页面上显示的数据,然后才允许他们查看该数据。这基本上归结为访问控制:您需要根据某些授权策略限制哪些用户可以查看哪些数据的控制。

听起来您有一系列页面,每个代理一个:

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但不能查看任何其他页面。行。

这是一个沼泽标准的情况,沼泽标准的防御是:访问控制。

要实现访问控制,您需要对 Web 应用程序进行编码,以便每个页面在允许用户查看该页面之前检查用户是否有权查看该页面。例如,对于上面列出的页面,实现该页面的逻辑将检查当前登录用户的身份。如果登录用户的 id 与 page 参数的 producerId 匹配,则向他们显示信息。如果 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 的十大Web 应用程序安全风险之一:不安全的直接对象引用有关更多信息,我建议阅读 OWASP 上的可用资源。OWASP 有很多关于 Web 应用程序安全的重要资源。

其他的建议。其他人建议使用 SSL。虽然这不会阻止参数篡改,但它是一种通用的良好安全实践,可以防御其他类型的问题。使用 SSL 很简单:只需将您的网站配置为使用 https,而不是 http(理想情况下,启用 HSTS 并设置secure所有 cookie 的位)。

此外,最好避免在 URL 参数中存储机密信息,其他条件相同。您可以将机密信息存储在会话状态或数据库中。

简而言之:不要加密 URL 参数,使用单独的查找

此外,如果您需要任何 Web 应用程序安全措施,使用 HTTPS 基本上是不可协商的。它在 2015 年是强制性的。熟悉 TLS 1.1+。


开发者想要做什么

开发者想要做什么

开发人员应该做什么

在此处输入图像描述

但我们需要防止销售代理偷看对方的线索

这反而意味着客户端是浏览器 - 您是否在某个时候将密钥作为明文发送?

多项式是正确的,您应该使用 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()返回一个静态加密密钥(通过从数据库中提取 128 位/dev/urandom并将其存储在数据库中来生成它)。

但是您仍然需要控制对生成 URL 的代码的访问。

这是我的解决方案

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

我创建了这样的网址

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

网址看起来像

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);
    }