Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exif_read_data #2161

Closed
maddrid opened this issue Aug 24, 2019 · 5 comments
Closed

exif_read_data #2161

maddrid opened this issue Aug 24, 2019 · 5 comments
Labels
waiting for info Issues or pull requests that need further clarification from the author

Comments

@maddrid
Copy link

maddrid commented Aug 24, 2019

Describe the bug
Trying to load a .GIF triggers the following error:

 -Aug-2019 19:19:33 Europe/Madrid] PHP Warning:  exif_read_data(auto_qqfile_5d6171922180a.gif): File not supported in N:\a\htdocs\project\includes\xxx\Codeigniter\Images\Handlers\BaseHandler.php on line 517

CodeIgniter 4 version
Latest develop branch

Affected module(s)
CodeIgniter\Images\Handlers\BaseHandler
Session

[24-Aug-2019 19:19:33 Europe/Madrid] PHP Fatal error:  Uncaught Exception: Serialization of 'CodeIgniter\HTTP\Files\UploadedFile' is not allowed in [no active file]:0
Stack trace:
#0 [internal function]: session_write_close()
#1 {main}
  thrown in [no active file] on line 0

Expected behavior, and steps to reproduce if appropriate
upload a gif image .

@jim-parry
Copy link
Contributor

Can you open the GIF outside of your app? (trying to see if this is a data problem)
Can you show the code that triggers the error ... serializing an UploadedFile object doesn't sound right.

@maddrid
Copy link
Author

maddrid commented Aug 25, 2019

Yes can be opened. The image is uploaded and resized .
The image is already on server temp dir uploaded with FineUploader

 $aItem['photos']        = CodeIgniter\HTTP\IncomingRequest::Instance()->getFiles();

 $ajax_photos            = ParamClass::getParam('ajax_photos');
 // $ajax_photos is an array of filenames of the photos uploaded by ajax to a temporary folder
            // fake insert them into the array of the form-uploaded photos
            if(is_array($ajax_photos)) {
                foreach($ajax_photos as $photo) {
                    if(file_exists(THE_PATH.'uploads/temp/'.$photo)) {
                        $aItem['photos']['name'][]      = $photo;
                        $aItem['photos']['type'][]      = 'image/*';
                        $aItem['photos']['tmp_name'][]  = THE_PATH.'uploads/temp/'.$photo;
                        $aItem['photos']['error'][]     = UPLOAD_ERR_OK;
                        $aItem['photos']['size'][]      = 0;
                    }
                }
            }
 $itemResourceManager = ItemResource::Instance();
                $folder = uploadsPath().(floor($itemId/100))."/";

                $numImagesItems = SiteConfig::max_images_per_item();
                $numImages = $itemResourceManager->countResources($itemId);
 $_use_imagick = false ; 
                if(extension_loaded('imagick') && SiteConfig::use_imagick()) {
               $_use_imagick = true;
            }
             if($_use_imagick) { 
                 $ImageManager = Config\Services::image('imagick',null,true);
             }else {
                 $ImageManager = Config\Services::image(null,null,true);
             }
             
                foreach ($aResources['error'] as $key => $error) {
                    if($numImagesItems==0 || ($numImagesItems>0 && $numImages<$numImagesItems)) {
                        if ($error == UPLOAD_ERR_OK) {
                            if(!is_dir($folder)) {
                                if (!@mkdir($folder, 0755, true)) {
                                    return 3; // PATH CAN NOT BE CREATED
                                }
                            }
                            $itemResourceManager->insert(array(
                                'fk_i_item_id' => $itemId
                            ));
                            $resourceId = $itemResourceManager->db->getinsertedId();
                            $tmpName = $aResources['tmp_name'][$key];
                            
                          
                            $file = new \CodeIgniter\Files\File($tmpName);
                            
                            $img  = $ImageManager->withFile($tmpName)->reorient()->rotate(90);
                            $extension =$file->guessExtension() ;
                            $mime = $file->getMimeType();

                            // Create normal size
                           $size = explode('x', SiteConfig::normal_dimensions());
                            $img->resize($size[0], $size[1],true);
                          $img->save($folder.$resourceId.'.'.$extension);
                           // Create preview
                          $size = explode('x', SiteConfig::preview_dimensions());
                           $ImageManager->withFile($tmpName)->resize($size[0],         $size[1])->save($folder.$resourceId.'_preview.'.$extension);
 // Create thumbnail
                     $size = explode('x', SiteConfig::thumbnail_dimensions());
                            $ImageManager->withFile($tmpName)->resize($size[0], $size[1])->save($folder.$resourceId.'_thumbnail.'.$extension);
 if( SiteConfig::keep_original_image() ) {
                                $path = $folder.$resourceId.'_original.'.$extension;
                                FileHelper::copy($tmpName, $path);
                            }
                            
                            @unlink($tmpName);

@maddrid
Copy link
Author

maddrid commented Aug 25, 2019

y also use phpseclib\Crypt

namespace phpseclib\Crypt;

use phpseclib\Crypt\Common\BlockCipher;

/**
 * Pure-PHP Random Number Generator
 *
 * @package Random
 * @author  Jim Wigginton <[email protected]>
 * @access  public
 */
abstract class Random
{
    /**
     * Generate a random string.
     *
     * Although microoptimizations are generally discouraged as they impair readability this function is ripe with
     * microoptimizations because this function has the potential of being called a huge number of times.
     * eg. for RSA key generation.
     *
     * @param int $length
     * @throws \RuntimeException if a symmetric cipher is needed but not loaded
     * @return string
     */
    public static function string($length)
    {
        try {
            return \random_bytes($length);
        } catch (\Exception $e) {
            // random_compat will throw an Exception, which in PHP 5 does not implement Throwable
        } catch (\Throwable $e) {
            // If a sufficient source of randomness is unavailable, random_bytes() will throw an
            // object that implements the Throwable interface (Exception, TypeError, Error).
            // We don't actually need to do anything here. The string() method should just continue
            // as normal. Note, however, that if we don't have a sufficient source of randomness for
            // random_bytes(), most of the other calls here will fail too, so we'll end up using
            // the PHP implementation.
        }
        // at this point we have no choice but to use a pure-PHP CSPRNG

        // cascade entropy across multiple PHP instances by fixing the session and collecting all
        // environmental variables, including the previous session data and the current session
        // data.
        //
        // mt_rand seeds itself by looking at the PID and the time, both of which are (relatively)
        // easy to guess at. linux uses mouse clicks, keyboard timings, etc, as entropy sources, but
        // PHP isn't low level to be able to use those as sources and on a web server there's not likely
        // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use
        // however, a ton of people visiting the website. obviously you don't want to base your seeding
        // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
        // by the user and (2) this isn't just looking at the data sent by the current user - it's based
        // on the data sent by all users. one user requests the page and a hash of their info is saved.
        // another user visits the page and the serialization of their data is utilized along with the
        // server envirnment stuff and a hash of the previous http request data (which itself utilizes
        // a hash of the session data before that). certainly an attacker should be assumed to have
        // full control over his own http requests. he, however, is not going to have control over
        // everyone's http requests.
        static $crypto = false, $v;
        if ($crypto === false) {
            // save old session data
            $old_session_id = session_id();
            $old_use_cookies = ini_get('session.use_cookies');
            $old_session_cache_limiter = session_cache_limiter();
            $_OLD_SESSION = isset($_SESSION) ? $_SESSION : false;
            if ($old_session_id != '') {
                session_write_close();
            }

            session_id(1);
            ini_set('session.use_cookies', 0);
            session_cache_limiter('');
            session_start();

            $v = (isset($_SERVER) ? self::safe_serialize($_SERVER) : '') .
                 (isset($_POST) ? self::safe_serialize($_POST) : '') .
                 (isset($_GET) ? self::safe_serialize($_GET) : '') .
                 (isset($_COOKIE) ? self::safe_serialize($_COOKIE) : '') .
                 self::safe_serialize($GLOBALS) .
                 self::safe_serialize($_SESSION) .
                 self::safe_serialize($_OLD_SESSION);
            $v = $seed = $_SESSION['seed'] = sha1($v, true);
            if (!isset($_SESSION['count'])) {
                $_SESSION['count'] = 0;
            }
            $_SESSION['count']++;

            session_write_close();

            // restore old session data
            if ($old_session_id != '') {
                session_id($old_session_id);
                session_start();
                ini_set('session.use_cookies', $old_use_cookies);
                session_cache_limiter($old_session_cache_limiter);
            } else {
                if ($_OLD_SESSION !== false) {
                    $_SESSION = $_OLD_SESSION;
                    unset($_OLD_SESSION);
                } else {
                    unset($_SESSION);
                }
            }

            // in SSH2 a shared secret and an exchange hash are generated through the key exchange process.
            // the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C.
            // if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the
            // original hash and the current hash. we'll be emulating that. for more info see the following URL:
            //
            // http://tools.ietf.org/html/rfc4253#section-7.2
            //
            // see the is_string($crypto) part for an example of how to expand the keys
            $key = sha1($seed . 'A', true);
            $iv = sha1($seed . 'C', true);

            // ciphers are used as per the nist.gov link below. also, see this link:
            //
            // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives
            switch (true) {
                case class_exists('\phpseclib\Crypt\AES'):
                    $crypto = new AES(BlockCipher::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\Twofish'):
                    $crypto = new Twofish(BlockCipher::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\Blowfish'):
                    $crypto = new Blowfish(BlockCipher::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\TripleDES'):
                    $crypto = new TripleDES(BlockCipher::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\DES'):
                    $crypto = new DES(BlockCipher::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\RC4'):
                    $crypto = new RC4();
                    break;
                default:
                    throw new \RuntimeException(__CLASS__ . ' requires at least one symmetric cipher be loaded');
            }

            $crypto->setKey(substr($key, 0, $crypto->getKeyLength() >> 3));
            $crypto->setIV(substr($iv, 0, $crypto->getBlockLength() >> 3));
            $crypto->enableContinuousBuffer();
        }

        //return $crypto->encrypt(str_repeat("\0", $length));

        // the following is based off of ANSI X9.31:
        //
        // http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
        //
        // OpenSSL uses that same standard for it's random numbers:
        //
        // http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c
        // (do a search for "ANS X9.31 A.2.4")
        $result = '';
        while (strlen($result) < $length) {
            $i = $crypto->encrypt(microtime()); // strlen(microtime()) == 21
            $r = $crypto->encrypt($i ^ $v); // strlen($v) == 20
            $v = $crypto->encrypt($r ^ $i); // strlen($r) == 20
            $result.= $r;
        }

        return substr($result, 0, $length);
    }

    /**
     * Safely serialize variables
     *
     * If a class has a private __sleep() it'll emit a warning
     *
     * @param mixed $arr
     * @access public
     */
    private static function safe_serialize(&$arr)
    {
        if (is_object($arr)) {
            return '';
        }
        if (!is_array($arr)) {
            return serialize($arr);
        }
        // prevent circular array recursion
        if (isset($arr['__phpseclib_marker'])) {
            return '';
        }
        $safearr = [];
        $arr['__phpseclib_marker'] = true;
        foreach (array_keys($arr) as $key) {
            // do not recurse on the '__phpseclib_marker' key itself, for smaller memory usage
            if ($key !== '__phpseclib_marker') {
                $safearr[$key] = self::safe_serialize($arr[$key]);
            }
        }
        unset($arr['__phpseclib_marker']);
        return serialize($safearr);
    }
}

the only reference for session_write_close in my script is in phpseclib\Crypt library

@jim-parry jim-parry added this to the 4.0.0-rc.2 milestone Sep 8, 2019
@jim-parry
Copy link
Contributor

I've submitted a PR which should address the exif_read ... #2246
I don't know why your session is blowig up - I see nothing in the code you have posted above to suggest that an UploadedFile is being serialized, nor do I see anything in that class which isn't serializable. You also pasted the code for a Crypt library, and its connection to the image uploads isn't clear :-/

@jim-parry jim-parry added the waiting for info Issues or pull requests that need further clarification from the author label Sep 21, 2019
@jim-parry
Copy link
Contributor

Closing this, as the session blowing up appears to be a separate problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting for info Issues or pull requests that need further clarification from the author
Projects
None yet
Development

No branches or pull requests

2 participants