保证正确Ajax调用参数值的最佳方法

时间:2014-05-20 00:48:35

标签: php ajax encryption

我正在开发一个需要一些ajax调用的网站,以提高灵活性和性能。 我的ajax调用是一个排名系统。我需要使用ajax(storeID,clientID,orderID)处理三个输入值。要使用ajax提交操作,我想确保使用Web工具的用户不会修改发送的参数值。所以我想到了三种不同的方法来保证发送的信息没有改变:

  1. 发送一个额外的值,即对所有发送数据进行加密。因此,在处理ajax时,在服务器端,我可以重新加密发送的数据,并查看加密结果是否与发送的加密值相匹配。

  2. 将所有数据作为一个加密值发送。然后在执行ajax时在服务器上,我可以解密数据并再次分配值。

  3. 仅发送orderID及其加密,然后使用方法(1)验证orderID未更改,并使用数据库查询获取另外两个信息。

  4. 以下是对以下三种方式的看法:

    1. 消耗内存,因为我必须发送orderID,clientID,storeID,encryptedID。此外,ajax电话中监控的信息将为人们提供有关订单评级时所发生情况的信息。

    2. 我在网上查了mcrypt_encrypt和mcrypt_decrypt,但我从未使用过它们。我看到他们生成了一个长字符串,但我更喜欢将我的数据保持简短或看起来像md5加密数据。有更好的方法吗?

    3. 这是一种优雅的方式,它看起来很直接,但它需要一些MySQL干预,这可能是耗时的,特别是在未来数据增长时。

    4. 那么你认为哪一个更好?如果你有更多的方式我欣赏,如果你在这里分享。谢谢

      我想避免的方案示例: 单击按钮将通过传递产品ID使用AJAX提交表单。用户转到源代码,并将产品的ID从X更改为Y.现在,用户单击按钮并提交表单,并且ID Y的产品受到影响。如您所见,发送的参数值不安全且可以修改。我正在寻找一种方法来保证发送的参数值是正确的而不是修改的。

      PS :这个问题与CSRF处理无关。

10 个答案:

答案 0 :(得分:5)

每个人都试图引导你到达以下几点:

在服务器上....

  1. 您知道用户是谁(auth)。
  2. 您应该定义该用户可以访问的内容(ACL)。
  3. 您应验证所请求的产品是否存在且允许用户访问。
  4. 如果允许用户对产品进行更改,那么根据定义,它是对X产品进行更改的权利,如果他们使用Y产品信息进行更改,因为它们在源代码中乱七八糟,那就是用户。
  5. 如果这是与购物车相关的,那么您应该忽略用户传递的所有其他数据(例如价格),并仅按ID加载该产品数据。所以他们不能以X产品的价格购买Y产品。然后,如果他们在Firebug中输入Y产品的ID,谁在乎呢?
  6. 如果你不做2和3,那么你就完全没有浪费你的时间了。无论你想出什么,都只会给你的不安全感增添晦涩。

答案 1 :(得分:3)

如果要使用加密,可以将Mcrypt函数与Rijndael密码一起使用。这给你一个128位的块来玩。

假设您的三个ID(storeID,clientID,orderID)各为32位,您可以将它们与4个字符串一起打包以形成128位块。该字符串将用于稍后检查解密数据。

$block = pack('a4LLL', 'MyID', $storeID, $clientID, $orderID);

然后创建一个随机IV并加密块:

$mod = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = openssl_random_pseudo_bytes(mcrypt_enc_get_iv_size($mod));
$mySecretKey = 'LA72U/aLEOcDoN0rqtM5sehNNjd7TUSILiBgI8bej6o=';

mcrypt_generic_init($mod, base64_decode($mySecretKey), $iv);

$encrypted = mcrypt_generic($mod, $block);

mcrypt_generic_deinit($mod);
mcrypt_module_close($mod);

在使用客户端之前,您需要对加密块和IV进行编码。使用base64并交换/和+字符会给你一个很好的值,可以作为GET参数发送:

$encodedBlock = substr(strtr(base64_encode($encrypted), '/+', '-_'), 0, -2);
$encodedIV = substr(strtr(base64_encode($iv), '/+', '-_'), 0, -2);

稍后当这些值发送到您的ajax脚本时,只需撤消该过程:

$encrypted = base64_decode(strtr($_GET['block'], '-_', '/+') . '==');
$iv = base64_decode(strtr($_GET['iv'], '-_', '/+') . '==');
$mod = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');

mcrypt_generic_init($mod, base64_decode($mySecretKey), $iv);

$block = mdecrypt_generic($mod, $encrypted);

mcrypt_generic_deinit($mod);
mcrypt_module_close($mod);

$data = unpack('a4str/Lstore/Lclient/Lorder', $block);

if ($data['str'] != 'MyID')
  throw new Exception('Something fishy is going on');

如果一切顺利,$ data将包含$ data [' store'],$ data [' client']和$ data [' order']。

您还可以将nonce值打包到块中,以防止重放攻击。

答案 2 :(得分:2)

我真的不认为加密会帮助你。

这里似乎你担心的是已经登录的用户摆弄了请求参数(正常的会话安全性应该让用户不用登录)。

我认为你只需要在服务器端验证请求。您应该能够通过会话变量识别您的用户。然后,ajax服务可以正常方式验证请求。

答案 3 :(得分:2)

对于安全问题,您需要保持简单。

这里的一个简单事实是任何客户端内容都可以更改。

所以你需要的是服务器端检查。

服务器制作了原始表单,此表单设置在专用于特定用户的页面上。此表单应具有som CSRF令牌,确保发布的数据来自服务器构建的页面,而不是来自其他网站,而不是来自某些自动脚本等。

因此,第一步是确保所有表单都包含一些令牌,并且服务器构建的所有表单都可以检查这些令牌一段时间,并确保令牌与先前生成的用户/表单匹配。这意味着服务器端的表单缓存,在这个缓存上有一些严重的清理任务,你还需要在包含过时令牌的发布表单中处理消息(在ajax中)(抱歉,你已经在等待了)时间过长)。一些简单的方法仅使用用户会话来存储反CSRF令牌,您需要找到适合您所有需求的解决方案(匿名表单,共享表单,多步表单等)

一旦您拥有了反CSRF令牌系统,您就可以轻松地在服务器端存储阻止用户更改表单元素的内容。 像你的" id"可以简单地在表单缓存上进行调整,甚至不发送到客户端。您还可以存储一些表单元素的签名等。

答案 4 :(得分:2)

你无缘无故地走了一条路。

您几乎不应强制在浏览器的请求和以下的请求之间建立链接。正确的逻辑(REST的基础)将是

  • 根据请求,向浏览器发送与orderID相关的信息
  • 通过浏览器处理修改请求而不必担心以前的请求:只需使用数据库检查修改请求是否有效且一致,并在这种情况下处理它

从数据库中获取两个值不应该让您担心:如果它很慢,则表示您没有做错,可以解决。

答案 5 :(得分:2)

一个相当简单的方法是salt三个参数的基本前向(即sha / md5等)哈希对照服务器上保留的密钥(并且从不以任何方式接触用户。)

然后,当您收到用户的请求时,您只需复制此仅向前哈希过程即可确保数据未被篡改,类似于大多数网站实施身份验证而无需存储明文的方式用户密码在他们的数据库中的副本。

例如:

define('SUPER_SECRET_KEY', 'foobar123'); // our 007 secret key to hash requests against

function generateToken($storeID, $clientID, $orderID) {
    return hash('sha256', SUPER_SECRET_KEY . "/{$storeID}/{$clientID}/{$orderID}");
}

function validateToken($token, $storeID, $clientID, $orderID) {
    return generateToken($storeID, $clientID, $orderID) === $token;
}

正如我所说,现在这是一个非常基本的起点。使用静态salt值并不会非常有效,并且您仍然可以对重放攻击保持开放状态(即有人反复向同一令牌/ clientID / storeID / orderID组合发送垃圾邮件)。< / p>

打击重播攻击的一种方法是为每个请求生成nonce以充当salt

使用nonces的另一个超级基本示例,前面是重伪代码:

function generateNonce() {
    return hash('sha512', mt_rand() . time()); // please make a more secure nonce generator than this :)
}

// We need a way to retrieve the nonce for a particular request:
function generateLookup($storeID, $clientID, $orderID) {
    return hash('sha256', "{$storeID}/{$clientID}/{$orderID}");
}

function generateToken($nonce, $storeID, $clientID, $orderID) {
    $lookup = generateLookup($storeID, $clientID, $orderID);

    // Store the $nonce and $lookup somewhere (i.e a database)
    $db->query("INSERT INTO ... (nonce,lookup,expired) VALUES ('$nonce','$lookup','N')");

    return hash('sha256', "{$nonce}/$storeID/$clientID/$orderID");
}

function validateToken($token, $storeID, $clientID, $orderID) {
    $lookup = generateLookup($storeID, $clientID, $orderID);
    $rows = $db->query("SELECT nonce FROM ... WHERE lookup = '$lookup' AND expired = 'N'"); // again, pseudocode for retrieving the nonce from the persistent store
    if (count($rows) !== 0) {
        $nonce = $rows[0]['nonce'];
        // expire the nonce (note: these database queries should be in a transaction)
        $db->query("UPDATE ... SET expired = 'Y' WHERE $lookup = '$lookup' AND nonce = '$nonce'");
        return generateToken($nonce, $storeID, $clientID, $orderID) === $token;
    }
    return false;
}

然后您的基本工作流程将成为:

GET

$data = // retrieve data
$token = generateToken(generateNonce(), $data['storeID'], $data['clientID'], $data['orderID']);

// output the $token value in a hidden field in your form

在发布时

if (validateToken($_POST['token'], $_POST['storeID'], $_POST['clientID'], $_POST['orderID'])) {
    // All good
}
else {
    echo "Back, ye filthy hacker!";
}

答案 6 :(得分:1)

我认为最好的方法是将发送值生成的额外值作为哈希(如md5)发送,然后在使用相同算法的服务器中将发送的哈希值与生成的值进行比较,如果等于,则发布的数据为有效的。

让我们使用Javscript md5库,只需用:

计算哈希值
hash=md5('sometext'+storeID+clientID+orderID);

然后用AJAX提交此哈希值,最后在服务器中再次计算并比较。

我看到的唯一问题是,如果在客户端完成工作,用户可以轻松获取哈希算法,计算并发送。我想你会使用一些混淆技术。

也可以生成随机数,将其包含在md5中并发送附加参数。这将每次生成一个不同的哈希值。

答案 7 :(得分:1)

永远不要相信客户。任何东西都可以被修改,并且假设他们最终能够猜出你在服务器端实现了什么也更安全。因此,我建议,只发送信息,让您确定所做出的决定。

例如,如果表单一次仅适用于1个产品,请将值存储在服务器端(例如,在$SESSION变量中)。当他们提交表单时,它只能应用于该产品,并且只会影响他们对UI的操作。

如果clientID特定于用户,则存储服务器端,并且根本不将其发送到客户端。

如果他们无论如何都可以更改客户端或带有UI的产品,那么他们是否修改脚本并不重要。

由于您似乎已经拥有某种身份验证机制,只需要解决这个问题,如果他们有权执行某些操作,他们是否关心客户端是否可以执行此操作,如果他们可以通过UI执行此操作。如果他们没有权限,请不要让他们首先访问信息。当然,模糊他们本来不应该拥有的数据可能看起来很安全,但它绝对不像发送他们不应该知道的数据一样安全,无论你做了多少混淆。

编辑:在对其他答案的评论之后,有很多方法可以实现这一点,但这里有一个让你思考:

您可以轻松地同时处理多个表单:使用mcrypt_create_iv生成随机令牌。比如说,长度为20个字符。将其存储在会话数组中

$formtoken = mcrypt_create_iv(20);
$_SESSION['infos'][$formtoken] = array($value,$anothervalue);

然后将$formtoken发送到浏览器。每次浏览器通过AJAX提交时,发送一个新的表单令牌并删除与前一个一起存储的数据,因此它不再作为提交有效。您需要发送的唯一信息是用户无法操作的令牌,因为它是每次请求表单时生成的,并且仅对该表单有效,并且无论如何都可以操作其他数据。

答案 8 :(得分:0)

首先,http无论如何都不安全。因此:

  

请使用https来保护客户端与客户之间的通信   服务器

通过使用https,您可以保护用户的会话免遭被劫持

如果使用ajax从客户端传输到服务器的数据对隐私不明智,我不会建议对其进行加密。

  

保持非隐私相关数据的不受欢迎。

使用此策略,没有人会猜测,某些敏感(例如跟踪)相关数据会以隐藏方式传输。转移非隐私相关的非加密数据不会影响用户的信任

来自客户端的数据不安全

当然,您的服务器应用程序无法信任来自客户端的数据。客户端可能是

  • 合法用户
  • 合法用户的木马劫持PC
  • 或从第三方注入的数据包。

验证客户端 - &gt;服务器 - &gt;客户周期

要验证来自客户端的数据确实来自客户端,需要客户端传回一些服务器提供的数据。

假设客户端允许使用V1, V2, ...传输某些值http,这些值在传递到服务器之前。

  

如果请求通过浏览器链接或正常直接点击来实现   间接的ajax调用是不相关的。

使用静态服务器端密码进行签名

由于值V1, V2, ..., Vn来自服务器,因此将值标记为来自服务器的应用程序。

使用服务器端密码TheSecret进行唱歌,例如这样一个非常长的价值:

SignValue := md5( V1 + V2 + ... Vn + TheSecret )

当然,您可以使用其他单向哈希。对于大多数实际案例,md5可能足够强大。

使用临时服务器端密钥进行签名

除了常量秘密TheSecret之外,您可能希望每天生成一个新秘密。 DailySecret可能会像这样生成:

DailySecret := TheSecret  + md5( TheSecret + date( 'Ymd' ) )
SignValue := md5( V1 + V2 + ... Vn + DailySecret )

答案 9 :(得分:0)

如果您的ajax发送了那么多敏感信息,您需要更改设计。

但是如果你不能这样做,你也可以只在服务器上散列值,而你的ajax只需要散列值。如果客户端更改了哈希值,它将变为无效,所以你去了。这是防止id操作的标准做法。

希望这有帮助。