From 2d10bf9cc5689976ab6bc458691abdd0525fb46f Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Thu, 31 Aug 2017 12:24:20 +0200 Subject: [PATCH 1/9] Omit the ending tag. As recommended in http://php.net/manual/en/language.basic-syntax.phptags.php --- php/settings.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/php/settings.php b/php/settings.php index 63239cd..12bb644 100644 --- a/php/settings.php +++ b/php/settings.php @@ -281,5 +281,3 @@ function ip_in_range( $ip, $range ) { $netmask_decimal = ~ $wildcard_decimal; return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); } - -?> From 3b8453806e075e7277cfc52af85710716fe92bf8 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Thu, 31 Aug 2017 12:26:09 +0200 Subject: [PATCH 2/9] Move configuration to a separate file. This will allow replacing the main file without needing to re-add the settings. For example, settings.php can be always kept up-to-date locally via a symbolic link, without altering the configuration. --- php/settings-config.inc.php | 14 ++++++++++++++ php/settings.php | 13 +------------ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 php/settings-config.inc.php diff --git a/php/settings-config.inc.php b/php/settings-config.inc.php new file mode 100644 index 0000000..2712039 --- /dev/null +++ b/php/settings-config.inc.php @@ -0,0 +1,14 @@ + Date: Sat, 2 Sep 2017 11:34:49 +0200 Subject: [PATCH 3/9] Report bad request if no valid action was specified. --- php/settings.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/php/settings.php b/php/settings.php index 32d7dcb..f68b24c 100644 --- a/php/settings.php +++ b/php/settings.php @@ -176,6 +176,10 @@ else if(isset($_REQUEST['q'])){ // read a record } echo $out; } +else{ + header('HTTP/1.0 400 Bad Request'); + die("400 Bad Request: No valid action specified."); +} function parse_llHTTPRequest_headers(){ $position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); From a1b4e36cf4f82c0a6f65b77eeb37d99f98e82fe7 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 2 Sep 2017 13:46:30 +0200 Subject: [PATCH 4/9] Simplify regular expression for UUID --- php/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/settings.php b/php/settings.php index f68b24c..f0b42fd 100644 --- a/php/settings.php +++ b/php/settings.php @@ -212,7 +212,7 @@ function parse_llHTTPRequest_headers(){ } function isValidGuid($guid){ - return !empty($guid) && preg_match('/^\{?[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}\}?$/', $guid); + return !empty($guid) && preg_match('/^\{?[a-zA-Z0-9]{8}(?:-[a-zA-Z0-9]{4}){4}[a-zA-Z0-9]{8}\}?$/', $guid); } function email_death($error){ From ae6745f26add6af79ef27a189b9598456fb1d988 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 2 Sep 2017 13:52:52 +0200 Subject: [PATCH 5/9] Fix table issues - Get rid of Y2K38 problems https://xkcd.com/607/ - This also removes default value and on-update value for the timestamp, but these were being set explicitly so no harm done. - Set the text field's character set to UTF-8. - Set the link's character set to UTF-8. --- php/settings.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/php/settings.php b/php/settings.php index f0b42fd..b1d8cf8 100644 --- a/php/settings.php +++ b/php/settings.php @@ -36,6 +36,8 @@ $link = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname) or die("Error " . mys if (mysqli_connect_errno()) { die ("Connect failed: " . mysqli_connect_error()); } +// Set the character set for communication with the database +mysqli_set_charset($link, 'utf8'); if($_REQUEST['action']=="install" && $allow_install==true){ $sql = "DROP TABLE IF EXISTS $avpos_table;"; @@ -45,11 +47,11 @@ if($_REQUEST['action']=="install" && $allow_install==true){ `webkey` varchar(36) default NULL, `owner_uuid` varchar(36) default NULL, `owner_name` varchar(63) default NULL, - `text` TEXT default NULL, + `text` TEXT CHARSET utf8 default NULL, `keep` tinyint(1) default 0, `count` int(5) default NULL, `ip` varbinary(16) default NULL, - `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `timestamp` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE (`webkey`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;"; From a8ec1d4747ee64baa2e764f5df55bc64e7e1da80 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 2 Sep 2017 16:32:50 +0200 Subject: [PATCH 6/9] Change the string escaping strategy All variable values in SQL statements should use IntSQL or StrSQL as appropriate, rather than variables directly, with the exception of the table name. This is akin to using htmlspecialchars to include text in HTML, or urlencode to include text in a URL. Normally you have the text in raw form and convert it as appropriate depending on where you're inserting it. --- php/settings.php | 109 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 25 deletions(-) diff --git a/php/settings.php b/php/settings.php index b1d8cf8..b65dd28 100644 --- a/php/settings.php +++ b/php/settings.php @@ -39,6 +39,13 @@ if (mysqli_connect_errno()) { // Set the character set for communication with the database mysqli_set_charset($link, 'utf8'); +// Pre-escape $avpos_table for convenience. That's the only variable +// that should go directly into a query. All others should go through +// IntSQL or StrSQL as appropriate. +$avpos_table = IdentSQL($avpos_table); + +undo_magic_quotes($_REQUEST); + if($_REQUEST['action']=="install" && $allow_install==true){ $sql = "DROP TABLE IF EXISTS $avpos_table;"; $result = mysqli_query($link,$sql) or die("Error creating table: ".mysqli_error($link)); @@ -64,21 +71,21 @@ if($_REQUEST['action']=="install" && $allow_install==true){ } } else if(isset($_REQUEST['w'])){ // write to a record - $given_webkey = mysqli_real_escape_string($link, $_REQUEST['w']); + $given_webkey = $_REQUEST['w']; $ip_address = $_SERVER['REMOTE_ADDR']; - $ip_packed = mysqli_real_escape_string($link, inet_pton($ip_address)); + $ip_packed = inet_pton($ip_address); if(!isValidGuid($given_webkey)){ echo "INVALID WEBKEY"; } else{ $headers = parse_llHTTPRequest_headers(); - $owner_key = mysqli_real_escape_string($link, $headers['X-SecondLife-Owner-Key']); - $object_name = mysqli_real_escape_string($link, $headers['X-SecondLife-Object-Name']); - $owner_name = mysqli_real_escape_string($link, $headers['X-SecondLife-Owner-Name']); - $object_key = mysqli_real_escape_string($link, $headers['X-SecondLife-Object-Key']); - $region = mysqli_real_escape_string($link, trim(substr($headers['X-SecondLife-Region'],0,strrpos($headers['X-SecondLife-Region'],'(')))); + $owner_key = $headers['X-SecondLife-Owner-Key']; + $object_name = $headers['X-SecondLife-Object-Name']; + $owner_name = $headers['X-SecondLife-Owner-Name']; + $object_key = $headers['X-SecondLife-Object-Key']; + $region = trim(substr($headers['X-SecondLife-Region'],0,strrpos($headers['X-SecondLife-Region'],'('))); $position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); $slurl = $region . "/" . round($position_array[0]) . "/" . round($position_array[1]) . "/" . round($position_array[2]); @@ -87,17 +94,27 @@ else if(isset($_REQUEST['w'])){ // write to a record } else{ $given_count = intval($_REQUEST['c']); - $given_text = mysqli_real_escape_string($link, $_REQUEST['t']); + $given_text = $_REQUEST['t']; - $sql = "SELECT * FROM $avpos_table WHERE webkey = '$given_webkey'"; + $sql = "SELECT * FROM $avpos_table" + . " WHERE webkey = " . StrSQL($given_webkey); $result = mysqli_query($link,$sql) or email_death("ERR01: " . mysqli_error($link)); if(mysqli_num_rows($result) == 0){ // a new webkey if($given_count == 1){ if(!isAllowedIP($ip_address)){ $response = "BAD IP"; - $sql = "INSERT INTO $avpos_table (owner_uuid,owner_name,webkey,text,count,ip,timestamp) - VALUES ('$owner_key','$owner_name','$given_webkey','The IP address of the sim ($ip_address) was not in the allowed range. Please report the problem if you think this is in error.','10001','$ip_packed',NOW())"; + $sql = "INSERT INTO $avpos_table" + . ' (owner_uuid,owner_name,webkey,text,count,ip,timestamp)' + . ' VALUES' + . '(' . StrSQL($owner_key) + . ',' . StrSQL($owner_name) + . ',' . StrSQL($given_webkey) + . ',' . StrSQL("The IP address of the sim ($ip_address) was not in the allowed range. Please report the problem if you think this is in error") + . ',10001' + . ',' . StrSQL($ip_packed) + . ',NOW()' + . ')'; } else{ $response = "ADDED NEW"; @@ -105,8 +122,17 @@ else if(isset($_REQUEST['w'])){ // write to a record $given_count+=10000; $response = "FINISHING"; } - $sql = "INSERT INTO $avpos_table (owner_uuid,owner_name,webkey,text,count,ip,timestamp) - VALUES ('$owner_key','$owner_name','$given_webkey','$given_text','$given_count','$ip_packed',NOW())"; + $sql = "INSERT INTO $avpos_table" + . ' (owner_uuid,owner_name,webkey,text,count,ip,timestamp)' + . ' VALUES' + . '(' . StrSQL($owner_key) + . ',' . StrSQL($owner_name) + . ',' . StrSQL($given_webkey) + . ',' . StrSQL($given_text) + . ',' . IntSQL($given_count) + . ',' . StrSQL($ip_packed) + . ',NOW()' + . ')'; } $result = mysqli_query($link,$sql) or email_death("ERR02: " . mysqli_error($link)); } @@ -120,7 +146,7 @@ else if(isset($_REQUEST['w'])){ // write to a record } else{ $row = mysqli_fetch_assoc($result); - $newtext = mysqli_real_escape_string($link,$row['text']) . $given_text; + $newtext = $row['text'] . $given_text; if($row['count']+1 == $given_count){ $response = "ADDING"; @@ -129,11 +155,11 @@ else if(isset($_REQUEST['w'])){ // write to a record $response = "FINISHING"; } - $sql = "UPDATE $avpos_table SET - text = '$newtext', - count = '$given_count', - timestamp = NOW() - WHERE webkey = '$given_webkey'"; + $sql = "UPDATE $avpos_table" + . ' SET text = ' . StrSQL($newtext) + . ', count = ' . IntSQL($given_count) + . ', timestamp = NOW()' + . ' WHERE webkey = ' . StrSQL($given_webkey); $result = mysqli_query($link,$sql) or email_death("ERR03: " . mysqli_error($link)); } @@ -148,8 +174,9 @@ else if(isset($_REQUEST['w'])){ // write to a record } else if(isset($_REQUEST['q'])){ // read a record - $given_webkey = mysqli_real_escape_string($link, $_REQUEST['q']); - $sql = "SELECT * FROM $avpos_table WHERE webkey = '$given_webkey'"; + $given_webkey = $_REQUEST['q']; + $sql = "SELECT * FROM $avpos_table" + . ' WHERE webkey = ' . StrSQL($given_webkey); $result = mysqli_query($link,$sql) or email_death("ERR04: " . mysqli_error($link)); if(mysqli_num_rows($result) == 0){ @@ -161,14 +188,16 @@ else if(isset($_REQUEST['q'])){ // read a record $out.= $row['text']; if(1==2){ // switch on to 'keep' any record that ever was accessed - $sql = "UPDATE $avpos_table SET - keep = '1' - WHERE webkey = '$given_webkey'"; + $sql = "UPDATE $avpos_table" + . ' SET keep = 1' + . ' WHERE webkey = ' . StrSQL($given_webkey); $result = mysqli_query($link,$sql) or email_death("ERR05: " . mysqli_error($link)); } // delete all entries older than 10 minutes that are not flagged keep - $sql = "DELETE FROM $avpos_table WHERE timestamp < DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND keep = '0'"; + $sql = "DELETE FROM $avpos_table" + . ' WHERE timestamp < DATE_SUB(NOW(), INTERVAL 10 MINUTE)' + . ' AND keep = 0'; $result = mysqli_query($link,$sql) or email_death("ERR06: " . mysqli_error($link)); } @@ -183,6 +212,36 @@ else{ die("400 Bad Request: No valid action specified."); } +function undo_magic_quotes(&$var) +{ + // Does anyone still use these? Probably not but just in case. + if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) + { + // This doesn't remove the slashes in the keys, but that doesn't matter for us. + foreach ($var as $k => &$v) + { + if (is_array($v)) + undo_magic_quotes($v); + else + $v = stripslashes($v); + } + } +} + +function IdentSQL($str){ + return '`' . str_replace('`', '``', $str) . '`'; +} + +function StrSQL($str){ + if ($str === null) + return "NULL"; + return "'" . mysqli_real_escape_string($GLOBALS['link'], strval($str)) . "'"; +} + +function IntSQL($int){ + return strval(intval($int)); +} + function parse_llHTTPRequest_headers(){ $position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); $rotation_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_ROTATION'],1,-1)); From 57028e44d1636c176d9d50ec164c856a1db10e8a Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Mon, 4 Sep 2017 00:55:17 +0200 Subject: [PATCH 7/9] Remove parse_llHTTPRequest_headers function. It served no practical purpose and used undefined variables to define values that weren't used anyway. Also, comment out the variables that aren't used after parsing the X-SecondLife headers. --- php/settings.php | 51 ++++++++++-------------------------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/php/settings.php b/php/settings.php index b65dd28..c774b49 100644 --- a/php/settings.php +++ b/php/settings.php @@ -80,14 +80,13 @@ else if(isset($_REQUEST['w'])){ // write to a record echo "INVALID WEBKEY"; } else{ - $headers = parse_llHTTPRequest_headers(); - $owner_key = $headers['X-SecondLife-Owner-Key']; - $object_name = $headers['X-SecondLife-Object-Name']; - $owner_name = $headers['X-SecondLife-Owner-Name']; - $object_key = $headers['X-SecondLife-Object-Key']; - $region = trim(substr($headers['X-SecondLife-Region'],0,strrpos($headers['X-SecondLife-Region'],'('))); - $position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); - $slurl = $region . "/" . round($position_array[0]) . "/" . round($position_array[1]) . "/" . round($position_array[2]); + $owner_key = $_SERVER['HTTP_X_SECONDLIFE_OWNER_KEY']; + //$object_name = $_SERVER['HTTP_X_SECONDLIFE_OBJECT_NAME']; + $owner_name = $_SERVER['HTTP_X_SECONDLIFE_OWNER_NAME']; + //$object_key = $_SERVER['HTTP_X_SECONDLIFE_OBJECT_KEY']; + //$region = trim(substr($_SERVER['HTTP_X_SECONDLIFE_REGION'],0,strrpos($_SERVER['HTTP_X_SECONDLIFE_REGION'],'('))); + //$position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); + //$slurl = rawurlencode($region) . "/" . round($position_array[0]) . "/" . round($position_array[1]) . "/" . round($position_array[2]); if(!isValidGuid($owner_key)){ echo "INVALID USER"; @@ -97,7 +96,7 @@ else if(isset($_REQUEST['w'])){ // write to a record $given_text = $_REQUEST['t']; $sql = "SELECT * FROM $avpos_table" - . " WHERE webkey = " . StrSQL($given_webkey); + . ' WHERE webkey = ' . StrSQL($given_webkey); $result = mysqli_query($link,$sql) or email_death("ERR01: " . mysqli_error($link)); if(mysqli_num_rows($result) == 0){ // a new webkey @@ -106,7 +105,7 @@ else if(isset($_REQUEST['w'])){ // write to a record $response = "BAD IP"; $sql = "INSERT INTO $avpos_table" . ' (owner_uuid,owner_name,webkey,text,count,ip,timestamp)' - . ' VALUES' + . ' VALUES ' . '(' . StrSQL($owner_key) . ',' . StrSQL($owner_name) . ',' . StrSQL($given_webkey) @@ -124,7 +123,7 @@ else if(isset($_REQUEST['w'])){ // write to a record } $sql = "INSERT INTO $avpos_table" . ' (owner_uuid,owner_name,webkey,text,count,ip,timestamp)' - . ' VALUES' + . ' VALUES ' . '(' . StrSQL($owner_key) . ',' . StrSQL($owner_name) . ',' . StrSQL($given_webkey) @@ -242,36 +241,6 @@ function IntSQL($int){ return strval(intval($int)); } -function parse_llHTTPRequest_headers(){ - $position_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_POSITION'],1,-1)); - $rotation_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_ROTATION'],1,-1)); - $velocity_array = explode(', ',substr($_SERVER['HTTP_X_SECONDLIFE_LOCAL_VELOCITY'],1,-1)); - list($global_x,$global_y) = explode(',',trim(substr($_SERVER['HTTP_X_SECONDLIFE_REGION'],$position_of_left_bracket + 1,-1))); - $region_array = array($region_name,(integer)$global_x,(integer)$global_y); - $headers = array('Accept'=>$_SERVER['HTTP_ACCEPT'], - 'User-Agent'=>$_SERVER['HTTP_USER_AGENT'], - 'X-SecondLife-Shard'=>$_SERVER['HTTP_X_SECONDLIFE_SHARD'], - 'X-SecondLife-Object-Name'=>$_SERVER['HTTP_X_SECONDLIFE_OBJECT_NAME'], - 'X-SecondLife-Object-Key'=>$_SERVER['HTTP_X_SECONDLIFE_OBJECT_KEY'], - 'X-SecondLife-Region'=>$_SERVER['HTTP_X_SECONDLIFE_REGION'], - 'X-SecondLife-Region-Array'=> $region_array, - 'X-SecondLife-Local-Position'=>array( 'x'=>(float)$position_array[0],'y'=>(float)$position_array[1],'z'=>(float)$position_array[2]), - 'X-SecondLife-Local-Rotation'=>array( 'x'=>(float)$rotation_array[0],'y'=>(float)$rotation_array[1],'z'=>(float)$rotation_array[2],'w'=>(float)$rotation_array[3]), - 'X-SecondLife-Local-Velocity'=>array( 'x'=>(float)$velocity_array[0],'y'=>(float)$velocity_array[1],'z'=>(float)$velocity_array[2]), - 'X-SecondLife-Owner-Name'=>$_SERVER['HTTP_X_SECONDLIFE_OWNER_NAME'], - 'X-SecondLife-Owner-Key'=>$_SERVER['HTTP_X_SECONDLIFE_OWNER_KEY'] - ); - if(!strstr($headers['X-SecondLife-Owner-Name'],' ') && $_POST['X-SecondLife-Owner-Name']){ - $headers['X-SecondLife-Owner-Name'] == $_POST['X-SecondLife-Owner-Name']; - } - if(is_array($headers)){ - return $headers; - } - else{ - return FALSE; - } -} - function isValidGuid($guid){ return !empty($guid) && preg_match('/^\{?[a-zA-Z0-9]{8}(?:-[a-zA-Z0-9]{4}){4}[a-zA-Z0-9]{8}\}?$/', $guid); } From ca2714c50a5cf36a2beb3f82d12218dcbb3e20c1 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Mon, 4 Sep 2017 01:12:15 +0200 Subject: [PATCH 8/9] Fix uninitialized variable in email_death() --- php/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/settings.php b/php/settings.php index c774b49..a3ec5dc 100644 --- a/php/settings.php +++ b/php/settings.php @@ -246,7 +246,7 @@ function isValidGuid($guid){ } function email_death($error){ - $body.="\n"; + $body="\n"; $body.="\n\$_SERVER\n"; foreach($_SERVER as $key_name => $key_value) { $body.= $key_name . " = " . $key_value . "\n"; From 27ae15b5ecc1eb35cade7f5a35dd3031277adf87 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 16 Sep 2017 00:02:10 +0200 Subject: [PATCH 9/9] Change utf8 to utf8mb4, checking if we succeed. --- php/settings.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/php/settings.php b/php/settings.php index a3ec5dc..7c45a3e 100644 --- a/php/settings.php +++ b/php/settings.php @@ -37,7 +37,9 @@ if (mysqli_connect_errno()) { die ("Connect failed: " . mysqli_connect_error()); } // Set the character set for communication with the database -mysqli_set_charset($link, 'utf8'); +if (!mysqli_set_charset($link, 'utf8mb4')) { + die('Invalid charset: utf8mb4'); +} // Pre-escape $avpos_table for convenience. That's the only variable // that should go directly into a query. All others should go through @@ -54,7 +56,7 @@ if($_REQUEST['action']=="install" && $allow_install==true){ `webkey` varchar(36) default NULL, `owner_uuid` varchar(36) default NULL, `owner_name` varchar(63) default NULL, - `text` TEXT CHARSET utf8 default NULL, + `text` TEXT CHARSET utf8mb4 default NULL, `keep` tinyint(1) default 0, `count` int(5) default NULL, `ip` varbinary(16) default NULL,