How to implement my PHP back-end server POST handler

Hello.

I am asking for the best means to implement this via CakePHP. Could you please direct me to which technologies of Cake I’d best be using, and a simple outline of the structure of it?

I am specifically concerned with the security access, having authorised logged in users, all SSL traffic, session tokens and a means to prevent cross-script and brute force attacks.

My situation is I [will] have a website which extracts data that’s on a Windows PC. I have already written the web-broker app on the PC, and all the communications with it. The PC communicates with the web-server via long polling, where the PC fulfills any requests and sends the relevant data back to the webpage.

This is the full source of my prototype. The actions it accepts are: -
‘d’ : The Data request from the webpage via AJAX, it returns with the fulfilled data from my PC,
‘p’ : The long Poll request from my PC checking if there are any data requests to be fulfilled, and
‘r’ : The Returned data sent to the server from my PC which can be passed back to the webpage.

<?php
//server.php
$action = filter_input(INPUT_POST, 'action');
if (strlen($action) !== 1 || strpos('pdr', $action) < 0) //Poll server / Data / Return request
  die("Error: Unknown action");
	
if ($action == 'p' || $action == 'r') { //only my computer is allowed to poll or return request
	$csrf = filter_input(INPUT_POST, 'csrf');
	if ($csrf != 'someUniqueStringGoesHereWhichIsSolelyKnownByMyPC') //To be complemented by session tokens?
		die("Error: CSRF Failed");
}
//some pre-validation, cut down on traffic before SQL
if ($action == 'd') { //query from customer
  $account = filter_input(INPUT_POST, 'account');
  if ($account == "")
    die("Enter account");
  if (strlen($account) > 8)
    die("Invalid account");
  $pincode = filter_input(INPUT_POST, 'pincode');
  if ($pincode == "")
    die("Enter your pin");
  if (strlen($pincode) > 4)
    die("Invalid pin");
}

require "../settings.php"; //lazy place to easily store settings out of reach from the Internet, parent of the web root. 
//Be aware I used ".." - if this server.php is not in the webroot then the settings.php is not out of reach.
if ($db_server == "") 
  die("Problem getting database settings.");

$mysqli = mysqli_connect($db_server, $db_username, $db_password, $db_database_wb); //vars from the required settings.php
if (!$mysqli)
  die("Specific database not found.");

if ($action == 'd') { //query for customer wanting to see his data
  $account = mysqli_real_escape_string($mysqli, $account); //clean up some
  $pincode = mysqli_real_escape_string($mysqli, $pincode);
  $stmt = $mysqli->prepare('INSERT INTO requests (account, pincode, status, html) VALUES (?, ?, "D", "");');
  if (!$stmt)
    die("Error: " . $mysqli->error);
  if (!$stmt->bind_param("ss", $account, $pincode))
    die("Error: " . $mysqli->error);
  if (!$stmt->execute())
    die("Error: " . $mysqli->error);
	$request_id = $mysqli->insert_id;
  $stmt->close();
	$stmt = $mysqli->prepare('SELECT html FROM requests WHERE account=? AND status="Z" AND request_id=?;');
	if (!$stmt)
		die("Error: " . $mysqli->error);
	if (!$stmt->bind_param("si", $account, $request_id)) //double check on account code
		die("Error: " . $mysqli->error);
	$expire = time() + 20; //seconds
	while (time() < $expire) {   //If my server crashes here somehow, the Z record will exist forever... 
		usleep(10000); //10 ms   //But it should never be referenced again, so not bad, just orphaned.
		if (!$stmt->execute())
			die("Error: " . $mysqli->error);
		$found = $stmt->get_result();
		if (mysqli_num_rows($found) > 0)
			break;
  }
	$stmt->close();
  if (mysqli_num_rows($found) > 0)
    echo mysqli_fetch_row($found)[0];
  else
    echo "Request timed out";
	$stmt = $mysqli->prepare('DELETE FROM requests WHERE request_id=?;');
  if (!$stmt)
    die("Error: " . $mysqli->error);
  if (!$stmt->bind_param("i", $request_id))
    die("Error: " . $mysqli->error);
  if (!$stmt->execute())
    die("Error: " . $mysqli->error);
  $stmt->close();
}

if ($action == 'p') { //long poll for broker
	$expire = time() + 30; //seconds
	$stmt = $mysqli->prepare('SELECT CAST(request_id AS CHAR), account, pincode, status ' .  //CAST needed for "'s in JSON
													 'FROM requests WHERE status="D";');
  if (!$stmt)
    die("Error: " . $mysqli->error);
	while (time() < $expire) {
		if (!$stmt->execute())
			die("Error: " . $mysqli->error);
		$result = $stmt->get_result();
		while ($row = mysqli_fetch_row($result)) 
			$table_data[] = $row;
		$result = json_encode($table_data);
		if ($result !== "null") { 
//			file_put_contents($expire . ".txt", $result);
			break;
		}
		usleep(10000); //10 ms
	}
  echo $result; //return the whole dataset
}

if ($action == 'r') { //PC replied
	$request_id = mysqli_real_escape_string($mysqli, filter_input(INPUT_POST, 'request_id'));
  $account = mysqli_real_escape_string($mysqli, filter_input(INPUT_POST, 'account'));
  $html = filter_input(INPUT_POST, 'html');
  $stmt = $mysqli->prepare('UPDATE requests SET status="Z", html=? WHERE account=? AND request_id=?;');
  if (!$stmt)
    die("Error: " . $mysqli->error);
  if (!$stmt->bind_param("ssi", $html, $account, $request_id)) //double check on account code
    die("Error: " . $mysqli->error);
  if (!$stmt->execute())
    die("Error: " . $mysqli->error);
  $stmt->close();	
}
mysqli_close($mysqli);

At the moment this could be flooded by illegitimate requests which I would like to negate. Also, much of the data can be highly sensitive (like Human Resource info) so I can’t emphasize the security aspects enough! (Which is why I chose to support CakePHP for the solution to this in the first place.)

If you need any other information, like the structure of the table used, or the html + js code, or even the windows app doing the brokering please don’t hesitate to ask.

I am not asking this casually. I have done several of the tutorials several times, and have read the online book end-to-end. So please any hints, guides or even code would be greatly appreciated.

Also note this is just the back-end part - I’ve yet to start the actual webpage itself in CakePHP, but one hurdle at a time :slight_smile:

Thanks
Jonathan

I do not see any sense in doing it; why just not have single web app instead of that fragile monstrum? How do you want to prevent brute forcing ?

I hear that, here’s the thing though.
My customer has a complex database running on a terminal server connected by many terminal sessions. He also sells this app (that we developed) to similar businesses.

The app itself stores some information which would be useful to his customers, so he wants a web interface so they can see the aspects of data that belongs to them. So he can provide them their credentials to the website (which will be written in CakePHP) which would then connect to the app running on windows. He does not want the MySQL of the website to also be storing this data. Therefore, the website needs a means to communicate to the app server to pull data. I can harden the actual website, but obviously I am more concerned about the website to app communication. I can restrict it to IP / DNS, and pass a bunch of hidden tokens, I also have it SSL end-to-end, webpage to the app server, which help negates MIM attacks. Obviously IPs can be spoofed, and maybe my crsf ./ tokens can be sniffed.

Of the data which goes into the Windows app, only say 1% is relevant to the customer. Like in an accounting package the debtor only gets to see his invoices and statements.

Now it gets a tad more interesting in that the similar businesses who have purchased the windows app may also want their website to provide access to their clients via the same means!

So it’s a bit of a mess, and this is as tidy as I can make it with what skills I have.

I don’t mind if anyone comes up with a better approach than my relayed server-to-server solution, but for now this is all I have.

Thanks so much for getting back to me here, I really appreciate you thinking of ways it can be better.

Edit: Brute force. I suppose some form of rate limiting, geofencing (or DNS whitelist) at my app server’s end - but all of that can be spoofed. I would assume a random string created on the request which is then passed with the request and return (plus SSL) should be as good as it can get. The perfect solution would be for me to generate a RSA key on my windows app, and give the public key to the webpage - or maybe just some heavy duty encryption as well as the SSL (as SSL is available to everyone so a AJAX request could be spoofed to the webserver). Of course, I may be overthinking it >.<

I’m to drunk to read entire post with understanding but …

I think that your customer (or someone who is making decisions in theirs name) has no technical background or literally lives in 90’s.

The approach they want to go with is something I would avoid at all cost (perhaps it’s because since ever I work for IT companies focused on newest technologies).

In the same time they want to go with SaaS model and they do everything to make it extremely faulty and difficult to maintain, scale and extend.

For me it has no any meaning if they store data on db server exposed to internet traffic or only to ethernet traffic, if app is vulnerable and is used by someone whose pc can connect to internet then you should assume that I can gain access to the db if I want it.

CSFR tokens can make brute-forcing slower, but they can’t prevent it. (CSFR token contains encrypted info --> everything that is encrypted can be cracked --> once I crack token I can generate my own tokes that will be validated by your servers, thus brute forcing prevention will not be possible).

I will back to you once I get some sleep :slight_smile:

lol thanks.

The app with the data is extremely complex and extensive; it is a bespoke app, custom built for a specific purpose. It would take years to rewrite as a web app, and as its only for in-house use such would be pointless. I could duplicate the whole dataset on a MySQL - but the transport to such is no less reliable than direct communication anyway (and my client would prefer not - and then the question of sync’ing etc). And, its an extremely small subset of the whole data which needs to be shared.

What I can put in place is locking in the IP of the app web-broker server so $action types of ‘p’ & ‘r’ will only be accepted by that IP. That traffic is also SSL as the webserver is on https. I intend to generate a unique token from the Request (from the web page), which then will be passed down to the app server via the Poll and returned with the Returned data. I could also encrypt the data between the web server and the app server with blowfish or something similar, with the key generated (perhaps even by transaction number, thus changing every request) by a routine only known to the web server PHP (the code above) and the Windows app.

As the poll itself is a post request to the web server, therefore a third party cannot initiate a request for data. What it would take is probably a need to DOS the web server offline, either hijack the website’s DNS or send spoofed poll returns to the windows app, which would require knowing the blowfish key - and, as it needs to capture the return it would also require a man in the middle packet sniffer near the connection of the app server (and note it’ll still be SSL). It would of course need to know all the parameters of the request, including a legitimate client account.

I’m confident enough that my approach is strong enough, my only real concern is how I deploy that code above with CakePHP. I know I can just stick that server.php in webroot and bypass Cake, but I prefer the AJAX system to be within Cake - thus my question on, or if, this can be done - and what classes / libraries would best suit.

I suspect though after having written all that I’m on my own with this! But I have no idea what abilities CakePHP can perform, and I am not a web developer and thus would like to rely on the framework as much as I can :slight_smile: