GNet?

Community Forums/Technical Discourse/GNet?

JoshK(Posted 2014) [#1]
Where can I download Mark's GNet code? Thanks.


BlitzSupport(Posted 2014) [#2]
It's here:

http://www.blitzbasic.com/toolbox/toolbox.php?tool=61


Derron(Posted 2014) [#3]
Please do NOT use the php-code "as-is", it includes vulnerabilities (GET-params are used without checks within the sql query).


bye
Ron


JoshK(Posted 2014) [#4]
Does anyone have a safe version?


Derron(Posted 2014) [#5]
So ... just for you I now recoded that script:




If your host is stoneage, you wont have PDO installed - but in that case you should not use this host anylonger (stoneage php version). Using PDO you could even use SQLITE (flatfile database) - if support is installed.


I changed some things: $_GET-check (isset should be enough), prepared statements for things needing character escaping, removing some queries (count + delete, count + update, both just check the affected rows now). Cache header corrected, removed all eventual existing "notices". The functionality is now encapsulated in a class.


bye
Ron


JoshK(Posted 2014) [#6]
Thank you. We're using this together with RakNet for networking.


JoshK(Posted May) [#7]
This does not work on the current version of PHP.


Derron(Posted May) [#8]
As you seem to be a bit lazy atm I installed php7.1.5 (the most current one) on my host, tricked apache to use that instead of my 5.6 for certain directories.

created the needed database, user, password, table ...

and it worked as soon as I replaced "localhost" with the real IP "127.0.0.1" (local ip of course) as it seems my freshly compiled PHP 7.1.5 is connecting to MySQL via sockets.





Successfully added a test game server:
http://php7.example.com/gnet.php?opt=add&server=testserver&game=testgame

Result: OK


Successfully listed the servers:
http://php7.example.com/gnet.php?opt=add&server=testserver&game=testgame

Result:
testgame testserver 127.0.0.1




bye
Ron


JoshK(Posted May) [#9]
I'm not lazy, I have no knowledge of MySQL.

I added in the database info and enabled the PDO extension, and no error_log file is being created. However, ref and rem operations result in the text "ERROR' being printed, and listservers doesn't find anything.

Class GNET {
	public $IP;
	public $game;
	public $server;
	public $DB;
	private $dbName = "THE DATABASE NAME";
	private $dbUser = "THE DATABASE USER";
	private $dbPwd = "PASSWORD";
	private $dbHost = "127.0.0.1";
	private $dbTableName = "gnet_servers";

If it could not connect to the database it would display an error, but the add operation prints out "OK". It's like it's silently failing to add the info to the database.

Here is the PHP info:
https://www.leadwerks.com/phpinfo


Derron(Posted May) [#10]
Please remove "phpinfo" as it might expose things people (potential attackers) should not know.


in my script above there is
<?php
/*
error_reporting(E_ALL);
ini_set('display_errors', 1);
*/


replace that with
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);


This will show potential PHP errors.

"Error" is printed if it was not able to recognize a "command". With above it might also print the actual DB error (which by default should not get exposed to avoid security flaw exposition). But like said it might not be the problem of the DB connection at all.


@ lazy
This was meant as "deferring" workload to others - which most of us do from time to time. I know that not everybody has to have knowledge of everything. But in your case ("company") your webmaster/admin should at least have the knowledge to fix such "simple" errors.

bye
Ron


Derron(Posted May) [#11]
To help you narrowing down the potential flaw (I am suspecting something...):

Replace
		//delete 'old' servers
		$maxAge = 300;
		$this->query("DELETE FROM " . $this->dbTableName . " WHERE time_to_sec(now()) - time_to_sec(t_time)>".$maxAge);

		switch($opt) {


with

		//delete 'old' servers
		$maxAge = 300;
		$this->query("DELETE FROM " . $this->dbTableName . " WHERE time_to_sec(now()) - time_to_sec(t_time)>".$maxAge);

		//debug output
		print "opt: " . $opt . "<br />" . PHP_EOL;
		if($opt == "") {
			print "opt empty, falling back to 'list'.<br />" . PHP_EOL;
			$opt = "list";
		}
		//debug output for "GET"
		print '$_GET: ' . print_r($_GET, True) ."<br />" . PHP_EOL;
		//debug output for "REQUEST"
		print '$_REQUEST: ' . print_r($_REQUEST, True) ."<br />" . PHP_EOL;

		switch($opt) {


When now running you might see $_GET being empty even with "gnet.php?opt=list". Request might be empty too (it contains GET and POST submitted values - and could contain even more, depending on the php.ini "request_order" settings).

Until PHP 5.4 there was "import_request_variables("gp");" which filled empty $_GET-arrays if something was borked, but it was removed in later versions.



bye
Ron


JoshK(Posted May) [#12]
https://www.leadwerks.com/gnet.php?opt=add&game=testgame
https://www.leadwerks.com/gnet.php?opt=ref&game=testgame
https://www.leadwerks.com/gnet.php?opt=list

Note that query is returning null (I think) in the list servers command.


Derron(Posted May) [#13]
Ok, so "get" is no problem - and as you see, you also do not get "ERROR" - which is what you only get if you do not have "?opt=***" in the url.
(you could fallback to "list" as I did in the above example).


the list-page contains a warning (exposed because we enabled that right on top of the script). We could add a check for existance ... if really needed.



Replace the function prepared_query($query) with the following:
	//query with prepared statement
	//call: "query", arg1, arg2
	public function preparedQuery($query) {
        $args = func_get_args();
		//remove first arg - is the query
        array_shift($args);

        $stmt = $this->GetDB()->prepare($query);
        $stmt->execute($args);

		//debug
		$errors = $stmt->errorInfo();
		if(isset($errors[0]) and $errors[0] != 0)
			print_r($errors);

        return $stmt;
    }


Now "add" and "remove" should print errors if the sql-query failed somehow.

So if I replaced the table field "game" in one query with "game12" (not existing) it would result in this:

opt: rem
$_GET: Array ( [opt] => rem [server] => testserver3 [game] => testgame3 ) 
$_REQUEST: Array ( [opt] => rem [server] => testserver3 [game] => testgame3 ) 
Array ( [0] => 42S22 [1] => 1054 [2] => Unknown column 'game12' in 'where clause' ) ERROR


Maybe your table structure differs to what I have printed on top of the script.



PS: at the end, remove these debugs again to avoid exposing information. Means: remove it, once you are using it in production.

bye
Ron


JoshK(Posted May) [#14]
It says the table does not exist. I did not create a table or anything when I made the MySQL database, it is just a blank/default DB.

There is a big commented-out section at the top of the script like "CREATE TABLE IF NOT EXIST...". What is that and do I need it?


Derron(Posted May) [#15]
This is what you have to enter within your mysql-tool-of-choice - eg. phpMyAdmin or MySQLWorkbench


CREATE TABLE IF NOT EXISTS `gnet_servers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `t_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `game` text COLLATE utf8_bin NOT NULL,
  `server` text COLLATE utf8_bin NOT NULL,
  `ip` text COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


As all the implementations here did not really expose the needed table structure (or better: I was not able to find them) I just created them like I think it is needed (the ones I found used "id - tinyint" without autoincrement - which is needed for MySQL - in comparison to sqlite, which has its own "rowid" field for every entry).


@ table does not exist
Didn't you write "This does not work on the current version of PHP." ?
I assumed it worked before - maybe you should check what tablename/user/password you used before (as the table might exist there).



bye
Ron


JoshK(Posted May) [#16]
Cool, it's working, thanks!


Derron(Posted May) [#17]
You should be able to use the "complete script" I linked some days ago (the one with the commented-out part at the beginning - and no added debugs).


BTW I would not use that script as it is - at least not for more serious tasks. It really lacks some security measurements.

Eg. a malicious buddy could fake/spoof the entry "HTTP_X_FORWARDED_FOR" in the HTTP header (using his own proxy...) so it sends out the IP of another hosting player. That way he could unlist the entry without much trouble.

To avoid that one would have to disable "HTTP_X_FORWARDED_FOR" usage (only relying on the remote_addr the server spits out) or - of course the better way - add a "security key" which has to be generated by the server (hash of (time + some secret key individual to the incoming request) and then send as response to the "add" request.

So the adding client knows the secret to manipulate its own entry.


This isnt that hard to implement but I am not using gnet(_servers) so I wont do that. Maybe there are people here interested in it too - and bringing in some suggestions on how to properly implement that.


All in all I would prefer the whole script to be "SOAP"-like so it behaves kind of "standard" (returning Json, XML, ..responses).



bye
Ron


JoshK(Posted May) [#18]
The remote_addr seems like a better value to use, because that could overcome the NAT punch-through issue, right?

Maybe if the user input their private key in their game code, and then had the same private key on their own server in the PHP file, that would provide security. Or maybe they can add a game name and private key on our website. How hard would that be to set up?

I like having an unencrypted default just because it makes it really easy to get started.


BlitzSupport(Posted May) [#19]
I added the forwarded-for years ago while on an ISP that forced all users' traffic through their own proxy/cache, so the Gnet server received the ISP's proxy/cache address instead of the user's address and therefore didn't work. Checking for forwarded-for gives the user's IP if behind one of these proxies.

It shouldn't have any effect if there's no proxy/cache in the way, ie. the script should just use remote_addr automatically for most users, yet give the correct IP if stuck behind a proxy.


Derron(Posted May) [#20]
@ BlitzSupport
The forwarded-thing is a HTTP header entry - which could get spoofed by using an own proxy.

Adding a "secret key"-thing would make the "identification by IP" only a part of the authentification. Of course it wont be that hard to implement it.

All it needs is a new entry in the table ("key" or "password") and added checks in the code -> the Update/Delete-statements get a "key=?" added in the "WHERE"-section and of course the key should be added as function param too.
The key itself could be given via $_GET too.


bye
Ron


JoshK(Posted May) [#21]
What if you XORed the data with a secret key so that you aren't broadcasting your password over the network?


Derron(Posted May) [#22]
the secret key must be individual to the game ... else one would look what data is send to the server when using "test" for name of the game.

Do not forget: the attacker knows _every_ data he sends and he receives.


So the only "easy" solution is to calculate the key on the server. The key should contain dynamic data (creation time in millisecs) which is not available to read from the outside (so attacker would need to guess a portion of the "ingredient" of the hash/password).

On creation of the game (initial "add") the returned value is the identifier/key to validate as "owner" of the entry.

To unlist an entry (or to update/edit) you need that key. The server scripts itself of course could still unlist outdated entries without needing the keys.


bye
Ron


JoshK(Posted May) [#23]
Is there anything I need to do to make those _GET[] variables safe?


Derron(Posted May) [#24]
As this is incoming data you should never trust it. Sanitization is needed ... In our case the prepared queries are doing that when inserting them to the DB.

To avoid manipulation by third party (knowing "game" and the ip of an entry) you need to use soke kind of verification (key...see above).


Bye
Ron