DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:exttab2

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
plugin:exttab2 [2010-05-13 15:03] 74.108.1.236plugin:exttab2 [2020-06-04 16:25] (current) StarArmy
Line 6: Line 6:
 email      : disorder.chang@gmail.com email      : disorder.chang@gmail.com
 type       : Syntax type       : Syntax
-lastupdate : 2007-11-06 +lastupdate : 2010-08-28 
-compatible : 2007-06-26b+compatible : 2007-06-26b, !Hogfather
 depends    :  depends    : 
 conflicts  conflicts 
-similar    : exttab1 +similar    : exttab1, exttab3 
-tags       : Mediawiki, tables +tags       : Mediawiki, tables, !obsolete 
-securityissue: XSS vulnerability allows arbitrary JavaScript insertion. Author informed on 2008-02-07.+ 
 +downloadurl: https://github.com/ssahara/dw-plugin-exttable/archive/exttab2.zip 
 +sourcerepo: https://​github.com/​ssahara/​dw-plugin-exttable/​tree/​exttab2 
 + 
 +securityissue: XSS vulnerability allows arbitrary JavaScript insertion. Author informed on 2008-02-07. (Hopefully fixed in the 2010-08-28 update below. Please replace this plugin by the exttab3 plugin.)
 ---- ----
  
Line 129: Line 133:
 now now
 | . | .
 +|}
 +</code>
 +
 +=== Failed attempts at XSS attacks ===
 +<code>
 +{| border="1" style="border-collapse:collapse;"
 +|-
 +|onmouseover="javascript:alert('BOO');" |
 +**javascript onmouseover attack**
 +|style="background-color: lightblue; background:url(javascript:alert(1))" |
 +**CSS url attack**
 +|-
 +|style='background-color: lightgray;' |
 +stuff
 +|
 +|-><td><span style="color:red;">BOO!</a></td><br
 +| **illegal close tag attack**
 +|col2
 +|-
 +|colspan='2' style='text-align:center;' | and most (not all) good stuff still works
 +|-
 +|rowspan='2'|yes || it
 +|-
 +| does
 |} |}
 </code> </code>
Line 142: Line 170:
  
 =====Revision History===== =====Revision History=====
 +  * 2010-08-28 0.3.0 (hopefully) fixed XSS vulnerability ([[marcianx@gmail.com|Ashish Myles]])
   * 2006-11-06 0.2.0 support nested table and many more...   * 2006-11-06 0.2.0 support nested table and many more...
   * 2006-10-04 0.1.0 Initial release   * 2006-10-04 0.1.0 Initial release
 =====Sources===== =====Sources=====
-<code php> +<code php syntax.php>
 <?php <?php
 //ini_set("display_errors", "On"); // for debugging //ini_set("display_errors", "On"); // for debugging
Line 154: Line 182:
  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
  * @author     disorde chang <disorder.chang@gmail.com>  * @author     disorde chang <disorder.chang@gmail.com>
- * @date       2007-10-04+ * @date       2010-08-28
  */  */
    
Line 168: Line 196:
  */  */
 class syntax_plugin_exttab2 extends DokuWiki_Syntax_Plugin { class syntax_plugin_exttab2 extends DokuWiki_Syntax_Plugin {
 + 
   var $stack = array();   var $stack = array();
 + 
   function syntax_plugin_exttab2(){   function syntax_plugin_exttab2(){
     define("EXTTAB2_TABLE", 0);     define("EXTTAB2_TABLE", 0);
Line 183: Line 211:
                   EXTTAB2_TD=>      array("td", "\t"."\t", "\n" ),                   EXTTAB2_TD=>      array("td", "\t"."\t", "\n" ),
                   EXTTAB2_TH=>      array("th", "\t"."\t", "\n" ),                   EXTTAB2_TH=>      array("th", "\t"."\t", "\n" ),
 + 
 /* // DOKU constant not work when preview /* // DOKU constant not work when preview
                   EXTTAB2_TABLE=>   array("table", "", DOKU_LF ),                   EXTTAB2_TABLE=>   array("table", "", DOKU_LF ),
Line 191: Line 219:
                   EXTTAB2_TH=>      array("th", DOKU_TAB.DOKU_TAB, DOKU_LF ),                   EXTTAB2_TH=>      array("th", DOKU_TAB.DOKU_TAB, DOKU_LF ),
 */                  ); */                  );
 +
 +    /* attribute whose value is a single word */
 +    $this->attrsmap = array(
 +      # table attributes
 +      # simple ones (value is a single word)
 +      'align', 'border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'width', 'class', 'dir', 'id', 'lang', 'xml:lang',
 +      # more complex ones (value is a string or style)
 +      'bgcolor', 'summary', 'title', 'style',
 +      # additional tr, thead, tbody, tfoot attributes
 +      'char', 'charoff', 'valign',
 +      # additional td attributes
 +      'abbr', 'colspan', 'axis', 'headers', 'rowspan', 'scope',
 +      'height', 'width', 'nowrap',
 +    );
   }   }
    
Line 197: Line 239:
           'author' => 'Disorder Chang',           'author' => 'Disorder Chang',
           'email'  => 'disorder.chang@gmail.com',           'email'  => 'disorder.chang@gmail.com',
-          'date'   => '2007-11-06',+          'date'   => '2010-08-28',
           'name'   => 'exttab2 Plugin',           'name'   => 'exttab2 Plugin',
           'desc'   => 'parses MediaWiki-like tables',           'desc'   => 'parses MediaWiki-like tables',
Line 226: Line 268:
   function postConnect() {    function postConnect() { 
     $para = "[^\|\n\[\{\!]+"; // parametes     $para = "[^\|\n\[\{\!]+"; // parametes
 + 
     // caption: |+ params | caption     // caption: |+ params | caption
     $this->Lexer->addPattern("\n\|\+(?:$para\|(?!\|))?",'plugin_exttab2');     $this->Lexer->addPattern("\n\|\+(?:$para\|(?!\|))?",'plugin_exttab2');
 + 
     // row: |- params     // row: |- params
     $this->Lexer->addPattern('\n\|\-[^\n]*','plugin_exttab2');     $this->Lexer->addPattern('\n\|\-[^\n]*','plugin_exttab2');
 + 
     // table open     // table open
     $this->Lexer->addPattern('\n\{\|[^\n]*','plugin_exttab2');     $this->Lexer->addPattern('\n\{\|[^\n]*','plugin_exttab2');
 + 
     // table close     // table close
     $this->Lexer->addPattern('\n\|\}','plugin_exttab2');     $this->Lexer->addPattern('\n\|\}','plugin_exttab2');
-    + 
     // header     // header
     $this->Lexer->addPattern("(?:\n|\!)\!(?:$para\|(?!\|))?",'plugin_exttab2');     $this->Lexer->addPattern("(?:\n|\!)\!(?:$para\|(?!\|))?",'plugin_exttab2');
 + 
     //cell     //cell
     $this->Lexer->addPattern("(?:\n|\|)\|(?:$para\|(?!\|))?",'plugin_exttab2');     $this->Lexer->addPattern("(?:\n|\|)\|(?:$para\|(?!\|))?",'plugin_exttab2');
 + 
     //end     //end
 //    $this->Lexer->addExitPattern('\n','plugin_exttab2');  //    $this->Lexer->addExitPattern('\n','plugin_exttab2'); 
Line 250: Line 292:
     $this->Lexer->addExitPattern("\n(?=\n)",'plugin_exttab2');      $this->Lexer->addExitPattern("\n(?=\n)",'plugin_exttab2'); 
   }   }
-  + 
   /**   /**
    * Handle the match    * Handle the match
Line 265: Line 307:
       if(preg_match ( '/\{\|([^\n]*)/', $match, $m)){ // table open       if(preg_match ( '/\{\|([^\n]*)/', $match, $m)){ // table open
         $func = "table_open";         $func = "table_open";
-        $params = $m[1];+        $params = $this->_cleanAttrString($m[1]);
         return array($state, $func, $params);         return array($state, $func, $params);
       }       }
Line 275: Line 317:
       else if(preg_match ("/^\n\|\+(?:(?:($para)\|)?)$/", $match, $m)){ // caption       else if(preg_match ("/^\n\|\+(?:(?:($para)\|)?)$/", $match, $m)){ // caption
         $func = "caption";         $func = "caption";
-        $params = $m[1];+        $params = $this->_cleanAttrString($m[1]);
         return array($state, $func, $params);         return array($state, $func, $params);
       }       }
       else if(preg_match ( '/\|-([^\n]*)/', $match, $m)){ // row       else if(preg_match ( '/\|-([^\n]*)/', $match, $m)){ // row
         $func = "row";         $func = "row";
-        $params = $m[1];+        $params = $this->_cleanAttrString($m[1]);
         return array($state, $func, $params);         return array($state, $func, $params);
       }       }
       else if(preg_match("/^(?:\n|\!)\!(?:(?:([^\|\n\!]+)\|)?)$/", $match, $m)){ // header       else if(preg_match("/^(?:\n|\!)\!(?:(?:([^\|\n\!]+)\|)?)$/", $match, $m)){ // header
         $func = "header";         $func = "header";
-        $params = $m[1];+        $params = $this->_cleanAttrString($m[1]);
         return array($state, $func, $params);         return array($state, $func, $params);
       }       }
       else if(preg_match("/^(?:\n|\|)\|(?:(?:($para)\|)?)$/", $match, $m)){ // cell       else if(preg_match("/^(?:\n|\|)\|(?:(?:($para)\|)?)$/", $match, $m)){ // cell
         $func = "cell";         $func = "cell";
-        $params = $m[1];+        $params = $this->_cleanAttrString($m[1]);
         return array($state, $func, $params);         return array($state, $func, $params);
       }       }
Line 298: Line 340:
     }     }
   }   }
-  + 
   /**   /**
    * Create output    * Create output
    */    */
-   + 
   function render($mode, &$renderer, $data) {   function render($mode, &$renderer, $data) {
 + 
     if($mode == 'xhtml'){     if($mode == 'xhtml'){
       list($state, $func, $params) = $data;       list($state, $func, $params) = $data;
-    + 
       switch ($state) {       switch ($state) {
         case DOKU_LEXER_UNMATCHED :           case DOKU_LEXER_UNMATCHED :  
Line 324: Line 366:
       }       }
       return true;       return true;
-    + 
     }     }
     return false;     return false;
   }   }
    
 + 
 +  /**
 +   * Make the attribute string safe to avoid XSS attacks.
 +   * WATCH OUT FOR
 +   * - event handlers (e.g. onclick="javascript:...", etc)
 +   * - CSS (e.g. background: url(javascript:...))
 +   * - closing the tag and opening a new one
 +   * WHAT IS DONE
 +   * - turn all whitespace into ' ' (to protect from removal)
 +   * - remove all non-printable characters and < and >
 +   * - parse and filter attributes using a whitelist
 +   * - styles with 'url' in them are altogether removed
 +   * (I know this is brutally aggressive and doesn't allow
 +   * some safe stuff, but better safe than sorry.)
 +   * NOTE: Attribute values MUST be in quotes now.
 +   */
 +  function _cleanAttrString($attr=""){
 +    if (is_null($attr)) return NULL;
 +    # Keep spaces simple
 +    $attr = trim(preg_replace('/\s+/', ' ', $attr));
 +    # Remove non-printable characters and angle brackets
 +    $attr = preg_replace('/[<>[:^print:]]+/', '', $attr);
 +    # This regular expression parses the value of an attribute and
 +    # the quotation marks surrounding it.
 +    # It assumes that all quotes within the value itself must be escaped, 
 +    # which is not technically true.
 +    # To keep the parsing simple (no look-ahead), the value must be in 
 +    # quotes.
 +    $val = "([\"'`])(?:[^\\\\\"'`]|\\\\.)*\g{-1}";
  
 +    $nattr = preg_match_all("/(\w+)\s*=\s*($val)/", $attr, $matches, PREG_SET_ORDER);
 +    if (!$nattr) return NULL;
 +
 +    $clean_attr = '';
 +    for ($i = 0; $i < $nattr; ++$i) {
 +      $m = $matches[$i];
 +      $attrname = strtolower($m[1]);
 +      $attrval  = $m[2];
 +      # allow only recognized attributes
 +      if (in_array($attrname, $this->attrsmap, true)) {
 +        # make sure that style attributes do not have a url in them
 +        if ($attrname != 'style' ||
 +            (stristr($attrval, 'url'   ) === FALSE &&
 +             stristr($attrval, 'import') === FALSE))
 +        {
 +          $clean_attr .= " $attrname=$attrval";
 +        }
 +      }
 +    }
 +
 +    return $clean_attr;
 +  }
 + 
   function _attrString($attr="", $before=" "){   function _attrString($attr="", $before=" "){
     if(is_null($attr) || trim($attr)=="") $attr = "";     if(is_null($attr) || trim($attr)=="") $attr = "";
Line 335: Line 429:
     return $attr;     return $attr;
   }   }
-  + 
   var $tagsmap = array();   var $tagsmap = array();
-                  + 
   function _starttag($tag, $params=NULL, $before="", $after=""){   function _starttag($tag, $params=NULL, $before="", $after=""){
     $tagstr = $this->tagsmap[$tag][0];     $tagstr = $this->tagsmap[$tag][0];
Line 345: Line 439:
     return $r;     return $r;
   }   }
-  + 
   function _endtag($tag, $before="", $after=""){   function _endtag($tag, $before="", $after=""){
     $tagstr = $this->tagsmap[$tag][0];     $tagstr = $this->tagsmap[$tag][0];
     $before = $this->tagsmap[$tag][1].$before;     $before = $this->tagsmap[$tag][1].$before;
     $after = $this->tagsmap[$tag][2].$after;     $after = $this->tagsmap[$tag][2].$after;
 + 
     $r = $before."</".$tagstr.">". $after;     $r = $before."</".$tagstr.">". $after;
     return $r;     return $r;
   }   }
 + 
   function table_open($params=NULL){   function table_open($params=NULL){
     $r .= $this->_closetags(EXTTAB2_TABLE);     $r .= $this->_closetags(EXTTAB2_TABLE);
Line 361: Line 455:
     return $r;     return $r;
   }   }
 + 
   function table_close($params=NULL){   function table_close($params=NULL){
     $t = end($this->stack);     $t = end($this->stack);
Line 385: Line 479:
         break;         break;
     }     }
-    + 
     while(($t = end($this->stack)) != EXTTAB2_TABLE){     while(($t = end($this->stack)) != EXTTAB2_TABLE){
       $r .= $this->_endtag($t);       $r .= $this->_endtag($t);
Line 393: Line 487:
     $r .= $this->_endtag(EXTTAB2_TABLE);     $r .= $this->_endtag(EXTTAB2_TABLE);
     return $r;        return $r;   
-    + 
   }   }
-  + 
   function end($params=NULL){   function end($params=NULL){
     while(!empty($this->stack)){     while(!empty($this->stack)){
Line 402: Line 496:
     return $r;     return $r;
   }   }
-  + 
   function caption($params=NULL){   function caption($params=NULL){
     if(($r = $this->_closetags(EXTTAB2_CAPTION)) === FALSE){     if(($r = $this->_closetags(EXTTAB2_CAPTION)) === FALSE){
Line 411: Line 505:
     return $r;     return $r;
   }   }
 + 
   function row($params=NULL){   function row($params=NULL){
     $r .= $this->_closetags(EXTTAB2_TR);     $r .= $this->_closetags(EXTTAB2_TR);
Line 418: Line 512:
     return $r;     return $r;
   }   }
 + 
   function header($params=NULL){   function header($params=NULL){
     $r .= $this->_closetags(EXTTAB2_TH);     $r .= $this->_closetags(EXTTAB2_TH);
Line 425: Line 519:
     return $r;     return $r;
   }   }
 + 
   function cell($params=NULL){   function cell($params=NULL){
     $r .= $this->_closetags(EXTTAB2_TD);     $r .= $this->_closetags(EXTTAB2_TD);
Line 432: Line 526:
     return $r;     return $r;
   }   }
-  + 
   function _closetags($tag){   function _closetags($tag){
     $r = "";     $r = "";
Line 439: Line 533:
       case EXTTAB2_TH:       case EXTTAB2_TH:
         $t = end($this->stack);         $t = end($this->stack);
-       + 
         switch($t){         switch($t){
           case EXTTAB2_TABLE:           case EXTTAB2_TABLE:
Line 462: Line 556:
       case EXTTAB2_TR:       case EXTTAB2_TR:
         $t = end($this->stack);         $t = end($this->stack);
-       + 
         switch($t){         switch($t){
           case EXTTAB2_TABLE:           case EXTTAB2_TABLE:
Line 546: Line 640:
  
 2010/05/01: Skimming through the code, I'm guessing the vulnerability is that the table can take arbitrary event attributes (e.g. ''onclick="javascript:{stuff}"'', etc), which allows insertion of malicious javascript code that could execute an XSS attack. This won't matter if only trusted people are editing, though. Again, this is by skimming the code at the high level, pointing out the most obvious "flaw" (feature?) in this plugin's design. I don't know if that's what they were actually referring to. 2010/05/01: Skimming through the code, I'm guessing the vulnerability is that the table can take arbitrary event attributes (e.g. ''onclick="javascript:{stuff}"'', etc), which allows insertion of malicious javascript code that could execute an XSS attack. This won't matter if only trusted people are editing, though. Again, this is by skimming the code at the high level, pointing out the most obvious "flaw" (feature?) in this plugin's design. I don't know if that's what they were actually referring to.
 +> That is a correct analysis of the vulnerability that was reported [by me, directly to the author] resulting in the security flag being set. The objective of the changes is good, although I haven't reviewed the code in detail or tested it. If you are confident that the issue is now addressed then feel free to remove the security flag.  --- [[user>andy.webber]] //2011/01/05 19:26//
 +
 +This plugin does not work with 2011-05-25a "Rincewind". Tables are not properly rendered. They are missing their internal lines and the right part of the bottom line. Can anyone verify this? Alternatives?
 +
 +> This plugin does work in Rincewind. You need to ensure you include ''class="inline"'' in the opening line of the table. ~~~~
 +
 +2012/02/09: I am having the same trouble in Angua as the previous comment regarding Rincewind. I tried assigning the class="inline" **{| class=“inline” **
 +but I think most everything on the table first line is ignored. If you contact me I can show you examples.  thanks for your help.  [[dwightj@mca.org.tw]] 2012/02/09
 +>UPDATE: It works!  When I pasted the class="inline" somehow it pasted curly quotes.  Not sure why it did that or why I didn't notice it sooner.
  
 +2014-03-19: The plugin code of exttab2 has moved to [[https://github.com/ssahara/dw-plugin-exttable/tree/exttab2|github repository]] for better accessibility and my further development. I hope  [[plugin:exttab3]] will become available soon.  --- [[user>s.sahara|s.sahara]] //2014/03/19 15:00//
plugin/exttab2.1273755792.txt.gz · Last modified: 2010-05-13 15:03 by 74.108.1.236

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki