Fossil SCM
Initialize the $title and $login variable for TH1 used during header/footer generation to a similar string that has characters that are special to HTML escaped to similar-looking unicode characters. This is an defense against XSS attacks that might otherwise result due to errors in a custom skin where the $title variable is misused.
Commit
5ea6e15bf1fc942d67d53d085b4a67fedaafc13f45a6d1028a8bd637e9d513a3
Parent
caf286d7480727d…
2 files changed
+72
+2
-2
+72
| --- src/encode.c | ||
| +++ src/encode.c | ||
| @@ -141,10 +141,82 @@ | ||
| 141 | 141 | break; |
| 142 | 142 | } |
| 143 | 143 | } |
| 144 | 144 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 145 | 145 | } |
| 146 | + | |
| 147 | +/* | |
| 148 | +** Make the given string safe for HTML by converting syntax characters | |
| 149 | +** into alternatives that do not have the special syntactic meaning. | |
| 150 | +** | |
| 151 | +** < --> U+227a | |
| 152 | +** > --> U+227b | |
| 153 | +** & --> + | |
| 154 | +** " --> U+201d | |
| 155 | +** ' --> U+2019 | |
| 156 | +** | |
| 157 | +** Return a pointer to a new string obtained from fossil_malloc(). | |
| 158 | +*/ | |
| 159 | +char *html_lookalike(const char *z, int n){ | |
| 160 | + unsigned char c; | |
| 161 | + int i = 0; | |
| 162 | + int count = 0; | |
| 163 | + unsigned char *zOut; | |
| 164 | + const unsigned char *zIn = (const unsigned char*)z; | |
| 165 | + | |
| 166 | + if( n<0 ) n = strlen(z); | |
| 167 | + while( i<n ){ | |
| 168 | + switch( zIn[i] ){ | |
| 169 | + case '<': count += 3; break; | |
| 170 | + case '>': count += 3; break; | |
| 171 | + case '"': count += 3; break; | |
| 172 | + case '\'': count += 3; break; | |
| 173 | + case 0: n = i; break; | |
| 174 | + } | |
| 175 | + i++; | |
| 176 | + } | |
| 177 | + i = 0; | |
| 178 | + zOut = fossil_malloc( count+n+1 ); | |
| 179 | + if( count==0 ){ | |
| 180 | + memcpy(zOut, zIn, n); | |
| 181 | + zOut[n] = 0; | |
| 182 | + return (char*)zOut; | |
| 183 | + } | |
| 184 | + while( n-->0 ){ | |
| 185 | + c = *(zIn++); | |
| 186 | + switch( c ){ | |
| 187 | + case '<': | |
| 188 | + zOut[i++] = 0xe2; | |
| 189 | + zOut[i++] = 0x89; | |
| 190 | + zOut[i++] = 0xba; | |
| 191 | + break; | |
| 192 | + case '>': | |
| 193 | + zOut[i++] = 0xe2; | |
| 194 | + zOut[i++] = 0x89; | |
| 195 | + zOut[i++] = 0xbb; | |
| 196 | + break; | |
| 197 | + case '&': | |
| 198 | + zOut[i++] = '+'; | |
| 199 | + break; | |
| 200 | + case '"': | |
| 201 | + zOut[i++] = 0xe2; | |
| 202 | + zOut[i++] = 0x80; | |
| 203 | + zOut[i++] = 0x9d; | |
| 204 | + break; | |
| 205 | + case '\'': | |
| 206 | + zOut[i++] = 0xe2; | |
| 207 | + zOut[i++] = 0x80; | |
| 208 | + zOut[i++] = 0x99; | |
| 209 | + break; | |
| 210 | + default: | |
| 211 | + zOut[i++] = c; | |
| 212 | + break; | |
| 213 | + } | |
| 214 | + } | |
| 215 | + zOut[i] = 0; | |
| 216 | + return (char*)zOut; | |
| 217 | +} | |
| 146 | 218 | |
| 147 | 219 | |
| 148 | 220 | /* |
| 149 | 221 | ** Encode a string for HTTP. This means converting lots of |
| 150 | 222 | ** characters into the "%HH" where H is a hex digit. It also |
| 151 | 223 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -141,10 +141,82 @@ | |
| 141 | break; |
| 142 | } |
| 143 | } |
| 144 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 145 | } |
| 146 | |
| 147 | |
| 148 | /* |
| 149 | ** Encode a string for HTTP. This means converting lots of |
| 150 | ** characters into the "%HH" where H is a hex digit. It also |
| 151 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -141,10 +141,82 @@ | |
| 141 | break; |
| 142 | } |
| 143 | } |
| 144 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 145 | } |
| 146 | |
| 147 | /* |
| 148 | ** Make the given string safe for HTML by converting syntax characters |
| 149 | ** into alternatives that do not have the special syntactic meaning. |
| 150 | ** |
| 151 | ** < --> U+227a |
| 152 | ** > --> U+227b |
| 153 | ** & --> + |
| 154 | ** " --> U+201d |
| 155 | ** ' --> U+2019 |
| 156 | ** |
| 157 | ** Return a pointer to a new string obtained from fossil_malloc(). |
| 158 | */ |
| 159 | char *html_lookalike(const char *z, int n){ |
| 160 | unsigned char c; |
| 161 | int i = 0; |
| 162 | int count = 0; |
| 163 | unsigned char *zOut; |
| 164 | const unsigned char *zIn = (const unsigned char*)z; |
| 165 | |
| 166 | if( n<0 ) n = strlen(z); |
| 167 | while( i<n ){ |
| 168 | switch( zIn[i] ){ |
| 169 | case '<': count += 3; break; |
| 170 | case '>': count += 3; break; |
| 171 | case '"': count += 3; break; |
| 172 | case '\'': count += 3; break; |
| 173 | case 0: n = i; break; |
| 174 | } |
| 175 | i++; |
| 176 | } |
| 177 | i = 0; |
| 178 | zOut = fossil_malloc( count+n+1 ); |
| 179 | if( count==0 ){ |
| 180 | memcpy(zOut, zIn, n); |
| 181 | zOut[n] = 0; |
| 182 | return (char*)zOut; |
| 183 | } |
| 184 | while( n-->0 ){ |
| 185 | c = *(zIn++); |
| 186 | switch( c ){ |
| 187 | case '<': |
| 188 | zOut[i++] = 0xe2; |
| 189 | zOut[i++] = 0x89; |
| 190 | zOut[i++] = 0xba; |
| 191 | break; |
| 192 | case '>': |
| 193 | zOut[i++] = 0xe2; |
| 194 | zOut[i++] = 0x89; |
| 195 | zOut[i++] = 0xbb; |
| 196 | break; |
| 197 | case '&': |
| 198 | zOut[i++] = '+'; |
| 199 | break; |
| 200 | case '"': |
| 201 | zOut[i++] = 0xe2; |
| 202 | zOut[i++] = 0x80; |
| 203 | zOut[i++] = 0x9d; |
| 204 | break; |
| 205 | case '\'': |
| 206 | zOut[i++] = 0xe2; |
| 207 | zOut[i++] = 0x80; |
| 208 | zOut[i++] = 0x99; |
| 209 | break; |
| 210 | default: |
| 211 | zOut[i++] = c; |
| 212 | break; |
| 213 | } |
| 214 | } |
| 215 | zOut[i] = 0; |
| 216 | return (char*)zOut; |
| 217 | } |
| 218 | |
| 219 | |
| 220 | /* |
| 221 | ** Encode a string for HTTP. This means converting lots of |
| 222 | ** characters into the "%HH" where H is a hex digit. It also |
| 223 |
+2
-2
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -746,11 +746,11 @@ | ||
| 746 | 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | 747 | fossil_free(zDfltCsp); |
| 748 | 748 | Th_Store("nonce", zNonce); |
| 749 | 749 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 750 | 750 | Th_Store("project_description", db_get("project-description","")); |
| 751 | - if( zTitle ) Th_Store("title", zTitle); | |
| 751 | + if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); | |
| 752 | 752 | Th_Store("baseurl", g.zBaseURL); |
| 753 | 753 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | 754 | Th_Store("home", g.zTop); |
| 755 | 755 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 | 756 | if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath); |
| @@ -772,11 +772,11 @@ | ||
| 772 | 772 | Th_Store("mainmenu", style_get_mainmenu()); |
| 773 | 773 | stylesheet_url_var(); |
| 774 | 774 | image_url_var("logo"); |
| 775 | 775 | image_url_var("background"); |
| 776 | 776 | if( !login_is_nobody() ){ |
| 777 | - Th_Store("login", g.zLogin); | |
| 777 | + Th_Store("login", html_lookalike(g.zLogin,-1)); | |
| 778 | 778 | } |
| 779 | 779 | Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) ); |
| 780 | 780 | if( g.ftntsIssues[0] || g.ftntsIssues[1] || |
| 781 | 781 | g.ftntsIssues[2] || g.ftntsIssues[3] ){ |
| 782 | 782 | char buf[80]; |
| 783 | 783 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -746,11 +746,11 @@ | |
| 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | fossil_free(zDfltCsp); |
| 748 | Th_Store("nonce", zNonce); |
| 749 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 750 | Th_Store("project_description", db_get("project-description","")); |
| 751 | if( zTitle ) Th_Store("title", zTitle); |
| 752 | Th_Store("baseurl", g.zBaseURL); |
| 753 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | Th_Store("home", g.zTop); |
| 755 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 | if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath); |
| @@ -772,11 +772,11 @@ | |
| 772 | Th_Store("mainmenu", style_get_mainmenu()); |
| 773 | stylesheet_url_var(); |
| 774 | image_url_var("logo"); |
| 775 | image_url_var("background"); |
| 776 | if( !login_is_nobody() ){ |
| 777 | Th_Store("login", g.zLogin); |
| 778 | } |
| 779 | Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) ); |
| 780 | if( g.ftntsIssues[0] || g.ftntsIssues[1] || |
| 781 | g.ftntsIssues[2] || g.ftntsIssues[3] ){ |
| 782 | char buf[80]; |
| 783 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -746,11 +746,11 @@ | |
| 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | fossil_free(zDfltCsp); |
| 748 | Th_Store("nonce", zNonce); |
| 749 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 750 | Th_Store("project_description", db_get("project-description","")); |
| 751 | if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); |
| 752 | Th_Store("baseurl", g.zBaseURL); |
| 753 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | Th_Store("home", g.zTop); |
| 755 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 | if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath); |
| @@ -772,11 +772,11 @@ | |
| 772 | Th_Store("mainmenu", style_get_mainmenu()); |
| 773 | stylesheet_url_var(); |
| 774 | image_url_var("logo"); |
| 775 | image_url_var("background"); |
| 776 | if( !login_is_nobody() ){ |
| 777 | Th_Store("login", html_lookalike(g.zLogin,-1)); |
| 778 | } |
| 779 | Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) ); |
| 780 | if( g.ftntsIssues[0] || g.ftntsIssues[1] || |
| 781 | g.ftntsIssues[2] || g.ftntsIssues[3] ){ |
| 782 | char buf[80]; |
| 783 |