Fossil SCM
Update the CGI extension documentation. Improved filename security in the CGI extension subsystem.
Commit
12c8cc709d3376ba65439b4c240c6683116215f287ea6631d3bf0e893c9c9b37
Parent
e91de28b69ca4e2…
2 files changed
+13
-12
+22
-2
+13
-12
| --- src/extcgi.c | ||
| +++ src/extcgi.c | ||
| @@ -110,13 +110,15 @@ | ||
| 110 | 110 | FILE *toChild = 0; /* FILE for sending to child */ |
| 111 | 111 | FILE *fromChild = 0; /* FILE for reading from child */ |
| 112 | 112 | int pidChild = 0; /* Process id of the child */ |
| 113 | 113 | int rc; /* Reply code from subroutine call */ |
| 114 | 114 | int nContent = -1; /* Content length */ |
| 115 | + const char *zPathInfo; /* Original PATH_INFO value */ | |
| 115 | 116 | Blob reply; /* The reply */ |
| 116 | 117 | char zLine[1000]; /* One line of the CGI reply */ |
| 117 | 118 | |
| 119 | + zPathInfo = P("PATH_INFO"); | |
| 118 | 120 | login_check_credentials(); |
| 119 | 121 | blob_init(&reply, 0, 0); |
| 120 | 122 | if( g.zExtRoot==0 ){ |
| 121 | 123 | zFailReason = "extroot is not set"; |
| 122 | 124 | goto ext_not_found; |
| @@ -127,13 +129,20 @@ | ||
| 127 | 129 | } |
| 128 | 130 | if( zName==0 ){ |
| 129 | 131 | zFailReason = "no path beyond /ext"; |
| 130 | 132 | goto ext_not_found; |
| 131 | 133 | } |
| 132 | - if( zName[0]=='.' || zName[0]=='-' ){ | |
| 133 | - zFailReason = "path element begins with '.' or '-'"; | |
| 134 | - goto ext_not_found; | |
| 134 | + for(i=0; zName[i]; i++){ | |
| 135 | + char c = zName[i]; | |
| 136 | + if( (c=='.' || c=='-') && (i==0 || zName[i-1]=='/') ){ | |
| 137 | + zFailReason = "path element begins with '.' or '-'"; | |
| 138 | + goto ext_not_found; | |
| 139 | + } | |
| 140 | + if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){ | |
| 141 | + zFailReason = "illegal character in path"; | |
| 142 | + goto ext_not_found; | |
| 143 | + } | |
| 135 | 144 | } |
| 136 | 145 | if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){ |
| 137 | 146 | zFailReason = "extroot is not a directory"; |
| 138 | 147 | goto ext_not_found; |
| 139 | 148 | } |
| @@ -143,18 +152,10 @@ | ||
| 143 | 152 | nScript = (int)strlen(zPath); |
| 144 | 153 | zScript = zPath; |
| 145 | 154 | }else{ |
| 146 | 155 | for(i=nRoot+1; zPath[i]; i++){ |
| 147 | 156 | char c = zPath[i]; |
| 148 | - if( (c=='.' || c=='-') && zPath[i-1]=='/' ){ | |
| 149 | - zFailReason = "path element begins with '.' or '-'"; | |
| 150 | - goto ext_not_found; | |
| 151 | - } | |
| 152 | - if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){ | |
| 153 | - zFailReason = "illegal character in path"; | |
| 154 | - goto ext_not_found; | |
| 155 | - } | |
| 156 | 157 | if( c=='/' ){ |
| 157 | 158 | int isDir, isFile; |
| 158 | 159 | zPath[i] = 0; |
| 159 | 160 | isDir = file_isdir(zPath, ExtFILE); |
| 160 | 161 | isFile = isDir==2 ? file_isfile(zPath, ExtFILE) : 0; |
| @@ -282,12 +283,12 @@ | ||
| 282 | 283 | if( zFailReason==0 ){ |
| 283 | 284 | document_render(&reply, zMime, zName, zName); |
| 284 | 285 | }else{ |
| 285 | 286 | cgi_set_status(404, "Not Found"); |
| 286 | 287 | @ <h1>Not Found</h1> |
| 287 | - @ <p>Page not found: %h(g.zPath)</p> | |
| 288 | + @ <p>Page not found: %h(zPathInfo)</p> | |
| 288 | 289 | if( g.perm.Debug ){ |
| 289 | 290 | @ <p>Reason for failure: %h(zFailReason)</p> |
| 290 | 291 | } |
| 291 | 292 | } |
| 292 | 293 | return; |
| 293 | 294 | } |
| 294 | 295 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -110,13 +110,15 @@ | |
| 110 | FILE *toChild = 0; /* FILE for sending to child */ |
| 111 | FILE *fromChild = 0; /* FILE for reading from child */ |
| 112 | int pidChild = 0; /* Process id of the child */ |
| 113 | int rc; /* Reply code from subroutine call */ |
| 114 | int nContent = -1; /* Content length */ |
| 115 | Blob reply; /* The reply */ |
| 116 | char zLine[1000]; /* One line of the CGI reply */ |
| 117 | |
| 118 | login_check_credentials(); |
| 119 | blob_init(&reply, 0, 0); |
| 120 | if( g.zExtRoot==0 ){ |
| 121 | zFailReason = "extroot is not set"; |
| 122 | goto ext_not_found; |
| @@ -127,13 +129,20 @@ | |
| 127 | } |
| 128 | if( zName==0 ){ |
| 129 | zFailReason = "no path beyond /ext"; |
| 130 | goto ext_not_found; |
| 131 | } |
| 132 | if( zName[0]=='.' || zName[0]=='-' ){ |
| 133 | zFailReason = "path element begins with '.' or '-'"; |
| 134 | goto ext_not_found; |
| 135 | } |
| 136 | if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){ |
| 137 | zFailReason = "extroot is not a directory"; |
| 138 | goto ext_not_found; |
| 139 | } |
| @@ -143,18 +152,10 @@ | |
| 143 | nScript = (int)strlen(zPath); |
| 144 | zScript = zPath; |
| 145 | }else{ |
| 146 | for(i=nRoot+1; zPath[i]; i++){ |
| 147 | char c = zPath[i]; |
| 148 | if( (c=='.' || c=='-') && zPath[i-1]=='/' ){ |
| 149 | zFailReason = "path element begins with '.' or '-'"; |
| 150 | goto ext_not_found; |
| 151 | } |
| 152 | if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){ |
| 153 | zFailReason = "illegal character in path"; |
| 154 | goto ext_not_found; |
| 155 | } |
| 156 | if( c=='/' ){ |
| 157 | int isDir, isFile; |
| 158 | zPath[i] = 0; |
| 159 | isDir = file_isdir(zPath, ExtFILE); |
| 160 | isFile = isDir==2 ? file_isfile(zPath, ExtFILE) : 0; |
| @@ -282,12 +283,12 @@ | |
| 282 | if( zFailReason==0 ){ |
| 283 | document_render(&reply, zMime, zName, zName); |
| 284 | }else{ |
| 285 | cgi_set_status(404, "Not Found"); |
| 286 | @ <h1>Not Found</h1> |
| 287 | @ <p>Page not found: %h(g.zPath)</p> |
| 288 | if( g.perm.Debug ){ |
| 289 | @ <p>Reason for failure: %h(zFailReason)</p> |
| 290 | } |
| 291 | } |
| 292 | return; |
| 293 | } |
| 294 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -110,13 +110,15 @@ | |
| 110 | FILE *toChild = 0; /* FILE for sending to child */ |
| 111 | FILE *fromChild = 0; /* FILE for reading from child */ |
| 112 | int pidChild = 0; /* Process id of the child */ |
| 113 | int rc; /* Reply code from subroutine call */ |
| 114 | int nContent = -1; /* Content length */ |
| 115 | const char *zPathInfo; /* Original PATH_INFO value */ |
| 116 | Blob reply; /* The reply */ |
| 117 | char zLine[1000]; /* One line of the CGI reply */ |
| 118 | |
| 119 | zPathInfo = P("PATH_INFO"); |
| 120 | login_check_credentials(); |
| 121 | blob_init(&reply, 0, 0); |
| 122 | if( g.zExtRoot==0 ){ |
| 123 | zFailReason = "extroot is not set"; |
| 124 | goto ext_not_found; |
| @@ -127,13 +129,20 @@ | |
| 129 | } |
| 130 | if( zName==0 ){ |
| 131 | zFailReason = "no path beyond /ext"; |
| 132 | goto ext_not_found; |
| 133 | } |
| 134 | for(i=0; zName[i]; i++){ |
| 135 | char c = zName[i]; |
| 136 | if( (c=='.' || c=='-') && (i==0 || zName[i-1]=='/') ){ |
| 137 | zFailReason = "path element begins with '.' or '-'"; |
| 138 | goto ext_not_found; |
| 139 | } |
| 140 | if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){ |
| 141 | zFailReason = "illegal character in path"; |
| 142 | goto ext_not_found; |
| 143 | } |
| 144 | } |
| 145 | if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){ |
| 146 | zFailReason = "extroot is not a directory"; |
| 147 | goto ext_not_found; |
| 148 | } |
| @@ -143,18 +152,10 @@ | |
| 152 | nScript = (int)strlen(zPath); |
| 153 | zScript = zPath; |
| 154 | }else{ |
| 155 | for(i=nRoot+1; zPath[i]; i++){ |
| 156 | char c = zPath[i]; |
| 157 | if( c=='/' ){ |
| 158 | int isDir, isFile; |
| 159 | zPath[i] = 0; |
| 160 | isDir = file_isdir(zPath, ExtFILE); |
| 161 | isFile = isDir==2 ? file_isfile(zPath, ExtFILE) : 0; |
| @@ -282,12 +283,12 @@ | |
| 283 | if( zFailReason==0 ){ |
| 284 | document_render(&reply, zMime, zName, zName); |
| 285 | }else{ |
| 286 | cgi_set_status(404, "Not Found"); |
| 287 | @ <h1>Not Found</h1> |
| 288 | @ <p>Page not found: %h(zPathInfo)</p> |
| 289 | if( g.perm.Debug ){ |
| 290 | @ <p>Reason for failure: %h(zFailReason)</p> |
| 291 | } |
| 292 | } |
| 293 | return; |
| 294 | } |
| 295 |
+22
-2
| --- www/serverext.wiki | ||
| +++ www/serverext.wiki | ||
| @@ -80,11 +80,11 @@ | ||
| 80 | 80 | that file to be executable, so it runs it as CGI and returns the result. |
| 81 | 81 | |
| 82 | 82 | The /sqlite-src-ext/checklist file is a |
| 83 | 83 | [https://wapp.tcl.tk|Wapp program]. The complete source code to the |
| 84 | 84 | this program can be seen at |
| 85 | -[https://www.sqlite.org/checklistapp/file/checklist.tcl]. | |
| 85 | +[https://www.sqlite.org/src/ext/checklist/self]. | |
| 86 | 86 | |
| 87 | 87 | There is a cascade of CGIs happening here. The webserver that receives |
| 88 | 88 | the initial HTTP request runs Fossil as a CGI based on the |
| 89 | 89 | "https://sqlite.org/src" portion of the URL. The Fossil instance then |
| 90 | 90 | runs the checklist sub-CGI based on the "/ext/checklists" suffix. The |
| @@ -213,11 +213,31 @@ | ||
| 213 | 213 | |
| 214 | 214 | Except for the three cases noted above, Fossil makes no changes or |
| 215 | 215 | additions to the CGI-generated content, but simply passes the verbatim |
| 216 | 216 | content back up the stack towards the requester. |
| 217 | 217 | |
| 218 | -<h2>5.0 Trouble-Shooting Hints</h2> | |
| 218 | +<h2>5.0 Filename Restrictions</h2> | |
| 219 | + | |
| 220 | +For security reasons, Fossil places restrictions on the names of files | |
| 221 | +in the extroot directory that can participate in the extension CGI | |
| 222 | +mechanism: | |
| 223 | + | |
| 224 | + 1. Filenames must consist of only ASCII alphanumeric characters, | |
| 225 | + ".", "_", and "-", and of course "/" as the file separator. | |
| 226 | + Files with names that includes spaces or | |
| 227 | + other punctuation or special characters are ignored. | |
| 228 | + | |
| 229 | + 2. No element of the pathname can begin with "." or "-". Files or | |
| 230 | + directories whose names begin with "." or "-" are ignored. | |
| 231 | + | |
| 232 | +If a CGI program requires separate data files, it is safe to put those | |
| 233 | +files in the same directory as the CGI program itself as long as the names | |
| 234 | +of the data files contain special characters that cause them to be ignored | |
| 235 | +by Fossil. For example, ensure that all datafile begin with "-" or | |
| 236 | +end with ",data" or "~data". | |
| 237 | + | |
| 238 | +<h2>6.0 Trouble-Shooting Hints</h2> | |
| 219 | 239 | |
| 220 | 240 | Remember that the /ext will return any file in the extroot directory |
| 221 | 241 | hierarchy as static content if the file is readable but not executable. |
| 222 | 242 | When initially setting up the /ext mechanism, it is sometimes helpful |
| 223 | 243 | to verify that you are able to receive static content prior to working |
| 224 | 244 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -80,11 +80,11 @@ | |
| 80 | that file to be executable, so it runs it as CGI and returns the result. |
| 81 | |
| 82 | The /sqlite-src-ext/checklist file is a |
| 83 | [https://wapp.tcl.tk|Wapp program]. The complete source code to the |
| 84 | this program can be seen at |
| 85 | [https://www.sqlite.org/checklistapp/file/checklist.tcl]. |
| 86 | |
| 87 | There is a cascade of CGIs happening here. The webserver that receives |
| 88 | the initial HTTP request runs Fossil as a CGI based on the |
| 89 | "https://sqlite.org/src" portion of the URL. The Fossil instance then |
| 90 | runs the checklist sub-CGI based on the "/ext/checklists" suffix. The |
| @@ -213,11 +213,31 @@ | |
| 213 | |
| 214 | Except for the three cases noted above, Fossil makes no changes or |
| 215 | additions to the CGI-generated content, but simply passes the verbatim |
| 216 | content back up the stack towards the requester. |
| 217 | |
| 218 | <h2>5.0 Trouble-Shooting Hints</h2> |
| 219 | |
| 220 | Remember that the /ext will return any file in the extroot directory |
| 221 | hierarchy as static content if the file is readable but not executable. |
| 222 | When initially setting up the /ext mechanism, it is sometimes helpful |
| 223 | to verify that you are able to receive static content prior to working |
| 224 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -80,11 +80,11 @@ | |
| 80 | that file to be executable, so it runs it as CGI and returns the result. |
| 81 | |
| 82 | The /sqlite-src-ext/checklist file is a |
| 83 | [https://wapp.tcl.tk|Wapp program]. The complete source code to the |
| 84 | this program can be seen at |
| 85 | [https://www.sqlite.org/src/ext/checklist/self]. |
| 86 | |
| 87 | There is a cascade of CGIs happening here. The webserver that receives |
| 88 | the initial HTTP request runs Fossil as a CGI based on the |
| 89 | "https://sqlite.org/src" portion of the URL. The Fossil instance then |
| 90 | runs the checklist sub-CGI based on the "/ext/checklists" suffix. The |
| @@ -213,11 +213,31 @@ | |
| 213 | |
| 214 | Except for the three cases noted above, Fossil makes no changes or |
| 215 | additions to the CGI-generated content, but simply passes the verbatim |
| 216 | content back up the stack towards the requester. |
| 217 | |
| 218 | <h2>5.0 Filename Restrictions</h2> |
| 219 | |
| 220 | For security reasons, Fossil places restrictions on the names of files |
| 221 | in the extroot directory that can participate in the extension CGI |
| 222 | mechanism: |
| 223 | |
| 224 | 1. Filenames must consist of only ASCII alphanumeric characters, |
| 225 | ".", "_", and "-", and of course "/" as the file separator. |
| 226 | Files with names that includes spaces or |
| 227 | other punctuation or special characters are ignored. |
| 228 | |
| 229 | 2. No element of the pathname can begin with "." or "-". Files or |
| 230 | directories whose names begin with "." or "-" are ignored. |
| 231 | |
| 232 | If a CGI program requires separate data files, it is safe to put those |
| 233 | files in the same directory as the CGI program itself as long as the names |
| 234 | of the data files contain special characters that cause them to be ignored |
| 235 | by Fossil. For example, ensure that all datafile begin with "-" or |
| 236 | end with ",data" or "~data". |
| 237 | |
| 238 | <h2>6.0 Trouble-Shooting Hints</h2> |
| 239 | |
| 240 | Remember that the /ext will return any file in the extroot directory |
| 241 | hierarchy as static content if the file is readable but not executable. |
| 242 | When initially setting up the /ext mechanism, it is sometimes helpful |
| 243 | to verify that you are able to receive static content prior to working |
| 244 |