From 72dd599070900fabb1e1501a7b39067703acec35 Mon Sep 17 00:00:00 2001 From: "ctrlaltca@gmail.com" <> Date: Sun, 2 Oct 2011 21:13:53 +0000 Subject: Added TReCaptcha control (ticket #345) and added a notice about the lack of security of TCaptcha (ticket #344) --- framework/Web/UI/WebControls/TCaptcha.php | 6 +- framework/Web/UI/WebControls/TCaptchaValidator.php | 6 +- framework/Web/UI/WebControls/TReCaptcha.php | 199 +++++++++++++++++++++ .../Web/UI/WebControls/TReCaptchaValidator.php | 72 ++++++++ 4 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 framework/Web/UI/WebControls/TReCaptcha.php create mode 100644 framework/Web/UI/WebControls/TReCaptchaValidator.php (limited to 'framework/Web') diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php index 9ca6aa76..7eff1294 100644 --- a/framework/Web/UI/WebControls/TCaptcha.php +++ b/framework/Web/UI/WebControls/TCaptcha.php @@ -4,7 +4,7 @@ * * @author Qiang Xue * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2011 PradoSoft + * @copyright Copyright © 2005-2011 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @package System.Web.UI.WebControls @@ -15,6 +15,10 @@ Prado::using('System.Web.UI.WebControls.TImage'); /** * TCaptcha class. * + * Notice: while this class is easy to use and implement, it does not provide full security. + * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack). + * A better alternative is provided by {@link TReCaptcha}. + * * TCaptcha displays a CAPTCHA (a token displayed as an image) that can be used * to determine if the input is entered by a real user instead of some program. * diff --git a/framework/Web/UI/WebControls/TCaptchaValidator.php b/framework/Web/UI/WebControls/TCaptchaValidator.php index 7854b639..b01cd786 100644 --- a/framework/Web/UI/WebControls/TCaptchaValidator.php +++ b/framework/Web/UI/WebControls/TCaptchaValidator.php @@ -4,7 +4,7 @@ * * @author Qiang Xue * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2011 PradoSoft + * @copyright Copyright © 2005-2011 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @package System.Web.UI.WebControls @@ -16,6 +16,10 @@ Prado::using('System.Web.UI.WebControls.TCaptcha'); /** * TCaptchaValidator class * + * Notice: while this class is easy to use and implement, it does not provide full security. + * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack). + * A better alternative is provided by {@link TReCaptchaValidator}. + * * TCaptchaValidator validates user input against a CAPTCHA represented by * a {@link TCaptcha} control. The input control fails validation if its value * is not the same as the token displayed in CAPTCHA. Note, if the user does diff --git a/framework/Web/UI/WebControls/TReCaptcha.php b/framework/Web/UI/WebControls/TReCaptcha.php new file mode 100644 index 00000000..bb956bcb --- /dev/null +++ b/framework/Web/UI/WebControls/TReCaptcha.php @@ -0,0 +1,199 @@ + + * @link http://www.devworx.hu/ + * @copyright Copyright © 2011 DevWorx + * @license http://www.pradosoft.com/license/ + * @package System.Web.UI.WebControls + */ + + Prado::using('System.3rdParty.ReCaptcha.recaptchalib'); + +/** + * TReCaptcha class. + * + * TReCaptcha displays a reCAPTCHA (a token displayed as an image) that can be used + * to determine if the input is entered by a real user instead of some program. It can + * also prevent multiple submits of the same form either by accident, or on purpose (ie. spamming). + * + * The reCAPTCHA to solve (a string consisting of two separate words) displayed is automatically + * generated by the reCAPTCHA system at recaptcha.net. However, in order to use the services + * of the site you will need to register and get a public and a private API key pair, and + * supply those to the reCAPTCHA control through setting the {@link setPrivateKey PrivateKey} + * and {@link setPublicKey PublicKey} properties. + * + * Currently the reCAPTCHA API supports only one reCAPTCHA field per page, so you MUST make sure that all + * your input is protected and validated by a single reCAPTCHA control. Placing more than one reCAPTCHA + * control on the page will lead to unpredictable results, and the user will most likely unable to solve + * any of them successfully. + * + * Upon postback, user input can be validated by calling {@link validate()}. + * The {@link TReCaptchaValidator} control can also be used to do validation, which provides + * server-side validation. Calling (@link validate()) will invalidate the token supplied, so all consecutive + * calls to the method - without solving a new captcha - will return false. Therefore if implementing a multi-stage + * input process, you must make sure that you call validate() only once, either at the end of the input process, or + * you store the result till the end of the processing. + * + * The following template shows a typical use of TReCaptcha control: + * + * + * + * + * + * @author Bérczi Gábor + * @package System.Web.UI.WebControls + * @since 3.2 + */ +class TReCaptcha extends TControl implements IValidatable +{ + private $_isValid=true; + + const ChallengeFieldName = 'recaptcha_challenge_field'; + const ResponseFieldName = 'recaptcha_response_field'; + + /** + * Returns true if this control validated successfully. + * Defaults to true. + * @return bool wether this control validated successfully. + */ + public function getIsValid() + { + return $this->_isValid; + } + /** + * @param bool wether this control is valid. + */ + public function setIsValid($value) + { + $this->_isValid=TPropertyValue::ensureBoolean($value); + } + + public function getValidationPropertyValue() + { + return $this->Request[$this->getChallengeFieldName()]; + } + + public function getPublicKey() + { + return $this->getViewState('PublicKey'); + } + + public function setPublicKey($value) + { + return $this->setViewState('PublicKey', TPropertyValue::ensureString($value)); + } + + public function getPrivateKey() + { + return $this->getViewState('PrivateKey'); + } + + public function setPrivateKey($value) + { + return $this->setViewState('PrivateKey', TPropertyValue::ensureString($value)); + } + + public function getThemeName() + { + return $this->getViewState('ThemeName'); + } + + public function setThemeName($value) + { + return $this->setViewState('ThemeName', TPropertyValue::ensureString($value)); + } + + public function getCustomTranslations() + { + return TPropertyValue::ensureArray($this->getViewState('CustomTranslations')); + } + + public function setCustomTranslations($value) + { + return $this->setViewState('CustomTranslations', TPropertyValue::ensureArray($value)); + } + + public function getLanguage() + { + return $this->getViewState('Language'); + } + + public function setLanguage($value) + { + return $this->setViewState('Language', TPropertyValue::ensureString($value)); + } + + protected function getChallengeFieldName() + { + return /*$this->ClientID.'_'.*/self::ChallengeFieldName; + } + + protected function getResponseFieldName() + { + return /*$this->ClientID.'_'.*/self::ResponseFieldName; + } + + public function getClientSideOptions() + { + $options = array(); + if ($theme = $this->getThemeName()) + $options['theme'] = $theme; + if ($lang = $this->getLanguage()) + $options['lang'] = $lang; + if ($trans = $this->getCustomTranslations()) + $options['custom_translations'] = $trans; + return $options; + } + + public function validate() + { + $resp = recaptcha_check_answer( + $this->getPrivateKey(), + $_SERVER["REMOTE_ADDR"], + $_POST[$this->getChallengeFieldName()], + $_POST[$this->getResponseFieldName()] + ); + return ($resp->is_valid==1); + } + + /** + * Checks for API keys + * @param mixed event parameter + */ + public function onPreRender($param) + { + parent::onPreRender($param); + if("" == $this->getPublicKey()) + throw new TConfigurationException('recaptcha_publickey_unknown'); + if("" == $this->getPrivateKey()) + throw new TConfigurationException('recaptcha_privatekey_unknown'); + } + + public function render($writer) + { + $writer->write(TJavaScript::renderScriptBlock( + 'var RecaptchaOptions = '.TJavaScript::jsonEncode($this->getClientSideOptions()).';' + )); + + $html = recaptcha_get_html($this->getPublicKey()); + /* + reCAPTCHA currently does not support multiple validations per page + $html = str_replace( + array(self::ChallengeFieldName,self::ResponseFieldName), + array($this->getChallengeFieldName(),$this->getResponseFieldName()), + $html + ); + */ + $writer->write($html); + } + +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/WebControls/TReCaptchaValidator.php b/framework/Web/UI/WebControls/TReCaptchaValidator.php new file mode 100644 index 00000000..ae94ea59 --- /dev/null +++ b/framework/Web/UI/WebControls/TReCaptchaValidator.php @@ -0,0 +1,72 @@ + + * @link http://www.devworx.hu/ + * @copyright Copyright © 2011 DevWorx + * @license http://www.pradosoft.com/license/ + * @package System.Web.UI.WebControls + */ + +Prado::using('System.Web.UI.WebControls.TBaseValidator'); +Prado::using('System.Web.UI.WebControls.TReCaptcha'); + +/** + * TReCaptchaValidator class + * + * TReCaptchaValidator validates user input against a reCAPTCHA represented by + * a {@link TReCaptcha} control. The input control fails validation if its value + * is not the same as the token displayed in reCAPTCHA. Note, if the user does + * not enter any thing, it is still considered as failing the validation. + * + * To use TReCaptchaValidator, specify the {@link setControlToValidate ControlToValidate} + * to be the ID path of the {@link TReCaptcha} control. + * + * @author Bérczi Gábor + * @package System.Web.UI.WebControls + * @since 3.2 + */ +class TReCaptchaValidator extends TBaseValidator +{ + protected $_isvalid = null; + + /** + * Gets the name of the javascript class responsible for performing validation for this control. + * This method overrides the parent implementation. + * @return string the javascript class name + */ + protected function getClientClassName() + { + return ''; + } + + public function getEnableClientScript() + { + return false; + } + + /** + * This method overrides the parent's implementation. + * The validation succeeds if the input control has the same value + * as the one displayed in the corresponding RECAPTCHA control. + * + * @return boolean whether the validation succeeds + */ + protected function evaluateIsValid() + { + // check validity only once (if trying to evaluate multiple times, all redundant checks would fail) + if (is_null($this->_isvalid)) + { + $control = $this->getValidationTarget(); + if(!($control instanceof TCaptcha)) + throw new TConfigurationException('recaptchavalidator_captchacontrol_invalid'); + $this->_isvalid = $control->validate(); + } + return ($this->_isvalid==true); + } + +} + +?> \ No newline at end of file -- cgit v1.2.3