/*
** 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.
if( db_get_boolean("tcl",0) ){
#ifdef FOSSIL_ENABLE_TH1_DOCS
if( Th_AreDocsEnabled() ){
@ DANGER:
}else{
@ WARNING:
}
#else
@ WARNING:
#endif
@ This server is compiled with -DFOSSIL_ENABLE_TCL and Tcl integration
@ is enabled for this repository. Anyone who can execute malicious
@ TH1 script on that server can also execute arbitrary Tcl script
@ under the identity of the operating system process of that server.
@ This is a serious security concern. Disable Tcl integration by recompiling Fossil without the
@ -DFOSSIL_ENABLE_TCL flag, and/or clear the 'tcl' setting.
if( hasSelfReg ){
if( hasAnyCap(zAnonCap,"j") || hasAnyCap(zSelfCap,"j") ){
@
if( zPubPages && zPubPages[0] ){
@
for(i=0; i
@
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. } } /* Providing hyperlink capability to user "nobody" can lead to robots ** making excessive requests resulting in DoS */ if( db_exists("SELECT 1 FROM user WHERE login='nobody' AND cap GLOB '*h*'") ){ int nobodyId = db_int(0,"SELECT uid FROM user WHERE login='nobody'"); int anonId = db_int(0, "SELECT uid FROM user WHERE login='anonymous' AND cap NOT GLOB '*h*'"); @
@ User "nobody" has "Hyperlink" privilege ('h') which can lead to @ robots walking a nearly endless progression of pages on public-facing @ repositories, causing excessive server load and possible DoS. @ Suggested remediation: @
Caution: @ 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) ){ @
Caution: @ 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 unsuccessful 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 the approximate @ 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: @
@ table_of_public_phantoms(); @@ INSERT INTO private SELECT rid FROM blob WHERE content IS NULL; @
@ The command that generated this page: @
@ %h(blob_str(&cmd)) @
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"); style_submenu_element("Admin-Log", "admin_log"); style_submenu_element("User-Log", "access_log"); style_submenu_element("Artifact-Log", "rcvfromlist"); 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(); }