CVE-2021-3785: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: yourls is vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-09-15T12:15Z
Repository: https://github.com/yourls/yourls
Language: PHP
Files changed: includes/functions-shorturls.php, tests/tests/shorturl/shorturl.php
Commit date: 2021-08-31 18:00:53 +0200
Commit message: Minor security improvements #2 (#3041)

* Escape HTML and attributes before returning
* Remove deprecated vars
Diff: diff --git a/includes/functions-shorturls.php b/includes/functions-shorturls.php
index a1bdb0cf..87e46f64 100644
--- a/includes/functions-shorturls.php
+++ b/includes/functions-shorturls.php
@@ -299,12 +299,10 @@ function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
     $table = YOURLS_DB_TABLE_URL;
     $url = yourls_sanitize_url($url);
     $keyword = yourls_sanitize_keyword($keyword);
     $title = yourls_sanitize_title($title);
     $newkeyword = yourls_sanitize_keyword($newkeyword, true);
-    $strip_url = stripslashes( $url );
-    $strip_title = stripslashes( $title );
 
     if(!$url OR !$newkeyword) {
         $return['status']  = 'fail';
         $return['message'] = yourls__( 'Long URL or Short URL cannot be blank' );
         return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title );
@@ -332,16 +330,22 @@ function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
     if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
             $sql   = "UPDATE `$table` SET `url` = :url, `keyword` = :newkeyword, `title` = :title WHERE `keyword` = :keyword";
             $binds = array('url' => $url, 'newkeyword' => $newkeyword, 'title' => $title, 'keyword' => $keyword);
             $update_url = $ydb->fetchAffected($sql, $binds);
         if( $update_url ) {
-            $return['url']     = array( 'keyword' => $newkeyword, 'shorturl' => yourls_link($newkeyword), 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) );
+            $return['url']     = array( 'keyword'       => $newkeyword,
+                                        'shorturl'      => yourls_link($newkeyword),
+                                        'url'           => yourls_esc_url($url),
+                                        'display_url'   => yourls_esc_html(yourls_trim_long_string($url)),
+                                        'title'         => yourls_esc_attr($title),
+                                        'display_title' => yourls_esc_html(yourls_trim_long_string( $title ))
+                                );
             $return['status']  = 'success';
             $return['message'] = yourls__( 'Link updated in database' );
         } else {
             $return['status']  = 'fail';
-            $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ;
+            $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_esc_html(yourls_trim_long_string($url)), $keyword ) ;
         }
 
     // Nope
     } else {
         $return['status']  = 'fail';
diff --git a/tests/tests/shorturl/shorturl.php b/tests/tests/shorturl/shorturl.php
index 55e61e5b..0bde5e5c 100644
--- a/tests/tests/shorturl/shorturl.php
+++ b/tests/tests/shorturl/shorturl.php
@@ -38,10 +38,13 @@ public function test_add_url() {
         $newurl = yourls_add_new_link( $url, $keyword, $title );
         $this->assertEquals( 'success', $newurl['status'] );
 
         $fail = yourls_add_new_link( $url, $keyword, $title );
         $this->assertEquals( 'fail', $fail['status'] );
+
+        $fail = yourls_add_new_link( $url, rand_str(), rand_str() );
+        $this->assertEquals( 'fail', $fail['status'] );
         $this->assertEquals( 'error:url', $fail['code'] );
 
         $fail = yourls_add_new_link( 'http://' . rand_str(), $keyword, $title );
         $this->assertEquals( 'fail', $fail['status'] );
         $this->assertEquals( 'error:keyword', $fail['code'] );

Vulnerable code:     $strip_url = stripslashes( $url );
    $strip_title = stripslashes( $title );
            $return['url']     = array( 'keyword' => $newkeyword, 'shorturl' => yourls_link($newkeyword), 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) );
            $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ;
Fixed code:             $return['url']     = array( 'keyword'       => $newkeyword,
                                        'shorturl'      => yourls_link($newkeyword),
                                        'url'           => yourls_esc_url($url),
                                        'display_url'   => yourls_esc_html(yourls_trim_long_string($url)),
                                        'title'         => yourls_esc_attr($title),
                                        'display_title' => yourls_esc_html(yourls_trim_long_string( $title ))
                                );
            $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_esc_html(yourls_trim_long_string($url)), $keyword ) ;

        $fail = yourls_add_new_link( $url, rand_str(), rand_str() );
        $this->assertEquals( 'fail', $fail['status'] );