DokuWiki Plugin Security — XSS Prevention, CSRF Protection, and Secure Coding
In this tutorial, you'll learn how to write secure DokuWiki plugins, covering XSS (Cross-Site Scripting) prevention, CSRF (Cross-Site Request Forgery) protection, input validation, output escaping, permission checks, and secure coding best practices.
What You'll Learn
- Common security vulnerabilities in plugins
- XSS prevention: escaping output
- CSRF protection: using security tokens
- Input validation for plugin parameters
- Permission checks in plugins
- Secure data storage
- DokuWiki's built-in security functions
Why It Matters
A vulnerable plugin can compromise your entire wiki. XSS attacks can steal session cookies, redirect users to malicious sites, or deface pages. CSRF attacks can make admin users perform actions without their knowledge. Because DokuWiki plugins run with full wiki permissions, a single insecure plugin creates a security hole. Understanding plugin security is essential for anyone writing or maintaining plugins.
Real-World Use
A developer writes a custom plugin that fetches data from an external API and displays it on wiki pages. Without proper output escaping, an attacker who controls the API response could inject JavaScript into the wiki page, stealing session cookies from all users who view that page. Following Secure Coding Practices prevents this.
Learning Path
flowchart LR A[Plugin Configuration] --> B[Plugin Security] B --> C[Template Anatomy] C --> D[Template Variables] D --> E[Bootstrap Template] E --> F[Custom Template]
XSS Prevention
XSS (Cross-Site Scripting) occurs when user-controlled data is rendered without escaping, allowing attackers to inject JavaScript.
Using hsc() for Output Escaping
DokuWiki provides the hsc() (HTML Special Characters) function:
<?php
// Unsafe (vulnerable to XSS)
$renderer->doc .= $userInput;
// Safe (escaped)
$renderer->doc .= hsc($userInput);
When to Escape
Always escape when outputting:
- User input (form data, URL parameters)
- Data from external sources (APIs, databases)
- Plugin configuration values
- File names and paths
Context-Specific Escaping
Different contexts need different escaping:
<?php
// HTML attribute context
$renderer->doc .= '<img alt="' . hsc($altText) . '" src="' . hsc($imageUrl) . '">';
// URL context
$renderer->doc .= '<a href="' . hsc($url) . '">' . hsc($linkText) . '</a>';
// JavaScript context (avoid if possible)
$renderer->doc .= '<script>var name = "' . hsc(addslashes($name)) . '";</script>';
Avoid Raw HTML in Syntax Plugins
Instead of generating raw HTML, use DokuWiki's renderer methods:
<?php
// Unsafe: raw HTML
$renderer->doc .= '<div class="my-class">' . $content . '</div>';
// Safer: use DokuWiki rendering
$renderer->divstart('my-class');
$renderer->cdata($content);
$renderer->divend();
CSRF Protection
CSRF (Cross-Site Request Forgery) tricks authenticated users into performing actions they did not intend.
Using Security Tokens
DokuWiki provides a CSRF token system. Always include tokens in forms:
<?php
// Generate form with CSRF token
$token = getSecurityToken();
echo '<form method="post">';
echo '<input type="hidden" name="sectok" value="' . $token . '">';
echo '<input type="submit" value="Delete Page">';
echo '</form>';
Validating Tokens
Check the token when processing form submissions:
<?php
// Validate CSRF token
if (!checkSecurityToken()) {
// Invalid token - reject request
http_response_code(403);
echo 'Security token validation failed.';
exit;
}
AJAX CSRF Protection
For AJAX requests, include the token in the request header:
// Using jQuery with DokuWiki
$.ajax({
url: '/wiki/lib/exe/ajax.php',
data: {
call: 'myplugin_function',
sectok: DOKU.sectok
}
});
Input Validation
Always validate input from users and external sources.
Validate URL Parameters
<?php
// Validate page ID parameter
$pageId = $_REQUEST['id'];
if (!page_exists($pageId) && !auth_quickaclcheck($pageId)) {
// Invalid or unauthorized page
return;
}
Validate Numeric Input
<?php
// Unsafe
$count = $_REQUEST['count'];
// Safe
$count = (int) $_REQUEST['count'];
if ($count < 1 || $count > 100) {
$count = 10; // Default value
}
Validate File Uploads
<?php
// Check file type
$allowedTypes = array('jpg', 'png', 'gif', 'pdf');
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
throw new Exception('File type not allowed.');
}
// Check file size
if ($_FILES['file']['size'] > 5000000) {
throw new Exception('File too large.');
}
Permission Checks
Plugins must check that the user has permission to perform actions.
Check ACL Permissions
<?php
// Check if user can read a page
if (auth_quickaclcheck($pageId) < AUTH_READ) {
http_response_code(403);
echo 'Access denied.';
return;
}
// Check if user can edit a page
if (auth_quickaclcheck($pageId) < AUTH_EDIT) {
http_response_code(403);
echo 'You do not have permission to edit this page.';
return;
}
Check Admin Status
<?php
// Check if user is admin
if (!auth_isadmin()) {
http_response_code(403);
echo 'Admin access required.';
return;
}
Action Plugin Permission Checks
For action plugins that modify data:
<?php
public function handle_page_save($event, $param) {
global $INPUT;
$pageId = $event->data['id'];
// Check that the user has edit permission
if ($INPUT->server->str('REMOTE_USER') && auth_quickaclcheck($pageId) >= AUTH_EDIT) {
// Proceed with the action
} else {
// Log unauthorized attempt
msg('You do not have permission to edit this page.', -1);
$event->preventDefault();
}
}
Secure Data Storage
File Permissions
If your plugin writes files, ensure proper permissions:
<?php
// Set restrictive permissions for plugin data
$dataDir = DOKU_INC . 'data/plugin-data/myplugin/';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$file = $dataDir . 'data.txt';
file_put_contents($file, $data);
chmod($file, 0644);
Avoid Storing Secrets in Plugin Files
Do not hard-code API keys or passwords in plugin files. Use configuration settings:
<?php
// In plugin's conf/default.php
$conf['api_key'] = '';
// In conf/local.php
$conf['plugin']['myplugin']['api_key'] = 'your-actual-key';
// In plugin code
$apiKey = $this->getConf('api_key');
SQL Injection Protection
If your plugin interacts with a database (uncommon in DokuWiki), use parameterized queries:
<?php
// Unsafe: string concatenation
$result = $db->query("SELECT * FROM users WHERE name = '" . $name . "'");
// Safe: parameterized query (if using PDO)
$stmt = $db->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute(array($name));
DokuWiki Security Functions Reference
| Function | Purpose |
|---|---|
hsc($string) |
Escape HTML special characters |
getSecurityToken() |
Generate CSRF token |
checkSecurityToken() |
Validate CSRF token |
auth_quickaclcheck($id) |
Check ACL permissions |
auth_isadmin() |
Check if current user is admin |
cleanID($id) |
Sanitize page ID |
media_savefile() |
Secure file upload handling |
mail_send() |
Secure email sending |
Common Mistakes
- Not escaping plugin output: The most common plugin vulnerability. Every piece of user-controlled data that appears in output must be escaped with
hsc(). - Not checking permissions: A plugin that performs admin actions without checking admin status can be exploited by regular users.
- Not validating CSRF tokens: Forms in admin plugins must include and validate security tokens.
- Storing sensitive data in readable files: Plugin configuration files should not contain plain-text passwords or API keys.
- Trusting external data sources: Data from APIs, databases, or file uploads may be malicious. Validate and escape everything.
Practice Questions
- What is the
hsc()function in DokuWiki, and when should it be used in plugin development? - How does DokuWiki's CSRF protection system work, and what functions are involved?
- What permission checks should an admin plugin perform before executing sensitive operations?
- Challenge: Review the plugin you wrote in Lesson 22 (or any existing plugin) for security vulnerabilities. Check for: unescaped output, missing CSRF protection, missing permission checks, unchecked input validation, and insecure data storage. Write a security audit report that includes each vulnerability found, its severity, and the code changes needed to fix it. Implement the fixes.
FAQ
Mini Project
Goal: Perform a security audit on a plugin.
- Select an existing plugin (either one you wrote or a community plugin)
- Review the plugin code for:
- Any
$renderer->doc .=withouthsc()wrapping - Forms without CSRF tokens
- Missing ACL or admin permission checks
- Unvalidated user input
- Insecure file operations
- Any
- Create a vulnerability report with findings and severities
- Fix each vulnerability found
- Test the fixed plugin to ensure functionality is preserved
- Document the security improvements made
What's Next
Plugins extend functionality. Now explore template anatomy to understand how DokuWiki's visual layer works.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro