使用“GET”的客户端请求表单,即使定义了“POST”.javascript iframe是原因吗

Clients using `GET` requests for a form, even though `POST` is defined. is javascript iframe the cause?

本文关键字:POST javascript iframe 定义 客户端 GET 请求 表单 使用      更新时间:2023-10-18

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

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

<form action="a.php" method="POST" target="_blank">
<input name="value" type="hidden" value="foo"/>
<div class="button"><label><span class="icon"></span>
<input type="submit" class="button-graphic ajax" value="Click Here"></label></div></form>

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

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

a.php包含另一种形式:

<form action="b.php" method="POST" target="_blank">
<input name="bar" type="hidden" value="none"/>
<div class="border"><label><input type="submit" class="button-graphic2 tracking" value="Continue"></label></div></form>

当用户在第二个表单中点击Submit时,会打开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 执行的第三方脚本跟踪转换

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

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

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

如何解决这个问题?

2015年4月4日版:

由于触发跟踪脚本可能会导致这种情况,我想知道是否还有另一个触发脚本(或跟踪广告词转换),而不会导致这些iPad用户对表单使用"GET"请求。

编辑10.4.2015

这是ajax类的jquery代码,它既影响first.php,也可能影响a.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,因此不应该有任何GET请求到b.php页面。

另一方面,只要大声思考一下,就有一些场景可能会因为"真实世界"用户而发生。

  1. 用户碰巧为b.php页面添加了书签,因此当他们试图使用书签重新打开页面时,导致他们使用GET打开页面。

  2. 用户试图刷新页面b.php,然后收到"表单重新提交"的警告。由于大多数真实用户都一无所知,他们取消了表单的重新提交,然后点击地址栏,在浏览器上点击GO,唯一的目的是重新加载页面。这也可能导致GET请求发送到b.php页面。

考虑到设计表单提交的页面流时的最佳实践,您最好在b.php中仅"process"表单数据,然后使用GET请求将302 Redirect返回到另一个显示结果的页面。这将允许用户在不重复提交表单的情况下"刷新"页面,还允许用户将结果页面添加书签。

这并不能回答你的问题,但它会导致GET故障,但就目前情况来看,大约5%的iPad访问者无法注册,因为代码只接受POST,到目前为止还没有人能解决这个问题。因此,我建议改变策略,至少在这段时间内是这样。

已知仅通过接受POST请求来阻止CSRF是无效的。您选择只接受此请求方法作为安全手段,最终导致405。有更好的方法。

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

CSRF令牌背后的想法是,当你生成表单时,你也会生成一个与表单绑定的"密钥"。当提交该表单时,如果它没有密钥或密钥不正确,你就不用麻烦处理表单了。Syncronizer令牌模式的奇特之处在于,除了值之外,它每次都会更改期望密钥(在表单字段实现中,每次都为<input type="hidden">字段赋予一个新的名称属性)。

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

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

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

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

另一种方法是在每次生成表单时将字段名设置为随机值。所以代替<input name="value" type="hidden" value="foo"/><input name="bar" type="hidden" value="none"/>

// ... 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 j.i.c.
// first.php
printf('<input type="hidden" name="%s" value="%s"/>', $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('<input type="hidden" name="%s" value="%s"/>', $tokens['bar'], $field_values['bar']);
// ...
// b.php
// ... You get the idea ...

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

编辑:在评论中具体回答OP的问题:

"(1)这需要使用cookie吗?(会话意味着cookie,对吗?)"

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

(2)我不理解"另一种方式"部分——它与你最初描述的方法有何不同

第一种方法是在表单中添加一个令牌,并在提交时查找具有预期值的令牌。如果表单中没有,则会抛出一个错误抱怨。

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

"(3)最重要的是,我不太担心CSRF攻击,我只是不希望机器人/爬行器爬进我的表单,这种方法会阻止它吗,而不是人类?为什么?有更容易的方法来实现这一点吗?"

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

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

我不确定当你说机器人时,你的意思是爬行器还是垃圾邮件机器人(试图提交数据的机器人)等等。如果是爬虫,robots.txt会照顾它们。如果是垃圾邮件机器人,你可以添加一个隐藏字段(用CSS而不是html隐藏),该字段具有一个通用名称,当填写时你知道该名称无效,你可以增加一个captcha等。

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

此外,您还可以通过malsup 查看类似ajaxFormPlugin的内容

我建议检查您的"b.php"页面的权限。请确保该页面对所有用户都具有"w"权限。这是一个不进行"POST"请求的机会。

我知道这是一个变通方法,但如果像我想的那样,您对$_POST变量进行了一系列检查,如果您收到GET请求,您可以尝试用GET:替换POST

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

因为我们不知道为什么这款ipad(…apple-.-)会出现问题,GET和POST之间没有太大区别——至少如果你不需要上传文件的话。。。

将post表单发送为get的唯一方法是使用脚本(直接更改方法属性,或者用ajax请求替换表单行为,绑定到事件"提交"另一个函数),因此我建议您检查在父页和子页中运行的每个脚本。

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