From 73119451ec32dabaa04ca1caed744466d7225136 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Fri, 5 Dec 2014 11:09:57 +1100 Subject: [PATCH 01/15] First pass of expiring end points. Works like a banking fob. Three codes work: * current * previous * next (this may be overreach) The code regenerates every twelve hours. Allowing for three codes ensures that brid.gy still works as it caches for 24 hours. --- webmention.php | 53 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/webmention.php b/webmention.php index f7854dc0..acd22759 100755 --- a/webmention.php +++ b/webmention.php @@ -69,6 +69,29 @@ public static function query_var($vars) { return $vars; } + /** + * get the three valid endpoint codes. In the returned array, + * the middle item is the most correct + * + * @return array + */ + public static function expire_codes() { + $format = 'Y-m-d a'; + $time_block = 12 * 60 * 60; + $valid_codes = array( + date( $format, time() - $time_block ), + date( $format, time() ), + date( $format, time() + $time_block ) + ); + + foreach ( $valid_codes as $key => $expire_code ) { + $valid_codes[$key] = wp_hash( $expire_code, 'nonce' ); + } + + return $valid_codes; + } + + /** * Parse the WebMention request and render the document * @@ -81,6 +104,24 @@ public static function parse_query($wp) { if (!array_key_exists('webmention', $wp->query_vars)) { return; } + else { + // check if the end point has expired + $valid_endpoint_codes = WebMentionPlugin::expire_codes(); + $endpoint_code = $valid_endpoint_codes[1]; + + $supplied_code = get_query_var( 'webmention' ); + $is_valid = false; + + foreach ( $valid_codes as $expire_code ) { + if ( $supplied_code == $expire_code ) { + $is_valid = true; + } + } + + if ( false == $is_valid ) { + return; + } + } $content = file_get_contents('php://input'); parse_str($content); @@ -556,8 +597,10 @@ public static function discover_endpoint($url) { */ public static function html_header() { // backwards compatibility with v0.1 - echo ''."\n"; - echo ''."\n"; + $valid_endpoint_codes = WebMentionPlugin::expire_codes(); + $endpoint_code = $valid_endpoint_codes[1]; + echo ''."\n"; + echo ''."\n"; } /** @@ -565,8 +608,10 @@ public static function html_header() { */ public static function http_header() { // backwards compatibility with v0.1 - header('Link: <'.site_url("?webmention=endpoint").'>; rel="http://webmention.org/"', false); - header('Link: <'.site_url("?webmention=endpoint").'>; rel="webmention"', false); + $valid_endpoint_codes = WebMentionPlugin::expire_codes(); + $endpoint_code = $valid_endpoint_codes[1]; + header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="http://webmention.org/"', false); + header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="webmention"', false); } /** From acae53370e3e769cc4203b2a172cb332a3117918 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Fri, 5 Dec 2014 13:43:35 +1100 Subject: [PATCH 02/15] Fix variable name when checking codes validity --- webmention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index acd22759..930f4175 100755 --- a/webmention.php +++ b/webmention.php @@ -112,7 +112,7 @@ public static function parse_query($wp) { $supplied_code = get_query_var( 'webmention' ); $is_valid = false; - foreach ( $valid_codes as $expire_code ) { + foreach ( $valid_endpoint_codes as $expire_code ) { if ( $supplied_code == $expire_code ) { $is_valid = true; } From 4313fb1754252c5aa7a580f00f6fc0b5a5dea005 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Fri, 5 Dec 2014 14:00:34 +1100 Subject: [PATCH 03/15] Make the first item the most valid code. This allows breaking out of the checking look to occur earlier --- webmention.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webmention.php b/webmention.php index 930f4175..c0e3a242 100755 --- a/webmention.php +++ b/webmention.php @@ -71,7 +71,7 @@ public static function query_var($vars) { /** * get the three valid endpoint codes. In the returned array, - * the middle item is the most correct + * the first item -index 0- is the most correct * * @return array */ @@ -79,8 +79,8 @@ public static function expire_codes() { $format = 'Y-m-d a'; $time_block = 12 * 60 * 60; $valid_codes = array( - date( $format, time() - $time_block ), date( $format, time() ), + date( $format, time() - $time_block ), date( $format, time() + $time_block ) ); @@ -107,7 +107,6 @@ public static function parse_query($wp) { else { // check if the end point has expired $valid_endpoint_codes = WebMentionPlugin::expire_codes(); - $endpoint_code = $valid_endpoint_codes[1]; $supplied_code = get_query_var( 'webmention' ); $is_valid = false; @@ -115,6 +114,7 @@ public static function parse_query($wp) { foreach ( $valid_endpoint_codes as $expire_code ) { if ( $supplied_code == $expire_code ) { $is_valid = true; + break; } } @@ -598,7 +598,7 @@ public static function discover_endpoint($url) { public static function html_header() { // backwards compatibility with v0.1 $valid_endpoint_codes = WebMentionPlugin::expire_codes(); - $endpoint_code = $valid_endpoint_codes[1]; + $endpoint_code = $valid_endpoint_codes[0]; echo ''."\n"; echo ''."\n"; } @@ -609,7 +609,7 @@ public static function html_header() { public static function http_header() { // backwards compatibility with v0.1 $valid_endpoint_codes = WebMentionPlugin::expire_codes(); - $endpoint_code = $valid_endpoint_codes[1]; + $endpoint_code = $valid_endpoint_codes[0]; header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="http://webmention.org/"', false); header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="webmention"', false); } From 1376d1c8d543f3d6810231b13d245c2881ad6506 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Thu, 18 Dec 2014 12:09:03 +1100 Subject: [PATCH 04/15] Change endpoint nonce calculation - never use future time, that's dumb - allow previous two to be valid - use logged out uid and token - name the action This more closely emulates how the WP nonce works. --- webmention.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/webmention.php b/webmention.php index c0e3a242..08d2cdfd 100755 --- a/webmention.php +++ b/webmention.php @@ -76,16 +76,26 @@ public static function query_var($vars) { * @return array */ public static function expire_codes() { - $format = 'Y-m-d a'; + $action = 'web mention endpoint'; + $time_format = 'Y-m-d a'; $time_block = 12 * 60 * 60; $valid_codes = array( - date( $format, time() ), - date( $format, time() - $time_block ), - date( $format, time() + $time_block ) + date( $time_format, time() ), + date( $time_format, time() - $time_block ), + date( $time_format, time() - ( 2 * $time_block ) ) ); + // always use logged out user code, endpoint may be looked up by a logged in user + // while the web mention comes from a logged out user (using curl or similar) + $uid = 0; + $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); + + // as above, always use the lgoged out token. + $token = ''; + + foreach ( $valid_codes as $key => $expire_code ) { - $valid_codes[$key] = wp_hash( $expire_code, 'nonce' ); + $valid_codes[$key] = wp_hash( $expire_code . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ); } return $valid_codes; From 15c037bd929c33057c05a4e8486aec1e134749c8 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 11:34:28 +1100 Subject: [PATCH 05/15] Use WordPress built in constant for 1/2 day --- webmention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index 08d2cdfd..55974002 100755 --- a/webmention.php +++ b/webmention.php @@ -78,7 +78,7 @@ public static function query_var($vars) { public static function expire_codes() { $action = 'web mention endpoint'; $time_format = 'Y-m-d a'; - $time_block = 12 * 60 * 60; + $time_block = DAY_IN_SECONDS / 2; $valid_codes = array( date( $time_format, time() ), date( $time_format, time() - $time_block ), From 5a1df6c71c5bfe22e5e563273eb1dd2e18fd2e11 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 11:40:23 +1100 Subject: [PATCH 06/15] Use hash_equals to avoid timing attacks --- webmention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index 55974002..eb95c900 100755 --- a/webmention.php +++ b/webmention.php @@ -122,7 +122,7 @@ public static function parse_query($wp) { $is_valid = false; foreach ( $valid_endpoint_codes as $expire_code ) { - if ( $supplied_code == $expire_code ) { + if ( hash_equals( $expire_code, $supplied_code ) ) { $is_valid = true; break; } From 4571a3050457e8e02856534d4f259de82a251a8d Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 11:44:42 +1100 Subject: [PATCH 07/15] Output status code for invalid endpoint --- webmention.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index eb95c900..11348947 100755 --- a/webmention.php +++ b/webmention.php @@ -129,7 +129,9 @@ public static function parse_query($wp) { } if ( false == $is_valid ) { - return; + status_header(400); + echo "invalid endpoint"; + exit; } } From 2448507fa6d035983dafd8ca33e88199bc5bb6ee Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 11:46:28 +1100 Subject: [PATCH 08/15] move invalid endpoiint error to after content type --- webmention.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/webmention.php b/webmention.php index 11348947..d2b561d0 100755 --- a/webmention.php +++ b/webmention.php @@ -127,12 +127,6 @@ public static function parse_query($wp) { break; } } - - if ( false == $is_valid ) { - status_header(400); - echo "invalid endpoint"; - exit; - } } $content = file_get_contents('php://input'); @@ -141,6 +135,14 @@ public static function parse_query($wp) { // plain text header header('Content-Type: text/plain; charset=' . get_option('blog_charset')); + // fail if invalide endpoint + if ( false == $is_valid ) { + status_header(400); + echo "invalid endpoint"; + exit; + } + + // check if source url is transmitted if (!isset($source)) { status_header(400); From 1b241665d07c3c3bbe0b1c957da72fd23328a3fc Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 12:05:07 +1100 Subject: [PATCH 09/15] 12 hours is better than 1/2 day --- webmention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index d2b561d0..b14b0a1c 100755 --- a/webmention.php +++ b/webmention.php @@ -78,7 +78,7 @@ public static function query_var($vars) { public static function expire_codes() { $action = 'web mention endpoint'; $time_format = 'Y-m-d a'; - $time_block = DAY_IN_SECONDS / 2; + $time_block = 12 * HOUR_IN_SECONDS; $valid_codes = array( date( $time_format, time() ), date( $time_format, time() - $time_block ), From 535258337db6b48983a7438f22907a87fa4027d5 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 12:28:45 +1100 Subject: [PATCH 10/15] replace call to error_codes --- webmention.php | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/webmention.php b/webmention.php index b14b0a1c..d1a9de2e 100755 --- a/webmention.php +++ b/webmention.php @@ -75,15 +75,17 @@ public static function query_var($vars) { * * @return array */ - public static function expire_codes() { + public static function expire_code( $tick = 0 ) { $action = 'web mention endpoint'; $time_format = 'Y-m-d a'; $time_block = 12 * HOUR_IN_SECONDS; - $valid_codes = array( - date( $time_format, time() ), - date( $time_format, time() - $time_block ), - date( $time_format, time() - ( 2 * $time_block ) ) - ); + $tick = abs( intval( $tick ) ); + if ( 3 < $tick ) { + // something wrong, tick too high/ + // use default + $tick = 0; + } + $expire_code = date( $time_format, time() - ( $tick * $time_block ) ); // always use logged out user code, endpoint may be looked up by a logged in user // while the web mention comes from a logged out user (using curl or similar) @@ -94,11 +96,9 @@ public static function expire_codes() { $token = ''; - foreach ( $valid_codes as $key => $expire_code ) { - $valid_codes[$key] = wp_hash( $expire_code . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ); - } + $expire_code = wp_hash( $expire_code . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ); - return $valid_codes; + return $expire_code; } @@ -116,13 +116,13 @@ public static function parse_query($wp) { } else { // check if the end point has expired - $valid_endpoint_codes = WebMentionPlugin::expire_codes(); + $valid_ticks = array( 0, -1, -2 ); $supplied_code = get_query_var( 'webmention' ); $is_valid = false; - foreach ( $valid_endpoint_codes as $expire_code ) { - if ( hash_equals( $expire_code, $supplied_code ) ) { + foreach ( $valid_ticks as $tick ) { + if ( hash_equals( WebMentionPlugin::expire_code( $tick ), $supplied_code ) ) { $is_valid = true; break; } @@ -611,8 +611,7 @@ public static function discover_endpoint($url) { */ public static function html_header() { // backwards compatibility with v0.1 - $valid_endpoint_codes = WebMentionPlugin::expire_codes(); - $endpoint_code = $valid_endpoint_codes[0]; + $endpoint_code = WebMentionPlugin::expire_code(); echo ''."\n"; echo ''."\n"; } @@ -622,8 +621,7 @@ public static function html_header() { */ public static function http_header() { // backwards compatibility with v0.1 - $valid_endpoint_codes = WebMentionPlugin::expire_codes(); - $endpoint_code = $valid_endpoint_codes[0]; + $endpoint_code = WebMentionPlugin::expire_code(); header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="http://webmention.org/"', false); header('Link: <'.site_url("?webmention=" . $endpoint_code).'>; rel="webmention"', false); } From 78deee472e7ca4e8b99c9e93ef4c53fe09d83522 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 13:00:09 +1100 Subject: [PATCH 11/15] Fix comment on expire code function --- webmention.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webmention.php b/webmention.php index d1a9de2e..e97eb0cc 100755 --- a/webmention.php +++ b/webmention.php @@ -70,8 +70,8 @@ public static function query_var($vars) { } /** - * get the three valid endpoint codes. In the returned array, - * the first item -index 0- is the most correct + * generate a valid expire code. + * Three possible values are valid at any one time, ticks: 0, 1, or 2 * * @return array */ From 83e188fe4b8bdc600c05d40f80b8705de77376b3 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 22 Dec 2014 13:01:56 +1100 Subject: [PATCH 12/15] Only get contents after the endpoint is confirmed valid --- webmention.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webmention.php b/webmention.php index e97eb0cc..dcf134f8 100755 --- a/webmention.php +++ b/webmention.php @@ -129,9 +129,6 @@ public static function parse_query($wp) { } } - $content = file_get_contents('php://input'); - parse_str($content); - // plain text header header('Content-Type: text/plain; charset=' . get_option('blog_charset')); @@ -142,6 +139,8 @@ public static function parse_query($wp) { exit; } + $content = file_get_contents('php://input'); + parse_str($content); // check if source url is transmitted if (!isset($source)) { From faf5146ac46252cffc49c8003d94782d9e832f13 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 23 Feb 2015 09:55:54 +1100 Subject: [PATCH 13/15] Respond with 403 for invalid endpoints. --- webmention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index dcf134f8..83acac0c 100755 --- a/webmention.php +++ b/webmention.php @@ -134,7 +134,7 @@ public static function parse_query($wp) { // fail if invalide endpoint if ( false == $is_valid ) { - status_header(400); + status_header(403); echo "invalid endpoint"; exit; } From bd4840cf9954b86b312f97ad9d41d1260204ce03 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 23 Feb 2015 10:00:04 +1100 Subject: [PATCH 14/15] Remove filter allowing plugins to link uid to session data --- webmention.php | 1 - 1 file changed, 1 deletion(-) diff --git a/webmention.php b/webmention.php index 83acac0c..a707646b 100755 --- a/webmention.php +++ b/webmention.php @@ -90,7 +90,6 @@ public static function expire_code( $tick = 0 ) { // always use logged out user code, endpoint may be looked up by a logged in user // while the web mention comes from a logged out user (using curl or similar) $uid = 0; - $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); // as above, always use the lgoged out token. $token = ''; From e0625ea3501617be95a49b92f6a0017eed2851eb Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 23 Feb 2015 10:10:35 +1100 Subject: [PATCH 15/15] Add comment explaining why native nonces are not used. --- webmention.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webmention.php b/webmention.php index a707646b..9deb0eed 100755 --- a/webmention.php +++ b/webmention.php @@ -94,7 +94,8 @@ public static function expire_code( $tick = 0 ) { // as above, always use the lgoged out token. $token = ''; - + // custom hash used rather than standard nonce to prevent session data polluting the + // web mention endpoint. The endpoint needs to remain the same for all users. $expire_code = wp_hash( $expire_code . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ); return $expire_code;