/*
** Copyright (c) 2017 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file implements various web pages use for running a security audit
** of a Fossil configuration.
*/
#include "config.h"
#include This repository is Wildly INSECURE because
@ it grants administrator privileges to anonymous users. You
@ should take this repository private
@ immediately! Or, at least remove the Setup and Admin privileges
@ for users "anonymous" and "login" on the
@ User Configuration page.
}else if( hasAnyCap(zSelfCap,"as") && hasSelfReg ){
@ This repository is Wildly INSECURE because
@ it grants administrator privileges to self-registered users. You
@ should take this repository private
@ and/or disable self-registration
@ immediately! Or, at least remove the Setup and Admin privileges
@ from the default permissions for new users.
}else if( hasAnyCap(zAnonCap,"y") ){
@ This repository is INSECURE because
@ it allows anonymous users to push unversioned files.
@ Fix this by taking the repository private
@ or by removing the "y" permission from users "anonymous" and
@ "nobody" on the User Configuration page.
}else if( hasAnyCap(zSelfCap,"y") ){
@ This repository is INSECURE because
@ it allows self-registered users to push unversioned files.
@ Fix this by taking the repository private
@ or by removing the "y" permission from the default permissions or
@ by disabling self-registration.
}else if( hasAnyCap(zAnonCap,"goz") ){
@ This repository is PUBLIC. All
@ checked-in content can be accessed by anonymous users.
@ Take it private.
}else if( hasAnyCap(zSelfCap,"goz") && hasSelfReg ){
@ This repository is PUBLIC because all
@ checked-in content can be accessed by self-registered users.
@ This repostory would be private if you disabled self-registration. This repository is Completely PRIVATE.
@ A valid login and password is required to access any content.
}else{
@ This repository is Mostly PRIVATE.
@ A valid login and password is usually required, however some
@ content can be accessed either anonymously or by self-registered
@ users:
@ Anoymous users are vested with capabilities "%h(zSelfCap)" on
@ public pages. See the "Public Pages" entry in the
@ "User capability summary" below.
}
@ Change GLOB patterns exceptions using the "Public pages" setting
@ on the Access Settings page. The canonical URL for this repository is
@ %h(zPublicUrl).
if( nOther==1 ){
@ This is also 1 other URL that has
@ been used to access this repository.
}else if( nOther>=2 ){
@ There are also
@ %d(nOther) other URLs that have
@ been used to access this repository.
}
}else{
int nUrl = db_int(0, "SELECT count(*) FROM config"
" WHERE name GLOB 'baseurl:*'");
@ This repository does not have a canonical access URL.
if( nUrl==1 ){
@ There is
@ 1 non-canonical URL
@ that has been used to access this repository.
}else if( nUrl>=2 ){
@ There are
@ %d(nUrl) non-canonical URLs
@ that have been used to access this repository.
}
}
/* Make sure the HTTPS is required for login, at least, so that the
** password does not go across the Internet in the clear.
*/
if( db_get_int("redirect-to-https",0)==0 ){
@ WARNING:
@ Sensitive material such as login passwords can be sent over an
@ unencrypted connection.
@ Fix this by changing the "Redirect to HTTPS" setting on the
@ Access Control page. If you were using
@ the old "Redirect to HTTPS on Login Page" setting, switch to the
@ new setting: it has a more secure implementation.
}
#ifdef FOSSIL_ENABLE_TH1_DOCS
/* The use of embedded TH1 is dangerous. Warn if it is possible.
*/
if( !Th_AreDocsEnabled() ){
@
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS. TH1 docs
@ are disabled for this particular repository, so you are safe for
@ now. However, to prevent future problems caused by accidentally
@ enabling TH1 docs in the future, it is recommended that you
@ recompile Fossil without the -DFOSSIL_ENABLE_TH1_DOCS flag. DANGER:
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS and TH1 docs
@ are enabled for this repository. Anyone who can check-in or push
@ to this repository can create a malicious TH1 script and then cause
@ that script to be run on the server. This is a serious security concern.
@ TH1 docs should only be enabled for repositories with a very limited
@ number of trusted committers, and the repository should be monitored
@ closely to ensure no hostile content sneaks in. If a bad TH1 script
@ does make it into the repository, the only want to prevent it from
@ being run is to shun it. Disable TH1 docs by recompiling Fossil without the
@ -DFOSSIL_ENABLE_TH1_DOCS flag, and/or clear the th1-docs setting
@ and ensure that the TH1_ENABLE_DOCS environment variable does not
@ exist in the environment. WARNING:
@ Anonymous users can view email addresses and other personally
@ identifiable information on tickets.
@ Fix this by removing the "Email" privilege
@ (capability "e") from users
@ "anonymous" and "nobody" on the
@ User Configuration page.
}
/* Anonymous users probably should not be allowed to push content
** to the repository.
*/
if( hasAnyCap(zAnonCap, "i") ){
@ WARNING:
@ Anonymous users can push new check-ins into the repository.
@ Fix this by removing the "Check-in" privilege
@ (capability "i") from users
@ "anonymous" and "nobody" on the
@ User Configuration page.
}
/* Anonymous users probably should not be allowed act as moderators
** for wiki or tickets.
*/
if( hasAnyCap(zAnonCap, "lq5") ){
@ WARNING:
@ Anonymous users can act as moderators for wiki, tickets, or
@ forum posts. This defeats the whole purpose of moderation.
@ Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
@ privileges (capabilities "fq5")
@ from users "anonymous" and "nobody"
@ on the User Configuration page.
}
/* Check to see if any TH1 scripts are configured to run on a sync
*/
if( db_exists("SELECT 1 FROM config WHERE name GLOB 'xfer-*-script'"
" AND length(value)>0") ){
@ WARNING:
@ TH1 scripts might be configured to run on any sync, push, pull, or
@ clone operation. See the the /xfersetup
@ page for more information. These TH1 scripts are a potential
@ security concern and so should be carefully audited by a human.
}
/* The strict-manifest-syntax setting should be on. */
if( db_get_boolean("strict-manifest-syntax",1)==0 ){
@ WARNING:
@ The "strict-manifest-syntax" flag is off. This is a security
@ risk. Turn this setting on (its default) to protect the users
@ of this repository.
}
/* Obsolete: */
if( hasAnyCap(zAnonCap, "d") ||
hasAnyCap(zDevCap, "d") ||
hasAnyCap(zReadCap, "d") ){
@ WARNING:
@ One or more users has the obsolete
@ "d" capability. You should remove it using the
@ User Configuration page in case we
@ ever reuse the letter for another purpose.
}
/* If anonymous users are allowed to create new Wiki, then
** wiki moderation should be activated to pervent spam.
*/
if( hasAnyCap(zAnonCap, "fk") ){
if( db_get_boolean("modreq-wiki",0)==0 ){
@ WARNING:
@ Anonymous users can create or edit wiki without moderation.
@ This can result in robots inserting lots of wiki spam into
@ repository.
@ Fix this by removing the "New-Wiki" and "Write-Wiki"
@ privileges from users "anonymous" and "nobody" on the
@ User Configuration page or
@ by enabling wiki moderation on the
@ Moderation Setup page.
}else{
@
@ Anonymous users can create or edit wiki, but moderator
@ approval is required before the edits become permanent.
}
}
/* Anonymous users should not be able to create trusted forum
** posts.
*/
if( hasAnyCap(zAnonCap, "456") ){
@ WARNING:
@ Anonymous users can create forum posts that are
@ accepted into the permanent record without moderation.
@ This can result in robots generating spam on forum posts.
@ Fix this by removing the "WriteTrusted-Forum" privilege
@ (capabilities "456") from
@ users "anonymous" and "nobody" on the
@ User Configuration page or
}
/* Anonymous users should not be able to send announcements.
*/
if( hasAnyCap(zAnonCap, "A") ){
@ WARNING:
@ Anonymous users can send announcements to anybody who is signed
@ up to receive announcements. This can result in spam.
@ Fix this by removing the "Announce" privilege
@ (capability "A") from
@ users "anonymous" and "nobody" on the
@ User Configuration page or
}
/* Administrative privilege should only be provided to
** specific individuals, not to entire classes of people.
** And not too many people should have administrator privilege.
*/
z = db_text(0,
"SELECT group_concat("
"printf('%%s',uid,login),"
"' and ')"
" FROM user"
" WHERE cap GLOB '*[as]*'"
" AND login in ('anonymous','nobody','reader','developer')"
);
if( z && z[0] ){
@ WARNING:
@ Administrative privilege ('a' or 's')
@ is granted to an entire class of users: %s(z).
@ Administrative privilege should only be
@ granted to specific individuals.
}
n = db_int(0,"SELECT count(*) FROM user WHERE fullcap(cap) GLOB '*[as]*'");
if( n==0 ){
@
@ No users have administrator privilege.
}else{
z = db_text(0,
"SELECT group_concat("
"printf('%%s',uid,login),"
"', ')"
" FROM user"
" WHERE fullcap(cap) GLOB '*[as]*'"
);
@
@ Users with administrator privilege are: %s(z)
fossil_free(z);
if( n>3 ){
@ WARNING:
@ Administrator privilege is granted to
@ %d(n) users.
@ Ideally, administrator privilege ('s' or 'a') should only
@ be granted to one or two users.
}
}
/* The push-unversioned privilege should only be provided to
** specific individuals, not to entire classes of people.
** And no too many people should have this privilege.
*/
z = db_text(0,
"SELECT group_concat("
"printf('%%s',uid,login),"
"' and ')"
" FROM user"
" WHERE cap GLOB '*y*'"
" AND login in ('anonymous','nobody','reader','developer')"
);
if( z && z[0] ){
@ WARNING:
@ The "Write-Unver" privilege is granted to an entire class of users: %s(z).
@ The Write-Unver privilege should only be granted to specific individuals.
fossil_free(z);
}
n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*y*'");
if( n>0 ){
z = db_text(0,
"SELECT group_concat("
"printf('%%s',uid,login),', ')"
" FROM user WHERE fullcap(cap) GLOB '*y*'"
);
@
@ Users with "Write-Unver" privilege: %s(z)
fossil_free(z);
if( n>3 ){
@ Caution:
@ The "Write-Unver" privilege ('y') is granted to an excessive
@ number of users (%d(n)).
@ Ideally, the Write-Unver privilege should only
@ be granted to one or two users.
}
}
/* Notify if REMOTE_USER or HTTP_AUTHENTICATION is used for login.
*/
if( db_get_boolean("remote_user_ok", 0) ){
@
@ This repository trusts that the REMOTE_USER environment variable set
@ up by the webserver contains the name of an authenticated user.
@ Fossil's built-in authentication mechanism is bypassed.
@ Fix this by deactivating the "Allow REMOTE_USER authentication"
@ checkbox on the Access Control page.
}
if( db_get_boolean("http_authentication_ok", 0) ){
@
@ This repository trusts that the HTTP_AUTHENITICATION environment
@ variable set up by the webserver contains the name of an
@ authenticated user.
@ Fossil's built-in authentication mechanism is bypassed.
@ Fix this by deactivating the "Allow HTTP_AUTHENTICATION authentication"
@ checkbox on the Access Control page.
}
/* Logging should be turned on
*/
if( db_get_boolean("access-log",0)==0 ){
@
@ The User Log is disabled. The user log
@ keeps a record of successful and unsucessful login attempts and is
@ useful for security monitoring.
}
if( db_get_boolean("admin-log",0)==0 ){
@
@ The Administrative Log is disabled.
@ The administrative log provides a record of configuration changes
@ and is useful for security monitoring.
}
#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE)
/* Make sure that the load-average limiter is armed and working */
if( load_average()==0.0 ){
@
@ Unable to get the system load average. This can prevent Fossil
@ from throttling expensive operations during peak demand.
@ If running in a chroot jail on Linux, verify that the /proc
@ filesystem is mounted within the jail, so that the load average
@ can be obtained from the /proc/loadavg file.
}else {
double r = atof(db_get("max-loadavg", 0));
if( r<=0.0 ){
@
@ Load average limiting is turned off. This can cause the server
@ to bog down if many requests for expensive services (such as
@ large diffs or tarballs) arrive at about the same time.
@ To fix this, set the "Server Load Average Limit" on the
@ Access Control page to approximately
@ the number of available cores on your server, or maybe just a little
@ less.
}else if( r>=8.0 ){
@
@ The "Server Load Average Limit" on the
@ Access Control page is set to %g(r),
@ which seems high. Is this server really a %d((int)r)-core machine?
}
}
#endif
if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
@
@ The server error log is disabled.
@ To set up an error log,
if( fossil_strcmp(g.zCmdName, "cgi")==0 ){
@ make an entry like "errorlog: FILENAME" in the
@ CGI script at %h(P("SCRIPT_FILENAME")).
}else{
@ add the "--errorlog FILENAME" option to the
@ "%h(g.argv[0]) %h(g.zCmdName)" command that launched this server.
}
}else{
FILE *pTest = fossil_fopen(g.zErrlog,"a");
if( pTest==0 ){
@
@ Error:
@ There is an error log at "%h(g.zErrlog)" but that file is not
@ writable and so no logging will occur.
}else{
fclose(pTest);
@
@ The error log at "%h(g.zErrlog)" is
@ %,lld(file_size(g.zErrlog, ExtFILE)) bytes in size.
}
}
if( g.zExtRoot ){
int nFile;
int nCgi;
ext_files();
nFile = db_int(0, "SELECT count(*) FROM sfile");
nCgi = nFile==0 ? 0 : db_int(0,"SELECT count(*) FROM sfile WHERE isexe");
@ CGI Extensions are enabled with a document root
@ at %h(g.zExtRoot) holding
@ %d(nCgi) CGIs and %d(nFile-nCgi) static content and data files.
}
if( fileedit_glob()!=0 ){
@ Online File Editing is enabled
@ for this repository. Clear the
@ "fileedit-glob" setting to
@ disable online editing. User capability summary:
capability_summary();
azCSP = parse_content_security_policy();
if( azCSP==0 ){
@ WARNING: No Content Security Policy (CSP) is specified in the
@ header. Though not required, a strong CSP is recommended. Fossil will
@ automatically insert an appropriate CSP if you let it generate the
@ HTML <head> element by omitting <body>
@ from the header configuration in your customized skin.
@
}else{
int ii;
@ Content Security Policy:
@ Email alert configuration summary:
@ Email alerts are disabled
}
n = db_int(0,"SELECT count(*) FROM ("
"SELECT rid FROM phantom EXCEPT SELECT rid FROM private)");
if( n>0 ){
@ \
@ There exists public phantom artifacts in this repository, shown below.
@ Phantom artifacts are artifacts whose hash name is referenced by some
@ other artifact but whose content is unknown. Some phantoms are marked
@ private and those are ignored. But public phantoms cause unnecessary
@ sync traffic and might represent malicious attempts to corrupt the
@ repository structure.
@
@ To suppress unnecessary sync traffic caused by phantoms, add the RID
@ of each phantom to the "private" table. Example:
@
if( hasSelfReg ){
if( hasAnyCap(zAnonCap,"j") || hasAnyCap(zSelfCap,"j") ){
@
if( zPubPages && zPubPages[0] ){
@
for(i=0; i
@
for(ii=0; azCSP[ii]; ii++){
@
}
fossil_free(azCSP);
if( alert_enabled() ){
@
stats_for_email();
@
}else{
@
@
@ INSERT INTO private SELECT rid FROM blob WHERE content IS NULL;
@
Click the "Make It Private" button below to disable all @ anonymous access to this repository. A valid login and password @ will be required to access this repository after clicking that @ button.
@ @Click the "Cancel" button to leave things as they are.
@ @ style_finish_page(); } /* ** The maximum number of bytes of log to show */ #define MXSHOWLOG 50000 /* ** WEBPAGE: errorlog ** ** Show the content of the error log. Only the administrator can view ** this page. */ void errorlog_page(void){ i64 szFile; FILE *in; char z[10000]; login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); return; } style_header("Server Error Log"); style_submenu_element("Test", "%R/test-warning"); style_submenu_element("Refresh", "%R/errorlog"); if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){ @To create a server error log: @
@ If the server is running as CGI, then create a line in the CGI file @ like this: @
@@ errorlog: FILENAME @
@ If the server is running using one of @ the "fossil http" or "fossil server" commands then add @ a command-line option "--errorlog FILENAME" to that @ command. @
The server error log at "%h(g.zErrlog)" is %,lld(szFile) bytes in size. style_submenu_element("Download", "%R/errorlog?download"); style_submenu_element("Truncate", "%R/errorlog?truncate"); in = fossil_fopen(g.zErrlog, "rb"); if( in==0 ){ @
Unable to open that file for reading!
style_finish_page(); return; } if( szFile>MXSHOWLOG && P("all")==0 ){ @ fseek(in, -MXSHOWLOG, SEEK_END); } @while( fgets(z, sizeof(z), in) ){ @ %h(z)\ } fclose(in); @style_finish_page(); }