Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-4520: Changeset 2957322 for fv-wordpress-flowplayer – WordPress Plugin Repository

The FV Flowplayer Video Player plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘_fv_player_user_video’ parameter saved via the ‘save’ function hooked via init, and the plugin is also vulnerable to Arbitrary Usermeta Update via the ‘save’ function in versions up to, and including, 7.5.37.7212 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page, and makes it possible to update the user metas arbitrarily, but the meta value can only be a string.

CVE
#xss#web#mac#amazon#wordpress#php#auth

fv-wordpress-flowplayer/trunk/flowplayer.php

r2946640

r2957322

4

4

Plugin URI: http://foliovision.com/wordpress/plugins/fv-wordpress-flowplayer

5

5

Description: Formerly FV WordPress Flowplayer. Supports MP4, HLS, MPEG-DASH, WebM and OGV. Advanced features such as overlay ads or popups. Uses Flowplayer 7.2.8.

6

Version: 7.5.37.7212

6

Version: 7.5.39.7212

7

7

Author URI: http://foliovision.com/

8

8

License: GPL-3.0

32

32

33

33

global $fv_wp_flowplayer_ver;

34

$fv_wp_flowplayer_ver = '7.5.37.7212’;

34

$fv_wp_flowplayer_ver = '7.5.39.7212’;

35

35

$fv_wp_flowplayer_core_ver = '7.2.12.3’;

36

36

include_once( dirname( __FILE__ ) . ‘/includes/extra-functions.php’ );

133

133

}

134

134

}

135

136

add_filter( 'tables_to_repair’, ‘fv_player_tables_to_repair’ );

137

138

function fv_player_tables_to_repair( $tables ) {

139

global $wpdb;

140

141

$tables[] = FV_Player_Db_Player::get_db_table_name();

142

$tables[] = FV_Player_Db_Player_Meta::get_db_table_name();

143

$tables[] = FV_Player_Db_Video::get_db_table_name();

144

$tables[] = FV_Player_Db_Video_Meta::get_db_table_name();

145

$tables[] = FV_Player_Stats::get_table_name();

146

$tables[] = $wpdb->prefix . 'fv_player_emails’;

147

$tables[] = $wpdb->prefix . 'fv_player_encoding_jobs’;

148

$tables[] = $wpdb->prefix . 'fv_fp_hls_access_tokens’;

149

150

return $tables;

151

}

fv-wordpress-flowplayer/trunk/models/checker.php

r2946640

r2957322

369

369

      $objVideo->updateMetaValue('last\_video\_meta\_check', time());

370

370

371

      if( $meta\_data\['is\_live'\] ) {

371

      if( is\_array($meta\_data) && !empty($meta\_data\['is\_live'\]) && $meta\_data\['is\_live'\] ) {

372

372

        $objVideo->updateMetaValue( 'live', true );

373

373

      } else {

375

375

      }

376

376

377

      if( $meta\_data\['duration'\] ) {

377

      if( is\_array($meta\_data) && !empty($meta\_data\['duration'\]) && $meta\_data\['duration'\] ) {

378

378

        $objVideo->updateMetaValue( 'duration', $meta\_data\['duration'\] );

379

379

      }

380

380

381

      if( $meta\_data\['error'\] ) {

381

      if( is\_array($meta\_data) && !empty($meta\_data\['error'\]) && $meta\_data\['error'\] ) {

382

382

        $objVideo->updateMetaValue( 'error', $meta\_data\['error'\] );

383

383

fv-wordpress-flowplayer/trunk/models/custom-videos.php

r2904314

r2957322

41

41

42

42

if( is\_admin() ) {

43

  global $fv\_fp;

44

43

  if( $this->have\_videos() ) {

45

44

    global $FV\_Player\_Pro;

65

64

$html .= $this->get\_html( $args );

66

65

67

if( !is\_admin() ) {

68

  $html .= wp\_nonce\_field( 'fv-player-custom-videos-'.$this->meta.'-'.get\_current\_user\_id(), 'fv-player-custom-videos-'.$this->meta.'-'.get\_current\_user\_id(), true, false );

69

}

70

71

if( !is\_admin() && !$args\['no\_form'\] ) {     

66

$html .= wp\_nonce\_field( 'fv-player-custom-videos-'.$this->meta.'-'.get\_current\_user\_id(), 'fv-player-custom-videos-'.$this->meta.'-'.get\_current\_user\_id(), true, false );

67

68

if( !is\_admin() && !$args\['no\_form'\] ) {

72

69

  $html .= "<input type='hidden' name='action' value='fv-player-custom-videos-save' />";

73

  $html .= "<input type='submit' value='Save Videos' />";   

70

  $html .= "<input type='submit' value='Save Videos' />";

74

71

  $html .= "</form>";

75

72

}

88

85

$args = !empty($post) && !empty($FV\_Player\_Custom\_Videos\_Master->aMetaBoxes\[$post->post\_type\]) ? $FV\_Player\_Custom\_Videos\_Master->aMetaBoxes\[$post->post\_type\]\[$this->meta\] : $defaults;

89

86

87

if( $video ) {

88

  $video = wp\_kses( $video, 'post' );

89

}

90

90

91

//  exp: what matters here is .fv-player-editor-field and .fv-player-editor-button wrapped in  .fv-player-editor-wrapper and .fv-player-editor-preview

91

92

if( $edit ) {

94

95

  $preview = false;

95

96

  $before = 0;

97

96

98

  if( $video ) {

99

    global $fv\_fp;

100

97

101

    $preview = do\_shortcode( str\_replace( '\[fvplayer ', '\[fvplayer autoplay="false" ', $video ) );

98

    global $fv\_fp;

102

99

103

    if( $fv\_fp->current\_player() ) {

100

104

      $before = count($fv\_fp->current\_player()->getVideos());

120

124

    </div>";

121

125

} else {

122

  $html = do\_shortcode($video);     

126

  $html = do\_shortcode($video);

123

127

}

124

128

return $html;

146

150

} else if( $args\['edit'\] ) {

147

151

  $html .= '<'.$args\['wrapper'\].' class="fv-player-custom-video">';

148

    $html .= $this->get\_html\_part(false, true);       

149

    $html .= '<div style="clear: both"></div>'."\\n";     

150

  $html .= '</'.$args\['wrapper'\].'>';     

152

    $html .= $this->get\_html\_part(false, true);

153

    $html .= '<div style="clear: both"></div>'."\\n";

154

  $html .= '</'.$args\['wrapper'\].'>';

151

155

}

152

156

159

163

public function get_videos() {

160

164

if( $this->type == 'user' ) {

161

  $aMeta = get\_user\_meta( $this->id, $this->meta );     

165

  $aMeta = get\_user\_meta( $this->id, $this->meta );

162

166

} else if( $this->type == 'post' ) {

163

167

  $aMeta = get\_post\_meta( $this->id, $this->meta );

324

328

325

329

function save() {

326

327

330

if( !isset($\_POST\['fv\_player\_videos'\]) || !isset($\_POST\['fv-player-custom-videos-entity-type'\]) || !isset($\_POST\['fv-player-custom-videos-entity-id'\]) ) {

328

331

  return;

329

332

}

330

331

332

333

//  todo: permission check!

333

334

334

foreach( $\_POST\['fv\_player\_videos'\] AS $meta => $videos ) {

335

  $meta = sanitize\_text\_field( $meta );

336

337

  if( !wp\_verify\_nonce($\_POST\['fv-player-custom-videos-'.$meta.'-'.get\_current\_user\_id()\] ,'fv-player-custom-videos-'.$meta.'-'.get\_current\_user\_id() ) ) {

338

    continue;

339

  }

340

335

341

  if( $\_POST\['fv-player-custom-videos-entity-type'\]\[$meta\] == 'user' ) {

336

342

    delete\_user\_meta( $\_POST\['fv-player-custom-videos-entity-id'\]\[$meta\], $meta );

338

344

    foreach( $videos AS $video ) {

339

345

      if( strlen($video) == 0 ) continue;

340

346

347

      // strip html tags to prevent XSS

348

      $video = sanitize\_text\_field( $video );

349

341

350

      add\_user\_meta( $\_POST\['fv-player-custom-videos-entity-id'\]\[$meta\], $meta, $video );

342

351

    }

343

  }

344

345

}

346

352

  }

353

}

347

354

}

348

355

351

358

  return;

352

359

}

353

354

//  todo: permission check!

355

360

356

361

foreach( $\_POST\['fv\_player\_videos'\] AS $meta => $value ) {

362

  $meta = sanitize\_text\_field( $meta );

363

364

  if( !wp\_verify\_nonce($\_POST\['fv-player-custom-videos-'.$meta.'-'.get\_current\_user\_id()\] ,'fv-player-custom-videos-'.$meta.'-'.get\_current\_user\_id() ) ) {

365

    continue;

366

  }

367

357

368

  if( $\_POST\['fv-player-custom-videos-entity-type'\]\[$meta\] == 'post' && $\_POST\['fv-player-custom-videos-entity-id'\]\[$meta\] == $post\_id ) {

358

369

    delete\_post\_meta( $post\_id, $meta );

359

370

360

371

    if( is\_array($value) && count($value) > 0 ) {

361

      foreach( $value AS $k => $v ) {           

372

      foreach( $value AS $k => $v ) {

362

373

        if( strlen($v) == 0 ) continue;

363

374

375

        // strip html tags to prevent XSS

376

        $v = sanitize\_text\_field( $v );

377

364

378

        add\_post\_meta( $post\_id, $meta, $v );

365

379

      }

fv-wordpress-flowplayer/trunk/models/db-player-meta.php

r2904314

r2957322

85

85

return self::$db\_table\_name;

86

86

}

87

87

88

/**

89

* Returns name of the video DB table.

90

*

91

* @return mixed The name of the video DB table.

92

*/

93

public static function get_db_table_name() {

94

if ( !self::$db\_table\_name ) {

95

  self::init\_db\_name();

96

}

97

98

return self::$db\_table\_name;

99

}

100

88

101

/**

89

102

* Checks for DB tables existence and creates it as necessary.

fv-wordpress-flowplayer/trunk/models/flowplayer.php

r2946640

r2957322

893

893

894

894

      $temp\_media = $this->get\_video\_src( $v\['src'\], array( 'dynamic' => true ) );

895

896

      // Encode components of the URL path

897

      $url\_path = wp\_parse\_url( $temp\_media, PHP\_URL\_PATH );

898

      $temp\_media = str\_replace( $url\_path, implode( '/', array\_map( 'urlencode', explode( '/', $url\_path ) ) ), $temp\_media );

899

900

895

      if( isset($FV\_Player\_Pro) && $FV\_Player\_Pro ) {

901

896

        if($FV\_Player\_Pro->is\_vimeo($temp\_media) || method\_exists($FV\_Player\_Pro, 'is\_vimeo\_event') && $FV\_Player\_Pro->is\_vimeo\_event($temp\_media) || $FV\_Player\_Pro->is\_youtube($temp\_media)) {

1526

1521

1527

1522

function css_option() {

1528

return 'css\_writeout-'.sanitize\_title(home\_url());

1523

global $fv\_wp\_flowplayer\_ver;

1524

return 'css\_writeout-'.sanitize\_title(home\_url()) . '-' . $fv\_wp\_flowplayer\_ver;

1529

1525

}

1530

1526

1734

1730

1735

1731

  $url\_components = parse\_url($resource);

1736

1737

  $iAWSVersion = $fv\_fp->\_get\_option( array( 'amazon\_region', $amazon\_key ) ) ? 4 : 2;

1738

1739

  if( $iAWSVersion == 4 ) {

1740

    $url\_components\['path'\] = str\_replace( array('%20','+'), ' ', $url\_components\['path'\]);

1741

  }

1742

1743

  $url\_components\['path'\] = rawurlencode($url\_components\['path'\]);

1732

1733

  // decode the path, as it might come partially URL encoded already

1734

  $url\_components\['path'\] = urldecode( $url\_components\['path'\] );

1735

1736

  // URL encode the decoded path

1737

  $url\_components\['path'\] = rawurlencode( $url\_components\['path'\] );

1738

1739

  // Restore the directory separators

1744

1740

  $url\_components\['path'\] = str\_replace('%2F', '/', $url\_components\['path'\]);

1745

  $url\_components\['path'\] = str\_replace('%2B', '+', $url\_components\['path'\]);

1746

  $url\_components\['path'\] = str\_replace('%2523', '%23', $url\_components\['path'\]);

1747

  $url\_components\['path'\] = str\_replace('%252B', '%2B', $url\_components\['path'\]); 

1748

  $url\_components\['path'\] = str\_replace('%2527', '%27', $url\_components\['path'\]); 

1749

1750

  if( $iAWSVersion == 4 ) {

1751

    $sXAMZDate = gmdate('Ymd\\THis\\Z');

1752

    $sDate = gmdate('Ymd');

1753

    $sCredentialScope = $sDate."/".$fv\_fp->\_get\_option( array('amazon\_region', $amazon\_key ) )."/s3/aws4\_request"; //  todo: variable

1754

    $sSignedHeaders = "host";

1755

    $sXAMZCredential = urlencode( $fv\_fp->\_get\_option( array('amazon\_key', $amazon\_key ) ).'/'.$sCredentialScope);

1756

1757

    //  1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html     

1758

    $sCanonicalRequest = "GET\\n";

1759

    $sCanonicalRequest .= $url\_components\['path'\]."\\n";

1760

    $sCanonicalRequest .= "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=$sXAMZCredential&X-Amz-Date=$sXAMZDate&X-Amz-Expires=$time&X-Amz-SignedHeaders=$sSignedHeaders\\n";

1761

    $sCanonicalRequest .= "host:".$url\_components\['host'\]."\\n";       

1762

    $sCanonicalRequest .= "\\n$sSignedHeaders\\n";

1763

    $sCanonicalRequest .= "UNSIGNED-PAYLOAD";

1764

1765

    //  2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html

1766

    $sStringToSign = "AWS4-HMAC-SHA256\\n";

1767

    $sStringToSign .= "$sXAMZDate\\n";

1768

    $sStringToSign .= "$sCredentialScope\\n";

1769

    $sStringToSign .= hash('sha256',$sCanonicalRequest);

1770

1771

    //  3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html

1772

    $sSignature = hash\_hmac('sha256', $sDate, "AWS4".$fv\_fp->\_get\_option( array('amazon\_secret', $amazon\_key) ), true );

1773

    $sSignature = hash\_hmac('sha256', $fv\_fp->\_get\_option( array('amazon\_region', $amazon\_key) ), $sSignature, true );  //  todo: variable

1774

    $sSignature = hash\_hmac('sha256', 's3', $sSignature, true );

1775

    $sSignature = hash\_hmac('sha256', 'aws4\_request', $sSignature, true );

1776

    $sSignature = hash\_hmac('sha256', $sStringToSign, $sSignature );

1777

1778

    //  4. http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html       

1779

    $resource .= "?X-Amz-Algorithm=AWS4-HMAC-SHA256";       

1780

    $resource .= "&X-Amz-Credential=$sXAMZCredential";

1781

    $resource .= "&X-Amz-Date=$sXAMZDate";

1782

    $resource .= "&X-Amz-Expires=$time";

1783

    $resource .= "&X-Amz-SignedHeaders=$sSignedHeaders";

1784

    $resource .= "&X-Amz-Signature=".$sSignature;             

1785

1786

  } else {

1787

    $expires = time() + $time;

1788

1789

    if( strpos( $url\_components\['path'\], $fv\_fp->\_get\_option( array('amazon\_bucket', $amazon\_key) ) ) === false ) {

1790

      $url\_components\['path'\] = '/'.$fv\_fp->\_get\_option( array('amazon\_bucket', $amazon\_key) ).$url\_components\['path'\];

1791

    }       

1792

1793

    do {

1794

      $expires++;

1795

      $stringToSign = "GET\\n\\n\\n$expires\\n{$url\_components\['path'\]}"; 

1796

1797

      $signature = utf8\_encode($stringToSign);

1798

1799

      $signature = hash\_hmac('sha1', $signature, $fv\_fp->\_get\_option( array('amazon\_secret', $amazon\_key ) ), true);

1800

      $signature = base64\_encode($signature);

1801

1802

      $signature = urlencode($signature);       

1803

    } while( stripos($signature,'%2B') !== false );     

1804

1805

    $resource .= '?AWSAccessKeyId='.$fv\_fp->\_get\_option( array('amazon\_key', $amazon\_key) ).'&Expires='.$expires.'&Signature='.$signature;

1806

1807

  }

1808

1741

1742

  $sXAMZDate = gmdate('Ymd\\THis\\Z');

1743

  $sDate = gmdate('Ymd');

1744

  $sCredentialScope = $sDate."/".$fv\_fp->\_get\_option( array('amazon\_region', $amazon\_key ) )."/s3/aws4\_request";

1745

  $sSignedHeaders = "host";

1746

  $sXAMZCredential = urlencode( $fv\_fp->\_get\_option( array('amazon\_key', $amazon\_key ) ).'/'.$sCredentialScope);

1747

1748

  //  1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html     

1749

  $sCanonicalRequest = "GET\\n";

1750

  $sCanonicalRequest .= $url\_components\['path'\]."\\n";

1751

  $sCanonicalRequest .= "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=$sXAMZCredential&X-Amz-Date=$sXAMZDate&X-Amz-Expires=$time&X-Amz-SignedHeaders=$sSignedHeaders\\n";

1752

  $sCanonicalRequest .= "host:".$url\_components\['host'\]."\\n";       

1753

  $sCanonicalRequest .= "\\n$sSignedHeaders\\n";

1754

  $sCanonicalRequest .= "UNSIGNED-PAYLOAD";

1755

1756

  //  2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html

1757

  $sStringToSign = "AWS4-HMAC-SHA256\\n";

1758

  $sStringToSign .= "$sXAMZDate\\n";

1759

  $sStringToSign .= "$sCredentialScope\\n";

1760

  $sStringToSign .= hash('sha256',$sCanonicalRequest);

1761

1762

  //  3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html

1763

  $sSignature = hash\_hmac('sha256', $sDate, "AWS4".$fv\_fp->\_get\_option( array('amazon\_secret', $amazon\_key) ), true );

1764

  $sSignature = hash\_hmac('sha256', $fv\_fp->\_get\_option( array('amazon\_region', $amazon\_key) ), $sSignature, true );

1765

  $sSignature = hash\_hmac('sha256', 's3', $sSignature, true );

1766

  $sSignature = hash\_hmac('sha256', 'aws4\_request', $sSignature, true );

1767

  $sSignature = hash\_hmac('sha256', $sStringToSign, $sSignature );

1768

1769

  //  4. http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html       

1770

  $resource .= "?X-Amz-Algorithm=AWS4-HMAC-SHA256";       

1771

  $resource .= "&X-Amz-Credential=$sXAMZCredential";

1772

  $resource .= "&X-Amz-Date=$sXAMZDate";

1773

  $resource .= "&X-Amz-Expires=$time";

1774

  $resource .= "&X-Amz-SignedHeaders=$sSignedHeaders";

1775

  $resource .= "&X-Amz-Signature=".$sSignature;             

1776

1809

1777

  $media = $resource;

1810

1778

fv-wordpress-flowplayer/trunk/models/stats.php

r2904314

r2957322

59

59

}

60

60

61

function get_table_name() {

61

public static function get_table_name() {

62

62

global $wpdb;

63

63

return $wpdb->prefix . 'fv\_player\_stats';

fv-wordpress-flowplayer/trunk/readme.txt

r2954757

r2957322

360

360

== Changelog ==

361

361

362

= 7.5.39.7212 - 2023/08/23 =

363

364

* Security - prevent XSS when “Enable profile videos” is on

365

* Allow DB tables to be fixed with WP_ALLOW_REPAIR

366

* Bugfix - Amazon S3 - fix when using commas in the URLs

367

* Bugfix - Video Checker - fix checking of dynamic URLs (Amazon S3, or FV Player Pro CDNs like Bunny CDN)

368

362

369

= 7.5.37.7212 - 2023/08/02 =

363

370

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907