01566: Security vulnerability in (:searchresults:)
(:searchresults:)Description:
Using the (:searchresults:) markup, I discovered another vulnerability in PmWiki.
It affects all wikis that have the $EnablePageListProtect variable set to 1.
First, the good news: Protected content cannot be read!
The problem concerns the variables $MatchCount and $MatchSearched.
Using these values, hidden pages can be found and their names determined.
To demonstrate this, I installed PmWiki 2.7.5 on my server without a config.php file.
I performed a standard search: "/pmwiki-2.7.5/pmwiki.php?n=Main.HomePage&action=search&q=."
and receive a nearly complete list of all wiki pages along with the message: "125 pages found out of 130 pages searched."
For clarity, I’ll condense the action and reaction to the essentials.
In this case: "." → 125/130: Five pages seem to be missing.
My curiosity was piqued, so I kept experimenting:
"name=Group*" → 6/7: All groups are displayed here, but one seems to be missing.
We both know which one it is. Of course, it’s the SiteAdmin group.
But can I figure that out this way? Without knowing the name?"group=[A-Z]* name=Group*" → 6/7: The same result. Using the "divide and conquer" technique, I narrow it down further."group=[A-M]* name=Group*" → 1/1: So, no such luck."group=[N-Z]* name=Group*" → 5/6: It's getting warmer."group=[N-S]* name=Group*" → 5/6: Same result."group=[N-P]* name=Group*" → 4/4: Not the letters "A" through "P"."group=[Q]* name=Group*" → 0/0: No "Q"!"group=[R]* name=Group*" → 0/0: No "R"!"group=[S]* name=Group*" → 1/2: So the hidden group starts with an "S"."group=S[A-Z]* name=Group*" → 1/2: Now I'm looking for the second letters.- …
"group=SIT[E]* name=Group*" → 1/2: The fourth letter has been found."group=SITE[A-Z]* name=Group*" → 0/1: Let's move on to the fifth letter.- …
"group=SITEADMI[N]* name=Group*" → 0/1: The ninth letter has been found."group=SITEADMIN[A-Z0-9]* name=Group*" → 0/0: So the hidden group is called SITEADMIN.
To find the pages in the group, we'll now type:
"group=SITEADMIN name=*" → 0/5: So, a maximum of 5 pages"group=SITEADMIN name=[A-Z0-9]*" → 0/5: None of them contain non-alphanumeric characters.
And so on.
Using this method, I can identify all hidden pages and bypass the "$EnablePageListProtect" security feature.
You could write an Exploid right now that can find all the hidden wiki pages. – But that's about it.
As I mentioned earlier, the problem involves the $MatchSearched variable, which counts all pages found—regardless of whether I have permission to view them.
The template variable {$$PageListCount} behaves correctly and counts only the pages I’m allowed to view.
To fix the vulnerability, $MatchSearched should really count only the pages that a user is allowed to view.
However, this doesn’t solve the actual problem yet.
I could achieve the same result using (:if exists:).
Possibly also with "?action=edit" (not tested).
This brings us to PmWiki’s security concept and the questions:
"Does the page exist in the wiki?" and "Do I have permission to access the page?"
Unfortunately, PmWiki normally answers both questions in the affirmative, meaning wiki pages cannot be easily protected or hidden.
This brings us to the final question: "Is PmWiki the right software to protect my data?"
And now, Petko, you’d better be brave. You’d better sit down and take another big sip, because the answer will surprise you:
PmWiki is the right software for the job! – Because PmWiki can already do that. – How? – With WikiFarms!
First, create a new directory next to "wiki.d", such as "secret.d".
Then, use a file manager to move all wiki pages into the "secret.d" directory.
In config.php, start the session and use the $_SESSION variable to check whether the authorized user is logged in. Finally, you can use the $WikiLibDirs variable and the "PageStore()" function to add the secret directories to the array.
The great thing about this is that if you’re not logged in, the pages don’t exist in the wiki. There would still be traces in "wiki.d/.pageindex", but at some point, you’ve got to keep things in perspective.
Michael Engelke
Fixed for 2.7.6, thank you! You know it is possible to enumerate all pages like Group.Aa, Group.Ab, Group.Ac... in the URL and look if there is a "Last Modified" date other than now. See for example the site footer at SiteAdmin.SiteAdmin (exists=shows an older date/time) and again at SiteAdmin.NoSuchPage (not exist=shows current date/time). To prevent this see PageVariables#security on how to enable "pvCoreAuthCache". --Petko