php-doc-en/reference/session/security.xml
Yasuo Ohgaki 767b987b91 Fixed INI option values
git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@340153 c90b9560-bf6c-de11-be94-00142212c4b1
2016-09-24 00:50:57 +00:00

749 lines
27 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<chapter xml:id="session.security" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Sessions and Security</title>
<para>
External links: <link xlink:href="&url.session-fixation;">Session fixation</link>
</para>
<para>
HTTP session management is core of web security. All of mitigation
should be adopted to make sure session security. Developer should
enable/use applicable security measures.
</para>
<sect1 xml:id="features.session.security.management">
<title>Session Management Basics</title>
<sect2 xml:id="features.session.security.management.basic">
<title>Session Security</title>
<para>
The session module cannot guarantee that the information you store
in a session is only viewed by the user who created the session. You
need to take additional measures to protect the confidentiality
of the session, depending on the value associated with it.
</para>
<para>
Assess the importance of the data carried by your sessions and
deploy additional protections -- this usually comes at a price,
reduced convenience for the user. For example, if you want to
protect users from simple social engineering tactics, you need to
enable <literal>session.use_only_cookies</literal>. In that case,
cookies must be enabled unconditionally on the user side, or
sessions will not work.
</para>
<para>
There are several ways to leak an existing session ID to third
parties. e.g. JavaScript injections, session ID in URLs, packet
sniffing, physical access to device. A leaked session ID enables
the third party to access all resources which are associated with
a specific ID. First, URLs carrying session IDs. If you link to
an external site, the URL including the session ID might be
stored in the external site's referrer logs. Second, a more
active attacker might listen to your network traffic. If it is
not encrypted, session IDs will flow in plain text over the
network. The solution here is to implement SSL/TLS on your server
and make it mandatory for users. HSTS should be used for better
security.
</para>
<note>
<simpara>
Even HTTPS cannot protect confidential data in contents sometimes.
e.g. CRIME, Beast attack. There are many networks that use
HTTPS MITM proxy for audit purpose. Attackers may setup such proxy
also.
</simpara>
</note>
</sect2>
<sect2 xml:id="features.session.security.management.non-adaptive-session">
<title>Non-Adaptive Session Management</title>
<para>
PHP's session manager is adaptive by default currently. Adaptive session manger
has additional risks.
</para>
<para>
Since PHP 5.5.2, <link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
is available. When it is enabled and session save handler supports
it, uninitialized session ID is rejected and new session ID is
created. This protects attack that forces users to use known session
ID. Attacker may paste links or send mail that contains session
ID. e.g. http://example.com/page.php?PHPSESSID=123456789 If <link
linkend="ini.session.use-trans-sid">session.use_trans_sid</link> is
enabled, victim will start session using attacker provided session
ID. <link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
mitigates the risk.
</para>
<warning>
<simpara>
User defined save handler can also support strict session mode by implementing
session ID validation function/method. All user defined save handlers must
implement session ID validation function/method.
</simpara>
</warning>
<para>
Session ID cookie could be set with domain, path, httponly, secure
attributes. There is precedence defined by browsers. By using the
precedence, attacker can set session ID that could be used
permanently. Use of <link
linkend="ini.session.use-only-cookies">session.use_only_cookies</link>
will not solve this issue. <link
linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
mitigates this risk. With <link
linkend="ini.session.use-strict-mode">session.use_strict_mode</link>=On,
uninitialized session ID will not be accepted. Session module
creates new session ID always when session ID is not initialized by
session module.
</para>
<note>
<simpara>
Even though <link
linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
mitigates risk of adoptive session management, attacker can force
users to use initialized session ID which is created by attacker. e.g.
JavaScript injection. This attack could be mitigated by this manual's
recommendations.
</simpara>
<simpara>
If you follow this manual, you will enable <link
linkend="ini.session.use-strict-mode">session.use_strict_mode</link>,
use time-stamp based session management, and regenerate session ID
by <function>session_regenerate_id</function> with recommended procedure.
If you do all of these, attacker generated session ID will eventually
deleted.
</simpara>
<simpara>
When obsolete session access happened, you should save all active
session data for the user. It will be useful for investigation later.
Then, force user to logout from all sessions. i.e. Require users
to re-authenticate. This way, you can prevent attackers from
keep abusing stolen sessions.
</simpara>
</note>
<warning>
<simpara>
Access to obsolete session data does not mean attack always.
Unstable network and/or immediate active session deletion will
cause legitimate users to use obsolete sessions.
</simpara>
</warning>
<para>
Since PHP 7.1.0, <function>session_create_id</function> is added.
This function could be used to prefix session ID by user ID to
access active sessions for a user efficiently. Enabling
<link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
is very important with this setup. Otherwise, malicious users can set
malicious session ID for other users.
</para>
<note>
<simpara>
Users prior to PHP 7.1.0 should use CSPRNG, e.g. /dev/urandom, or
<function>random_bytes</function> and hash functions to generate
new session ID. <function>session_create_id</function> has
collision detection and generates session ID according to session
INI settings. Use of <function>session_create_id</function> is
preferred.
</simpara>
</note>
</sect2>
<sect2 xml:id="features.session.security.management.session-id-regeneration">
<title>Session ID Regeneration</title>
<para>
<link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
is good mitigation, but it is not enough mitigation. Developer must use
<function>session_regenerate_id</function> for session security.
</para>
<para>
Session ID regeneration reduces risk of stolen session ID, thus
<function>session_regenerate_id</function> must be called periodically.
e.g. Regenerate session ID for every 15 minutes for security
sensitive content. Even when session ID is stolen, either legitimate
user or attacker session will be expired. i.e. User or attacker access
will generate obsolete session access error.
</para>
<para>
Session ID must be regenerated when user is authenticated.
<function>session_regenerate_id</function> must be called prior to
set authentication information to $_SESSION. (Since PHP 7.0.0,
<function>session_regenerate_id</function> saves current session
data automatically in order to save time-stamp/etc to current session.)
Make sure only new session contains authenticated flag.
</para>
<para>
Developer must NOT rely on session ID expiration by <link
linkend="ini.session.gc-maxlifetime">session.gc_maxlifetime</link>.
Attackers may access victim's session ID periodically to prevent
expiration and keep exploiting victim's session ID including
authenticated sessions.
</para>
<para>
Instead, you must implement time-stamp based session data management
by yourself.
</para>
<warning>
<simpara>
Although session manager can manage time-stamp transparently, but this
feature is not implemented. Old session data must be kept until
GC. At the same time, developers must make sure obsolete session data
is removed. However, developers must NOT remove active session data
immediately.
i.e. Never call <literal>session_regenerate_id(true);</literal>
and <function> session_destroy</function> for active session.
This may sound contradictory, but this is the mandatory requirement.
</simpara>
</warning>
<para>
<function>session_regenerate_id</function> does not delete
old session by default. Old authenticated session may be available
for use. Developers must prevent old session to be used by anyone,
must prohibit access to obsolete session data by themselves using
time-stamp.
</para>
<warning>
<simpara>
Immediate active session removal has unwanted side effects.
Session could be vanished when there are concurrent connections
to web application and/or network is unstable.
</simpara>
<simpara>
Possibly malicious access cannot be detected with immediate active
session removal also.
</simpara>
<simpara>
Instead of deleting old session immediately, you must set short term
expiration time (time-stamp) in $_SESSION, and prohibit access to
the session data by yourself.
</simpara>
<simpara>
You must not prohibit access to old session data immediately after
<function>session_regenerate_id</function>. It must be prohibited
a little later. e.g. A few seconds later for stable wired network.
A few minutes later for unstable network such as mobile or WiFi.
</simpara>
<simpara>
If user accesses to obsolete session(expired session),
deny access to it. It is recommended to remove authenticated status
from all of the users' session because it is likely an attack.
</simpara>
</warning>
<para>
Proper use of <link linkend="ini.session.use-only-cookies">session.use_only_cookies</link>
and <function>session_regenerate_id</function> could cause personal
DoS by undeletable cookies set by attackers. When this is the case,
you may ask users to remove cookies and warn users that there could
be possible security issues. Attackers may set malicious cookies via
vulnerable web application, vulnerable/malicious browser plugins,
physically compromised device, etc.
</para>
<warning>
<simpara>
Do not misunderstand the DoS risk. <literal>use_strict_mode=On</literal>
is mandatory for general session ID security! All sites are advised
to enable <literal>use_strict_mode</literal>.
</simpara>
<simpara>
DoS could happen only when the account is under attack by crackers.
JavaScript injection vulnerability in application is the most
common cause.
</simpara>
</warning>
</sect2>
<sect2 xml:id="features.session.security.management.session-data-deletion">
<title>Session Data Deletion</title>
<para>
Obsolete session data must be inaccessible and deleted. Current
session module does not handle this well.
</para>
<para>
Obsolete session data is better to be removed as soon as possible.
However, active sessions MUST NOT be removed immediately. To satisfy
these requirements, you MUST implement time-stamp based session
data management by yourself.
</para>
<para>
Set and manage expiration time-stamp in $_SESSION. Prohibit access
to obsolete session data. When obsolete session data access is detected,
it is advised to remove all authenticated status from the user's sessions
and force them to re-authenticated. Obsolete session data access could be
an attack. To do this, you must keep track active sessions per user.
</para>
<note>
<simpara>
Access to obsolete session could happen by unstable network and/or
concurrent access to web site also. Server tried to set new session
ID via cookie, but Set-Cookie packet may not be reached to client due to
lost connection. One connection may issue new session ID by
<function>session_regenerate_id</function>, but another conncurrent
connection may not get the new session ID yet.
Therefore, you must prohibit access to obsolete session a while later.
i.e. Time-stamp based session management is mandatory.
</simpara>
</note>
<para>
In short, do not destroy session data by <function>session_regenerate_id</function>
nor <function>session_destroy</function>, but use time-stamp to control
access to session data. Let <function>session_gc</function> to remove
obsolete data from session data storage.
</para>
</sect2>
<sect2 xml:id="features.session.security.management.session-locking">
<title>Session and Locking</title>
<para>
Session data is locked to avoid races by default. Locking is mandatory
to keep session data consistent across requests.
</para>
<para>
However, locking can be abused by attacker to perform DoS attacks. To
mitigate risks of DoS by session lock, minimize lock. Use read
only sessions when session data update is not required.
Use 'read_and_close' option with <function>session_start</function>.
<literal>session_start(['read_and_close'=>1]);</literal>
Close session as soon as you finish updating $_SESSION by
using <function>session_commit</function>.
</para>
<para>
Current session module does not detect $_SESSION modification while
session is inactive. It is your responsibility not to modify $_SESSION
when session is inactive.
</para>
</sect2>
<sect2 xml:id="features.session.security.management.active-sessions">
<title>Active Sessions</title>
<para>
Developers should keep track active sessions per user and notify the user
how many active sessions, from which IP (and area), how long is active, etc.
PHP does not keep track these. You are supposed to do so.
</para>
<para>
There are number of ways for the implementation. You may setup a
database that keeps track required data and store information
to it. Since session data is GCed, you have to take care of GCed data
to maintain the active session database consistency.
</para>
<para>
One of the simplest implementation is "User ID prefixed session ID" and
store required information to $_SESSION. Many databases have good
performance for selecting string prefix. You can use
<function>session_regenerate_id</function> and
<function>session_create_id</function> for this.
</para>
<warning>
<simpara>
Never use confidential data as prefix. If user ID is confidential,
consider to use <function>hash_hmac</function>.
</simpara>
</warning>
<warning>
<simpara>
Enabling <link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>
is mandatory for this setup. Make sure it is enabled, otherwise
active session database can be compromised.
</simpara>
</warning>
<para>
Time-stamp based session management is mandatory to detect obsolete session
access. When access to obsolete session is detected, you should remove authentication
flags from all of active sessions for the user. This prevents attackers to
keep exploiting stolen session.
</para>
</sect2>
<sect2 xml:id="features.session.security.management.session-and-autologin">
<title>Session and Auto Login</title>
<para>
Developers must NOT use long life session ID for auto login because it
increases risk of stolen session. Auto login should be implemented
by developer.
</para>
<para>
Use secure one time hash key as auto login key using
<function>setcookie</function>. Use secure hash stronger than SHA-2.
e.g. SHA-256 or greater with random data from <function>random_bytes</function>
or /dev/urandom.
</para>
<para>
If user is not authenticated, check the one time auto login key is
valid or not. If key is valid, authenticate user and set new secure
one time hash key. Auto login key must be able to be used
only once. i.e. Never reuse auto login key, generate new auto login key
always.
</para>
<para>
Auto login key is long life authentication key, this key should be
protected as much as possible. Use path/httponly/secure
cookie attributes to protect. i.e. Never transmit auto login key
unless it is required.
</para>
<para>
Developer must implement feature that disables auto login and removes
unneeded auto login key cookie.
</para>
</sect2>
<sect2 xml:id="features.session.security.management.csrf">
<title>CSRF(Cross Site Request Forgeries)</title>
<para>
Session and authentication does not protect against CSRF attack. Developers
must implement CSRF protection by themselves.
</para>
<para>
<function>output_add_rewrite_var</function> can be used for CSRF
protection. Refer to the manual page for details.
</para>
<note>
<simpara>
PHP prior to 7.2.0 uses the same output buffer and INI setting
as trans sid. Therefore, use of <function>output_add_rewrite_var</function>
with PHP prior to 7.2.0 is not recommended.
</simpara>
</note>
<para>
Most web application frameworks support CSRF protection. Refer
to your web application framwork manual for detials.
</para>
</sect2>
</sect1>
<sect1 xml:id="session.security.ini">
<title>Securing Session INI Settings</title>
<para>
By securing session related INI settings, you can improve session security.
Some of important INI settings do not have recommended settings. You
are responsible to hardening session settings.
</para>
<itemizedlist>
<listitem>
<para>
<link linkend="ini.session.cookie-lifetime">session.cookie_lifetime</link>=0
</para>
<para>
0 have special meaning. It tells browsers not to store cookie to
permanent storage. Therefore, when browser is terminated, session
ID cookie is deleted immediately. If developer set other than 0, it may
allow other users to use the session ID. Most applications should
use "0" for this.
</para>
<para>
If auto login feature is required, implement your own secure auto login
feature. Do not use long life session ID for it.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.use-cookies">session.use_cookies</link>=On
</para>
<para>
<link linkend="ini.session.use-only-cookies">session.use_only_cookies</link>=On
</para>
<para>
Although HTTP cookie has some problems, cookie is preferred way to
manage session ID. Use only cookies for session ID management when
it is possible. Most applications should use cookie for session
ID.
</para>
<para>
If <literal>session.use_only_cookies</literal>=Off,
session module will use session ID values set by GET/POST/URL is used when
session ID cookie is not initialized already.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.use-strict-mode">session.use_strict_mode</link>=On
</para>
<para>
Although, enabling <literal>session.use_strict_mode</literal> is
mandatory. It is not enabled by default.
</para>
<para>
This prevents session module to use uninitialized session ID. In
other word, session module only accepts valid session ID generated
by session module. It rejects session ID supplied by
users.
</para>
<para>
Because of cookie spec, attackers are able to set undeletable session ID
cookies via locally setting cookie database or JavaScript injections.
<literal>session.use_strict_mode</literal> can prevent attacker
initialized session ID being used.
</para>
<note>
<para>
Attackers may initialize session ID by their device and may set the
session ID to victim. They must keep the session ID active to abuse.
Attackers require additional steps to perform attack with this attack
scenario. Therefore, <literal>session.use_strict_mode</literal> works
as the mitigation.
</para>
</note>
</listitem>
<listitem>
<para>
<link linkend="ini.session.cookie-httponly">session.cookie_httponly</link>=On
</para>
<para>
Disallow access to session cookie by JavaScript. This setting
prevents cookies stolen by JavaScript injection.
</para>
<para>
It is possible to use session ID as CSRF protection key, but this is not
recommended. For example, HTML source may be saved and sent to
other users. Developer should not write session ID in web pages for
better security. Almost all applications must use httponly attribute for
session ID cookie.
</para>
<note>
<para>
CSRF protection token should be renewed periodically just like
session ID
</para>
</note>
</listitem>
<listitem>
<para>
<link linkend="ini.session.cookie-secure">session.cookie_secure</link>=On
</para>
<para>
Allows access to session ID cookie only when protocol is HTTPS. If
your web site is HTTPS only web site, you must enable this
setting.
</para>
<para>
HSTS should be considered for HTTPS only web site.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.gc-maxlifetime">session.gc_maxlifetime</link>=[choose smallest possible]
</para>
<para>
<literal>session.gc_maxlifetime</literal>
is setting for deleting obsolete session ID. Reliance on this setting
is NOT recommend. You should manage session life time by time-stamp
by yourself.
</para>
<para>
Session GC(garbage collection) is better to be performed by <function>session_gc</function>.
<function>session_gc</function> function is better to be executed by task
managers. e.g. cron on UNIX like systems.
</para>
<para>
GC is performed by probability by default. This setting does not
guarantee old session deletion. Although developer cannot rely
on this setting, setting this to smallest possible value is
recommended. Adjust <link
linkend="ini.session.gc-probability">session.gc_probability</link>
and <link
linkend="ini.session.gc-divisor">session.gc_divisor</link> so
that obsolete sessions are deleted by appropriate frequency. If
auto login feature is required, implement your own secure auto
login feature. Never use long life session ID for it.
</para>
<note>
<para>
Some session save handler modules do not use this setting for
probability based expiration . e.g. memcached, memcache.
Refer to session save handler documentation for details.
</para>
</note>
</listitem>
<listitem>
<para>
<link linkend="ini.session.use-trans-sid">session.use_trans_sid</link>=Off
</para>
<para>
Use of transparent session ID management is not prohibited. You
may use it when it is needed. However, disabling transparent
session ID management would improve general session ID security by
removing possibility of session ID injection and session ID leak.
</para>
<note>
<para>
Session ID may leak from bookmarked URL, e-mailed URL, saved HTML
source.
</para>
</note>
</listitem>
<listitem>
<para>
<link linkend="ini.session.trans-sid-tags">session.trans_sid_tags</link>=[limitted tags]
</para>
<para>
(PHP 7.1.0 &gt;=) You should not rewrite unneeded HTML tags. Default
would be good enough for most usage. Older PHP uses
<link linkend="ini.url-rewriter.tags">url_rewriter.tags</link> for this.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.trans-sid-hosts">session.trans_sid_hosts</link>=[limitted hosts]
</para>
<para>
(PHP 7.1.0 &gt;=) This INI defines whitelist hosts that allows trans sid
rewrite. Never add untrusted host. Session module allow only $_SERVER['HTTP_HOST']
when this setting is empty.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.referer-check">session.referer_check</link>=[your originating URL]
</para>
<para>
When <link
linkend="ini.session.use-trans-sid">session.use_trans_sid</link>
is enabled, use of this setting is recommended.
It reduces risk of session ID injection. If your site is
http://example.com/, set http://example.com/ to it. Note that when
HTTPS is used, browser will not send referrer header. Browser may
not send referrer header by configuration. Therefore, this setting
is not reliable security measure.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.cache-limiter">session.cache_limiter</link>=nocache
</para>
<para>
Make sure HTTP contents are not cached for authenticated
session. Allow caching only when contents is not private.
Otherwise, contents may be exposed. "private" may be used
if HTTP content does not include security sensitive data. Note
that "private" may leave private data cached by shared
clients. "public" may be used only when HTTP content does not
contain any private data at all.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.sid-length">session.sid_length</link>="48"
</para>
<para>
(PHP 7.1.0 &gt;=) Longer session ID results in stronger session
ID. Developer should consider session ID length 32 chars or more. At
least 26 chars is required when
<link linkend="ini.session.sid-bits-per-character">session.sid_bits_per_character</link>="5".
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.sid-bits-per-character">session.sid_bits_per_character</link>="6"
</para>
<para>
(PHP 7.1.0 &gt;=) The more bits in a session ID char, session module
generates stronger session ID for the same session ID length.
</para>
</listitem>
<listitem>
<para>
<link linkend="ini.session.hash-function">session.hash_function</link>="sha256"
</para>
<para>
(PHP 7.1.0 &lt;) Stronger hash function will generates stronger session
ID. Although hash collision is unlikely even with MD5 hash, developers
should use SHA-2 or stronger hash functions for the task. Developers may
use stronger hashes like sha384 and sha512. Make sure you feed long
enough <link linkend="ini.session.entropy-length">entropy</link> for
the hash function used.
</para>
</listitem>
</itemizedlist>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->