使用`GET`请求表单的客户端,即使`POST`被定义。 是javascript iframe的原因?

我的网站上有两个后续表格,使用POST方法。

我的网站first.php的第一页包含以下代码:

 

a.php只能通过此POST请求访问(否则用户将获取方法不允许405错误)

提交后,此表单将打开带有AJAX模式窗口的a.php

a.php包含另一种forms:

 

当用户单击第二种forms的提交时,它将打开b.php ,也可以仅通过POST请求访问(否则 – 405错误)。

我可以在这些表单之间考虑的唯一区别是第二个包含跟踪js类(打开iframe)。 这是js代码:

 $(document).ready(function() { $(".tracking").click(function(){ var iframe = document.createElement('iframe'); iframe.style.width = '0px'; iframe.style.height = '0px'; iframe.style.display = 'block'; document.body.appendChild(iframe); iframe.src = '/track.htm'; }); 

这样做是为了使用从track.htm第三方脚本来跟踪转换

我注意到我的iPad访问量约占5%。 他们使用POST请求正确打开a.php ,但是当他们继续并继续打开b.php ,大约5%发出GET请求而不是所需的POST请求,导致他们得到405错误并离开网站。

我知道这些是真正的人类用户,因为我可以看到他们中的一些人多次尝试打开b.php并继续得到这些405错误。

这可能是因为他们的设备同时使用GET请求获取track.htm吗? 这是一些小故障?

怎么解决这个问题?

编辑4.4.2015:

由于有可能触发跟踪脚本导致此问题,我想知道是否有另一个火来触发它(或跟踪adwords转换),而不会导致这些iPad用户也对表单使用“GET”请求。

编辑10.4.2015

这是ajax类的jquery代码,它同时影响first.phpa.php ,因为first.php是父框架:

 $(document).ready(function() { $(".ajax").click(function(t) { t.preventDefault(); var e = $(this).closest("form"); return $.colorbox({ href: e.attr("action"), transition: "elastic", overlayClose: !1, maxWidth: $("html").hasClass("ie7") ? "45%" : "false", opacity: .7, data: { value: e.find('input[name="value"]').val(), } }), !1 }) }), 

从技术上讲,它不应该发生。 跟踪脚本创建的iframe指向/track.htm ,因此不应该对b.php页面发出任何GET请求。

另一方面,只是在这里大声思考,由于“真实世界”的用户,可能会发生一些情况。

  1. 用户碰巧有b.php页面的书签,因此当他们尝试使用他们的书签重新打开页面时,使用GET打开它。

  2. 用户试图刷新页面b.php ,然后收到有关“表单重新提交”的警告。 像大多数真实用户一样无能为力,他们取消了表单重新提交,然后点击地址栏并单击他们浏览器上的GO ,只是为了重新加载页面。 这也可能导致GET请求发送到b.php页面。

在设计表单提交的页面流时考虑最佳实践,最好只在b.phpprocess ”表单数据,然后将302 Redirect返回到另一个使用GET请求显示结果的页面。 这将允许用户“刷新”页面而无需双重提交表单,并且还允许用户将结果页面添加为书签。

这并没有回答你的问题,但因为它需要GET故障,但事实上,大约5%的iPad访问者无法注册,因为代码只接受POST,到目前为止没有人可以解决这个问题。 所以我建议改变战略,至少在同一时间。

通过仅接受POST请求来防止CSRF 已知不起作用 。 您选择仅接受此请求方法作为安全手段,最终会导致405.有更好的方法。

一个例子是使用CSRF令牌 ,特别是同步器令牌模式 。

CSRF令牌背后的想法是,当您生成表单时,您还会生成一个与表单绑定的“键”。 提交该表单时,如果它没有密钥或密钥不正确,则不需要处理表单。 Syncronizer令牌模式很有趣,因为它每次都会更改expect键(在表单字段实现中,每次都给字段一个新的名称属性)以及值。

让你的代码在a.php生成一个随机令牌并将其作为会话变量存储在服务器上。 将表单中的标记输出为隐藏字段。

b.php处理请求之前,请确保令牌值在请求数据中并确保其具有预期值。

您可以先检查$_POST数据,如果缺少,请检查$_GET数据。 无论哪个数组包含数据,如果数据没有有效的CSRF令牌,则响应4xx错误。

如果令牌是好的,请使用令牌并处理请求。 如果令牌丢失或无效,请返回4xx响应代码。

另一种方法是每次生成表单时将字段名称设置为随机值。 因此,而不是

 // ... in an importable file somewhere ... // Generate our tokens function token($len = 13) { $chrs = 'abcdefghijklmnopqrstuvwxyz0123456789_'; $str = ''; $upper_lim = strlen($chrs) - 1; for ($i = 0; $i < $len; $i++) { $idx = rand(0, $upper_lim); $str .= rand(0, 1) ? strtoupper($chrs[$idx]) : $chrs[$idx]; } return $str; } function magic_set_function($key, $value) { $_SESSION[$key] = $value; } function magic_get_function($key) { return (array_key_exists($key, $_SESSION) ? $_SESSION[$key] : NULL) } function validate_request() { $data = !empty($_POST) ? $_POST : $_GET; if ( empty($data) ) { return false; } // Ensure the tokens exist (hopefully not too costly) $field_tokens = magic_get_function('field_tokens'); if ( $field_tokens) === NULL ) { return false; } $csrf_token_name = $field_tokens['token']; $given_csrf_token = $data[$csrf_token_name]; // Get our CSRF token $expected_csrf_token = magic_get_function('csrf_token'); // ensure we're expecting a request / that we have generated a CSRF if ( $expected_csrf_token === NULL || $expected_csrf_token !== $given_csrf_token) { return FALSE; } // After whatever other checks you want... return TRUE; } function fetch_data() { $data = empty($_POST) == FALSE ? $_POST : $_GET; if (empty($data ) { throw new DataLoadException(); } // Ensure the tokens exist (hopefully not too costly) $field_tokens = magic_get_function('field_tokens'); if ( $field_tokens) === NULL ) { throw new TokenLoadException(); } foreach ($field_tokens as $field_name => $token_name) { if ( isset($data[$token_name]) ) { $data[$field_name] = $data[$token_name]; unset($data[$token_name]); } } return $data; } // first.php/a.php/b.php (wherever necessary) // ... $tokens = array(); // our csrf token $csrf_token = token(); $field_names = array('value', 'bar', 'token'); $field_values = array('value'=>'foo', 'bar' => 'none', 'token' => $csrf_token); // Tokenize errthing... foreach ($field_names as $k => $field_name) { // and generate random strings $tokens[$field_name] = token(); } // You NEED TO STORE THESE TOKENS otherwise submissions lose context magic_set_function('field_tokens', $tokens); magic_set_function('csrf_token', $csrf_token); // dup, but jic // first.php printf('', $tokens['value'], $field_values['value']); // ... // a.php // Get the data... (POST/GET) if (ensure_valid_request() !== TRUE) { handle_invalid_request(); } $data = fetch_data(); // ... // Tokenize errthing, generate a csrf, store the values, etc. // ... printf('', $tokens['bar'], $field_values['bar']); // ... // b.php // ... You get the idea ... 

它没有回答你为什么 5%发送GET请求的问题,但它确实解决了安全性和用户级别的整体问题。

编辑:在评论中专门回答OPs问题:

“(1)这是否需要使用cookies?(会话是指对吧?)”

阅读PHP会话并查找会话库。 很多,一个重量级是Zend( http://framework.zend.com/manual/1.12/en/zend.session.html )。 您可以保存到数据库,而不是受保护的服务器端会话。 我做了一个类似于Kohana的。

(2)我不理解“另一种方式”部分 – 它与你最初描述的方法有什么不同?

第一种方法是只在表单中添加一个令牌,并在提交时查找令牌以获得预期值。 如果表单没有它,则会引发错误抱怨。

第二种方法在表单生成时动态设置字段名称并添加令牌字段。 现在首先从程序,机器人或外部源提交正确的表单数据需要获取表单,因为他们不知道要使用哪些字段名称(而不是仅使用设置的字段名称发布数据)。

“(3)最重要的是,我不太担心CSRF攻击,我只是不希望机器人/爬虫爬进我的表格,这种方法是否会阻止它们,而不是人类?为什么?并且更容易实现这个的方法?“

如果你的意思是像谷歌/搜索引擎优化/尊重网络爬虫这样的机器人, robots.txt为了这个目的而存在的。 robots.txt是一个非常简单的文本文件,放在站点的根目录中。 您将在Web服务器的访问日志中看到/robots.txt的请求。 此文件告诉搜索引擎和其他机器人您的站点允许访问和索引哪些区域。 您可以在许多(网站) 5上阅读有关(机器人排除标准) 4的更多信息。

如第二个链接所示,请勿使用robots.txt隐藏信息。 它是一个公共文件,任何人都可以看到。 此外,恶意机器人不会尊重该文件。

我不确定当你说机器人时你的意思是爬行器或垃圾箱机器人(机器人试图提交数据)等等。 如果它是爬虫, robots.txt负责处理它们。 如果它是垃圾邮件程序,你可以添加一个隐藏字段(用CSS而不是html隐藏),其中有一个通用名称,当你填写时你知道它是无效的,你可以添加validation码等,等等。

尝试对原始请求的回调进行跟踪以确保其已加载?

你也可以通过malsup查看类似ajaxFormPlugin的内容

我想建议检查“b.php”页面的许可。 请确保该页面具有所有用户的“w”权限。 这是一个不发出“POST”请求的机会。

我知道这是一种解决方法,但是如果我假设您对$ _POST变量进行了大量检查,那么如果收到GET请求,您可以尝试使用GET替换POST:

 if (empty($_POST) && !empty($_GET)) $_POST = $_GET; //here the check of $_POST //... 

因为我们不知道为什么这个ipads(… apple -.-)有问题,而且在GET和POST之间没有那么大的区别 – 至少如果你不需要上传文件……

邮件表单可以作为get发送的唯一方法是使用脚本(直接更改方法属性,或者替换表单行为,例如使用ajax请求,绑定到事件“提交”另一个函数),所以我建议你检查在父页面和子页面中运行的每个脚本。

你的ajax调用不包含method: "POST" 。 这可能是原因。