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: 26

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: 26

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

 

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