CVE-2021-3734: Improper Restriction of Rendered UI Layers or Frames (CWE-1021)
value: yourls is vulnerable to Improper Restriction of Rendered UI Layers or Frames
CWE description: The web application does not restrict or incorrectly restricts frame objects or UI layers that belong to another application or domain, which can lead to user confusion about which interface the user is interacting with.
CVSS v3 base score: 8.8
Published: 2021-08-26T13:15Z
Repository: https://github.com/yourls/yourls
Language: PHP
Files changed: admin/admin-ajax.php, includes/functions-auth.php, includes/functions-html.php, includes/functions.php
Commit date: 2021-08-24 18:10:08 +0200
Commit message: Minor security improvements (#3034)

* Prevent iframe jacking
* Enforce nonce on admin login screen
Diff: diff --git a/admin/admin-ajax.php b/admin/admin-ajax.php
index 8dbc0b2..77f68ac 100644
--- a/admin/admin-ajax.php
+++ b/admin/admin-ajax.php
@@ -5,10 +5,11 @@
 yourls_maybe_require_auth();
 
 // This file will output a JSON string
 yourls_content_type_header( 'application/json' );
 yourls_no_cache_headers();
+yourls_no_frame_header();
 
 if( !isset( $_REQUEST['action'] ) )
 	die();
 
 // Pick action
diff --git a/includes/functions-auth.php b/includes/functions-auth.php
index 303d394..d34b3d5 100644
--- a/includes/functions-auth.php
+++ b/includes/functions-auth.php
@@ -120,10 +120,16 @@ function yourls_is_valid_user() {
  * Check auth against list of login=>pwd. Sets user if applicable, returns bool
  *
  */
 function yourls_check_username_password() {
 	global $yourls_user_passwords;
+
+	// If login form (not API), check for nonce
+    if(!yourls_is_API()) {
+        yourls_verify_nonce('admin_login');
+    }
+
 	if( isset( $yourls_user_passwords[ $_REQUEST['username'] ] ) && yourls_check_password_hash( $_REQUEST['username'], $_REQUEST['password'] ) ) {
 		yourls_set_user( $_REQUEST['username'] );
 		return true;
 	}
 	return false;
diff --git a/includes/functions-html.php b/includes/functions-html.php
index 3d68c1d..1cb7fa6 100644
--- a/includes/functions-html.php
+++ b/includes/functions-html.php
@@ -57,10 +57,11 @@ function yourls_html_head( $context = 'index', $title = '' ) {
 	}
 
 	// Force no cache for all admin pages
 	if( yourls_is_admin() && !headers_sent() ) {
         yourls_no_cache_headers();
+        yourls_no_frame_header();
 		yourls_content_type_header( yourls_apply_filter( 'html_head_content-type', 'text/html' ) );
 		yourls_do_action( 'admin_headers', $context, $title );
 	}
 
 	// Store page context
@@ -723,10 +724,11 @@ function yourls_login_screen( $error_msg = '' ) {
 			</p>
 			<?php
 				yourls_do_action( 'login_form_bottom' );
 			?>
 			<p style="text-align: right;">
+			    <?php yourls_nonce_field('admin_login'); ?>
 				<input type="submit" id="submit" name="submit" value="<?php yourls_e( 'Login' ); ?>" class="button" />
 			</p>
 			<?php
 				yourls_do_action( 'login_form_end' );
 			?>
diff --git a/includes/functions.php b/includes/functions.php
index 4ee9ce9..3c755c8 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -274,11 +274,34 @@ function yourls_no_cache_headers() {
         header( 'Pragma: no-cache' );
     }
 }
 
 /**
- * Send a filerable content type header
+ * Send header to prevent display within a frame from another site (avoid clickjacking)
+ *
+ * This header makes it impossible for an external site to display YOURLS admin within a frame,
+ * which allows for clickjacking.
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ * This said, the whole function is shuntable : legit uses of iframes should be still possible.
+ *
+ * @since 1.8.1
+ * @return void|mixed
+ */
+function yourls_no_frame_header() {
+    // Allow plugins to short-circuit the whole function
+    $pre = yourls_apply_filter( 'shunt_no_frame_header', false );
+    if ( false !== $pre ) {
+        return $pre;
+    }
+
+    if( !headers_sent() ) {
+        header( 'X-Frame-Options: SAMEORIGIN' );
+    }
+}
+
+/**
+ * Send a filterable content type header
  *
  * @since 1.7
  * @param string $type content type ('text/html', 'application/json', ...)
  * @return bool whether header was sent
  */

Vulnerable code:  * Send a filerable content type header
Fixed code: yourls_no_frame_header();

	// If login form (not API), check for nonce
    if(!yourls_is_API()) {
        yourls_verify_nonce('admin_login');
    }

        yourls_no_frame_header();
			    <?php yourls_nonce_field('admin_login'); ?>
 * Send header to prevent display within a frame from another site (avoid clickjacking)
 *
 * This header makes it impossible for an external site to display YOURLS admin within a frame,
 * which allows for clickjacking.
 * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
 * This said, the whole function is shuntable : legit uses of iframes should be still possible.
 *
 * @since 1.8.1
 * @return void|mixed
 */
function yourls_no_frame_header() {
    // Allow plugins to short-circuit the whole function
    $pre = yourls_apply_filter( 'shunt_no_frame_header', false );
    if ( false !== $pre ) {
        return $pre;
    }

    if( !headers_sent() ) {
        header( 'X-Frame-Options: SAMEORIGIN' );
    }
}

/**
 * Send a filterable content type header