Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 24-04-2018 17:54:37

zigo
Membre
Date d'inscription: 24-02-2016
Messages: 30

Faille XSS

Bonjour,

Suite à un scan OWASP, j'ai une page qui remonte une faille XSS. Mais en regardant un peu comment s'en prévenir avec ZEND, je comprend pas très bien comment cette faille est déclenchée.

Voici le code du form :

Code:

[lang=php]<?php

namespace Core\Form;

use Zend\Escaper\Escaper;
use Zend\Form\Form;
use Zend\ServiceManager\ServiceLocatorInterface;

class ContactForm extends Form
{
    protected $escaper;

    public function __construct($affiliation, ServiceLocatorInterface $serviceLocator)
    {
        parent::__construct('contact');

        $this->escaper = new Escaper('UTF-8');
        $viewHelperManager = $serviceLocator->get('ViewHelperManager');
        $translate = $viewHelperManager->get('translate');

        $this->add(
            [
                'name'    => 'csrf',
                'type'    => 'csrf',
                'options' => [
                    'csrf_options' => [
                        'timeout' => 300
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name' => 'id_contact',
                'type' => 'hidden',
            ]
        );

        $this->add(
            [
                'name'       => 'names',
                'attributes' => [
                    'id'          => 'names',
                    'type'        => 'text',
                    'required'    => true,

                ],
                'options'    => [
                    'label' => $translate('Nom, Prénom')
                ]
            ]
        );

        $this->add(
            [
                'name'       => 'email',
                'attributes' => [
                    'id'          => 'email',
                    'type'        => 'email',
                    'required'    => true,
                ],
                'options'    => [
                    'label' => $translate('Email')
                ]
            ]
        );

        $this->add(
            [
                'name'       => 'affiliation',
                'attributes' => [
                    'id'          => 'affiliation',
                    'type'        => 'hidden',
                    'value'       => $affiliation,
                    'required'    => true,
                ],
            ]
        );

        $this->add(
            [
                'name'       => 'objetmail',
                'attributes' => [
                    'id'          => 'objetmail',
                    'type'        => 'text',
                    'maxlength'      =>'50',
                    'required'    => true,
                ],
                'options'    => [
                    'label' => $translate('objet')
                ]
            ]
        );

        $this->add(
            [
                'name'       => 'question',
                'attributes' => [
                    'id'          => 'question',
                    'type'        => 'textarea',
                    'required'    => true,
                    'rows'        => 5 ,
                    'maxlength'    =>256
                ],
                'options'    => [
                    'label' => $translate('Message')
                ]
            ]
        );

        $this->add(
            [
                'name'       => 'submit',
                'type'       => 'submit',
                'attributes' => [
                    'value' => $this->escaper->escapeHtmlAttr($translate('envoyer')),
                    'class' => 'btn btn-success inblue-btn inblue-btn-submit',
                ],
            ]
        );
    }
}

Les Filtres du model :

Code:

[lang=php]public function getInputFilter()
    {
        if (!$this->inputFilter) {
            $inputFilter = new InputFilter();

            $inputFilter->add(
                [
                    'name'     => 'id_contact',
                    'required' => true,
                    'filters'  => [
                        ['name' => 'Int'],
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim']
                    ],
                ]
            );

            $inputFilter->add(
                [
                    'name'       => 'affiliation',
                    'required'   => true,
                    'filters'    => [
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim'],
                    ],
                    'validators' => [
                        [
                            'name'    => 'StringLength',
                            'options' => [
                                'min' => 24,
                                'max' => 24
                            ],
                        ],
                    ],
                ]
            );
            $inputFilter->add(
                [
                    'name'=>'objetmail',
                    'required'=> true,
                    'filters'    => [
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim'],
                    ],
                    'validators' => [
                        [
                            'name'    => 'StringLength',
                            'options' => [
                                'encoding' => 'UTF-8'
                            ],
                        ],
                    ],
                ]
            );
            $inputFilter->add(
                [
                    'name'       => 'question',
                    'required'   => true,
                    'filters'    => [
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim'],
                    ],
                    'validators' => [
                        [
                            'name'    => 'StringLength',
                            'options' => [
                                'encoding' => 'UTF-8'
                            ],
                        ],
                    ],
                ]
            );

            $inputFilter->add(
                [
                    'name'       => 'email',
                    'required'   => true,
                    'filters'    => [
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim'],
                    ],
                    'validators' => [
                        [
                            'name' => 'EmailAddress',
                        ],
                    ],
                ]
            );

            $inputFilter->add(
                [
                    'name'       => 'names',
                    'required'   => true,
                    'filters'    => [
                        ['name' => 'StripTags'],
                        ['name' => 'StringTrim'],
                    ]
                ]
            );

            $this->inputFilter = $inputFilter;
        }

        return $this->inputFilter;
    }

La Vue :

Code:

[lang=php]
<?php $this->headTitle($this->translate("Contactez-nous")); ?>
<?php if ($this->sendMail):?>
    <div class="alert alert-success"><?php echo $this->translate("Votre demande a été envoyée."); ?></div>
<?php endif; ?>

<div class="page-header">
    <h1><?php echo $this->translate('Une question ? Contactez-nous'); ?></h1>
</div>

<?php
    $this->contactForm->setAttribute('action', $this->url('aide/contact', ['action' => 'contact']));
    echo $this->inBlueForm($this->contactForm);
?>

Et le controller :

Code:

[lang=php]
public function contactAction()
    {
        $sendMail = false;

        //Si utilisateur est authentifié, les champs sont pré-remplies
        //et le formulaire est affiché dans sa langue
        if ($this->isAllowed('logged_in')) {
            $authService = $this->getServiceLocator()
                ->get('zfcuser_auth_service');
            $affiliation = $this->zfcUserAuthentication()
                               ->getIdentity()
                               ->affiliation['id'];

            $langageContainer = new Container('userLangage');
            if ($langageContainer->offsetGet('langCode')) {
                $language = $langageContainer->langCode;
            }

            $contactForm = new ContactForm(
                $affiliation, $this->getServiceLocator()
            );
            $contactForm->get('names')->setValue(
                $nom = $authService->getIdentity()->nom . ' ' .
                    $prenom = $authService->getIdentity()->prenom
            );
            $contactForm->get('email')->setValue(
                $authService->getIdentity()->email
            );
        } else {
            //Récupération de la langue
            $translator = $this->getServiceLocator()
                               ->get('LanguageBrowserFactory')
                               ->getLanguageBrowser();

            $aLanguage = explode('_', $translator->getLocale());
            $language = $aLanguage[0];

            /* @var \Affiliation\Model\Affiliation $affiliation */
            $affiliation = $this->getServiceLocator()->get(
                'Core\Service\PortailAffiliationService'
            )->getAffiliation();
            $contactForm = new ContactForm(
                $affiliation->getIdAffiliation(), $this->getServiceLocator()
            );
        }

        /** @var Request $request */
        $request = $this->getRequest();
        if ($request->isPost()) {
            $contact = new ContactDmd();
            $contactForm->setInputFilter($contact->getInputFilter());
            $contactForm->setData($request->getPost());

            if ($contactForm->isValid()) {
                $contact->exchangeArray($contactForm->getData());
                $restService = $this->getServiceLocator()->get('RestService');
                $restService->call(
                    'contacts', array_merge($contact->toRest(), ['language' => $language]),
                    Request::METHOD_POST
                );

                $sendMail = true;

                return new ViewModel(
                    [
                        'contactForm' => $contactForm,
                        'sendMail'    => $sendMail
                    ]
                );
            }
        }

        return new ViewModel(
            [
                'contactForm' => $contactForm,
                'sendMail'    => $sendMail
            ]
        );
    }

Les données du formulaires passent par un Service pour être envoyé en POST vers une API.

Le Zap dit :
Method POST
Parameter Submit
Attack onMouseOver=alert(1);
Evidence  onMouseOver=alert(1);

Je comprends pas très bien où peut être le problème.. Des idées ?

ps: Oui il y'a beaucoup de mochetés niveau Zend mais c'est pas le sujet wink

Hors ligne

 

#2 26-04-2018 13:24:43

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Faille XSS

Hello ça fait quoi ?

Code:

[lang=php]
echo $this->inBlueForm($this->contactForm);

Hors ligne

 

#3 26-04-2018 13:47:39

zigo
Membre
Date d'inscription: 24-02-2016
Messages: 30

Re: Faille XSS

Salut,

C'est un helper, le voici :

Code:

[lang=php]
<?php

namespace Core\Form\View\Helper;

use Zend\Form\FieldsetInterface;
use Zend\Form\FormInterface;
use Zend\Form\View\Helper\Form;

class InBlueForm extends Form
{
    /**
     * Invoque cette classe gr�ce � son nom
     * (@see inBlueForm())
     *
     * @param FormInterface|null $form
     * @return $this|string|void
     */
    public function __invoke(FormInterface $form = null)
    {
        if (!$form) {
            return $this;
        }

        return $this->render($form);
    }

    /**
     * Affiche le formulaire
     *
     * @param FormInterface $form
     * @return string
     */
    public function render(FormInterface $form)
    {
        if (method_exists($form, 'prepare')) {
            $form->prepare();
        }

        $form->setAttributes(['role' => 'form', 'class' => 'form-inblue clearfix']);

        $formContent = '';

        foreach ($form as $element) {
            if ($element instanceof FieldsetInterface) {
                $formContent .= $this->getView()->formCollection($element);
            } else {
                $formContent .= $this->getView()->inBlueFormRow($element);
            }
        }

        $translator = $this->getView()->plugin('translate');
        $formContent .= '<strong>* ' . $translator('Champs obligatoires') . '</strong>';
        return $this->openTag($form) . $formContent . $this->closeTag();
    }
}

Et tu me diras peut être, inBlueFormRow ça fait quoi. Donc j'anticipe !

Code:

[lang=php]
<?php

namespace Core\Form\View\Helper;

use DomainException;
use Zend\Form\View\Helper\FormRow;
use Zend\Form\ElementInterface;
use Zend\Form\LabelAwareInterface;
use Zend\Form\Element\Button;
use Zend\Form\Element\Submit;
use Zend\Escaper\Escaper;

class InBlueFormRow extends FormRow
{
    /**
     * @var string
     */
    protected static $formGroupFormat = '<div class="form-group%s">%s</div>';

    /**
     * @var string
     */
    protected static $gridLayout = '<div class="row"><div class="%s">%s</div></div>';

    /**
     * @var string
     */
    protected static $checkboxFormat = '<div class="checkbox">%s</div>';

    /**
     * @var string
     */
    protected static $helpBlockFormat = '<p class="help-block">%s</p>';

    /**
     * The class that is added to element that have errors
     *
     * @var string
     */
    protected $inputErrorClass = '';

    /**
     * @var string
     */
    protected $requiredFormat = '*';

    /**
     * @see FormRow::render()
     * @param ElementInterface $element
     * @param null             $labelPosition
     * @return string
     */
    public function render(ElementInterface $element, $labelPosition = null)
    {
        $escaper = new Escaper('UTF-8');
        $elementType = $element->getAttribute('type');

        if ($elementType === 'hidden' && !$element->getMessages()) {
            return parent::render($element);
        }

        if($element->getValue()) {
            $element->setValue($escaper->escapeHtml($element->getValue()));
        }

        // Partial rendering
        if ($this->partial) {
            return $this->view->render(
                $this->partial,
                [
                    'element'         => $element,
                    'label'           => $this->renderLabel($element),
                    'labelAttributes' => $this->labelAttributes,
                    'labelPosition'   => $this->labelPosition,
                    'renderErrors'    => $this->renderErrors,
                ]
            );
        }

        $rowClass = '';

        if (($sValidationState = $element->getOption('validation-state'))) {
            $rowClass .= ' has-' . $sValidationState;
        }

        if ($element->getMessages()) {
            $rowClass .= ' has-error';

            if ($sInputErrorClass = $this->getInputErrorClass()) {
                if ($sElementClass = $element->getAttribute('class')) {
                    if (!preg_match('/(\s|^)' . preg_quote($sInputErrorClass, '/') . '(\s|$)/', $sElementClass)) {
                        $element->setAttribute('class', trim($sElementClass . ' ' . $sInputErrorClass));
                    }
                } else {
                    $element->setAttribute('class', $sInputErrorClass);
                }
            }
        }

        $elementContent = $this->renderElement($element);

        // Afficher une ligne d'éléments dans une grille
        if (($columnSize = $element->getOption('column-size'))) {
            $elementContent = sprintf(self::$gridLayout, $columnSize, $elementContent);
        }

        return sprintf(self::$formGroupFormat, $rowClass, $elementContent) . PHP_EOL;
    }

    /**
     * Render element's label
     *
     * @param ElementInterface $element
     * @return string
     */
    protected function renderLabel(ElementInterface $element)
    {
        if (($label = $element->getLabel()) && ($translator = $this->getTranslator())) {
            $label = $translator->translate($label, $this->getTranslatorTextDomain());
        }

        return $label;
    }

    /**
     * Render element
     *
     * @param ElementInterface $element
     * @throws DomainException
     * @return string
     */
    protected function renderElement(ElementInterface $element)
    {
        $labelOpen    = '';
        $labelClose   = '';
        $labelContent = $element->getLabel();

        if ($this->requiredFormat
            && $element->getAttribute('required')
            && strpos($this->requiredFormat, $labelContent) === false
        ) {
            $labelContent = $this->requiredFormat . ' ' . $labelContent;
        }

        /*
         * Multicheckbox elements have to be handled differently
         * as the HTML standard does not allow nested labels.
         * The semantic way is to group them inside a fieldset
         */
        $elementType = $element->getAttribute('type');

        //Button element is a special case, because label is always rendered inside it
        if (($element instanceof Button) or ($element instanceof Submit)) {
            $labelContent = '';
        } else {
            if ($element instanceof LabelAwareInterface) {
                $labelAttributes = $element->getLabelAttributes();
            }

            $labelAttributes['class'] = 'control-label';

            if ($element->getOption('validation-state') || $element->getMessages()) {
                if (!preg_match('/(\s|^)control-label(\s|$)/', $labelAttributes['class'])) {
                    $labelAttributes['class'] = trim($labelAttributes['class'] . ' control-label');
                }
            }

            $element->setLabelAttributes($labelAttributes);

            $labelHelper = $this->getLabelHelper();
            $labelOpen   = $labelHelper->openTag($element->getAttribute('id') ? $element : $labelAttributes);
            $labelClose  = $labelHelper->closeTag();

            if (!$element instanceof LabelAwareInterface || !$element->getLabelOption('disable_html_escape')) {
                $labelContent = $this->getEscapeHtmlHelper()->__invoke($labelContent);
            }
        }

        $elementContent = $this->getElementHelper()->render($element);

        // Checkbox elements are a special case, element is already rendered into label
        if ($elementType === 'checkbox') {
            $elementContent = sprintf(self::$checkboxFormat, $elementContent);
        } else {
            if ($this->getLabelPosition() === self::LABEL_PREPEND) {
                $elementContent = $labelOpen . $labelContent . $labelClose . $elementContent;
            } else {
                $elementContent = $elementContent . $labelOpen . $labelContent . $labelClose;
            }
        }

        $elementContent .= $this->renderHelpBlock($element);

        if ($this->renderErrors) {
            $elementContent .= $this->getElementErrorsHelper()->render($element);
        }

        return $elementContent;
    }

    /**
     * Affiche le texte "help-block" d'un élément
     *
     * @param ElementInterface $element
     * @return string
     */
    protected function renderHelpBlock(ElementInterface $element)
    {
        return ($helpBlock = $element->getOption('help-block')) ? sprintf(
            self::$helpBlockFormat,
            $this->getEscapeHtmlHelper()->__invoke(
                ($translator = $this->getTranslator()) ? $translator->translate(
                    $helpBlock,
                    $this->getTranslatorTextDomain()
                ) : $helpBlock
            )
        ) : '';
    }
}

La partie "escaper" je l'ai rajouté après pour tester.

Je sais pas si il est possible que ce soit un faux positif et je me dis que je vais tenter d'utiliser la probable faille pour comprendre clairement où peut être le problème.

Hors ligne

 

#4 04-07-2018 11:27:15

zigo
Membre
Date d'inscription: 24-02-2016
Messages: 30

Re: Faille XSS

Bonjour,

J'ai un peu avancé le sujet et j'ai pu un peu plus identifier d'où venait l'alerte sécurité.

Voici un exemple : https://ibb.co/iy7dpy

https://preview.ibb.co/dhk9wd/snap_postman.png
Le script est exécuté dans la réponse. Et je ne vois pas comment nettoyer ça dans Zend hmm

Merci

Dernière modification par zigo (04-07-2018 11:29:35)

Hors ligne

 

#5 04-07-2018 19:39:18

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Faille XSS

Bonjour !

Peux-tu montrer le body complet de la response ? Tu as une vue ? Un $this->escapeHtml($myVar) ?

Hors ligne

 

#6 05-07-2018 11:31:11

zigo
Membre
Date d'inscription: 24-02-2016
Messages: 30

Re: Faille XSS

Salut,

J'ai des escapeHtml dans mes vue oui mais là c'est pas tant une page spécifique.
C'est un scan Qualys qui a remonté ça.

Leur exemple donné était :
Results:
GET /RaNdoM_JuNk HTTP/1.0 Host: "><script>alert(document.domain)
</script> 500 Error: Illegal request, HTTP Host Header : ">
<script>alert(document.domain)</script>, found. Rejected.

Ce que j'ai reproduit dans Postman via le screen précedent.

La page Random_Junk n'est pas existante chez moi, il tente d'appeler ça en modifiant le Host. Une erreur 500 est levée et le script est balancé dans la réponse. C'est ce que je comprends. Mais par contre, je ne vois pas où tout ce processus se situe coté Zend et si ce n'est pas qu'un faux positif

EDIT : Je pense que le problème vient du serveur, je ne rentre pas dans l'application Zend. Je cherche au niveau d'Apache voir si je trouve quelque chose au sujet de la gestion de ces erreurs.

Dernière modification par zigo (05-07-2018 15:29:37)

Hors ligne

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages