Vulnerability: Directory traversal vulnerability in wiki.c in didiwiki allows remote attackers to read arbitrary files via the page parameter to api/page/get.
Vulnerability ID: CVE-2013-7448
CWE: CWE-22
Severity score: 2.9
Project: https://github.com/yarolig/didiwiki
Published: 2016-02-23
File: src/wiki.c
Commit message: page_name_is_good function
Commit author: Alexander Izmailov

Vulnerable version (before fix):
 wiki_show_footer(HttpResponse *res) 		       ); }  void wiki_handle_rest_call(HttpRequest  *req,  		      HttpResponse *res, wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && (access(page, R_OK) == 0))  	    { 	      http_response_printf(res, "%s", file_read(page)); 	      http_response_send(res); wiki_handle_rest_call(HttpRequest  *req, 	  if( ( (wikitext = http_request_param_get(req, "text")) != NULL) 	      && ( (page = http_request_param_get(req, "page")) != NULL)) 	    { 	      file_write(page, wikitext);	       	      http_response_printf(res, "success"); 	      http_response_send(res); 	      return; 	    } 	}       else if (!strcmp(func, "page/delete")) 	{ wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && (unlink(page) > 0)) 	    { 	      http_response_printf(res, "success"); 	      http_response_send(res); wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && (access(page, R_OK) == 0))  	    { 	      http_response_printf(res, "success"); 	      http_response_send(res); wiki_handle_http_request(HttpRequest *req)   /* A little safety. issue a malformed request for any paths,    * There shouldn't need to be any..    */   if (strchr(page, '/'))     {       http_response_set_status(res, 404, "Not Found");       http_response_printf(res, "<html><body>404 Not Found</body></html>\n");

Fixed version (after fix):
 wiki_show_footer(HttpResponse *res) 		       ); }  int page_name_is_good(char* page_name) { /* We should give access only to subdirs of didiwiki root.    I guess that check for absense of '/' is enough.     TODO: Use realpath() */     if (!page_name)         return FALSE;      if (!isalnum(page[0]))         return FALSE;      if (strstr(page, ".."))         return FALSE;      return TRUE; }   void wiki_handle_rest_call(HttpRequest  *req,  		      HttpResponse *res, wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && page_name_is_good(page) && (access(page, R_OK) == 0)) 	    { 	      http_response_printf(res, "%s", file_read(page)); 	      http_response_send(res); wiki_handle_rest_call(HttpRequest  *req, 	  if( ( (wikitext = http_request_param_get(req, "text")) != NULL) 	      && ( (page = http_request_param_get(req, "page")) != NULL)) 	    { 	  if (page_name_is_good(page)) 	    { 	      file_write(page, wikitext); 	      http_response_printf(res, "success"); 	      http_response_send(res); 	      return; 	    } 	    } 	}       else if (!strcmp(func, "page/delete")) 	{ wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && page_name_is_good(page) && (unlink(page) > 0)) 	    { 	      http_response_printf(res, "success"); 	      http_response_send(res); wiki_handle_rest_call(HttpRequest  *req, 	  if (page == NULL) 	    page = http_request_get_query_string(req);  	  if (page && page_name_is_good(page) && (access(page, R_OK) == 0)) 	    { 	      http_response_printf(res, "success"); 	      http_response_send(res); wiki_handle_http_request(HttpRequest *req)   /* A little safety. issue a malformed request for any paths,    * There shouldn't need to be any..    */   if (!page_name_is_good(page))     {       http_response_set_status(res, 404, "Not Found");       http_response_printf(res, "<html><body>404 Not Found</body></html>\n");

Patch:
patch: @@ -851,6 +851,26 @@ wiki_show_footer(HttpResponse *res)
 		       );
 }
 
+int page_name_is_good(char* page_name)
+{
+/* We should give access only to subdirs of didiwiki root.
+   I guess that check for absense of '/' is enough.
+
+   TODO: Use realpath()
+*/
+    if (!page_name)
+        return FALSE;
+
+    if (!isalnum(page[0]))
+        return FALSE;
+
+    if (strstr(page, ".."))
+        return FALSE;
+
+    return TRUE;
+}
+
+
 void
 wiki_handle_rest_call(HttpRequest  *req, 
 		      HttpResponse *res,
@@ -866,7 +886,7 @@ wiki_handle_rest_call(HttpRequest  *req,
 	  if (page == NULL)
 	    page = http_request_get_query_string(req);
 
-	  if (page && (access(page, R_OK) == 0)) 
+	  if (page && page_name_is_good(page) && (access(page, R_OK) == 0))
 	    {
 	      http_response_printf(res, "%s", file_read(page));
 	      http_response_send(res);
@@ -879,11 +899,14 @@ wiki_handle_rest_call(HttpRequest  *req,
 	  if( ( (wikitext = http_request_param_get(req, "text")) != NULL)
 	      && ( (page = http_request_param_get(req, "page")) != NULL))
 	    {
-	      file_write(page, wikitext);	      
+	  if (page_name_is_good(page))
+	    {
+	      file_write(page, wikitext);
 	      http_response_printf(res, "success");
 	      http_response_send(res);
 	      return;
 	    }
+	    }
 	}
       else if (!strcmp(func, "page/delete"))
 	{
@@ -892,7 +915,7 @@ wiki_handle_rest_call(HttpRequest  *req,
 	  if (page == NULL)
 	    page = http_request_get_query_string(req);
 
-	  if (page && (unlink(page) > 0))
+	  if (page && page_name_is_good(page) && (unlink(page) > 0))
 	    {
 	      http_response_printf(res, "success");
 	      http_response_send(res);
@@ -906,7 +929,7 @@ wiki_handle_rest_call(HttpRequest  *req,
 	  if (page == NULL)
 	    page = http_request_get_query_string(req);
 
-	  if (page && (access(page, R_OK) == 0)) 
+	  if (page && page_name_is_good(page) && (access(page, R_OK) == 0))
 	    {
 	      http_response_printf(res, "success");
 	      http_response_send(res);
@@ -1005,7 +1028,7 @@ wiki_handle_http_request(HttpRequest *req)
   /* A little safety. issue a malformed request for any paths,
    * There shouldn't need to be any..
    */
-  if (strchr(page, '/'))
+  if (!page_name_is_good(page))
     {
       http_response_set_status(res, 404, "Not Found");
       http_response_printf(res, "<html><body>404 Not Found</body></html>\n");
path: src/wiki.c