locale_string_is_safe() is not suitable for user provided text in non-default textgroups

valthebald - December 29, 2008 - 15:59
Project:Drupal
Version:6.12
Component:locale.module
Category:bug report
Priority:normal
Assigned:Unassigned
Status:closed
Issue tags:i18n sprint, needs backport to D6
Description

...which makes it almost impossible to implement translatable blocks.
People, blocks without divs and imgs are useless!

Yes, I've read explanation for this in locale.inc (lines 837-839), but IMHO this is simply not correct.

abstract from locale.inc:

* The allowed tag list is like filter_xss_admin(), but omitting div and img as
* not needed for translation and likely to cause layout issues (div) or a
* possible attack vector (img).
*/
function locale_string_is_safe($string) {
return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')));
}

#1

valthebald - December 30, 2008 - 07:11
Priority:critical» normal

#2

Gábor Hojtsy - December 30, 2008 - 11:33

Got a tip at http://drupal.org/node/276111 that it caused problems.

Allowing img and div in translations would open all Drupal sites to XSS attacks once we put the online translation server in place with automated distribution of translations. People seem to want to have more automated distribution for translations, but we could not do it without having proper protection against attacks on your site.

We might be able to use the textgroup to assume non-default textgroups strings as safe (given we treat them as user input) regardless of their content to let i18nstrings module to work. The non-default textgroups are used to store stuff which will be displayed through the filter system or other mechanisms to escape content (at least they are assumed to be used as such).

#3

Gábor Hojtsy - December 30, 2008 - 11:34
Title:Div and img tags missing in locale_string_is_safe tag list» locale_string_is_safe() is not suitable for user provided text in non-default textgroups

Retitled.

#4

valthebald - December 30, 2008 - 15:22
Status:active» needs review

I'm not sure how translation server is invoked to import translations.

Does it call locale_translate_edit_form_validate()? 'Cause if not, and locale_translate_edit_form_validate is called in case of user input,
then it's possible to patch locale.inc somewhat like (attached as patch as well)

--- includes/locale.inc
+++ includes/locale.inc
@@ -847,10 +847,7 @@
*/
function locale_translate_edit_form_validate($form, &$form_state) {
foreach ($form_state['values']['translations'] as $key => $value) {
- if (!locale_string_is_safe($value)) {
- form_set_error('translations', t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
- watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
- }
+ $form_state['values']['translations'][$key] = check_plain($value);
}
}

AttachmentSizeStatusTest resultOperations
locale.inc_.patch751 bytesIdleFailed: 8849 passes, 8 fails, 0 exceptionsView details | Re-test

#5

Gábor Hojtsy - December 30, 2008 - 15:26
Status:needs review» needs work

Grm, I am just saying that if the form state says we deal with a non-default textgroup, we should skip this security check. Your patch always removes this check which is not right.

#6

valthebald - December 30, 2008 - 15:57
Status:needs work» needs review

Ok, didn't understand your initial comment.
Here's updated version (is it what you meant?):

Index: includes/locale.inc
===================================================================
--- includes/locale.inc
+++ includes/locale.inc
@@ -846,8 +846,9 @@
* Validate string editing form submissions.
*/
function locale_translate_edit_form_validate($form, &$form_state) {
+ $safe_check_needed = $form_state['values']['textgroup']=='default'; // locale string check is needed for default textgroup only
foreach ($form_state['values']['translations'] as $key => $value) {
- if (!locale_string_is_safe($value)) {
+ if ($safe_check_needed && !locale_string_is_safe($value)) {
form_set_error('translations', t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
}

AttachmentSizeStatusTest resultOperations
locale.inc_.patch911 bytesIdlePassed: 8994 passes, 0 fails, 0 exceptionsView details | Re-test

#7

Gábor Hojtsy - December 30, 2008 - 16:00

Yes, except some coding style changes. We'd need to have the comment on the line before in sentence format (capitalized at the start, dot at the end). Also, the same needs to be done with .po files imported with the textgroup specified. That should cover the cases we have in core IMHO.

#8

valthebald - December 30, 2008 - 16:02

the same needs to be done with .po files imported with the textgroup specified - what do you mean?

#9

Gábor Hojtsy - December 30, 2008 - 16:14
Status:needs review» needs work

When .po files are imported on the locale interface, you can choose a textgroup. .po files not imported for the default textgroup should also omit this check to be in line with what we do here.

#10

valthebald - December 31, 2008 - 07:52

I still don't get the point why add some global check for all locale strings, when in fact, we need only to check input from translation server?
Maybe it's better to check input from translation server, and that's it? I'm not so aware of drupal core internals to quickly find callback which handles such input, the only thing I found is i10n_server/i10n_client pair. If I'm right, then it's no need to place check in locale.inc at all, but place it in i10n_client.

#11

valthebald - December 31, 2008 - 07:52

I still don't get the point why add some global check for all locale strings, when in fact, we need only to check input from translation server?
Maybe it's better to check input from translation server, and that's it? I'm not so aware of drupal core internals to quickly find callback which handles such input, the only thing I found is i10n_server/i10n_client pair. If I'm right, then it's no need to place check in locale.inc at all, but place it in i10n_client.

#12

Gábor Hojtsy - December 31, 2008 - 09:51

@valthebald: The l10n_client currently does not pull translations from a server, and the reason was exactly this problem (of using unfiltered strings) not being fixed. Now that we are filtering those, we can finally implement it. However, the l10n_server's currently export .po files for import in Drupal. Now the .po import always does this check. But you can export and do your block translations outside of Drupal as well with the same .po mechanism. So there we should also have a textgroup dependent check.

#13

valthebald - January 8, 2009 - 13:30
Status:needs work» needs review

Sorry for delay - you know, all these holidays and damn fanatics on our South...
Anyway, attached patch added check on strings import (for default textgroup only), as well as form submit check

AttachmentSizeStatusTest resultOperations
locale.inc_.patch1.47 KBIdleFailed: Failed to apply patch.View details | Re-test

#14

Damien Tournoud - January 8, 2009 - 13:35
Version:6.8» 7.x-dev
Status:needs review» needs work

The patch looks good. Some (minor) coding style issues:

<?php
--- includes/locale.inc
+++ includes/locale.inc
@@ -846,8 +846,9 @@
  *
Validate string editing form submissions.
  */
function
locale_translate_edit_form_validate($form, &$form_state) {
$safe_check_needed = $form_state['values']['textgroup']=='default'; // locale string check is needed for default textgroup only
  
foreach ($form_state['values']['translations'] as $key => $value) {
-    if (!
locale_string_is_safe($value)) {
+    if (
$safe_check_needed && !locale_string_is_safe($value)) {
      
form_set_error('translations', t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
      
watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
     }
?>

We (virtually) always put comments on their own line. Plus comments should be proper sentences (beginning with an uppercase letter, ending with a period).

<?php
@@ -1338,8 +1339,11 @@
  
$lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup));

   if (!empty(
$translation)) {
-    
// Skip this string unless it passes a check for dangerous code.
-     if (!locale_string_is_safe($translation)) {
+    
/**
+      * Skip this string unless it passes a check for dangerous code.
+      * Yet text groups other than default still can contain HTML tags (i.e. translatable blocks)
+      */
+     if ($textgroup=="default" && !locale_string_is_safe($translation)) {
       
$report['skips']++;
       
$lid = 0;
      }
?>

We don't use C style comments inline. Please transform this block using "//"-style comments. Plus a period is missing at the end.

This should apply (unmodified) to 7 too, bumping version.

#15

valthebald - January 8, 2009 - 14:05

Another try...

AttachmentSizeStatusTest resultOperations
locale.inc_.patch1.29 KBIdleFailed: Failed to apply patch.View details | Re-test

#16

Damien Tournoud - January 8, 2009 - 14:09

We are getting there :) Some additional code style issues (including one I missed on first review, sorry):

<?php
@@ -1339,7 +1341,8 @@

   if (!empty(
$translation)) {
     
// Skip this string unless it passes a check for dangerous code.
-     if (!locale_string_is_safe($translation)) {
+    
// Text groups other than default still can contain HTML tags (i.e. translatable blocks).
+     if ($textgroup=="default" && !locale_string_is_safe($translation)) {
       
$report['skips']++;
       
$lid = 0;
      }
?>

- The comment line should not cross the 80 character boundary. You will need to split that comment line (for example before "(i.e.").
- We always add a space before and after an operator: $textgroup=="default" should be $textgroup == "default"

#17

valthebald - January 8, 2009 - 15:03
Status:needs work» needs review

...and once again.

AttachmentSizeStatusTest resultOperations
locale.inc_.patch1.27 KBIdleFailed: Failed to apply patch.View details | Re-test

#18

System Message - January 8, 2009 - 15:50
Status:needs review» needs work

The last submitted patch failed testing.

#19

lilou - January 8, 2009 - 22:18
Status:needs work» needs review

Small typo catch by Damien :

We always add a space before and after an operator: $textgroup=="default" should be $textgroup == "default"

AttachmentSizeStatusTest resultOperations
issue-352121.patch1.59 KBIdlePassed: 8975 passes, 0 fails, 0 exceptionsView details | Re-test

#20

valthebald - January 12, 2009 - 09:22

So, what's the next step? :)

#21

Damien Tournoud - January 12, 2009 - 09:44
Status:needs review» reviewed & tested by the community

The patch itself looks good.

I extended the test to cover this case, confirmed that the test fails when run alone and that the patch makes it pass.

Because the test bot was happy earlier (http://testing.drupal.org/pifr/file/1/issue-352121.patch says Passed: 8866 passes, 0 fails, 0 exceptions), and because the approach got Gabor approval, I'm marking this RTBC right away.

AttachmentSizeStatusTest resultOperations
352121-locale-string-safe-non-default.patch4.93 KBIdlePassed: 8980 passes, 0 fails, 0 exceptionsView details | Re-test

#22

nedjo - January 20, 2009 - 16:58

#23

webchick - January 22, 2009 - 03:29
Version:7.x-dev» 6.x-dev

Committed to HEAD, per Gábor.

Marking back to 6.x.

#24

Steven Jones - January 22, 2009 - 10:20
Priority:normal» critical
Status:reviewed & tested by the community» needs work

Err.. I'm fairly sure the CVS commit didn't go smoothly, and the local_test module wasn't added to CVS, so HEAD is broken.

#25

Damien Tournoud - January 22, 2009 - 10:23
Version:6.x-dev» 7.x-dev

#26

mr.baileys - February 5, 2009 - 16:32

+1 Subscribe.

Trying to set up a site with 2 languages using i18n, but we're hampered by the problems outlined in this issue when it comes to translating blocks.

A quick glance does indicate that the above patch did make it into 7.x-dev so not sure what the status of this issue is (should it be backported, or did something go wrong against 7.x-dev?)

#27

Steven Jones - February 5, 2009 - 16:34
Version:7.x-dev» 6.x-dev
Priority:critical» normal
Status:needs work» reviewed & tested by the community

HEAD is fixed now.

#28

Gábor Hojtsy - February 16, 2009 - 12:45
Status:reviewed & tested by the community» patch (to be ported)

#19 seems to be the "right" patch for Drupal 6 (it does only include the fix without the test, as Drupal 6 expects), BUT it does not apply anymore. Porting required. If #20 changed the logic as well, then porting that would be desired.

#29

nonsie - February 16, 2009 - 21:15

subscribing (broke a site upgrading from 6.6 to 6.9)

#30

mr.baileys - February 17, 2009 - 07:17
Status:patch (to be ported)» needs review
Issue tags:+needs backport to D6

re-rolled the patch from #19 so it applies to Drupal 6 HEAD again. #21 did not change the logic, only added the tests.

AttachmentSizeStatusTest resultOperations
352121-locale-string-safe-non-default-backport.patch1.44 KBIgnoredNoneNone

#31

twirlingsky - February 25, 2009 - 02:19
Version:6.x-dev» 6.9

I've applied this patch...but it doesn't seem to solve my problem. I'm am translating a page which has javascript (insert of flash xml slideshow) and it won't display on the translated page - so I'm assuming the code isn't being allow. Any ideas on a way to fix this?
Example: http://cima.laurelterlesky.ca/shipping-logistics (english works fine / spanish won't display)
This is with il8n version 6.1

#32

valthebald - February 25, 2009 - 08:56

Seems like JS error (XMLFlashSlideshow_v3 not defined), not translation-related. Try to compare JSs loaded for English and Spanish versions

#33

twirlingsky - February 25, 2009 - 19:16

Awesome thanks! It just wasn't picking up the javascript path correctly on the translated pages. off and running now.

#34

ao2 - June 11, 2009 - 14:10
Version:6.9» 6.12
Status:needs review» reviewed & tested by the community

I am using the patch in #30 on drupal-6.12 and works ok for me.
Now I can translate custom blocks, thanks.

Can you please consider applying it to D6 as well?

Regards,
Antonio

#35

Gábor Hojtsy - June 18, 2009 - 12:51
Status:reviewed & tested by the community» fixed

Committed to Drupal 6 too, thanks!

#36

Pasqualle - June 18, 2009 - 16:21

which makes it almost impossible to implement translatable blocks

just a note on the original problem: translation of custom block should be done as node translation. It is wrong to use the string translation functionality to translate blocks.. This idea of translating large blob of text same as simple string translation with t() is absolutely unusable..

So for D6 the right approach would be to create a new block and assign language for that block..

#37

Pasqualle - June 18, 2009 - 16:34

this patch is wrong.

+  // Locale string check is needed for default textgroup only.
+  $safe_check_needed = $form_state['values']['textgroup'] == 'default';

WTF? How can I create a new textgroup where the safe string check should be applied?

The check should be applied for every textgroup, if you want to omit the check then you should specially say so (in configuration or hook). It is wrong to omit the check for every textgroup as most of the strings in different textgroups should not contain disallowed html..

#38

Gábor Hojtsy - June 18, 2009 - 17:07

Pasqualle: Features like nodes as blocks is not part of core. Also, as said multiple times above, the non-default textgroups are used for things like blocks, menus, taxonomy, etc, which is assumed to be user input and is therefore filtered or escaped on use.

#39

Pasqualle - June 18, 2009 - 17:22

The block translation is also not part of core. I just say when you want to translate a block it is better to create a new block than using t() on a HTML text blob..

The textgroups should have a format value associated. As format used in views_handler_field_markup in views module. So the strings can be easily checked with check_markup($value, $format, FALSE); at entry also.

#40

Pasqualle - June 18, 2009 - 17:25

I correct myself: the patch is wrong the patch is not good enough for Drupal 7

#41

System Message - July 2, 2009 - 17:30
Status:fixed» closed

Automatically closed -- issue fixed for 2 weeks with no activity.

#42

hass - August 29, 2009 - 18:44

Marked #368173: Full HTML Blocks cannot be translated as a duplicate of this issue.

#43

hass - August 29, 2009 - 19:34

Marked #421228: Blocks html code differs between languages as a duplicate of this issue.

#44

hass - August 29, 2009 - 19:36
 
 

Drupal is a registered trademark of Dries Buytaert.