防止表单重复提交的几种策略

表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题,比如:

  • 点击提交按钮两次
  • 点击刷新按钮
  • 使用浏览器后退按钮重复之前的操作,导致重复提交表单
  • 使用浏览器历史记录重复提交表单
  • 浏览器重复的HTTP请求

#几种防止表单重复提交的方法

禁掉提交按钮

表单提交后使用Javascript使提交按钮disable。这种方法防止心急的用户多次点击按钮。但有个问题,如果客户端把Javascript给禁止掉,这种方法就无效了。

Post/Redirect/Get模式

在提交后执行页面重定向,这就是所谓的Post-Redirect-Get (PRG)模式。简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。

这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。

在session中存放一个特殊标志

当表单页面被请求时,生成一个特殊的字符标志串,存在session中,同时放在表单的隐藏域里。接受处理表单数据时,检查标识字串是否存在,并立即从session中删除它,然后正常处理数据。

如果发现表单提交里没有有效的标志串,这说明表单已经被提交过了,忽略这次提交。

这使你的web应用有了更高级的XSRF保护。

##在数据库里添加约束。
在数据库里添加唯一约束或创建唯一索引,防止出现重复数据。这是最有效的防止重复提交数据的方法。

转自外刊IT评论

mongodb注入攻击

一直以为注入只存在关系型数据库中,看了这篇文章才知道mongodb也会中招.
翻看php手册发现早就发现有这个问题,以前看手册一直没注意.

数组绑定时的注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
$mongo = new mongoclient();
//选择数据库
$db = $mongo->myinfo;
//选择集合
$coll = $db->test;
$username = $_GET['username'];
$password = $_GET['password'];
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $coll->find($data);
$count = $data->count();
if ($count>0) {
foreach ($data as $user) {
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
}
}
else{
echo '未找到';
}
?>

如果传入url为

1
http://127.0.0.1/2.php?username[$ne]=test&password[$ne]=test

mongodb最终执行:

1
db.test.find({username:{'$ne':'test'},password:{'$ne':'test'}});

php会把test集合内的所有数据全部便利出来。

防止注入的方法也很简单, 注意参数的检验.

1
2
3
4
5
6
7
8
9
<?php
...
$username = $_GET['username'];
$password = $_GET['password'];
$username = is_string($username) ? $username : '';
$password = is_string($password) ? $password : '';
...
?>

接受参数拼接直接以命令行执行的注入可以参考php手册 使用MongoCode 进行转椅后再使用.

curl的https问题

php 使用curl访问https网站时遇到的问题。
代码如下

1
2
3
4
5
6
7
8
9
10
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
$return = curl_exec($ch);
curl_close($ch);
var_dump($return);
?>

返回false

修改代码 在curl_exec 后加入

1
2
3
if(curl_errno($ch)){
var_dump(curl_error($ch));
}

可以看到 证书获取不到的提示信息

SSL certificate problem: unable to get local issuer certificate

  • 简单解决 加入一下选项 禁用证书检查
    1
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  • 添加证书解决

    1. 获取证书

      1. 在浏览器(firefox)打开请求的接口地址,
      2. 选择菜单(工具–查看页面信息)
      3. 安全–查看证书–详细信息–选择顶级证书机构–导出(X.509(pem))
2. 修改代码 添加如下选项
1
2
3
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/BuiltinObjectToken-EquifaxSecureCA.crt');

SSL CA cert (path? access rights?) 问题 参考stackoverflow直接重启php-fpm

api签名

为了提高传输过程参数的防篡改性,必须使用签名参数sig.
本文只是一个签名的简单实现 验证参数完整性 请求超时.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
class SomeClass
{
CONST API_PUBLIC_KEY = "asdadasdaxxxx";
CONST URL = 'http://example.com/api/';
public function signature(array $param,
$timeOut = 60)
{
$param['expire'] = time() + $timeOut;
$param['sig'] = hash_hmac('sha256',
join('', $param), self::API_PUBLIC_KEY);
$this->param = $param;
return $this;
}
public function getRequestUrl()
{
return self::URL . '?' . http_build_query($this->param);
}
public function validateSignature($param, $sig)
{
return hash_hmac('sha256', join('', $param), self::API_PUBLIC_KEY)
== $sig;
}
}
$obj = new SomeClass();
$param = array('useranme' => 'zhangsan');
//http://example.com/api/?useranme=zhangsan&expire=1408628093&sig=27b11880e19691b7e110cc8665c55160fc2b321b74c33b4877fac2da0545640e
echo $obj->signature($param, 300)->getRequestUrl();
$sig = isset($_GET['sig']) ? $_GET['sig'] : '';
$expire = isset($_GET['expire']) ? $_GET['expire'] : '';
if(!$obj->validateSignature($param, $sig)){
echo "签名验证错误";
exit;
}
if($expire < time()) {
echo "请求超时";
exit;
}
echo "processing";
?>

检查文档是否存在

<?php
    $mongo = new MongoClient();
    $db = $mongo->dbName;
    $collection = $db->collectionName;

    $cursor = $collection->find(
        $criteria, 
        array('_id' => 1)
    )->limit(1);

    if($coursor->count() == 0){
      exit('not exist');
    }

    exit('exits');