Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
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 :
[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 :
[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 :
[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 :
[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
Hors ligne
Hello ça fait quoi ?
[lang=php] echo $this->inBlueForm($this->contactForm);
Hors ligne
Salut,
C'est un helper, le voici :
[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 !
[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
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
Le script est exécuté dans la réponse. Et je ne vois pas comment nettoyer ça dans Zend
Merci
Dernière modification par zigo (04-07-2018 11:29:35)
Hors ligne
Bonjour !
Peux-tu montrer le body complet de la response ? Tu as une vue ? Un $this->escapeHtml($myVar) ?
Hors ligne
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