clean:
$(RM) $(addprefix $(DOC).,aux log fmt toc bbl blg mlog run.xml out)\
$(DOC)-blx.bib logo.png
+
+clobber: clean
+ $(RM) $(DOC).pdf
Fortify's results can be summarized to the following:
\begin{enumerate}[label=(\Alph*)]
- \item 50 cases of \textbf{XSS} vurnerabilities, all labeled \textbf{critical}, because none of the CMS's forms include nonces / protection against XSS is indeed missing.
+ \item 50 cases of \XSS{} vurnerabilities, all labeled \textbf{critical}, because none of the \CMS{}'s forms include nonces / protection against \XSS{} is indeed missing.
\item \textbf{Password management}. In a user password reset form in \code{reset.php}, if the resetting fails, the password the user just entered reappears in the password field. This is not a database-retrieved password, and hence not actually as \textbf{critical} as Fortify labels it, but of course bad practice nonetheless.
\item In the \textbf{privact violation} category, Fortify found errors and warnings printed back to the browser, and labelled it \textbf{critical}. However, this happens in the installer script, which we have decided to treat separately, as explained earlier.
- \item \textbf{SQL injection} attacks are possible on the installer script, labelled \textbf{critical}. Yet again: the installer script.
+ \item \textbf{\SQL{} injection} attacks are possible on the installer script, labelled \textbf{critical}. Yet again: the installer script.
\item \textbf{Cookie security}: the \code{HttpOnly} header is not set, labelled \textbf{high}.
- \item \textbf{Privacy violation}: HTML forms don't disable autocompletion. Labelled \textbf{high}. However, autocompletion of HTML forms by means of the \code{autocompletion="none"} attribute notoriously doesn't really work. The larger problem is that the post/redirect/get pattern is not followed, as stated above at our analysis of OWASP requirement (9.1).
- \item Fortify complains that PHP's \code{crypt(...)} function is \textbf{weak encryption} and labels the 5 usages \textbf{high}.
+ \item \textbf{Privacy violation}: \HTML{} forms don't disable autocompletion. Labelled \textbf{high}. However, autocompletion of \HTML{} forms by means of the \code{autocompletion="none"} attribute notoriously doesn't really work. The larger problem is that the post/redirect/get pattern is not followed, as stated above at our analysis of OWASP requirement (9.1).
+ \item Fortify complains that \PHP{}'s \code{crypt(...)} function is \textbf{weak encryption} and labels the 5 usages \textbf{high}.
\end{enumerate}
\begin{description}
\item[V4.9] Verify that the same access control rules implied by the presentation layer are enforced on the server side. \\
- (\textit{The CMS failed this requirement in our analysis.})
- \item[V5.17] Verify that the application has defenses against HTTP parameter pollution attacks, particularly if the application framework makes no distinction about the source of request parameters (GET, POST, cookies, headers, environment, etc.. \\
- (\textit{The CMS passed this requirement in our analysis.})
+ (\textit{The \CMS{} failed this requirement in our analysis.})
+ \item[V5.17] Verify that the application has defenses against \HTTP{} parameter pollution attacks, particularly if the application framework makes no distinction about the source of request parameters (\GET{}, \POST{}, cookies, headers, environment, etc.. \\
+ (\textit{The \CMS{} passed this requirement in our analysis.})
\end{description}
-For this reason, Fortify was nowhere near able to identifying all the problems we found in the CMS. An overview of our findings, where Fortify's concurrences are outlined explicitly, is given by the table below.
+For this reason, Fortify was nowhere near able to identifying all the problems we found in the \CMS{}. An overview of our findings, where Fortify's concurrences are outlined explicitly, is given by the table below.
\newcommand{\p}{{\color{lightgray}\pass}}
innertopmargin=2pt,
innerbottommargin=2pt,
skipabove=\topsep,
- skipbelow=\topsep
+ skipbelow=\topsep%
]{result}
% Tools afkortingen
+\newcommand{\API}{\emph{API}}
+\newcommand{\CMS}{\emph{CMS}}
+\newcommand{\CSRF}{\emph{CSRF}}
+\newcommand{\DELETE}{\emph{DELETE}}
+\newcommand{\DOM}{\emph{DOM}}
+\newcommand{\GET}{\emph{GET}}
+\newcommand{\GUID}{\emph{GUID}}
+\newcommand{\HTMLF}{\textsc{HTML5}}
+\newcommand{\HTML}{\textsc{HTML}}
+\newcommand{\HTTPS}{\textsc{HTTPS}}
+\newcommand{\HTTP}{\textsc{HTTP}}
+\newcommand{\JQuery}{\textsc{JQuery}}
+\newcommand{\JSON}{\textsc{JSON}}
+\newcommand{\LDAP}{\textsc{LDAP}}
\newcommand{\PHP}{\textsc{PHP}}
+\newcommand{\PII}{\emph{PII}}
+\newcommand{\POST}{\emph{POST}}
+\newcommand{\PUT}{\emph{PUT}}
+\newcommand{\REST}{\emph{REST}}
+\newcommand{\RSS}{\emph{RSS}}
+\newcommand{\SMTP}{\emph{SMTP}}
\newcommand{\SQL}{\textsc{SQL}}
-\newcommand{\LDAP}{\textsc{LDAP}}
+\newcommand{\SSO}{\emph{SSO}}
+\newcommand{\TOTP}{\emph{TOTP}}
+\newcommand{\TRACE}{\emph{TRACE}}
\newcommand{\XML}{\textsc{XML}}
-\newcommand{\HTML}{\textsc{HTML}}
-\newcommand{\JSON}{\textsc{JSON}}
-\newcommand{\JQuery}{\textsc{JQuery}}
+\newcommand{\XSS}{\emph{XSS}}
+
% Reference naar de source
\newcommand{\srcref}[2]{{\small\texttt{#1}} (line (s) #2)}
\item\fail{}
Verify that the application accepts only a defined
-set of required HTTP request methods, such as
-GET and POST are accepted, and unused methods
-(e.g. TRACE, PUT, and DELETE) are explicitly
+set of required \HTTP{} request methods, such as
+\GET{} and \POST{} are accepted, and unused methods
+(e.g. \TRACE{}, \PUT{}, and \DELETE{}) are explicitly
blocked.
\begin{result}
- The application treats only \texttt{POST} requests as different from
+ The application treats only \POST{} requests as different from
others and in an opportunistic manner. It assumes all other methods to be
- treated as \texttt{GET} requests.
+ treated as \GET{} requests.
\end{result}
\item\pass{}
-Verify that every HTTP response contains a
+Verify that every \HTTP{} response contains a
content type header specifying a safe character set
-(e.g., UTF-8, ISO 8859-1).
+(e.g., \emph{UTF-8}, \emph{ISO 8859{-}1}).
\begin{result}
Content type headers may be set anywhere in the application. Furthermure,
- \texttt{Response::send} ensures that if no content type header is set, all
- responses will fall back to using \texttt{text/html; charset=UTF-8}.
+ \code{Response::send} ensures that if no content type header is set, all
+ responses will fall back to using \code{text/html; charset=UTF-8}.
\end{result}
\notapplicable{\item
-Verify that HTTP headers added by a trusted proxy
-or SSO devices, such as a bearer token, are
+Verify that \HTTP{} headers added by a trusted proxy
+or \SSO{} devices, such as a bearer token, are
authenticated by the application.}
% No proxies are present
in use for sites where content should not be
viewed in a 3rd-party X-Frame.
\begin{result}
- The application will never supply an \texttt{X-FRAME-OPTIONS} header. While
+ The application will never supply an \code{X-FRAME-OPTIONS} header. While
this is not really a problem for the home page, a 3rd party X-Frame should
not be able to refer to the administrative interfaces of the application.
\end{result}
\item\pass{}
-Verify that the HTTP headers or any part of the
-HTTP response do not expose detailed version
+Verify that the \HTTP{} headers or any part of the
+\HTTP{} response do not expose detailed version
information of system components.
\begin{result}
- The headers provide information about the PHP version (these are added by
- the PHP interpreter by default) and information about the webserver. This
+ The headers provide information about the \PHP{} version (these are added by
+ the \PHP{} interpreter by default) and information about the webserver. This
information is not specific for the application. It would be advisable to
- hide the PHP version to the client, but this is specific to the way the
+ hide the \PHP{} version to the client, but this is specific to the way the
application is installed.
\end{result}
\item\fail{}
-Verify that all API responses contain X-Content-Type-Options:
-nosniff and Content-Disposition:
-attachment; filename="api.json" (or other
+Verify that all \API{} responses contain \code{X-Content-Type-Options:
+nosniff} and\\
+\code{Content-Disposition: attachment; filename="api.json"} (or other
appropriate filename for the content type).
\begin{result}
- The application does not supply the \texttt{X-Content-Type-Options} header.
+ The application does not supply the \code{X-Content-Type-Options} header.
\end{result}
\item\fail{}
\item\fail{}
Verify that the X-XSS-Protection: 1; mode=block
-header is in place to enable browser reflected XSS
+header is in place to enable browser reflected \XSS{}
filters.
\begin{result}
- The application does not supply the \texttt{X-XSS-Protection} header.
+ The application does not supply the \code{X-XSS-Protection} header.
\end{result}
\end{enumerate}
\begin{result}
The input to various forms is not sanitized at all. This makes the implementation
- in (not only) \texttt{Users::find} vulnerable to SQL injections. The login form
- is also vulnerable. Any user can execute arbitrary SQL code from the username field.
+ in (not only) \code{Users::find} vulnerable to \SQL{} injections. The login form
+ is also vulnerable. Any user can execute arbitrary \SQL{} code from the username field.
The following example code can submitted as username in the login form, which
- will set the password of the \texttt{admin} user to \texttt{s3cret}:
+ will set the password of the \code{admin} user to \code{s3cret}:
\code{"; UPDATE users SET password='\$1\$OWgsBb90\$Lkko6aZwmp9XOVrFI09Ab0' WHERE \\username='admin' AND 'a' = "a}
\begin{result}
The application allows the user to use any password (except ones that contain
- SQL code).
+ \SQL{} code).
\end{result}
\item
expire, however. It would be even better to require that a token be used
withing a day (or so) after creation.
- The security of the SMTP connection is at the discretion of the web server.
+ The security of the \SMTP{} connection is at the discretion of the web server.
Often these connections are not very secure, but this worry is beyond the
scope of this audit.
\end{result}
force and password hash recovery attacks.
\begin{result}
- Password are stored in database using the PHP function \texttt{crypt}. Internally, this
+ Password are stored in database using the \PHP{} function \code{crypt}. Internally, this
function uses salted MD5. This is way too reverse with brute-force attacks using dictionary files.
- Instead it would be better to use the \texttt{argon2} password hashing algorithm
- or the PHP \texttt{password\_hash} function (which currently uses BCRYPT).
+ Instead it would be better to use the \code{argon2} password hashing algorithm
+ or the \PHP{} \code{password\_hash} function (which currently uses BCRYPT).
\end{result}
\setcounter{enumi}{15}
link.
\begin{result}
- The app allows admin users to log in over HTTP. This is insecure, as it allows
+ The app allows admin users to log in over \HTTP{}. This is insecure, as it allows
eavesdroppers to intercept password.
- The app should force HTTPS for at least the login form, the \texttt{admin\_controller} and
+ The app should force \HTTPS{} for at least the login form, the \code{admin\_controller} and
for the installation script (because the users posts secrets like the database
password to this page).
\end{result}
login, password reset, or forgot account functionality.
\begin{result}
- All these forms are vulnerable to SQL injection attacks. So any information
+ All these forms are vulnerable to \SQL{} injection attacks. So any information
can leak any information from the database.
\end{result}
\begin{result}
No secrets are initialized by predefined values. The admin user will have
- username \texttt{admin} by default. This is no secret and therefore not
+ username \code{admin} by default. This is no secret and therefore not
considered unsafe.
\end{result}
stored in a protected location.
\begin{result}
- The database credentials are hardcoded in \texttt{config.php}. While it
+ The database credentials are hardcoded in \code{config.php}. While it
would be better to pass secrets as environment variables, this is not
really bad practice.
\end{verbatim}
This implies making the configuration file readable for all users on the
system. This information should not be accessible for any user other than
- running the PHP script.
+ running the \PHP{} script.
\end{result}
\item
\pass{}
Verify that forgotten password and other recovery paths
-use a TOTP or other soft token, mobile push, or other
+use a \TOTP{} or other soft token, mobile push, or other
offline recovery mechanism. Use of a random value in an
e-mail or SMS should be a last resort and is known weak.
\notapplicable{\item
Verify that if shared knowledge based questions (also
-known as "secret questions") are required, the questions
+known as ``secret questions'') are required, the questions
do not violate privacy laws and are sufficiently strong to
protect accounts from malicious recovery.}
\begin{result}
No password strengthening measures are implemented. The app should
- use some password strength estimator like \texttt{zxcvbn}.
+ use some password strength estimator like \code{zxcvbn}.
\end{result}
\item
\item
\fail{}
-Verify that secrets, API keys, and passwords are not
+Verify that secrets, \API{} keys, and passwords are not
included in the source code, or online source code
repositories.
\begin{result}
- The database credentials are hard coded in \texttt{config.php}. These
+ The database credentials are hard coded in \code{config.php}. These
credentials should ideally be passed using environment variables.
\end{result}
\begin{result}
The logout functionality is plainly visible on the top right of the
application on every page that requires authentication. This is defined in
- \srcref{admin/themes/header.php}{16-30}
+ \srcref{admin/themes/header.php}{16{-}30}
\end{result}
session tokens additionally set the “HttpOnly” and “secure” attributes.
\begin{result}
There is just one cookie for tha application and it's path includes the whole
- site. However this seems appropriate. The "HttpOnly" and "secure"
+ site. However this seems appropriate. The ``HttpOnly'' and ``secure''
attributes are not set for this cookie.
\end{result}
\noindent
-The CMS has the following access control mechanisms:
+The \CMS{} has the following access control mechanisms:
\begin{itemize}
\item A login mechanism, where logged in users are allowed to access the backend, and anonymous users are not.
\item
\fail{}
-Verify that the principle of least privilege exists - users
+Verify that the principle of least privilege exists {-} users
should only be able to access functions, data files, URLs,
controllers, services, and other resources, for which they
possess specific authorization. This implies protection
parameter to see or alter another user's account).
\begin{result}
-There is no different access context for distinct users. In particular, they are allowed to access and edit each others' account info, including password. Taken one way, this could be said to be by design and thus OK. But in any reasonable design concept allowing for distinct user accounts, this is clearly not the desired setup.
+There is no different access context for distinct users. In particular, they
+ are allowed to access and edit each others' account info, including
+ password. Taken one way, this could be said to be by design and thus OK\. But in any reasonable design concept allowing for distinct user accounts, this is clearly not the desired setup.
\end{result}
\item
\begin{result}
\begin{itemize}[leftmargin=*]
\item \code{.gitignore} accessible, as well as any other dot-preceded file (except \code{.htaccess} itself by default Apache rules), as well as files such as \code{Thumbs.db} and \code{.DS\_Store}.
- \item Directory contents were listed in my simple setup. A global apache setting may disable by default, but the \code{.htaccess} file doesn't explicitly disable (with \code{Options -Indexes}), so that the CMS's codebase basically enables the listing by default.
+ \item Directory contents were listed in my simple setup. A global apache setting may disable by default, but the \code{.htaccess} file doesn't explicitly disable (with \code{Options -Indexes}), so that the \CMS{}'s codebase basically enables the listing by default.
\end{itemize}
\end{result}
manipulated by end users unless specifically authorized.
\begin{result}
-This item is the main remaining security concern. I haven't found any obvious fail in the login system, but given the architecture and security status of the whole CMS, I'm not very sure of it.
+This item is the main remaining security concern. I haven't found any obvious fail in the login system, but given the architecture and security status of the whole \CMS{}, I'm not very sure of it.
\end{result}
\notapplicable{
\item
\fail{}
Verify that the application or framework uses strong
-random anti-CSRF tokens or has another transaction
+random anti-\CSRF{} tokens or has another transaction
protection mechanism.
\begin{result}
\srcref{classes/users.php}{145}.
\end{result}
- \item\pass{} Verify that the application is not susceptible to LDAP
- Injection, or that security controls prevent LDAP Injection.
+ \item\pass{} Verify that the application is not susceptible to \LDAP{}
+ Injection, or that security controls prevent \LDAP{} Injection.
\begin{result}
\LDAP{} is not used, thus the application is not susceptible.
recovery\\
(\srcref{classes/user.php}{115}) filepaths are calculated on the
hash of the password. All non standard filepaths, such as admin or
- theme files, are generated using functions. CMS urls are parsed using a
+ theme files, are generated using functions. \CMS{} urls are parsed using a
standard system wide \code{parse} function.
\end{result}
web client code is either properly contextually encoded manually, or
utilize templates that automatically encode contextually to ensure the
application is not susceptible to reflected, stored and DOM Cross-Site
- Scripting (XSS) attacks.
+ Scripting (\XSS{}) attacks.
\begin{result}
- A lot of \HTML{} tags are allowed in the post screen, therefore an XSS
+ A lot of \HTML{} tags are allowed in the post screen, therefore an \XSS{}
attack is trivial. Even the comment section uses no input validation
whatsoever.
\end{result}
malicious automatic binding.
\begin{result}
- There is some automatic variable binding happening in the POST and GET
+ There is some automatic variable binding happening in the \POST{} and \GET{}
however, defaults are always given and there is no possibility of
accidentally binding extra variables. Also the variables are in an
array.
\end{result}
- \item\pass{} Verify that the application has defenses against HTTP
+ \item\pass{} Verify that the application has defenses against \HTTP{}
parameter pollution attacks, particularly if the application framework
- makes no distinction about the source of request parameters (GET, POST,
+ makes no distinction about the source of request parameters (\GET{}, \POST{},
cookies, headers, environment, etc.)
\begin{result}
\end{result}
\item\fail{} Verify that all input data is validated, not only \HTML{} form
- fields but all sources of input such as REST calls, query parameters,
- HTTP headers, cookies, batch files, RSS feeds, etc; using positive
+ fields but all sources of input such as \REST{} calls, query parameters,
+ \HTTP{} headers, cookies, batch files, \RSS{} feeds, etc; using positive
validation (whitelisting), then lesser forms of validation such as
greylisting (eliminating known bad strings), or rejecting bad inputs
(blacklisting).
\begin{result}
- REST calls are validated using whitelisting, query parameters are not,
+ \REST{} calls are validated using whitelisting, query parameters are not,
headers are not, cookies not, batch files are non-existent and RSS feed
output is not filtered.
\end{result}
\addtocounter{enumi}{3}
\notapplicable{
\item
- Verify that all random numbers, random file names, random GUIDs, and random
+ Verify that all random numbers, random file names, random \GUID{}s, and random
strings are generated using the cryptographic module’s approved random
number generator when these random values are intended to be not guessable
by an attacker.
\item
\TODO{}
Verify that cryptographic algorithms used by the application have been
- validated against FIPS 140-2 or an equivalent standard.
+ validated against FIPS 140{-}2 or an equivalent standard.
\begin{result}
The application uses md-5 for password hashing, which should be insecure by
now.
features.
\begin{result}
- The login and page/post editing/creation forms post back to the same page, thereby incentivising the browser to cache the form inputs as well. This is as opposed to the common post/redirect/get model (see \url{http://en.wikipedia.org/wiki/Post/Redirect/Get}). Also, the \texttt{Cache-Control} isn't explicitly used anywhere in the CMS to aid the situation. In my test setup, the response does send \texttt{Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0}, but that'll be a default global Apache setting, I think.
+ The login and page/post editing/creation forms post back to the same page, thereby incentivising the browser to cache the form inputs as well. This is as opposed to the common post/redirect/get model (see \url{http://en.wikipedia.org/wiki/Post/Redirect/Get}). Also, the \code{Cache-Control} isn't explicitly used anywhere in the \CMS{} to aid the situation. In my test setup, the response does send \code{Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0}, but that'll be a default global Apache setting, I think.
\end{result}
\notapplicable{\item Verify that the list of sensitive data processed by the
directives.}
\item\pass{} Verify that all sensitive data is sent to the server in the
- HTTP message body or headers (i.e., URL parameters are
+ \HTTP{} message body or headers (i.e., URL parameters are
never used to send sensitive data).
\begin{result}
\end{verbatim}
\begin{result}
- Cache control header are never set by the CMS. The fact that headers as these are indeed sent to the broswer in my test setup is probably due to default global Apache settings.
+ Cache control header are never set by the \CMS{}. The fact that headers as these are indeed sent to the broswer in my test setup is probably due to default global Apache settings.
\end{result}
\item\pass{} Verify that on the server, all cached or temporary copies
variables, cookies and header values.
\begin{result}
- The CMS by no means sends any larger amount of parameters than would be expected, seen as it is but a simple app and mostly lacks extra functionality often leading to this kind of excessive parameter transferring, so I count this as a pass.
+ The \CMS{} by no means sends any larger amount of parameters than would be expected, seen as it is but a simple app and mostly lacks extra functionality often leading to this kind of excessive parameter transferring, so I count this as a pass.
\end{result}
\notapplicable{\item Verify the application has the ability to detect and alert
an example screen scraping.}
\item\pass{} Verify that data stored in client side storage (such as
- HTML5 local storage, session storage, IndexedDB, regular
+ \HTMLF{} local storage, session storage, IndexedDB, regular
cookies or Flash cookies) does not contain sensitive data
- or PII.
+ or \PII{}.
\begin{result}
Vacuously: data is not stored on the client side.
to mitigate memory dumping attacks.
\begin{result}
- I consider this outside of the scope of the CMS's security requirements, as it is written in, and thus relies on the (memory) security of, PHP.
+ I consider this outside of the scope of the \CMS{}'s security requirements, as it is written in, and thus relies on the (memory) security of, \PHP{}.
\end{result}
\end{enumerate}