|
|
|
@@ -2,7 +2,7 @@
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<title>Invision Community Update Extractor</title>
|
|
|
|
|
<style type='text/css'>body{font-family:BlinkMacSystemFont, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.ipsProgressBar{width:50%;margin:auto;height:26px;overflow:hidden;background:rgb(237, 242, 247);background-image:linear-gradient(to bottom,rgba(23, 126, 201,0.1),rgba(23, 126, 201,0.1));border-radius:4px;}.ipsProgressBar_animated .ipsProgressBar_progress{background-color:rgb(23, 126, 201);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px;animation:progress-bar-stripes 2s linear infinite}.ipsProgressBar_progress{display:flex;align-items:center;width:0;height:100%;font-size:12px;color:#ffffff;text-align:right;background:rgb(23, 126, 201);position:relative;white-space:nowrap;line-height:26px;text-indent:6px;padding-right:2px;}.ipsProgressBar>span:first-child{padding-left:7px;}.ipsProgressBar_progress[data-progress]:after{top:0;color:#fff;content:attr(data-progress);display:block;right:5px;}</style>
|
|
|
|
|
<style type='text/css'>body{font-family:BlinkMacSystemFont, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.ipsProgressBar{width:50%;margin:auto;height:26px;overflow:hidden;background:rgb(237, 242, 247);background-image:linear-gradient(to bottom,rgba(23, 126, 201,0.1),rgba(23, 126, 201,0.1));border-radius:4px;}.ipsProgressBar_animated .ipsProgressBar_progress{background-color:rgb(23, 126, 201);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px;animation:progress-bar-stripes 2s linear infinite}.ipsProgressBar_progress{display:flex;align-items:center;width:0;height:100%;font-size:12px;color:#ffffff;text-align:right;background:rgb(23, 126, 201);position:relative;white-space:nowrap;line-height:26px;text-indent:6px;padding-right:2px;}.ipsProgressBar>span:first-child{padding-left:7px;}.ipsProgressBar_progress[data-progress]:after{top:0;color:#fff;content:attr(data-progress);display:block;right:5px;}@media (prefers-color-scheme: dark) { body{ background-color: rgb(45, 49, 57); } }</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body style="margin:0">
|
|
|
|
|
<?php
|
|
|
|
@@ -26,7 +26,7 @@ if ( file_exists( "../../constants.php" ) )
|
|
|
|
|
{
|
|
|
|
|
require "../../constants.php";
|
|
|
|
|
}
|
|
|
|
|
foreach ( array( 'FOLDER_PERMISSION_NO_WRITE' => 0755, 'FILE_PERMISSION_NO_WRITE' => 0644, 'IPS_FILE_PERMISSION' => 0666, 'TEMP_DIRECTORY' => sys_get_temp_dir() ) as $k => $v )
|
|
|
|
|
foreach ( [ 'FOLDER_PERMISSION_NO_WRITE' => 0755, 'FILE_PERMISSION_NO_WRITE' => 0644, 'IPS_FILE_PERMISSION' => 0666, 'TEMP_DIRECTORY' => sys_get_temp_dir(), 'TEXT_ENCRYPTION_KEY' => NULL ] as $k => $v )
|
|
|
|
|
{
|
|
|
|
|
if ( !\defined( $k ) )
|
|
|
|
|
{
|
|
|
|
@@ -80,6 +80,16 @@ class Extractor
|
|
|
|
|
* @brief SFTP Directory
|
|
|
|
|
*/
|
|
|
|
|
private $sftpDir;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Database connection
|
|
|
|
|
*/
|
|
|
|
|
public $db;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Database prefix
|
|
|
|
|
*/
|
|
|
|
|
public $dbPrefix;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
@@ -109,7 +119,8 @@ class Extractor
|
|
|
|
|
{
|
|
|
|
|
require "../../conf_global.php";
|
|
|
|
|
|
|
|
|
|
$db = new mysqli( $INFO['sql_host'], $INFO['sql_user'], $INFO['sql_pass'], $INFO['sql_database'], !empty( $INFO['sql_port'] ) ? $INFO['sql_port'] : null, !empty( $INFO['sql_socket'] ) ? $INFO['sql_socket'] : null );
|
|
|
|
|
$this->db = new mysqli( $INFO['sql_host'], $INFO['sql_user'], $INFO['sql_pass'], $INFO['sql_database'], !empty( $INFO['sql_port'] ) ? $INFO['sql_port'] : null, !empty( $INFO['sql_socket'] ) ? $INFO['sql_socket'] : null );
|
|
|
|
|
$this->dbPrefix = $INFO['sql_tbl_prefix'] ?? '';
|
|
|
|
|
|
|
|
|
|
/* If the connection failed, do not continue */
|
|
|
|
|
if( $error = mysqli_connect_error() )
|
|
|
|
@@ -118,7 +129,7 @@ class Extractor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check that there is an upgrade in progress */
|
|
|
|
|
$query = $db->query( "SELECT conf_value FROM {$INFO['sql_tbl_prefix']}core_sys_conf_settings WHERE conf_key='setup_in_progress'" );
|
|
|
|
|
$query = $this->db->query( "SELECT conf_value FROM {$this->dbPrefix}core_sys_conf_settings WHERE conf_key='setup_in_progress'" );
|
|
|
|
|
if( !$query )
|
|
|
|
|
{
|
|
|
|
|
return FALSE;
|
|
|
|
@@ -378,6 +389,176 @@ class Extractor
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encrypted - code from \IPS\Text\Encrypt
|
|
|
|
|
*/
|
|
|
|
|
class Encrypt
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Get Key
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function key()
|
|
|
|
|
{
|
|
|
|
|
require "../../conf_global.php";
|
|
|
|
|
return \TEXT_ENCRYPTION_KEY ?: md5( $INFO['sql_pass'] . $INFO['sql_database'] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Cipher
|
|
|
|
|
*/
|
|
|
|
|
public $cipher;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief IV
|
|
|
|
|
*/
|
|
|
|
|
protected $iv = NULL;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Tag
|
|
|
|
|
*/
|
|
|
|
|
protected $tag = NULL;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Hash of cipher
|
|
|
|
|
*/
|
|
|
|
|
protected $hmac = NULL;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From plaintext
|
|
|
|
|
*
|
|
|
|
|
* @param string $plaintext Plaintext
|
|
|
|
|
* @return static
|
|
|
|
|
*/
|
|
|
|
|
public static function fromPlaintext( $plaintext )
|
|
|
|
|
{
|
|
|
|
|
$obj = new static;
|
|
|
|
|
|
|
|
|
|
/* Try to use OpenSSL if it's available... */
|
|
|
|
|
if ( \function_exists( 'openssl_get_cipher_methods' ) )
|
|
|
|
|
{
|
|
|
|
|
/* If GCM is available (PHP 7.1+), use that as if provides authenticated encryption natively */
|
|
|
|
|
if ( \in_array( 'aes-128-gcm', openssl_get_cipher_methods() ) )
|
|
|
|
|
{
|
|
|
|
|
$obj->iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-128-gcm' ) );
|
|
|
|
|
$obj->cipher = openssl_encrypt( $plaintext, 'aes-128-gcm', static::key(), 0, $obj->iv, $obj->tag );
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Otherwise, use CBC and store the hash so we can do our own authentication when decrypting */
|
|
|
|
|
elseif ( \in_array( 'aes-128-cbc', openssl_get_cipher_methods() ) )
|
|
|
|
|
{
|
|
|
|
|
$obj->iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-128-cbc' ) );
|
|
|
|
|
$obj->cipher = openssl_encrypt( $plaintext, 'aes-128-cbc', static::key(), OPENSSL_RAW_DATA, $obj->iv );
|
|
|
|
|
$obj->hmac = hash_hmac( 'sha256', $obj->cipher, static::key(), TRUE );
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're still here, fallback to the PHP library */
|
|
|
|
|
require_once '../../system/3rd_party/AES/AES.php';
|
|
|
|
|
$obj->cipher = \AesCtr::encrypt( $plaintext, static::key(), 256 );
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From plaintext
|
|
|
|
|
*
|
|
|
|
|
* @param string $cipher Cipher
|
|
|
|
|
* @param string|null $iv The IV, or if null, will use the PHP library rather than built-in openssl_*() methods
|
|
|
|
|
* @param string|null $tag The tag if using AES-128-GCM
|
|
|
|
|
* @param string|null $hash The hash if using AES-128-CBC
|
|
|
|
|
* @return static
|
|
|
|
|
*/
|
|
|
|
|
public static function fromCipher( $cipher, $iv = NULL, $tag = NULL, $hash = NULL )
|
|
|
|
|
{
|
|
|
|
|
$obj = new static;
|
|
|
|
|
$obj->cipher = $cipher;
|
|
|
|
|
$obj->iv = $iv;
|
|
|
|
|
$obj->tag = $tag;
|
|
|
|
|
$obj->hmac = $hash;
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From tag
|
|
|
|
|
*
|
|
|
|
|
* @param string $tag Tag
|
|
|
|
|
* @return static
|
|
|
|
|
*/
|
|
|
|
|
public static function fromTag( $tag )
|
|
|
|
|
{
|
|
|
|
|
if ( preg_match( '/^\[\!AES128GCM\[(.+?)\]\[(.+?)\]\[(.+?)\]\]/', $tag, $matches ) )
|
|
|
|
|
{
|
|
|
|
|
return static::fromCipher( $matches[1], hex2bin( $matches[2] ), hex2bin( $matches[3] ) );
|
|
|
|
|
}
|
|
|
|
|
elseif ( preg_match( '/^\[\!AES128CBC\[(.+?)\]\]/', $tag, $matches ) )
|
|
|
|
|
{
|
|
|
|
|
$cipher = base64_decode( $matches[1] );
|
|
|
|
|
$ivLength = openssl_cipher_iv_length('aes-128-cbc');
|
|
|
|
|
|
|
|
|
|
return static::fromCipher( \substr( $cipher, $ivLength + 32 ), \substr( $cipher, 0, $ivLength ), NULL, \substr( $cipher, $ivLength, 32 ) );
|
|
|
|
|
}
|
|
|
|
|
elseif ( preg_match( '/^\[\!AES\[(.+?)\]\]/', $tag, $matches ) )
|
|
|
|
|
{
|
|
|
|
|
return static::fromCipher( $matches[1] );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return static::fromPlaintext( $tag );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrap in a tag to use later with fromTag
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function tag()
|
|
|
|
|
{
|
|
|
|
|
if ( $this->tag )
|
|
|
|
|
{
|
|
|
|
|
return '[!AES128GCM[' . $this->cipher . '][' . bin2hex( $this->iv ) . '][' . bin2hex( $this->tag ) . ']]';
|
|
|
|
|
}
|
|
|
|
|
elseif ( $this->hmac )
|
|
|
|
|
{
|
|
|
|
|
return '[!AES128CBC[' . base64_encode( $this->iv . $this->hmac . $this->cipher ) . ']]';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return '[!AES[' . $this->cipher . ']]';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decript
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function decrypt()
|
|
|
|
|
{
|
|
|
|
|
if ( $this->tag )
|
|
|
|
|
{
|
|
|
|
|
return openssl_decrypt( $this->cipher, 'aes-128-gcm', static::key(), 0, $this->iv, $this->tag );
|
|
|
|
|
}
|
|
|
|
|
elseif ( $this->hmac )
|
|
|
|
|
{
|
|
|
|
|
$decrypted = openssl_decrypt( $this->cipher, 'aes-128-cbc', static::key(), OPENSSL_RAW_DATA, $this->iv );
|
|
|
|
|
if ( hash_equals( $this->hmac, hash_hmac( 'sha256', $this->cipher, static::key(), TRUE ) ) )
|
|
|
|
|
{
|
|
|
|
|
return $decrypted;
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
require_once '../../system/3rd_party/AES/AES.php';
|
|
|
|
|
return \AesCtr::decrypt( $this->cipher, static::key(), 256 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function to write a log file to disk
|
|
|
|
|
*
|
|
|
|
@@ -432,7 +613,29 @@ try
|
|
|
|
|
/* Establish an FTP connection if necessary */
|
|
|
|
|
if ( $_GET['ftp'] )
|
|
|
|
|
{
|
|
|
|
|
$extractor->connectToFtp( $_GET['ftp'] );
|
|
|
|
|
/* Get encrypted FTP credentials */
|
|
|
|
|
$query = $extractor->db->query( "SELECT conf_value FROM " . $extractor->dbPrefix . "core_sys_conf_settings WHERE conf_key='upgrade_ftp_details'" );
|
|
|
|
|
if( !$query )
|
|
|
|
|
{
|
|
|
|
|
throw new \RuntimeException('Cannot find (S)FTP credentials');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ftpCredentials = $query->fetch_assoc()['conf_value'];
|
|
|
|
|
if( empty( $ftpCredentials ) )
|
|
|
|
|
{
|
|
|
|
|
throw new \RuntimeException('Cannot load (S)FTP credentials');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( \substr( $ftpCredentials, 0, 5 ) === '[!AES' )
|
|
|
|
|
{
|
|
|
|
|
$decodedFtpDetails = Encrypt::fromTag( $ftpCredentials )->decrypt();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$decodedFtpDetails = Encrypt::fromCipher( $ftpCredentials )->decrypt();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$extractor->connectToFtp( json_decode( $decodedFtpDetails, TRUE ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Extract a batch of files */
|
|
|
|
|