dongyulian5801 2018-04-19 15:25
浏览 61
已采纳

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

I am working on an application that I need to post a secret variable. I wrote this code.

<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>

My problem is that if the user inspect the element with chrome or whatever, he can see the value and change it before POST. I could use SESSION but every user has a different session ID and this way I would need to POST the session ID (because they are separete applications), which I think is not secure. Or is it ok?

How can I prevent this? I am new to programming...

Thank you

  • 写回答

1条回答 默认 最新

  • douwengzao5790 2018-04-24 13:40
    关注

    Maintain HTML Form State safely ('Conversation' Tracking)

    Keep track of the 'state' of an HTML Form as it is processed by the client and the server.

    The typical 'conversation' is:

    1. Send a new form to the client, often for a specific user who has to login.
    2. The client enters data and returns it.
    3. It is validated and may be sent out again.
    4. The data changes are applied.
    5. the client is informed of the result.

    It sounds simple. Alas, we need to keep track of the 'state' of the form during the 'conversation'.

    We need to record the state in a hidden field. This can open us up to various 'failure modes'.

    This answer is one method of reliably keeping track of the 'conversations'.

    Including people being 'malicious'. It happens. ;-/

    This is a data change form so we don't want it applied to the wrong person.

    There are various requirements:

    Sensible ones:

    1. prevent a form being processed twice
    2. Ask a user to confirm the data if the form is too old

    Malicious ones:

    1. Changing the form to appear to be from a different user
    2. Using an old copy of the form
    3. Changing other hidden data to corrupt the user data

    Now, we cannot prevent the client changing the hidden data, or storing it to replay later. etc.

    What to do?

    1. We need to ensure that if it is changed then we can detect that it is tampered with and tell the user about it. We do nothing.
    2. If they send us an old stored valid copy then we can detect that as well.

    Is there a simple way of doing this? Oh yes! :)

    Answers:

    1. Give each form a unique id: makes it easy to determine if we have already seen it.
    2. Give each form a timestamp of when it was first created.

    we can then decide the max age we allow to use it. If it is too old then we just copy the entered data to a new form and ask the user to confirm it. see Captcha :)

    When we process the form we store the form id.

    The first check before processing a form is to see if we have already processed it

    Identifying 'tampering'?

    We encrypt it with AES! :) Only the server needs to know the password so there are no client issues.

    If it is changed then the decrypt will fail and we just issue a new form to the user with the data input on it. :)

    Is it a lot of code? Not really. And it makes forms processing safe.

    One advantage is that has the protection for the CSRF attack built in so no separate code needed.

    Program Code (FormState Class)

    <?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;
        }
    }
    

    Example Code

    Full Example Application Source Code (Q49924789)

    Website Using the supplied Source Code

    FormState source code

    Do we have an existing form?

    $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();    
    }
    

    Start New Form

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

    Store UserId (Database Id) in the FormState

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

    Check a form being to old?

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

    Reload State from the form hidden field.

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

    Check if a form has already been processed.

      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.";
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 孟德尔随机化结果不一致
  • ¥15 深度学习残差模块模型
  • ¥50 怎么判断同步时序逻辑电路和异步时序逻辑电路
  • ¥15 差动电流二次谐波的含量Matlab计算
  • ¥15 Can/caned 总线错误问题,错误显示控制器要发1,结果总线检测到0
  • ¥15 C#如何调用串口数据
  • ¥15 MATLAB与单片机串口通信
  • ¥15 L76k模块的GPS的使用
  • ¥15 请帮我看一看数电项目如何设计
  • ¥23 (标签-bug|关键词-密码错误加密)