You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have discovered several directory traversal vulnerabilities. The capabilities are as follows:
Any .md file on the system can be read by an unauthenticated attacker
The contents of sibling directories to the wiki directory can be listed by unauthenticated attacker in certain circumstances.
Any .md file on the system can be overwritten by an unauthenticated attacker by default (authentication required if edit password protection is enabled)
Any .md file on the system can be deleted by an unauthenticated attacker by default (authentication required if edit password protection is enabled)
New directories and .md files can be created on the system with arbitrary contents and paths by an unauthenticated attacker by default (authentication required if edit password protection is enabled)
Details as follows:
Directory traversal in /list/
The /list/ request can be used to get a list of files in the wiki, or in a subdirectory of the wiki.
In wiki.py the list_wiki function that handles /list/ requests checks whether the canonical form of the requested path shares a common prefix with the configured "safe" wiki directory:
This is intended to prevent directory traversal. However, this does not prevent traversal to sibling directories whose names start with the same name as the configured wiki directory.
For example, consider the following directory structure:
home
└── user
└── wikmd
├── wiki
│ ├── homepage.md
│ └── foo.md
└── wiki_secret
└── secret.md
If an attacker requests /list/../wiki_secret/, the following happens:
folderpath is set to ../wiki_secret/
cfg.wiki_directory is configured to serve out of the wiki directory, so safe_folder becomes /home/user/wikmd/wiki
requested_path is created by joining cfg.wiki_directory and folderpath, which results in /home/user/wikmd/wiki/../wiki_secret/
requested_path is canonicalised with os.path.realpath(), which returns /home/user/wikmd/wiki_secret.
os.path.commonprefix() is called on the canonicalised request path and safe_folder, which searches character-by-character and returns the prefix substring up until the first difference. Since /home/user/wikmd/wiki and /home/user/wikmd/wiki_secret are the arguments, the result is /home/user/wikmd/wiki
This is compared to safe_folder, which is /home/user/wikmd/wiki, and therefore the check passes.
The contents of /home/user/wikmd/wiki_secret are listed.
This can be demonstrated simply by sending GET /list/../wiki_secret/ and two enter key presses to the server using netcat.
Directory traversal in page display
There is no directory traversal protection inside the file_page function. As such, any .md file on the system can be displayed.
This can be trivially demonstrated with any default install by sending GET /../README and two enter presses to the server using netcat. This will return the HTML rendered copy of the README.md file in the wikmd directory.
Directory traversal in page edit
There is no directory traversal protection inside the edit function. This can be used both for reading and writing any .md file on the system. This function is unauthenticated by default, but may be configured to require a password.
When a GET request is sent to an /edit/ route, this function displays the existing contents of the file. This can be demonstrated by sending GET /edit/../README and two enter presses to the server using netcat. This will display the HTML of the edit template with the contents of the README.md file in the wikmd directory included.
When a POST request is sent to an /edit/ route, the following code path is taken:
The fetch_page_name() function acquires the page name from the PN form variable.
The page variable contains the portion of the URL after /edit/. No traversal check is performed. If the value provided in the PN form variable does not match the path in the URL, the file specified by the URL is deleted. Since the path is vulnerable to traversal, any .md file on the system can be deleted using this method.
The save() function saves the page. Since the page name is taken from the PN form variable without validation, this allows any .md file on the system to be written to. The save() function creates the full directory path to the target file name, so this enables the creation of directories anywhere on the system.
Directory traversal in new page creation
The add_new() function handles /add_new. This function is unauthenticated by default, but may be configured to require a password.
The PN form variable is used to specify the name/path of the page to create, but no traversal protection is performed. This function calls into save() in the same way that the page edit functionality does, with the same impact.
Directory traversal in remove
The remove() function handles /remove/ route requests. This function is unauthenticated by default, but may be configured to require a password.
The path is taken from the URL and is not validated or protected against traversal. This allows for any .md file on the system to be deleted.
Remediation
The existing commonprefix() traversal check in list_wiki() is broken and should be removed.
The safe_join() function provided by Flask should be utilised instead of os.path.join() in all cases where one or more of the path arguments is untrusted. This function will raise a NotFound exception if traversal out of the base directory is attempted.
The text was updated successfully, but these errors were encountered:
I have discovered several directory traversal vulnerabilities. The capabilities are as follows:
.md
file on the system can be read by an unauthenticated attacker.md
file on the system can be overwritten by an unauthenticated attacker by default (authentication required if edit password protection is enabled).md
file on the system can be deleted by an unauthenticated attacker by default (authentication required if edit password protection is enabled).md
files can be created on the system with arbitrary contents and paths by an unauthenticated attacker by default (authentication required if edit password protection is enabled)Details as follows:
Directory traversal in /list/
The
/list/
request can be used to get a list of files in the wiki, or in a subdirectory of the wiki.In
wiki.py
thelist_wiki
function that handles/list/
requests checks whether the canonical form of the requested path shares a common prefix with the configured "safe" wiki directory:wikmd/wiki.py
Lines 102 to 107 in 04482d3
This is intended to prevent directory traversal. However, this does not prevent traversal to sibling directories whose names start with the same name as the configured wiki directory.
For example, consider the following directory structure:
If an attacker requests
/list/../wiki_secret/
, the following happens:folderpath
is set to../wiki_secret/
cfg.wiki_directory
is configured to serve out of thewiki
directory, sosafe_folder
becomes/home/user/wikmd/wiki
requested_path
is created by joiningcfg.wiki_directory
andfolderpath
, which results in/home/user/wikmd/wiki/../wiki_secret/
requested_path
is canonicalised withos.path.realpath()
, which returns/home/user/wikmd/wiki_secret
.os.path.commonprefix()
is called on the canonicalised request path andsafe_folder
, which searches character-by-character and returns the prefix substring up until the first difference. Since/home/user/wikmd/wiki
and/home/user/wikmd/wiki_secret
are the arguments, the result is/home/user/wikmd/wiki
safe_folder
, which is/home/user/wikmd/wiki
, and therefore the check passes./home/user/wikmd/wiki_secret
are listed.This can be demonstrated simply by sending
GET /list/../wiki_secret/
and two enter key presses to the server using netcat.Directory traversal in page display
There is no directory traversal protection inside the
file_page
function. As such, any.md
file on the system can be displayed.This can be trivially demonstrated with any default install by sending
GET /../README
and two enter presses to the server using netcat. This will return the HTML rendered copy of theREADME.md
file in the wikmd directory.Directory traversal in page edit
There is no directory traversal protection inside the
edit
function. This can be used both for reading and writing any.md
file on the system. This function is unauthenticated by default, but may be configured to require a password.When a GET request is sent to an
/edit/
route, this function displays the existing contents of the file. This can be demonstrated by sendingGET /edit/../README
and two enter presses to the server using netcat. This will display the HTML of the edit template with the contents of theREADME.md
file in the wikmd directory included.When a POST request is sent to an
/edit/
route, the following code path is taken:wikmd/wiki.py
Lines 276 to 282 in 04482d3
The
fetch_page_name()
function acquires the page name from thePN
form variable.The
page
variable contains the portion of the URL after/edit/
. No traversal check is performed. If the value provided in thePN
form variable does not match the path in the URL, the file specified by the URL is deleted. Since the path is vulnerable to traversal, any.md
file on the system can be deleted using this method.The
save()
function saves the page. Since the page name is taken from thePN
form variable without validation, this allows any.md
file on the system to be written to. Thesave()
function creates the full directory path to the target file name, so this enables the creation of directories anywhere on the system.Directory traversal in new page creation
The
add_new()
function handles/add_new
. This function is unauthenticated by default, but may be configured to require a password.The
PN
form variable is used to specify the name/path of the page to create, but no traversal protection is performed. This function calls intosave()
in the same way that the page edit functionality does, with the same impact.Directory traversal in remove
The
remove()
function handles/remove/
route requests. This function is unauthenticated by default, but may be configured to require a password.The path is taken from the URL and is not validated or protected against traversal. This allows for any
.md
file on the system to be deleted.Remediation
The existing
commonprefix()
traversal check inlist_wiki()
is broken and should be removed.The
safe_join()
function provided by Flask should be utilised instead ofos.path.join()
in all cases where one or more of the path arguments is untrusted. This function will raise aNotFound
exception if traversal out of the base directory is attempted.The text was updated successfully, but these errors were encountered: