PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sat Sep 23, 2017 8:36 pm

All times are UTC - 5 hours




Post new topic Reply to topic  [ 1 post ] 
Author Message
PostPosted: Sun Mar 15, 2015 7:46 pm 
Offline
Forum Contributor
User avatar

Joined: Wed Sep 09, 2009 5:55 am
Posts: 323
Location: Australia
This was mainly a proof of concept system as a way for exploring the possible ways of securing a form on a website without the form being inside a PHP file (so the form itself is in a plain .html file with no PHP executing).

Note: Yes i do realise that inherently over non-ssl connections exposing a token like this may be hazardous, however the point of this test was have the form generated without PHP.

The Structure:
Syntax: [ Download ] [ Hide ]
index.html
req.php
js/main.js
css/style.css
lib/loader.php
lib/stdOut.php
lib/token.php


Overview:
  • index.html contains a form with X number of fields plus a submit button.
  • When the user finishes filling in all of the fields, they press the submit button to process the form.
  • jQuery intercepts the form's onsubmit event and handles it manually.
  • Submit validates to make sure that data exists in the fields (plus any html5 validation patterns)
  • Submit calls the javascript function 'getToken()' and assigns the output to the variable 'nToken'
  • getToken() submits an ajax GET request to 'req.php?gettoken' to obtain a new token
  • req.php creates a 'new token;' class and requests a new token.
  • token generates a new token on __construct as there is no $_POST['token'] to check.
  • New token is also stored to $_SESSION['token'] with a variable key name and the token as the value ( array('token_random'=>'thetoken') )
  • req.php returns the generated token in the json format {response:'thetoken',status:'success'}
  • Submit then checks to see if the form contains a field with the jQuery selector $('#formModToken'), if it exists it updates with nToken, else it creates a new dom element and appends it to the form.
  • Submit (now with the token field), submits the form (via ajax) to req.php (handling the original for submission)
  • req.php finds there to be a $_POST, and when 'new token;' is reconstructed, finds a $_POST['token'] field and assigns that as the current token.
  • req.php then calls token::checkToken()
  • token checks current stored token against the session variable.
  • token returns a boolean to req.php
  • req.php deals with the actions/response
  • Submit removes the token field from the form after a response

There is 2 sections in particular I'm a little unsure about whether or not it is viable security wise to be implementing them in the current way.

1. Storing the token inside a predefined session variable as an array (mashed keyboard example below). Given that the token is always stored inside $_SESSION['token'], is there a more appropriate way to handle this? This also comes back to how i accessed the token by using array_keys() on $_SESSION['token'] to retrieve the current token.
Syntax: [ Download ] [ Hide ]
$_SESSION['token'] = array('token_asdfa87g63g83a' => 'fawbf97w9f982');


2. Retrieving the token via ajax and dynamically adding/removing the token field to the form. Would this actually stop something like a BOT from processing the form? My first thoughts are that i perhaps should not be processing the standard onsubmit event, but rather using a separate button not tied to the forms events... thoughts?

Are there any other issues that I'm just not seeing in the implementation or way in which it operates?

Feedback would be appreciated :)

Code:
Here is the actual code used in the above.

index.html
Syntax: [ Download ] [ Hide ]
<html>
<head>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/bootstrap-dialog.min.css">
    <link rel="stylesheet" href="css/pack.css"/>
</head>
<body>

<div class='container'>

    <div class='row'>
        <div class='col-md-6'>
            <h2>Pack Info</h2>
            <div id='pack-info'></div>
            <h2>Request</h2>
            <form method='post' onsubmit="return false;">
                <div class='form-group'>
                    <label for='formName'>Name</label>
                    <input type='text' name='formName' class='form-control' id='formName' placeholder='Joe Blogs' required aria-required="true" />
                </div>
                <div class='form-group'>
                    <label for='formURL'>URL</label>
                    <input type='url' pattern="https?://.+" name='formUrl' class='form-control' id='formURL' placeholder='http://myurl.com' required $
               </div>
                <div class='form-group'>
                    <label for='formDesc'>Description</label>
                    <textarea id='formDesc' name='formDesc' class='form-control' rows='5' required aria-required="true"></textarea>
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
        </div>

    </div>
    <script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <script src="js/bootstrap-dialog.min.js"></script>
    <script src="js/main.js"></script>
</body>
</html>
 

main.js
Syntax: [ Download ] [ Hide ]
jQuery(document).ready(function(){
    $("form").submit(function (e) {
        e.preventDefault();
        // Silly validation to make sure form isn't empty
        var n = 0;
        if( $('#formName').val() == "" ){ n++; }
        if( $('#formURL').val() == "" ){ n++; }
        if( $('#formDesc').val() == "" ){ n++; }
        if( n > 0 ){
            dialog('Form Validation', 'Please fill in all fields correctly', BootstrapDialog.TYPE_DANGER);
            return false;
        }

        var nToken = getToken();
        if( $('#formToken').length == 0 ){
            var t = $('<input>');
            t.attr({id:'formToken',name:'token',value:nToken,type:'hidden'});
            $("form").append(t);
        } else {
            $('#formToken').val(nToken);
        }

        $.ajax({method:'post',url:'req.php',data:$("form").serialize()})
            .done(function(data){
                if( data.status == "success" ){
                    dialog('Request', data.response, BootstrapDialog.TYPE_SUCCESS);
                }else if( data.status == "Token.SUCCESS" ){
                    dialog('Request', data.response, BootstrapDialog.TYPE_SUCCESS);
                    $('#formToken').remove();
                    $("form")[0].reset();
                }else if( data.status == "error" || data.status == 'Token.ERROR' ){
                    dialog('Request', data.response, BootstrapDialog.TYPE_DANGER);
                }
            })
            .fail(function(data){ dialog('Request', data.response, BootstrapDialog.TYPE_DANGER); });
    });
});

var getToken = function(){
    var token = '';
    $.ajax({method:'get',url:'req.php?gettoken',async:false}).done(function(data){token=data.response;});
    return token;
}

var dialog = function(title, body, type){
    BootstrapDialog.show({
        type: type,
        title: title,
        message: body,
        buttons: [
            { label: 'Close', action: function(dialog){ dialog.close(); } }
        ]
    });
}
 

req.php
Syntax: [ Download ] [ Hide ]
<?php
session_start();

define("_EXEC",true);
require_once 'lib/loader.php';
loader::load('token.php');

$token = new token;

if( isset($_GET['gettoken']) )
{
    $obj = new stdClass;
    $obj->response = $token->getToken();
    $obj->status = 'success';
}
if(isset($_POST) && !empty($_POST)){
    $obj = new stdClass;
    if(!$token->checkToken()) {
        $obj->response = "Invalid Token";
        $obj->status = 'Token.ERROR';
    } else {
        /**
         * Now we add the request to the database
         */

        if( database result ) {
            $obj->response = "Thanks for adding your request for a modpack!";
            $obj->status = 'Token.SUCCESS';
        } else {
            $obj->response = "There was a problem processing your request.";
            $obj->response = 'Token.ERROR';
        }
    }
}

if( empty($obj) ){ die(); }
header('Content-Type: application/json');
echo json_encode($obj);
 

token.php
Syntax: [ Download ] [ Hide ]
<?php
if(!defined("_EXEC")){ die("No Access"); }

class token {

    private $token;
    private $stName;

    public function __construct( )
    {
        if( !isset( $_POST['token']) ){
            $this->createToken();
            $newToken = $this->getToken();
        }
        $this->token = ( isset( $_POST['token']) ) ? $_POST['token'] : $newToken;
    }

    private function createToken()
    {
        $token = md5(uniqid(rand(), TRUE));
        $this->stName = 'token_'.md5(session_id().$token);

        unset($_SESSION['token']);
        $this->token = $_SESSION['token'][$this->stName] = $token;
        return $token;
    }

    public function checkToken()
    {
        $keys = array_keys($_SESSION['token']);
        $key = $keys[0];
        $return = ($this->token == $_SESSION['token'][$key]) ? true : false;

        // Create a new token
        $this->createToken();
        return $return;
    }

    public function getToken()
    {
        $useToken = (empty($_POST['token'])) ?  $this->token : $_POST['token'];
        return $useToken;
    }

}
 


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 1 post ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group