HTML表单:输入类型隐藏值可见且可更改 - 记录已处理状态

时间:2018-04-19 15:25:10

标签: php html forms post session-variables

我正在处理一个需要发布秘密变量的应用程序。我写了这段代码。

<form target="_blank" action="/validate/lista.php" method="POST">
            <input type="hidden" name="evento" value="<?php echo $pname ?>" />
            <button class="btn btn-block btn-md btn-outline-success">Lista</button>
</form>

我的问题是,如果用户使用chrome或其他任何东西检查元素,他可以看到值并在POST之前更改它。 我可以使用SESSION,但每个用户都有不同的会话ID,这样我就需要POST会话ID(因为它们是separete应用程序),我认为这是不安全的。或者可以吗?

我该怎样防止这种情况?我是编程新手......

谢谢

1 个答案:

答案 0 :(得分:0)

安全地维护HTML表单状态(&#39;对话&#39;跟踪)

跟踪“状态”&#39;由客户端和服务器处理的HTML表单。

典型的对话&#39;是:

  1. 将新表单发送给客户端,通常是针对必须登录的特定用户。
  2. 客户输入数据并将其返回。
  3. 经过验证,可能会再次发送。
  4. 应用数据更改。
  5. 告知客户结果。
  6. 听起来很简单。唉,我们需要跟踪“状态”。在谈话期间的形式#39;

    我们需要在隐藏字段中记录状态。这可以打开我们的各种失败模式&#39;。

    这个答案是一种可靠地跟踪对话的方法。

    包括恶意的人物#39;它发生了。 ; - /

    这是一个数据更改表单,因此我们不希望它应用于错误的人。

    有各种要求:

    明智的:

    1. 防止表单被处理两次
    2. 如果表单太旧,请让用户确认数据
    3. 恶意:

      1. 将表单更改为显示为来自其他用户
      2. 使用表单的旧副本
      3. 更改其他隐藏数据以破坏用户数据
      4. 现在,我们无法阻止客户端更改隐藏数据,也不能将其存储为稍后重播。等。

        怎么做?

        1. 我们需要确保如果它被更改,那么我们可以检测到它被篡改并告诉用户它。我们什么都不做。
        2. 如果他们向我们发送旧的存储有效副本,那么我们也可以检测到它。
        3. 有一种简单的方法吗?哦,是的! :)

          <强>数目:

          1. 为每个表单添加一个唯一ID:可以轻松确定我们是否已经看过它。
          2. 为每个表单提供首次创建时间的时间戳。
          3. 然后我们可以决定允许使用它的最大年龄。 如果它太旧,我们只需将输入的数据复制到新表格并要求用户确认。见Captcha:)

            当我们处理表单时,我们存储表单ID。

            处理表单前的第一项检查是查看我们是否已经处理过

            识别&#39;?

            我们用AES加密它! :)只有服务器需要知道密码,所以没有客户端问题。

            如果它被更改,那么解密将失败,我们只是向用户发出一个新表单,并在其上输入数据。 :)

            这是很多代码吗?并不是的。它使表格处理安全。

            一个优点是可以保护内置的CSRF攻击,因此不需要单独的代码。

            程序代码(FormState类)

            <?php
            /**
            * every 'data edit' form has one of these - without exeception.
            *
            * This ensures that the form I sent out came from me. 
            * 
            * It has: 
            *    1) A unique @id
            *    2) A date time stamp and a lifetime
            * 
            *  Can be automatically generated and checked. 
            */
            
            class FormState {
            
                const MAX_FORM_AGE = 600; // seconds 
            
                const ENC_PASSWORD = '327136823981d9e57652bba2acfdb1f2';   
                const ENC_IV       = 'f9928260b550dbb2eecb6e10fcf630ba';   
            
                protected $state = array();
            
                public function __construct($prevState = '')
                {
                    if (!empty($prevState)) {
                        $this->reloadState($prevState); // will not be valid if fails
                        return;
                    }
            
                    $this->setNewForm();
                }
            
                /**
                 * Generate a new unique id and timestanp
                 *
                 * @param $name - optional name for the form
                 */
                public function setNewForm($name = '')
                {
                    $this->state = array();
                    $this->state['formid'] = sha1(uniqid(true)); // each form has a unique id 
                    $this->state['when'] = time();
            
                    if (!empty($name)) {
                        $this->setAttribute('name', $name);
                    }
                }
            
                /**
                 * retrieve attribute value
                 *
                 * @param $name     attribute name to use
                 * @param $default  value to return if attribute does not exist
                 * 
                 * @return string / number
                 */
                public function getAttribute($name, $default = null) 
                {
                        if (isset($this->state[$name])) {
                               return $this->state[$name];
                        } else {
                               return $default;
                        }   
                }
            
                /**
                 * store attribute value
                 *
                 * @param $name     attribute name to use
                 * @param $value    value to save
                 */
                public function setAttribute($name, $value) 
                {
                        $this->state[$name] = $value;
                }
            
                /**
                 * get the array
                 */
                public function getAllAttributes()
                {
                    return $this->state;
                } 
            
                /**
                 * the unique form id
                 *  
                 * @return hex string
                 */
                public function getFormId()
                {
                    return $this->getAttribute('formid');    
                }
            
                /**
                 * Age of the form in seconds
                 * @return int seconds
                 */
                public function getAge()
                {
                    if ($this->isValid()) {
                        return time() - $this->state['when'];
                    }
                    return 0;
                }
            
                /**
                 * check the age of the form
                 * 
                 *@param $ageSeconds is age older than the supplied age 
                 */
                public function isOutOfDate($ageSeconds = self::MAX_FORM_AGE)
                {
                    return $this->getAge() >= $ageSeconds;
                }
            
                /**
                 * was a valid string passed when restoring it
                 * @return boolean
                 */
                public function isValid()
                { 
                    return is_array($this->state) && !empty($this->state);
                }
            
                /** -----------------------------------------------------------------------
                 * Encode as string - these are encrypted to ensure they are not tampered with  
                 */
            
                public function asString()
                {        
                    $serialized = serialize($this->state);
                    $encrypted = $this->encrypt_decrypt('encrypt', $serialized);
            
                    $result = base64_encode($encrypted);
                    return $result;
                }
            
                /**
                 * Restore the saved attributes - it must be a valid string 
                 *
                 * @Param $prevState
                 * @return array Attributes
                 */
                public function fromString($prevState)
                {
                    $encrypted = @base64_decode($prevState);
                    if ($encrypted === false) {
                       return false; 
                    }
            
                    $serialized = $this->encrypt_decrypt('decrypt', $encrypted);
                    if ($serialized === false) {
                       return false; 
                    }
            
                    $object = @unserialize($serialized);
                    if ($object === false) {
                       return false; 
                    }
            
                    if (!is_array($object)) {
                        throw new \Exception(__METHOD__ .' failed to return object: '. $object, 500);
                    }
                    return $object; 
                }
            
                public function __toString()
                {
                    return $this->asString();
                }
            
                /**
                 * Restore the previous state of the form 
                 *    will not be valid if not a valid string
                 *
                 * @param  $prevState  an encoded serialized array
                 * @return bool isValid or not
                 */
                public function reloadState($prevState)
                {
                    $this->state = array();
            
                    $state = $this->fromString($prevState);
                    if ($state !== false) {
                        $this->state = $state;
                    }
            
                    return $this->isValid();
                }
            
                /**
                 * simple method to encrypt or decrypt a plain text string
                 * initialization vector(IV) has to be the same when encrypting and decrypting
                 * 
                 * @param string $action: can be 'encrypt' or 'decrypt'
                 * @param string $string: string to encrypt or decrypt
                 *
                 * @return string
                 */
                public function encrypt_decrypt($action, $string) 
                {
                    $output = false;
            
                    $encrypt_method = "AES-256-CBC";
                    $secret_key = self::ENC_PASSWORD;
            
            
                    // iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
                    $secret_iv_len = openssl_cipher_iv_length($encrypt_method);
                    $secret_iv = substr(self::ENC_IV, 0, $secret_iv_len);
            
                    if ( $action == 'encrypt' ) {
                        $output = openssl_encrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
            
                    } else if( $action == 'decrypt' ) {
                        $output = openssl_decrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
                    }
            
                    if ($output === false) {
                        // throw new \Exception($action .' failed: '. $string, 500);
                    }
            
                    return $output;
                }
            }
            

            示例代码

            Full Example Application Source Code (Q49924789)

            Website Using the supplied Source Code

            FormState source code

            我们是否有现有表格?

            $isExistingForm = !empty($_POST['formState']);
            $selectedAction = 'start-NewForm'; // default action
            
              if  ($isExistingForm) { // restore state  
                $selectedAction = $_POST['selectedAction'];      
                $formState = new \FormState($_POST['formState']); // it may be invalid
                if (!$formState->isValid() && $selectedAction !== 'start-NewForm') {
                    $selectedAction = "formState-isWrong"; // force user to start a new form
                }
            
            } else {
                $_POST = array();  // yes, $_POST is just another PHP array 
                $formState = new \FormState();    
            }
            

            开始新表单

                $formState = new \FormState();
                $_POST = array(); 
                $displayMsg = "New formstate created. FormId: ". $formState->getFormId();
            

            在FormState中存储UserId(数据库ID)

                    $formState->setAttribute('userId' $userId);
            

            检查表单是否旧?

              $secsToBeOutOfDate = 3;
            
              if ($formState->isOutOfDate($secsToBeOutOfDate)) {
                $errorMsg = 'Out Of Date Age: '. $secsToBeOutOfDate .'secs'
                            .', ActualAge: '. $formState->getAge();
              }             
            

            从表单隐藏字段重新加载状态。

              $formState = new \FormState('this is rubbish!!');
              $errorMsg = "formState: isValid(): ". ($formState->isValid() ? 'True' : 'False');        
            

            检查表单是否已被处理。

              if (isset($_SESSION['processedForms'][$formState->getFormId()])) {
                  $errorMsg = 'This form has already been processed. (' . $formState->getFormId() .')';
                  break;
              }
            
              $_SESSION['processedForms'][$formState->getFormId()]  = true;
              $displayMsg = "Form processed and added to list.";