Fossil SCM

fossil-scm / src / security_audit.c
Blame History Raw 1044 lines
1
/*
2
** Copyright (c) 2017 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file implements various web pages use for running a security audit
19
** of a Fossil configuration.
20
*/
21
#include "config.h"
22
#include <assert.h>
23
#include "security_audit.h"
24
25
/*
26
** Return TRUE if any of the capability letters in zTest are found
27
** in the capability string zCap.
28
*/
29
static int hasAnyCap(const char *zCap, const char *zTest){
30
while( zTest[0] ){
31
if( strchr(zCap, zTest[0]) ) return 1;
32
zTest++;
33
}
34
return 0;
35
}
36
37
/*
38
** Parse the content-security-policy
39
** into separate fields, and return a pointer to a null-terminated
40
** array of pointers to strings, one entry for each field. Or return
41
** a NULL pointer if no CSP could be located in the header.
42
**
43
** Memory to hold the returned array and of the strings is obtained from
44
** a single memory allocation, which the caller should free to avoid a
45
** memory leak.
46
*/
47
static char **parse_content_security_policy(void){
48
char **azCSP = 0;
49
int nCSP = 0;
50
char *zAll;
51
char *zCopy;
52
int nAll = 0;
53
int jj;
54
int nSemi;
55
56
zAll = style_csp(0);
57
nAll = (int)strlen(zAll);
58
for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
59
azCSP = fossil_malloc( nAll+1+(nSemi+2)*sizeof(char*) );
60
zCopy = (char*)&azCSP[nSemi+2];
61
memcpy(zCopy,zAll,nAll);
62
zCopy[nAll] = 0;
63
while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
64
azCSP[0] = zCopy;
65
nCSP = 1;
66
for(jj=0; zCopy[jj]; jj++){
67
if( zCopy[jj]==';' ){
68
int k;
69
for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
70
zCopy[jj] = 0;
71
while( jj+1<nAll
72
&& (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
73
){
74
jj++;
75
}
76
assert( nCSP<nSemi+1 );
77
azCSP[nCSP++] = zCopy+jj;
78
}
79
}
80
assert( nCSP<=nSemi+2 );
81
azCSP[nCSP] = 0;
82
fossil_free(zAll);
83
return azCSP;
84
}
85
86
/*
87
** WEBPAGE: secaudit0
88
**
89
** Run a security audit of the current Fossil setup, looking
90
** for configuration problems that might allow unauthorized
91
** access to the repository.
92
**
93
** This page requires administrator access. It is usually
94
** accessed using the Admin/Security-Audit menu option
95
** from any of the default skins.
96
*/
97
void secaudit0_page(void){
98
const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
99
const char *zDevCap; /* Capabilities of user group "developer" */
100
const char *zReadCap; /* Capabilities of user group "reader" */
101
const char *zPubPages; /* GLOB pattern for public pages */
102
const char *zSelfCap; /* Capabilities of self-registered users */
103
int hasSelfReg = 0; /* True if able to self-register */
104
const char *zPublicUrl; /* Canonical access URL */
105
const char *zVulnReport; /* The vuln-report setting */
106
Blob cmd;
107
char *z;
108
int n, i;
109
CapabilityString *pCap;
110
char **azCSP; /* Parsed content security policy */
111
112
login_check_credentials();
113
if( !g.perm.Admin ){
114
login_needed(0);
115
return;
116
}
117
style_header("Security Audit");
118
@ <ol>
119
120
/* Step 1: Determine if the repository is public or private. "Public"
121
** means that any anonymous user on the internet can access all content.
122
** "Private" repos require (non-anonymous) login to access all content,
123
** though some content may be accessible anonymously.
124
*/
125
zAnonCap = db_text("", "SELECT fullcap(NULL)");
126
zDevCap = db_text("", "SELECT fullcap('v')");
127
zReadCap = db_text("", "SELECT fullcap('u')");
128
zPubPages = db_get("public-pages",0);
129
hasSelfReg = db_get_boolean("self-register",0);
130
pCap = capability_add(0, db_get("default-perms","u"));
131
capability_expand(pCap);
132
zSelfCap = capability_string(pCap);
133
capability_free(pCap);
134
if( hasAnyCap(zAnonCap,"as") ){
135
@ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
136
@ it grants administrator privileges to anonymous users. You
137
@ should <a href="takeitprivate">take this repository private</a>
138
@ immediately! Or, at least remove the Setup and Admin privileges
139
@ for users "anonymous" and "login" on the
140
@ <a href="setup_ulist">User Configuration</a> page.
141
}else if( hasAnyCap(zSelfCap,"as") && hasSelfReg ){
142
@ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
143
@ it grants administrator privileges to self-registered users. You
144
@ should <a href="takeitprivate">take this repository private</a>
145
@ and/or disable self-registration
146
@ immediately! Or, at least remove the Setup and Admin privileges
147
@ from the default permissions for new users.
148
}else if( hasAnyCap(zAnonCap,"y") ){
149
@ <li><p>This repository is <big><b>INSECURE</b></big> because
150
@ it allows anonymous users to push unversioned files.
151
@ Fix this by <a href="takeitprivate">taking the repository private</a>
152
@ or by removing the "y" permission from users "anonymous" and
153
@ "nobody" on the <a href="setup_ulist">User Configuration</a> page.
154
}else if( hasAnyCap(zSelfCap,"y") ){
155
@ <li><p>This repository is <big><b>INSECURE</b></big> because
156
@ it allows self-registered users to push unversioned files.
157
@ Fix this by <a href="takeitprivate">taking the repository private</a>
158
@ or by removing the "y" permission from the default permissions or
159
@ by disabling self-registration.
160
}else if( hasAnyCap(zAnonCap,"goz") ){
161
@ <li><p>This repository is <big><b>PUBLIC</b></big>. All
162
@ checked-in content can be accessed by anonymous users.
163
@ <a href="takeitprivate">Take it private</a>.<p>
164
}else if( hasAnyCap(zSelfCap,"goz") && hasSelfReg ){
165
@ <li><p>This repository is <big><b>PUBLIC</b></big> because all
166
@ checked-in content can be accessed by self-registered users.
167
@ This repostory would be private if you disabled self-registration.</p>
168
}else if( !hasAnyCap(zAnonCap, "jrwy234567")
169
&& (!hasSelfReg || !hasAnyCap(zSelfCap, "jrwy234567"))
170
&& (zPubPages==0 || zPubPages[0]==0) ){
171
@ <li><p>This repository is <big><b>Completely PRIVATE</b></big>.
172
@ A valid login and password is required to access any content.
173
}else{
174
@ <li><p>This repository is <big><b>Mostly PRIVATE</b></big>.
175
@ A valid login and password is usually required, however some
176
@ content can be accessed either anonymously or by self-registered
177
@ users:
178
@ <ul>
179
if( hasSelfReg ){
180
if( hasAnyCap(zAnonCap,"j") || hasAnyCap(zSelfCap,"j") ){
181
@ <li> Wiki pages
182
}
183
if( hasAnyCap(zAnonCap,"r") || hasAnyCap(zSelfCap,"r") ){
184
@ <li> Tickets
185
}
186
if( hasAnyCap(zAnonCap,"234567") || hasAnyCap(zSelfCap,"234567") ){
187
@ <li> Forum posts
188
}
189
}
190
if( zPubPages && zPubPages[0] ){
191
Glob *pGlob = glob_create(zPubPages);
192
int i;
193
@ <li> "Public Pages" are URLs that match any of these GLOB patterns:
194
@ <p><ul>
195
for(i=0; i<pGlob->nPattern; i++){
196
@ <li> %h(pGlob->azPattern[i])
197
}
198
@ </ul>
199
@ <p>Anoymous users are vested with capabilities "%h(zSelfCap)" on
200
@ public pages. See the "Public Pages" entry in the
201
@ "User capability summary" below.
202
}
203
@ </ul>
204
if( zPubPages && zPubPages[0] ){
205
@ <p>Change GLOB patterns exceptions using the "Public pages" setting
206
@ on the <a href="setup_access">Access Settings</a> page.</p>
207
}
208
}
209
210
zPublicUrl = public_url();
211
if( zPublicUrl!=0 ){
212
int nOther = db_int(0, "SELECT count(*) FROM config"
213
" WHERE name GLOB 'baseurl:*'"
214
" AND name<>'baseurl:%q'", zPublicUrl);
215
@ <li><p>The <a href="setup_config#eurl">canonical URL</a> for this
216
@ repository is <a href="%s(zPublicUrl)">%h(zPublicUrl)</a>.
217
if( nOther==1 ){
218
@ This is also <a href="urllist?urlonly">1 other URL</a> that has
219
@ been used to access this repository.
220
}else if( nOther>=2 ){
221
@ There are also
222
@ <a href="urllist?all&urlonly">%d(nOther) other URLs</a> that have
223
@ been used to access this repository.
224
}
225
}else{
226
int nUrl = db_int(0, "SELECT count(*) FROM config"
227
" WHERE name GLOB 'baseurl:*'");
228
@ <li><p>This repository does not have a
229
@ <a href="setup_config#eurl">canonical access URL</a>.
230
if( nUrl==1 ){
231
@ There is
232
@ <a href="urllist?urlonly">1 non-canonical URL</a>
233
@ that has been used to access this repository.
234
}else if( nUrl>=2 ){
235
@ There are
236
@ <a href="urllist?all&urlonly">%d(nUrl) non-canonical URLs</a>
237
@ that have been used to access this repository.
238
}
239
}
240
241
/* Make sure the HTTPS is required for login, at least, so that the
242
** password does not go across the Internet in the clear.
243
*/
244
if( db_get_int("redirect-to-https",0)==0 ){
245
@ <li><p><b>WARNING:</b>
246
@ Sensitive material such as login passwords can be sent over an
247
@ unencrypted connection.
248
@ Fix this by changing the "Redirect to HTTPS" setting on the
249
@ <a href="setup_access">Access Control</a> page. If you were using
250
@ the old "Redirect to HTTPS on Login Page" setting, switch to the
251
@ new setting: it has a more secure implementation.
252
}
253
254
#ifdef FOSSIL_ENABLE_TH1_DOCS
255
/* The use of embedded TH1 is dangerous. Warn if it is possible.
256
*/
257
if( !Th_AreDocsEnabled() ){
258
@ <li><p>
259
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS. TH1 docs
260
@ are disabled for this particular repository, so you are safe for
261
@ now. However, to prevent future problems caused by accidentally
262
@ enabling TH1 docs in the future, it is recommended that you
263
@ recompile Fossil without the -DFOSSIL_ENABLE_TH1_DOCS flag.</p>
264
}else{
265
@ <li><p><b>DANGER:</b>
266
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS and TH1 docs
267
@ are enabled for this repository. Anyone who can check-in or push
268
@ to this repository can create a malicious TH1 script and then cause
269
@ that script to be run on the server. This is a serious security concern.
270
@ TH1 docs should only be enabled for repositories with a very limited
271
@ number of trusted committers, and the repository should be monitored
272
@ closely to ensure no hostile content sneaks in. If a bad TH1 script
273
@ does make it into the repository, the only want to prevent it from
274
@ being run is to shun it.</p>
275
@
276
@ <p>Disable TH1 docs by recompiling Fossil without the
277
@ -DFOSSIL_ENABLE_TH1_DOCS flag, and/or clear the th1-docs setting
278
@ and ensure that the TH1_ENABLE_DOCS environment variable does not
279
@ exist in the environment.</p>
280
}
281
#endif
282
283
#if FOSSIL_ENABLE_TCL
284
@ <li><p>
285
if( db_get_boolean("tcl",0) ){
286
#ifdef FOSSIL_ENABLE_TH1_DOCS
287
if( Th_AreDocsEnabled() ){
288
@ <b>DANGER:</b>
289
}else{
290
@ <b>WARNING:</b>
291
}
292
#else
293
@ <b>WARNING:</b>
294
#endif
295
@ This server is compiled with -DFOSSIL_ENABLE_TCL and Tcl integration
296
@ is enabled for this repository. Anyone who can execute malicious
297
@ TH1 script on that server can also execute arbitrary Tcl script
298
@ under the identity of the operating system process of that server.
299
@ This is a serious security concern.</p>
300
@
301
@ <p>Disable Tcl integration by recompiling Fossil without the
302
@ -DFOSSIL_ENABLE_TCL flag, and/or clear the 'tcl' setting.</p>
303
}else{
304
@ This server is compiled with -DFOSSIL_ENABLE_TCL. Tcl integration
305
@ is disabled for this particular repository, so you are safe for
306
@ now. However, to prevent potential problems caused by accidentally
307
@ enabling Tcl integration in the future, it is recommended that you
308
@ recompile Fossil without the -DFOSSIL_ENABLE_TCL flag.</p>
309
}
310
#endif
311
312
/* Anonymous users should not be able to harvest email addresses
313
** from tickets.
314
*/
315
if( hasAnyCap(zAnonCap, "e") ){
316
@ <li><p><b>WARNING:</b>
317
@ Anonymous users can view email addresses and other personally
318
@ identifiable information on tickets.
319
@ Fix this by removing the "Email" privilege
320
@ (<a href="setup_ucap_list">capability "e"</a>) from users
321
@ "anonymous" and "nobody" on the
322
@ <a href="setup_ulist">User Configuration</a> page.
323
}
324
325
/* Anonymous users probably should not be allowed to push content
326
** to the repository.
327
*/
328
if( hasAnyCap(zAnonCap, "i") ){
329
@ <li><p><b>WARNING:</b>
330
@ Anonymous users can push new check-ins into the repository.
331
@ Fix this by removing the "Check-in" privilege
332
@ (<a href="setup_ucap_list">capability</a> "i") from users
333
@ "anonymous" and "nobody" on the
334
@ <a href="setup_ulist">User Configuration</a> page.
335
}
336
337
/* Anonymous users probably should not be allowed act as moderators
338
** for wiki or tickets.
339
*/
340
if( hasAnyCap(zAnonCap, "lq5") ){
341
@ <li><p><b>WARNING:</b>
342
@ Anonymous users can act as moderators for wiki, tickets, or
343
@ forum posts. This defeats the whole purpose of moderation.
344
@ Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
345
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
346
@ from users "anonymous" and "nobody"
347
@ on the <a href="setup_ulist">User Configuration</a> page.
348
}
349
350
/* Check to see if any TH1 scripts are configured to run on a sync
351
*/
352
if( db_exists("SELECT 1 FROM config WHERE name GLOB 'xfer-*-script'"
353
" AND length(value)>0") ){
354
@ <li><p><b>WARNING:</b>
355
@ TH1 scripts might be configured to run on any sync, push, pull, or
356
@ clone operation. See the <a href="%R/xfersetup">/xfersetup</a>
357
@ page for more information. These TH1 scripts are a potential
358
@ security concern and so should be carefully audited by a human.
359
}
360
361
/* The strict-manifest-syntax setting should be on. */
362
if( db_get_boolean("strict-manifest-syntax",1)==0 ){
363
@ <li><p><b>WARNING:</b>
364
@ The "strict-manifest-syntax" flag is off. This is a security
365
@ risk. Turn this setting on (its default) to protect the users
366
@ of this repository.
367
}
368
369
zVulnReport = db_get("vuln-report","log");
370
if( fossil_strcmp(zVulnReport,"block")!=0
371
&& fossil_strcmp(zVulnReport,"fatal")!=0
372
){
373
@ <li><p><b>WARNING:</b>
374
@ The <a href="%R/help/vuln-report">vuln-report setting</a>
375
@ has a value of "%h(zVulnReport)". This disables defenses against
376
@ XSS or SQL-injection vulnerabilities caused by coding errors in
377
@ custom TH1 scripts. For the best security, change
378
@ the value of the vuln-report setting to "block" or "fatal".
379
}
380
381
/* Obsolete: */
382
if( hasAnyCap(zAnonCap, "d") ||
383
hasAnyCap(zDevCap, "d") ||
384
hasAnyCap(zReadCap, "d") ){
385
@ <li><p><b>WARNING:</b>
386
@ One or more users has the <a
387
@ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">obsolete</a>
388
@ "d" capability. You should remove it using the
389
@ <a href="setup_ulist">User Configuration</a> page in case we
390
@ ever reuse the letter for another purpose.
391
}
392
393
/* If anonymous users are allowed to create new Wiki, then
394
** wiki moderation should be activated to prevent spam.
395
*/
396
if( hasAnyCap(zAnonCap, "fk") ){
397
if( db_get_boolean("modreq-wiki",0)==0 ){
398
@ <li><p><b>WARNING:</b>
399
@ Anonymous users can create or edit wiki without moderation.
400
@ This can result in robots inserting lots of wiki spam into
401
@ repository.
402
@ Fix this by removing the "New-Wiki" and "Write-Wiki"
403
@ privileges from users "anonymous" and "nobody" on the
404
@ <a href="setup_ulist">User Configuration</a> page or
405
@ by enabling wiki moderation on the
406
@ <a href="setup_modreq">Moderation Setup</a> page.
407
}else{
408
@ <li><p>
409
@ Anonymous users can create or edit wiki, but moderator
410
@ approval is required before the edits become permanent.
411
}
412
}
413
414
/* Anonymous users should not be able to create trusted forum
415
** posts.
416
*/
417
if( hasAnyCap(zAnonCap, "456") ){
418
@ <li><p><b>WARNING:</b>
419
@ Anonymous users can create forum posts that are
420
@ accepted into the permanent record without moderation.
421
@ This can result in robots generating spam on forum posts.
422
@ Fix this by removing the "WriteTrusted-Forum" privilege
423
@ (<a href="setup_ucap_list">capabilities</a> "456") from
424
@ users "anonymous" and "nobody" on the
425
@ <a href="setup_ulist">User Configuration</a> page or
426
}
427
428
/* Anonymous users should not be able to send announcements.
429
*/
430
if( hasAnyCap(zAnonCap, "A") ){
431
@ <li><p><b>WARNING:</b>
432
@ Anonymous users can send announcements to anybody who is signed
433
@ up to receive announcements. This can result in spam.
434
@ Fix this by removing the "Announce" privilege
435
@ (<a href="setup_ucap_list">capability</a> "A") from
436
@ users "anonymous" and "nobody" on the
437
@ <a href="setup_ulist">User Configuration</a> page or
438
}
439
440
/* Administrative privilege should only be provided to
441
** specific individuals, not to entire classes of people.
442
** And not too many people should have administrator privilege.
443
*/
444
z = db_text(0,
445
"SELECT group_concat("
446
"printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
447
"' and ')"
448
" FROM user"
449
" WHERE cap GLOB '*[as]*'"
450
" AND login in ('anonymous','nobody','reader','developer')"
451
);
452
if( z && z[0] ){
453
@ <li><p><b>WARNING:</b>
454
@ Administrative privilege ('a' or 's')
455
@ is granted to an entire class of users: %s(z).
456
@ Administrative privilege should only be
457
@ granted to specific individuals.
458
}
459
n = db_int(0,"SELECT count(*) FROM user WHERE fullcap(cap) GLOB '*[as]*'");
460
if( n==0 ){
461
@ <li><p>
462
@ No users have administrator privilege.
463
}else{
464
z = db_text(0,
465
"SELECT group_concat("
466
"printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
467
"', ')"
468
" FROM user"
469
" WHERE fullcap(cap) GLOB '*[as]*'"
470
);
471
@ <li><p>
472
@ Users with administrator privilege are: %s(z)
473
fossil_free(z);
474
if( n>3 ){
475
@ <li><p><b>WARNING:</b>
476
@ Administrator privilege is granted to
477
@ <a href='setup_ulist?with=as'>%d(n) users</a>.
478
@ Ideally, administrator privilege ('s' or 'a') should only
479
@ be granted to one or two users.
480
}
481
}
482
483
/* The push-unversioned privilege should only be provided to
484
** specific individuals, not to entire classes of people.
485
** And no too many people should have this privilege.
486
*/
487
z = db_text(0,
488
"SELECT group_concat("
489
"printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
490
"' and ')"
491
" FROM user"
492
" WHERE cap GLOB '*y*'"
493
" AND login in ('anonymous','nobody','reader','developer')"
494
);
495
if( z && z[0] ){
496
@ <li><p><b>WARNING:</b>
497
@ The "Write-Unver" privilege is granted to an entire class of users: %s(z).
498
@ The Write-Unver privilege should only be granted to specific individuals.
499
fossil_free(z);
500
}
501
n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*y*'");
502
if( n>0 ){
503
z = db_text(0,
504
"SELECT group_concat("
505
"printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),', ')"
506
" FROM user WHERE fullcap(cap) GLOB '*y*'"
507
);
508
@ <li><p>
509
@ Users with "Write-Unver" privilege: %s(z)
510
fossil_free(z);
511
if( n>3 ){
512
@ <p><b>Caution:</b>
513
@ The "Write-Unver" privilege ('y') is granted to an excessive
514
@ number of users (%d(n)).
515
@ Ideally, the Write-Unver privilege should only
516
@ be granted to one or two users.
517
}
518
}
519
520
/* Providing hyperlink capability to user "nobody" can lead to robots
521
** making excessive requests resulting in DoS
522
*/
523
if( db_exists("SELECT 1 FROM user WHERE login='nobody' AND cap GLOB '*h*'") ){
524
int nobodyId = db_int(0,"SELECT uid FROM user WHERE login='nobody'");
525
int anonId = db_int(0,
526
"SELECT uid FROM user WHERE login='anonymous' AND cap NOT GLOB '*h*'");
527
@ <li><p>
528
@ User "nobody" has "Hyperlink" privilege ('h') which can lead to
529
@ robots walking a nearly endless progression of pages on public-facing
530
@ repositories, causing excessive server load and possible DoS.
531
@ Suggested remediation:
532
@ <ol type="a">
533
@ <li>Remove the 'h' privilege from the
534
@ <a href="%R/setup_uedit?id=%d(nobodyId)">'nobody' user</a> so that
535
@ robots cannot see hyperlinks.
536
@ <li>Activate <a href="%R/setup_robot">autohyperlink</a> so that
537
@ human readers can still see hyperlinks even if they are not logged in.
538
@ Set the delay to at least 50 milliseconds and require a mouse
539
@ event for maximum robot defense.
540
if( anonId>0 ){
541
@ <li>Perhaps set the 'h' privilege on the
542
@ <a href="%R/setup_uedit?id=%d(anonId)">'anonymous' user</a> so
543
@ that humans that have javascript disabled in their browsers can
544
@ still see hyperlinks if they will log in as "anonymous".
545
}
546
@ </ol>
547
}
548
549
/* Notify if REMOTE_USER or HTTP_AUTHENTICATION is used for login.
550
*/
551
if( db_get_boolean("remote_user_ok", 0) ){
552
@ <li><p><b>Caution:</b>
553
@ This repository trusts that the REMOTE_USER environment variable set
554
@ up by the webcontains the name of an authenticated user.
555
@ Fossil's built-in authentication mechanism is bypassed.
556
@ Fix this by deactivating the "Allow REMOTE_USER authentication"
557
@ checkbox on the <a href="setup_access">Access Control</a> page.
558
}
559
if( db_get_boolean("http_authentication_ok", 0) ){
560
@ <li><p><b>Caution:</b>
561
@ This repository trusts that the HTTP_AUTHENTICATION environment
562
@ variable set up by the webserver contains the name of an
563
@ authenticated user.
564
@ Fossil's built-in authentication mechanism is bypassed.
565
@ Fix this by deactivating the "Allow HTTP_AUTHENTICATION authentication"
566
@ checkbox on the <a href="setup_access">Access Control</a> page.
567
}
568
569
/* Logging should be turned on
570
*/
571
if( db_get_boolean("access-log",1)==0 ){
572
@ <li><p>
573
@ The <a href="access_log">User Log</a> is disabled. The user log
574
@ keeps a record of successful and unsuccessful login attempts and is
575
@ useful for security monitoring.
576
}
577
if( db_get_boolean("admin-log",1)==0 ){
578
@ <li><p>
579
@ The <a href="admin_log">Administrative Log</a> is disabled.
580
@ The administrative log provides a record of configuration changes
581
@ and is useful for security monitoring.
582
}
583
584
#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE)
585
/* Make sure that the load-average limiter is armed and working */
586
if( load_average()==0.0 ){
587
@ <li><p>
588
@ Unable to get the system load average. This can prevent Fossil
589
@ from throttling expensive operations during peak demand.
590
@ If running in a chroot jail on Linux, verify that the /proc
591
@ filesystem is mounted within the jail, so that the load average
592
@ can be obtained from the /proc/loadavg file.
593
}else {
594
double r = fossil_atof(db_get("max-loadavg", "0.0"));
595
if( r<=0.0 ){
596
@ <li><p>
597
@ Load average limiting is turned off. This can cause the server
598
@ to bog down if many requests for expensive services (such as
599
@ large diffs or tarballs) arrive at about the same time.
600
@ To fix this, set the
601
@ <a href='%R/setup_access#slal'>"Server Load Average Limit"</a> on the
602
@ <a href='%R/setup_access'>Access Control</a> page to the approximate
603
@ the number of available cores on your server, or maybe just a little
604
@ less.
605
}else if( r>=8.0 ){
606
@ <li><p>
607
@ The <a href='%R/setup_access#slal'>"Server Load Average Limit"</a> on
608
@ the <a href="setup_access">Access Control</a> page is set to %g(r),
609
@ which seems high. Is this server really a %d((int)r)-core machine?
610
}
611
}
612
#endif
613
614
if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
615
@ <li><p>
616
@ The server error log is disabled.
617
@ To set up an error log,
618
if( fossil_strcmp(g.zCmdName, "cgi")==0 ){
619
@ make an entry like "errorlog: <i>FILENAME</i>" in the
620
@ CGI script at %h(P("SCRIPT_FILENAME")).
621
}else{
622
@ add the "--errorlog <i>FILENAME</i>" option to the
623
@ "%h(g.argv[0]) %h(g.zCmdName)" command that launched this server.
624
}
625
}else{
626
FILE *pTest = fossil_fopen(g.zErrlog,"a");
627
if( pTest==0 ){
628
@ <li><p>
629
@ <b>Error:</b>
630
@ There is an error log at "%h(g.zErrlog)" but that file is not
631
@ writable and so no logging will occur.
632
}else{
633
fclose(pTest);
634
@ <li><p>
635
@ The error log at "<a href='%R/errorlog'>%h(g.zErrlog)</a>" is
636
@ %,lld(file_size(g.zErrlog, ExtFILE)) bytes in size.
637
}
638
}
639
640
if( g.zExtRoot ){
641
int nFile;
642
int nCgi;
643
ext_files();
644
nFile = db_int(0, "SELECT count(*) FROM sfile");
645
nCgi = nFile==0 ? 0 : db_int(0,"SELECT count(*) FROM sfile WHERE isexe");
646
@ <li><p> CGI Extensions are enabled with a document root
647
@ at <a href='%R/extfilelist'>%h(g.zExtRoot)</a> holding
648
@ %d(nCgi) CGIs and %d(nFile-nCgi) static content and data files.
649
}
650
651
if( fileedit_glob()!=0 ){
652
@ <li><p><a href='%R/fileedit'>Online File Editing</a> is enabled
653
@ for this repository. Clear the
654
@ <a href='%R/setup_settings'>"fileedit-glob" setting</a> to
655
@ disable online editing.</p>
656
}
657
658
@ <li><p> User capability summary:
659
capability_summary();
660
661
662
azCSP = parse_content_security_policy();
663
if( azCSP==0 ){
664
@ <li><p> WARNING: No Content Security Policy (CSP) is specified in the
665
@ header. Though not required, a strong CSP is recommended. Fossil will
666
@ automatically insert an appropriate CSP if you let it generate the
667
@ HTML <tt>&lt;head&gt;</tt> element by omitting <tt>&lt;body&gt;</tt>
668
@ from the header configuration in your customized skin.
669
@
670
}else{
671
int ii;
672
@ <li><p> Content Security Policy:
673
@ <ol type="a">
674
for(ii=0; azCSP[ii]; ii++){
675
@ <li>%h(azCSP[ii])
676
}
677
@ </
678
fossil_free(azCSP);
679
680
if( alert_enabled() ){
681
char * zListId = db_get("email-listid", 0);
682
@ <li><p> Email alert configuration summary:
683
if( !zListId || !zListId[0] ){
684
@ <br><strong>WARNING:</strong> <code>email-listid</code> is not set,
685
@ so notifications will not include unsubscribe links.
686
}
687
fossil_free(zListId);
688
@ <table class="label-value">
689
stats_for_email();
690
@ </table>
691
}else{
692
@ <li><p> Email alerts are disabled
693
}
694
695
n = db_int(0,"SELECT count(*) FROM ("
696
"SELECT rid FROM phantom EXCEPT SELECT rid FROM private)");
697
if( n>0 ){
698
@ <li><p>\
699
@ There exists public phantom artifacts in this repository, shown below.
700
@ Phantom artifacts are artifacts whose hash name is referenced by some
701
@ other artifact but whose content is unknown. Some phantoms are marked
702
@ private and those are ignored. But public phantoms cause unnecessary
703
@ sync traffic and might represent malicious attempts to corrupt the
704
@ repository structure.
705
@ </p><p>
706
@ To suppress unnecessary sync traffic caused by phantoms, add the RID
707
@ of each phantom to the "private" table. Example:
708
@ <blockquote><pre>
709
@ INSERT INTO private SELECT rid FROM blob WHERE content IS NULL;
710
@ </pre></blockquote>
711
@ </p>
712
table_of_public_phantoms();
713
@ </li>
714
}
715
716
@ <li><p>Robot Defenses:
717
@ <ol type="a">
718
switch( db_get_int("auto-hyperlink",1) ){
719
default:
720
@ <li> No auto-enable of hyperlinks.
721
break;
722
case 1:
723
@ <li> Hyperlinks auto-enabled based on UserAgent and Javascript.
724
break;
725
case 2:
726
@ <li> Hyperlinks auto-enabled based on UserAgent only.
727
break;
728
}
729
z = db_get("max-loadavg",0);
730
if( z && fossil_atof(z)>0.0 ){
731
@ <li> Maximum load average for expensive requests: %h(z);
732
}else{
733
@ <li> No limits on the load average
734
}
735
z = db_get("robot-restrict",0);
736
if( z==0 ){
737
@ <li> No complex-request constraints on robots
738
}else{
739
@ <li> Complex requests limited for pages matching: %h(z)
740
}
741
@ </ol>
742
743
blob_init(&cmd, 0, 0);
744
for(i=0; g.argvOrig[i]!=0; i++){
745
blob_append_escaped_arg(&cmd, g.argvOrig[i], 0);
746
}
747
@ <li><p>
748
if( g.zCgiFile ){
749
Blob fullname;
750
blob_init(&fullname, 0, 0);
751
file_canonical_name(g.zCgiFile, &fullname, 0);
752
@ The CGI control file for this page is "%h(blob_str(&fullname))".
753
}
754
@ The command that generated this page:
755
@ <blockquote>
756
@ <tt>%h(blob_str(&cmd))</tt>
757
@ </blockquote></li>
758
blob_zero(&cmd);
759
760
@ </ol>
761
style_finish_page();
762
}
763
764
/*
765
** WEBPAGE: takeitprivate
766
**
767
** Disable anonymous access to this website
768
*/
769
void takeitprivate_page(void){
770
login_check_credentials();
771
if( !g.perm.Admin ){
772
login_needed(0);
773
return;
774
}
775
if( P("cancel") ){
776
/* User pressed the cancel button. Go back */
777
cgi_redirect("secaudit0");
778
}
779
if( P("apply") ){
780
db_unprotect(PROTECT_ALL);
781
db_multi_exec(
782
"UPDATE user SET cap=''"
783
" WHERE login IN ('nobody','anonymous');"
784
"DELETE FROM config WHERE name='public-pages';"
785
);
786
db_set("self-register","0",0);
787
db_protect_pop();
788
cgi_redirect("secaudit0");
789
}
790
style_header("Make This Website Private");
791
@ <p>Click the "Make It Private" button below to disable all
792
@ anonymous access to this repository. A valid login and password
793
@ will be required to access this repository after clicking that
794
@ button.</p>
795
@
796
@ <p>Click the "Cancel" button to leave things as they are.</p>
797
@
798
@ <form action="%s(g.zPath)" method="post">
799
@ <input type="submit" name="apply" value="Make It Private">
800
@ <input type="submit" name="cancel" value="Cancel">
801
@ </form>
802
803
style_finish_page();
804
}
805
806
/*
807
** Output a message explaining that no error log is available.
808
*/
809
static void no_error_log_available(void){
810
@ <p>No error log is configured.
811
if( g.zCgiFile==0 ){
812
@ To create an error log, add the "--errorlog FILENAME"
813
@ command-line option to the command that launches the Fossil server.
814
}else{
815
Blob fullname;
816
blob_init(&fullname, 0, 0);
817
file_canonical_name(g.zCgiFile, &fullname, 0);
818
@ To create an error log, edit the CGI control file
819
@ named "%h(blob_str(&fullname))" to add a line like this:
820
@ <blockquote><pre>
821
@ errorlog: <i>FILENAME</i>
822
@ </pre></blockquote>
823
blob_reset(&fullname);
824
}
825
}
826
827
/*
828
** WEBPAGE: errorlog
829
**
830
** Show the content of the error log. Only the administrator can view
831
** this page.
832
**
833
** y=0x001 Show only hack attempts
834
** y=0x002 Show only panics and assertion faults
835
** y=0x004 Show hung backoffice processes
836
** y=0x008 Show POST requests from a different origin
837
** y=0x010 Show SQLITE_AUTH and similar
838
** y=0x020 Show SMTP error reports
839
** y=0x040 Show TH1 vulnerability reports
840
** y=0x080 Show SQL errors
841
** y=0x100 Show timeouts
842
** y=0x200 Show WAL recoveries
843
** y=0x8000 Show other uncategorized messages
844
**
845
** If y is omitted or is zero, a count of the various message types is
846
** shown.
847
*/
848
void errorlog_page(void){
849
i64 szFile;
850
FILE *in;
851
char *zLog;
852
const char *zType = P("y");
853
static const int eAllTypes = 0x83ff;
854
long eType = 0;
855
int bOutput = 0;
856
int prevWasTime = 0;
857
int nHack = 0;
858
int nPanic = 0;
859
int nOther = 0;
860
int nHang = 0;
861
int nXPost = 0;
862
int nAuth = 0;
863
int nSmtp = 0;
864
int nVuln = 0;
865
int nSqlErr = 0;
866
int nTimeout = 0;
867
int nRecover = 0;
868
char z[10000];
869
char zTime[10000];
870
871
login_check_credentials();
872
if( !g.perm.Admin ){
873
login_needed(0);
874
return;
875
}
876
if( zType ){
877
eType = strtol(zType,0,0) & eAllTypes;
878
}
879
style_header("Server Error Log");
880
style_submenu_element("Test", "%R/test-warning");
881
style_submenu_element("Refresh", "%R/errorlog");
882
style_submenu_element("Download", "%R/errorlog?download");
883
style_submenu_element("Truncate", "%R/errorlog?truncate");
884
style_submenu_element("Log-Menu", "%R/setup-logmenu");
885
if( eType ){
886
style_submenu_element("Summary", "%R/errorlog");
887
}
888
889
if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
890
no_error_log_available();
891
style_finish_page();
892
return;
893
}
894
if( P("truncate1") && cgi_csrf_safe(2) ){
895
fclose(fopen(g.zErrlog,"w"));
896
}
897
if( P("download") ){
898
Blob log;
899
blob_read_from_file(&log, g.zErrlog, ExtFILE);
900
cgi_set_content_type("text/plain");
901
cgi_set_content(&log);
902
return;
903
}
904
szFile = file_size(g.zErrlog, ExtFILE);
905
if( P("truncate") ){
906
@ <form action="%R/errorlog" method="POST">
907
login_insert_csrf_secret();
908
@ <p>Confirm that you want to truncate the %,lld(szFile)-byte error log:
909
@ <input type="submit" name="truncate1" value="Confirm">
910
@ <input type="submit" name="cancel" value="Cancel">
911
@ </form>
912
style_finish_page();
913
return;
914
}
915
zLog = file_canonical_name_dup(g.zErrlog);
916
@ <p>The server error log at "%h(zLog)" is %,lld(szFile) bytes in size.
917
fossil_free(zLog);
918
in = fossil_fopen(g.zErrlog, "rb");
919
if( in==0 ){
920
@ <p class='generalError'>Unable to open that file for reading!</p>
921
style_finish_page();
922
return;
923
}
924
if( eType==0 ){
925
/* will do a summary */
926
}else if( (eType&eAllTypes)!=eAllTypes ){
927
@ Only the following types of messages displayed:
928
@ <ul>
929
if( eType & 0x01 ){
930
@ <li>Hack attempts
931
}
932
if( eType & 0x02 ){
933
@ <li>Panics and assertion faults
934
}
935
if( eType & 0x04 ){
936
@ <li>Hung backoffice processes
937
}
938
if( eType & 0x08 ){
939
@ <li>POST requests from different origin
940
}
941
if( eType & 0x10 ){
942
@ <li>SQLITE_AUTH and similar errors
943
}
944
if( eType & 0x20 ){
945
@ <li>SMTP malfunctions
946
}
947
if( eType & 0x40 ){
948
@ <li>TH1 vulnerabilities
949
}
950
if( eType & 0x80 ){
951
@ <li>SQL errors
952
}
953
if( eType & 0x100 ){
954
@ <li>Timeouts
955
}
956
if( eType & 0x200 ){
957
@ <li>WAL recoveries
958
}
959
if( eType & 0x8000 ){
960
@ <li>Other uncategorized messages
961
}
962
@ </ul>
963
}
964
@ <hr>
965
if( eType ){
966
@ <pre>
967
}
968
while( fgets(z, sizeof(z), in) ){
969
if( prevWasTime ){
970
if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){
971
bOutput = (eType & 0x01)!=0;
972
nHack++;
973
}else
974
if( strncmp(z,"panic: ", 7)==0 ){
975
if( strncmp(z+7,"Timeout",7)==0 ){
976
bOutput = (eType & 0x100)!=0;
977
nTimeout++;
978
}else{
979
bOutput = (eType & 0x02)!=0;
980
nPanic++;
981
}
982
}else
983
if( strstr(z,"assertion fault")!=0 ){
984
bOutput = (eType & 0x02)!=0;
985
nPanic++;
986
}else
987
if( strncmp(z,"SMTP:", 5)==0 ){
988
bOutput = (eType & 0x20)!=0;
989
nSmtp++;
990
}else
991
if( sqlite3_strglob("warning: SQLITE_NOTICE(283):*",z)==0 ){
992
bOutput = (eType & 0x200)!=0;
993
nRecover++;
994
}else
995
if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
996
bOutput = (eType & 0x04)!=0;
997
nHang++;
998
}else
999
if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
1000
bOutput = (eType & 0x08)!=0;
1001
nXPost++;
1002
}else
1003
if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
1004
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
1005
){
1006
bOutput = (eType & 0x10)!=0;
1007
nAuth++;
1008
}else
1009
if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
1010
bOutput = (eType & 0x40)!=0;
1011
nVuln++;
1012
}else
1013
if( strstr(z,"statement aborts at ") ){
1014
bOutput = (eType & 0x80)!=0;
1015
nSqlErr++;
1016
}else
1017
{
1018
bOutput = (eType & 0x8000)!=0;
1019
nOther++;
1020
}
1021
if( bOutput ){
1022
@ %h(zTime)\
1023
}
1024
}
1025
if( strncmp(z, "--------", 8)==0 ){
1026
size_t n = strlen(z);
1027
memcpy(zTime, z, n+1);
1028
prevWasTime = 1;
1029
bOutput = 0;
1030
}else{
1031
prevWasTime = 0;
1032
}
1033
if( bOutput && eType ){
1034
@ %h(z)\
1035
}
1036
}
1037
fclose(in);
1038
if( eType ){
1039
@ </pre>
1040
}
1041
if( eType==0 ){
1042
int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther + nSqlErr;
1043
int nTotal = nNonHack + nHack + nXPost;
1044
@ <p><table border="a" cellspacing="0" cell

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button