01261: Problems with PageExists() by Cookbook:SQLite PageStore class

Summary: Problems with PageExists() by Cookbook:SQLite PageStore class
Created: 2011-07-18 10:05
Status: Closed (fixed for version 20110718)
Category: Cookbook
Assigned:
Priority: 3
Version: 2.2.27
OS: Win32/Apache 2.2.18 PHP 5.2.17

Description:
<Babelfish>
There is a problem with the extension SQLite page class and the PageExists() function.

Since no sites are found, where the group by "/" is specified (eg Group/Page or /pmwiki/pmwiki.php?setprefs=Profiles/User-Prefs)
Easily it goes, if the group is indicated by ".". (eg Group.Page or /pmwiki/pmwiki.php?setprefs=Profiles.User-Prefs)
This only concerns the PageExists() function. Other functions, such as PageRead() or WritePage() will clear any problems with the "/"- acknowledged. (Internally, the str_replace('/','.',MakePageName($pagename,$pagename)) converted - In PageExists() is missing this)

And I have a SQL Injection vulnerability found, because the variable $pagename is taken over virtually unfiltered.

In the PmWiki core is the variable $pagename only when called from stdconfig.php cleaned up. i.e. Are Subqueries in the wiki config.php performed, SQLite is vulnerable.
Example: If you combine the extensions SQLite with CommentBox can be with /pmwiki/pmwiki.php?n=%27SQL-Injection.Diary a provoke error.
It goes well with internal expansion SitePreferences: http://galleries.accent.bg/?setprefs=%27SQL-Injection (Demo Wiki)

Since only the first specified select command is executed, the risk is limited. With a small test-extension (die(PageExists($pagename));) I could at least retrieve individual records (eg ?n=sql' union select text from pages where name='SiteAdmin.AuthUser)

I also recommend as a remedy to the pagename with quote() to validate, because you basically can not trust an entry!
</Babelfish>


<German>
Probleme mit PageExists() durch Cookbook:SQLite PageStore class Es gibt ein Problem bei der Erweiterung SQLite PageStore class und der Funktion PageExists().

Da werden keine Seiten gefunden, wo die Gruppe per "/" angegeben wird (z.B. Group/Page oder /pmwiki/pmwiki.php?setprefs=Profiles/User-Prefs)
Problemlos geht es, wenn die Gruppe per "." angegeben wird. (z.B. Group.Page oder /pmwiki/pmwiki.php?setprefs=Profiles.User-Prefs)
Das Betrifft nur die Funktion PageExists() andere Funktionen wie z.B. ReadPage() oder WritePage() kommen problemlos mit der "/"-Angabe klar. (Intern wird mit str_replace('/','.',MakePageName($pagename,$pagename)) umgewandelt - Bei PageExists() fehlt dies!)

Und ich habe eine SQL-Injection-Schwachstelle gefunden, da die Variable $pagename quasi ungefiltert übernommen wird.

Im PmWiki-Core wird die Variable $pagename erst bei Aufruf von stdconfig.php bereinigt. d.H. Werden Wiki-Abfragen in der config.php durchgeführt, ist SQLite angreifbar.
Beispiel: Kombiniert man die Erweiterungen SQLite mit CommentBox kann man mit /pmwiki/pmwiki.php?n=%27SQL-Injection.Diary eine Fehlermeldung provozieren.
Es geht auch mit internen Erweiterung SitePreferences: http://galleries.accent.bg/?setprefs=%27SQL-Injection (Demo-Wiki)

Da nur der erste vorgegebene Select-Befehl ausgeführt wird, hält sich die Gefahr in grenzen. Mit einer kleinen Test-Erweiterung (die(PageExists($pagename));) konnte ich zumindest einzelne Datensätze abfragen (z.B. ?n=sql' union select text from pages where name='SiteAdmin.AuthUser)

Ich empfehle als Abhilfe auch den Seitennamen mit quote() zu entwerten, da man grundsätzlich keine Eingaben trauen darf!
</German>

Hello Michael. Thanks a lot for your work uncovering bugs and vulnerabilities in PmWiki and in the recipes! It is very much appreciated. (The bug was fixed in SQLite version 20110718.) --Petko July 18, 2011, at 02:11 PM


Thanks for the quick response! It could be the variables $oldname and $newname in sqlrename() also validate, it's just an internal function, but you might get the idea to use a Rename-Recipe... - Now it itches in my fingers... (have one hour later...)

I have a suggestion for your SQLite recipe: A Rename-Handle for the functional sqlrename() The code should work and hoping at last to be error-free ...

 SDV($HandleActions['rename'], 'HandleSQLiteRename');
 SDV($HandleAuth['rename'], 'attr');
 function HandleSQLiteRename($pagename, $auth='admin') {
  global $Author, $ChangeSummary, $PageStartFmt, $PageEndFmt, $RenamePromptFmt,
 	$action, $ScriptUrl, $WikiLibDirs, $DefaultGroup, $DefaultName;
  for($i=0; $i<count($WikiLibDirs); $i++) {
   $sql = &$WikiLibDirs[$i];
   if(@$sql->type == 'SQLite')
    break;
  }
  if(!(boolean)RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT))
   exit;
  if($sql and isset($_POST['renameto'])) {
   $rename = MakePagename("$DefaultGroup.$DefaultName",$_POST['renameto']);
   if(!$sql->exists($rename)) {
    $sql->sqlrename($pagename, $rename, $ChangeSummary, (boolean)$_POST['redirect']);
    Redirect($rename);
   }
  }
  SDV($RenamePromptFmt,array(&$PageStartFmt,"
<form name='renameform' action='$ScriptUrl' method='post'>
<b>$[Please enter new Pagename for:] $pagename</b><br /><table class='renameform'><tr>
<td><b>$[New Pagename]:</b></td>
<td><input type='text' name='renameto' value='$pagename' /></td></tr>
<tr><td><b>$[Author]:</b></td>
<td><input type='text' name='author' value='$Author' /></td></tr>
<tr><td><b>$[Summary]:</b></td>
<td><input type='text' name='csum' value='$[Rename]' /></td></tr>
<tr><td><b>$[Redirect]:</b></td>
<td><input type='checkbox' name='redirect' value='1' /></td></tr>
<tr><td colspan='2'>
<input type='submit' value='$[Rename]' />
<input type='hidden' name='pagename' value='$pagename' />
<input type='hidden' name='action' value='$action' />
</td></tr></table></form>",&$PageEndFmt));
   PrintFmt($pagename,$RenamePromptFmt);
 }

Michael Engelke July 18, 2011, at 05:09 PM