My server (AWS) has compromised by an uploaded file in Symfony

Today I saw that my server was compromised, I found in the uploads folder a php file added two weeks ago that asks me for a password to enter, I deleted this file, but I don’t understand how an attacker uploaded this file, I’m afraid the attacker will be able to upload this file again, but I can’t find where the problem is.

My code:

 /**
 * Write the images using the LiipImagineBundle in s3
 * 
 * @param Object $entity object
 * @param string $directory path
 * @param Object $container Dependency Injection Object, if calling from controller just pass $this
 * @param string $type to validate the image type
 */
public final function resizeAndUploadImages($entity, $directory, $container, $type, $log, $isCropedFile = false) {
    $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
            "Is environmentEnabled -> " . Util::environmentEnabled($container)
        ]);
    
    if (Util::environmentEnabled($container)) {
        $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
            "Loading to process $entity record..."
        ]);
        if (!$isCropedFile) {
            $pathUpload = '/uploads/' . $directory . '/' . $entity->getFileName();
            $originalName = $entity->getFileName();
            $mimeType = $entity->getMimetype();
        } else {
            $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
                "Uploading croped file"
            ]);
            $pathUpload = '/uploads/' . $directory . '/' . $entity->getFileCropedName();
            $originalName = $entity->getFileCropedName();
            $mimeType = $entity->getMimetypeCroped();
        }
        $pathUpload = str_replace('//', '/', $pathUpload);
        # The controller service
        $imagemanagerResponse = $container->get('liip_imagine.controller');
        # The filter configuration service
        $filterConfiguration = $container->get('liip_imagine.config.stack_collection');
        $filterArray = $filterConfiguration->getStacks();
        $auxFullPathFile = $container->getParameter('app_directory_web') . $pathUpload;
        $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
            "The path to file '$auxFullPathFile' for the upload process"
        ]);
        if (is_file($auxFullPathFile)) {
            $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
                "Into 'is file condition' and the type is: $type "
            ]);
            foreach ($filterArray as $options) {
                $filter = $options->getName();
                if (strpos($filter, $type) !== false) {
                    try {
                        # Apply the filter
                        $somethingThatIWontGonnaUse = $imagemanagerResponse->filterAction(new Request(), $pathUpload, $filter);
                        sleep(3);
                        if ($somethingThatIWontGonnaUse instanceof Exception) {
                            $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
                                "Error applaying filter action: " . $somethingThatIWontGonnaUse->getMessage() . " The evil trace: " . $somethingThatIWontGonnaUse->getTraceAsString()
                            ]);
                        } else {
                            $log->addNotice('AmazonS3Service', ['resizeAndUploadImages' =>
                                "All beautiful! "
                            ]);
                        }
                        # Move the img from temp
                        $pathMedia = $container->getParameter('app_directory_web') . 'media/cache/' . $filter . $pathUpload;
                        $tmpFile = new UploadedFile($pathMedia, $originalName, $mimeType);
                        #remove part until @
                        $removeSymbol = explode("@", $originalName, 2);
                        if (isset($removeSymbol[1])) {
                            $nameWithoutSymbool = $removeSymbol[1];
                        } else {
                            $nameWithoutSymbool = $originalName;
                        }
                        #we just need the name without the extension.
                        $arrName = explode(".", $nameWithoutSymbool, 2);
                        $newName = $arrName[0];
                        #get it the name of filter, removing the "rest_" string
                        $arrFilter = explode($type . "_", $filter);
                        $filter = $arrFilter[1];
                        $filterString = str_replace("_", "-", $filter);
                        $url = $this->uploadFile($tmpFile, $directory, $newName . '-' . $filterString);
                        #Remove files after them were uploaded
                        if ($url) {
                            if ($tmpFile->getRealPath()) {
                                if (file_exists($tmpFile->getRealPath()) &&
                                        is_writable($tmpFile->getRealPath())) {
                                    unlink($tmpFile->getRealPath());
                                }
                            }
                        }
                    } catch (Exception $ex) {
                        $theBadError = $ex->getMessage();
                        $log->addNotice('AmazonS3Service', ['resizeAndUploadImages Error Obtained: ' =>
                            $ex->getMessage()
                        ]);
                        return ['status' => false, 'msg' => substr($theBadError, -70)];
                    }
                }
            }
        } else {
            return null;
        }
        return $url;
    }
    return null;
}

S3 Service:

 /**
 * @api {post} /cli/profileImage 04.
 * @apiGroup Client Profile
 * @apiVersion 0.1.0
 * @apiPermission client
 * @apiDescription
 *
 * @apiHeader {String} Api-Key              
 * @apiHeader {String} [Accept-Language = en]
 *
 * @apiParam {Object} file               
 * @apiSuccess {Object}     data                  
 * @apiSuccess {Number}     data.id                
 * @apiSuccess {String}     data.title              
 * @apiSuccess {String}     data.description           
 * @apiSuccess {Number}     data.sorting               
 * @apiSuccess {String}     data.originalName          
 * @apiSuccess {String}     data.fileName             
 * @apiSuccess {String}     data.mimetype              
 * @apiSuccess {String}     data.extension            
 * @apiSuccess {Number}     data.size                  
 * @apiSuccess {Boolean}    data.isMain                 
 * @apiSuccess {Boolean}    data.isUploadedOnAWS        
 * @apiSuccess {String}     data.path                   
 * @apiSuccess {String}     data.created_at             
 * @apiSuccess {String}     data.updated_at              
 * @apiSuccess {Number}     data.client            
 *
 * @Route("cli/profileImage", methods={"POST"})
 *
 */
public function profileImage(Request $request, UserInterface $user, AmazonS3Service $storage) {
    $em = $this->getDoctrine()->getEntityManager();
    $translated = $this->get('translator');
    $userId = $user->getId();
    $file = $request->files->get('file');
    
    if ($file == null) {
        return $this->setStatusCode(WebService::CODE_ERR_BAD_REQUEST)
                        ->respondWithError($translated->trans('errors_wrong_arguments') . " : " . $translated->trans('image_file_required')
                                , WebService::CODE_ERR_BAD_REQUEST, $this->getMeta($request));
    }
    if (!$this->match($this->needles, $file->getClientOriginalName())) {
        return $this->setStatusCode(WebService::CODE_ERR_BAD_REQUEST)
                        ->respondWithError($translated->trans('not_allowed_image_extension')
                                , WebService::CODE_ERR_BAD_REQUEST, $this->getMeta($request));
    }
    $repository = $em->getRepository('Car:ClientImageProfile');
    $repository->setContainer($this->container);
    $clientImageProfile = $repository->findBy([ 'client' => $userId,'sorting' => 1]);
    $environment = $this->getParameter('AWS_ENVIRONMENT_FOLDER');
    $directory = $environment . "images/client/profile";
    foreach ($clientImageProfile as $key => $image) {
        if (isset($image) && $image) {
            if ($image->getIsUploadedInAws()) {
                /**
                 * Function that returns arrangement of images to delete with their corresponding name in s3
                 */
                #this variable indicates which filters to use
                $type = EntityClientImageProfile::IMAGE_FILTER;
                $objects = $storage->getImageSizesToDelete($this->container, $directory, $image, $type);
                if (!$storage->delete($objects)) {
                    return $this->setStatusCode(WebService::CODE_OK_ACCEPTED)
                                    ->respondWithError($translated->trans('image_error_deleting_from_aws')
                                            , WebService::CODE_OK_ACCEPTED, $this->getMeta($request, ['Success']));
                }
                #this line deletes the original image hosted on the server.
                $storage->delete($image, true, $directory, $userId);
            }
            $em->remove($image);
            $em->flush();
        }
    }
    #the uploadLocal function returns an object of type UploadedFile
    $originalFileUuidNameArray = $storage->uploadLocal($file, $directory, $userId);
    $originalFileUuidName = $originalFileUuidNameArray['file'];
    $originalFileExtension = $originalFileUuidNameArray['fileExtension'];
    #$originalFile is passed which is an object to the uploadFile function to upload it to s3
    #We also verify that the environment is adequate to upload the image to s3 in this case prod
    if (Util::environmentEnabled($this->container)) {
        $storage->uploadFile($originalFileUuidName, $directory);
    }
    #we keep the required information in the entityClientImageProfile
    $clientImageProfileEntity = new EntityClientImageProfile();
    $clientImageProfileEntity->setOriginalName($file->getClientOriginalName());
    $clientImageProfileEntity->setMimetype($file->getClientMimeType());
    $clientImageProfileEntity->setExtension($originalFileExtension);
    $clientImageProfileEntity->setFileName($originalFileUuidName->getClientOriginalName());
    $clientImageProfileEntity->setSize($originalFileUuidName->getSize());
    $clientImageProfileEntity->setIsMain(true);
    $clientImageProfileEntity->setIsUploadedInAws(false);
    $clientImageProfileEntity->setSorting(1);
    $clientImageProfileEntity->setClient($user);
    $em->persist($clientImageProfileEntity);
    $em->flush();
    #function to enqueue the request, which allows generating new versions of the image, validating the type of request
    #if it is app_dev it will not enqueue, unless the debug variable is set to true
    $storage->generateQueueIfCan($this->container, $clientImageProfileEntity, $directory, EntityClientImageProfile::IMAGE_PROFILE);
    $arrayClientImageProfile = $this->serializerAndTranslate($clientImageProfileEntity);
    $result = $repository->handleCreatePathProfileURL($arrayClientImageProfile, $clientImageProfileEntity, 'path', 1);
    if (isset($result['justData'])) {
        $arrayClientImageProfile['path'] = $result['justData'];
    }
    return $this->metaResponse($request, $arrayClientImageProfile
                    , WebService::CODE_OK_CREATED, ["Success"]);
}

Source: Symfony Questions

Was this helpful?

0 / 0

Leave a Reply 0

Your email address will not be published. Required fields are marked *