| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="zend.session.global_session_management">
- <title>Global Session Management</title>
- <para>
- The default behavior of sessions can be modified using the static methods of <classname>Zend_Session</classname>. All management and
- manipulation of global session management occurs using <classname>Zend_Session</classname>, including configuration of the
- <ulink url="http://www.php.net/session#session.configuration">usual options provided by ext/session</ulink>,
- using <methodname>Zend_Session::setOptions()</methodname>. For example, failure to insure the use of a safe
- <code>save_path</code> or a unique cookie name by ext/session using <methodname>Zend_Session::setOptions()</methodname> may
- result in security issues.
- </para>
- <sect2 id="zend.session.global_session_management.configuration_options">
- <title>Configuration Options</title>
- <para>
- When the first session namespace is requested, <classname>Zend_Session</classname> will automatically start the <acronym>PHP</acronym> session, unless
- already started with
- <link linkend="zend.session.advanced_usage.starting_a_session"><methodname>Zend_Session::start()</methodname></link>.
- The underlying <acronym>PHP</acronym> session will use defaults from <classname>Zend_Session</classname>, unless modified first by
- <methodname>Zend_Session::setOptions()</methodname>.
- </para>
- <para>
- To set a session configuration option, include the basename (the part of the name after
- "<code>session.</code>") as a key of an array passed to <methodname>Zend_Session::setOptions()</methodname>. The
- corresponding value in the array is used to set the session option value. If no options are set by the
- developer, <classname>Zend_Session</classname> will utilize recommended default options first, then the default php.ini settings.
- Community feedback about best practices for these options should be sent to
- <ulink url="mailto:fw-auth@lists.zend.com">fw-auth@lists.zend.com</ulink>.
- </para>
- <example id="zend.session.global_session_management.setoptions.example">
- <title>Using Zend_Config to Configure Zend_Session</title>
- <para>
- To configure this component using
- <link linkend="zend.config.adapters.ini"><classname>Zend_Config_Ini</classname></link>, first add the
- configuration options to the <acronym>INI</acronym> file:
- </para>
- <programlisting language="ini"><![CDATA[
- ; Accept defaults for production
- [production]
- ; bug_compat_42
- ; bug_compat_warn
- ; cache_expire
- ; cache_limiter
- ; cookie_domain
- ; cookie_lifetime
- ; cookie_path
- ; cookie_secure
- ; entropy_file
- ; entropy_length
- ; gc_divisor
- ; gc_maxlifetime
- ; gc_probability
- ; hash_bits_per_character
- ; hash_function
- ; name should be unique for each PHP application sharing the same
- ; domain name
- name = UNIQUE_NAME
- ; referer_check
- ; save_handler
- ; save_path
- ; serialize_handler
- ; use_cookies
- ; use_only_cookies
- ; use_trans_sid
- ; remember_me_seconds = <integer seconds>
- ; strict = on|off
- ; Development inherits configuration from production, but overrides
- ; several values
- [development : production]
- ; Don't forget to create this directory and make it rwx (readable and
- ; modifiable) by PHP.
- save_path = /home/myaccount/zend_sessions/myapp
- use_only_cookies = on
- ; When persisting session id cookies, request a TTL of 10 days
- remember_me_seconds = 864000
- ]]></programlisting>
- <para>
- Next, load the configuration file and pass its array representation to
- <methodname>Zend_Session::setOptions()</methodname>:
- </para>
- <programlisting language="php"><![CDATA[
- $config = new Zend_Config_Ini('myapp.ini', 'development');
- Zend_Session::setOptions($config->toArray());
- ]]></programlisting>
- </example>
- <para>
- Most options shown above need no explanation beyond that found in the standard <acronym>PHP</acronym> documentation, but those
- of particular interest are noted below.
- <itemizedlist mark="opencircle">
- <listitem>
- <para>
- boolean <code>strict</code> - disables automatic starting of <classname>Zend_Session</classname> when
- using <code>new Zend_Session_Namespace()</code>.
- </para>
- </listitem>
- <listitem>
- <para>
- integer <code>remember_me_seconds</code> - how long should session id cookie persist, after user
- agent has ended (e.g., browser application terminated).
- </para>
- </listitem>
- <listitem>
- <para>
- string <code>save_path</code> - The correct value is system dependent, and should be provided by
- the developer using an <emphasis>absolute path</emphasis> to a directory readable
- and writable by the <acronym>PHP</acronym> process. If a writable path is not supplied, then
- <classname>Zend_Session</classname> will throw an exception when started (i.e., when <methodname>start()</methodname>
- is called).
- </para>
- <note>
- <title>Security Risk</title>
- <para>
- If the path is readable by other applications, then session hijacking might be possible. If
- the path is writable by other applications, then
- <ulink url="http://en.wikipedia.org/wiki/Session_poisoning">session poisoning</ulink>
- might be possible. If this path is shared with other users or other <acronym>PHP</acronym> applications,
- various security issues might occur, including theft of session content, hijacking of
- sessions, and collision of garbage collection (e.g., another user's application might cause
- <acronym>PHP</acronym> to delete your application's session files).
- </para>
- <para>
- For example, an attacker can visit the victim's website to obtain a session cookie. Then, he
- edits the cookie path to his own domain on the same server, before visiting his own website
- to execute <methodname>var_dump($_SESSION)</methodname>. Armed with detailed knowledge of the victim's
- use of data in their sessions, the attacker can then modify the session state (poisoning the
- session), alter the cookie path back to the victim's website, and then make requests from
- the victim's website using the poisoned session. Even if two applications on the same server
- do not have read/write access to the other application's <code>save_path</code>, if the
- <code>save_path</code> is guessable, and the attacker has control over one of these two
- websites, the attacker could alter their website's <code>save_path</code> to use the other's
- save_path, and thus accomplish session poisoning, under some common configurations of <acronym>PHP</acronym>.
- Thus, the value for <code>save_path</code> should not be made public knowledge and should be
- altered to a secure location unique to each application.
- </para>
- </note>
- </listitem>
- <listitem>
- <para>
- string <code>name</code> - The correct value is system dependent and should be provided by the
- developer using a value <emphasis>unique</emphasis> to the application.
- </para>
- <note>
- <title>Security Risk</title>
- <para>
- If the <code>php.ini</code> setting for <code>session.name</code> is the same (e.g., the
- default "PHPSESSID"), and there are two or more <acronym>PHP</acronym> applications accessible through the same
- domain name then they will share the same session data for visitors to both websites.
- Additionally, possible corruption of session data may result.
- </para>
- </note>
- </listitem>
- <listitem>
- <para>
- boolean <code>use_only_cookies</code> - In order to avoid introducing additional security risks,
- do not alter the default value of this option.
- <note>
- <title>Security Risk</title>
- <para>
- If this setting is not enabled, an attacker can easily fix victim's session ids, using
- links on the attacker's website, such as
- <code>http://www.example.com/index.php?PHPSESSID=fixed_session_id</code>. The fixation
- works, if the victim does not already have a session id cookie for example.com. Once a
- victim is using a known session id, the attacker can then attempt to hijack the session
- by pretending to be the victim, and emulating the victim's user agent.
- </para>
- </note>
- </para>
- </listitem>
- </itemizedlist>
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.headers_sent">
- <title>Error: Headers Already Sent</title>
- <para>
- If you see the error message, "Cannot modify header information - headers already sent", or, "You must call
- ... before any output has been sent to the browser; output started in ...", then carefully examine the
- immediate cause (function or method) associated with the message. Any actions that require sending <acronym>HTTP</acronym>
- headers, such as sending a cookie, must be done before sending normal output (unbuffered output), except
- when using <acronym>PHP</acronym>'s output buffering.
- </para>
- <itemizedlist mark="opencircle">
- <listitem>
- <para>
- Using <ulink url="http://php.net/outcontrol">output buffering</ulink> often is sufficient to prevent
- this issue, and may help improve performance. For example, in <code>php.ini</code>,
- "<code>output_buffering = 65535</code>" enables output buffering with a 64K buffer. Even though
- output buffering might be a good tactic on production servers to increase performance, relying only
- on buffering to resolve the "headers already sent" problem is not sufficient. The application must
- not exceed the buffer size, or the problem will occur whenever the output sent (prior to the <acronym>HTTP</acronym>
- headers) exceeds the buffer size.
- </para>
- </listitem>
- <listitem>
- <para>
- Alternatively, try rearranging the application logic so that actions manipulating headers are
- performed prior to sending any output whatsoever.
- </para>
- </listitem>
- <listitem>
- <para>
- If a <classname>Zend_Session</classname> method is involved in causing the error message, examine the method carefully, and
- make sure its use really is needed in the application. For example, the default usage of
- <methodname>destroy()</methodname> also sends an <acronym>HTTP</acronym> header to expire the client-side session cookie. If this
- is not needed, then use <methodname>destroy(false)</methodname>, since the instructions to set cookies are sent
- with <acronym>HTTP</acronym> headers.
- </para>
- </listitem>
- <listitem>
- <para>
- Alternatively, try rearranging the application logic so that all actions manipulating headers are
- performed prior to sending any output whatsoever.
- </para>
- </listitem>
- <listitem>
- <para>
- Remove any closing "<code>?></code>" tags, if they occur at the end of a <acronym>PHP</acronym> source file. They
- are not needed, and newlines and other nearly invisible whitespace following the closing tag can
- trigger output to the client.
- </para>
- </listitem>
- </itemizedlist>
- </sect2>
- <sect2 id="zend.session.global_session_management.session_identifiers">
- <title>Session Identifiers</title>
- <para>
- Introduction: Best practice in relation to using sessions with Zend Framework calls for using a browser cookie (i.e.
- a normal cookie stored in your web browser), instead of embedding a unique session identifier in <acronym>URL</acronym>s as
- a means to track individual users. By default this component uses only cookies to maintain session
- identifiers. The cookie's value is the unique identifier of your browser's session. <acronym>PHP</acronym>'s ext/session
- uses this identifier to maintain a unique one-to-one relationship between website visitors, and
- persistent session data storage unique to each visitor. <classname>Zend_Session</classname>* wraps this storage mechanism
- (<varname>$_SESSION</varname>) with an object-oriented interface. Unfortunately, if an attacker gains access
- to the value of the cookie (the session id), an attacker might be able to hijack a visitor's session.
- This problem is not unique to <acronym>PHP</acronym>, or Zend Framework. The <methodname>regenerateId()</methodname> method allows
- an application to change the session id (stored in the visitor's cookie) to a new, random, unpredictable
- value. Note: Although not the same, to make this section easier to read, we use the terms "user agent"
- and "web browser" interchangeably.
- </para>
- <para>
- Why?: If an attacker obtains a valid session identifier, an attacker might be able to impersonate a
- valid user (the victim), and then obtain access to confidential information or otherwise manipulate the
- victim's data managed by your application. Changing session ids helps protect against session hijacking.
- If the session id is changed, and an attacker does not know the new value, the attacker can not use the
- new session id in their attempts to hijack the visitor's session. Even if an attacker gains access to an
- old session id, <methodname>regenerateId()</methodname> also moves the session data from the old session id "handle"
- to the new one, so no data remains accessible via the old session id.
- </para>
- <para>
- When to use regenerateId(): Adding <methodname>Zend_Session::regenerateId()</methodname> to your Zend Framework
- bootstrap yields one of the safest and most secure ways to regenerate session id's in user agent
- cookies. If there is no conditional logic to determine when to regenerate the session id, then there are
- no flaws in that logic. Although regenerating on every request prevents several possible avenues of
- attack, not everyone wants the associated small performance and bandwidth cost. Thus, applications
- commonly try to dynamically determine situations of greater risk, and only regenerate the session ids in
- those situations. Whenever a website visitor's session's privileges are "escalated" (e.g. a visitor
- re-authenticates their identity before editing their personal "profile"), or whenever a security
- "sensitive" session parameter change occurs, consider using <methodname>regenerateId()</methodname> to create a new
- session id. If you call the <methodname>rememberMe()</methodname> function, then don't use
- <methodname>regenerateId()</methodname>, since the former calls the latter. If a user has successfully logged into
- your website, use <methodname>rememberMe()</methodname> instead of <methodname>regenerateId()</methodname>.
- </para>
- <sect3 id="zend.session.global_session_management.session_identifiers.hijacking_and_fixation">
- <title>Session Hijacking and Fixation</title>
- <para>
- Avoiding <ulink url="http://en.wikipedia.org/wiki/Cross_site_scripting">cross-site script (XSS)
- vulnerabilities</ulink> helps preventing session hijacking. According to
- <ulink url="http://secunia.com/">Secunia's</ulink> statistics XSS problems occur frequently, regardless
- of the languages used to create web applications. Rather than expecting to never have a XSS problem with
- an application, plan for it by following best practices to help minimize damage, if it occurs. With XSS,
- an attacker does not need direct access to a victim's network traffic. If the victim already has a
- session cookie, Javascript XSS might allow an attacker to read the cookie and steal the session. For
- victims with no session cookies, using XSS to inject Javascript, an attacker could create a session id
- cookie on the victim's browser with a known value, then set an identical cookie on the attacker's
- system, in order to hijack the victim's session. If the victim visited an attacker's website, then the
- attacker can also emulate most other identifiable characteristics of the victim's user agent. If your
- website has an XSS vulnerability, the attacker might be able to insert an <acronym>AJAX</acronym> Javascript that secretly
- "visits" the attacker's website, so that the attacker knows the victim's browser characteristics and
- becomes aware of a compromised session at the victim website. However, the attacker can not arbitrarily
- alter the server-side state of <acronym>PHP</acronym> sessions, provided the developer has correctly set the value for the
- <code>save_path</code> option.
- </para>
- <para>
- By itself, calling <methodname>Zend_Session::regenerateId()</methodname> when the user's session is first used, does
- not prevent session fixation attacks, unless you can distinguish between a session originated by an
- attacker emulating the victim. At first, this might sound contradictory to the previous statement above,
- until we consider an attacker who first initiates a real session on your website. The session is "first
- used" by the attacker, who then knows the result of the initialization (<methodname>regenerateId()</methodname>).
- The attacker then uses the new session id in combination with an XSS vulnerability, or injects the
- session id via a link on the attacker's website (works if <code>use_only_cookies = off</code>).
- </para>
- <para>
- If you can distinguish between an attacker and victim using the same session id, then session hijacking
- can be dealt with directly. However, such distinctions usually involve some form of usability tradeoffs,
- because the methods of distinction are often imprecise. For example, if a request is received from an IP
- in a different country than the IP of the request when the session was created, then the new request
- probably belongs to an attacker. Under the following conditions, there might not be any way for a
- website application to distinguish between a victim and an attacker:
- <itemizedlist mark='opencircle'>
- <listitem>
- <para>
- attacker first initiates a session on your website to obtain a valid session id
- </para>
- </listitem>
- <listitem>
- <para>
- attacker uses XSS vulnerability on your website to create a cookie on the victim's browser
- with the same, valid session id (i.e. session fixation)
- </para>
- </listitem>
- <listitem>
- <para>
- both the victim and attacker originate from the same proxy farm (e.g. both are behind the
- same firewall at a large company, like AOL)
- </para>
- </listitem>
- </itemizedlist>
- The sample code below makes it much harder for an attacker to know the current victim's session id,
- unless the attacker has already performed the first two steps above.
- </para>
- <example id="zend.session.global_session_management.session_identifiers.hijacking_and_fixation.example">
- <title>Session Fixation</title>
- <programlisting language="php"><![CDATA[
- $defaultNamespace = new Zend_Session_Namespace();
- if (!isset($defaultNamespace->initialized)) {
- Zend_Session::regenerateId();
- $defaultNamespace->initialized = true;
- }
- ]]></programlisting>
- </example>
- </sect3>
- </sect2>
- <sect2 id="zend.session.global_session_management.rememberme">
- <title>rememberMe(integer $seconds)</title>
- <para>
- Ordinarily, sessions end when the user agent terminates, such as when an end user exits a web browser
- program. However, your application may provide the ability to extend user sessions beyond the lifetime of
- the client program through the use of persistent cookies. Use <methodname>Zend_Session::rememberMe()</methodname> before
- a session is started to control the length of time before a persisted session cookie expires. If you do not
- specify a number of seconds, then the session cookie lifetime defaults to <code>remember_me_seconds</code>,
- which may be set using <methodname>Zend_Session::setOptions()</methodname>. To help thwart session fixation/hijacking,
- use this function when a user successfully authenticates with your application (e.g., from a "login" form).
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.forgetme">
- <title>forgetMe()</title>
- <para>
- This function complements <methodname>rememberMe()</methodname> by writing a session cookie that has a lifetime ending
- when the user agent terminates.
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.sessionexists">
- <title>sessionExists()</title>
- <para>
- Use this method to determine if a session already exists for the current user agent/request. It may be used
- before starting a session, and independently of all other <classname>Zend_Session</classname> and
- <classname>Zend_Session_Namespace</classname> methods.
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.destroy">
- <title>destroy(bool $remove_cookie = true, bool $readonly = true)</title>
- <para>
- <methodname>Zend_Session::destroy()</methodname> destroys all of the persistent data associated with the current
- session. However, no variables in <acronym>PHP</acronym> are affected, so your namespaced sessions (instances of
- <classname>Zend_Session_Namespace</classname>) remain readable. To complete a "logout", set the optional parameter to
- <constant>TRUE</constant> (the default) to also delete the user agent's session id cookie. The optional
- <varname>$readonly</varname> parameter removes the ability to create new <classname>Zend_Session_Namespace</classname>
- instances and for <classname>Zend_Session</classname> methods to write to the session data store.
- </para>
- <para>
- If you see the error message, "Cannot modify header information - headers already sent", then either avoid
- using <constant>TRUE</constant> as the value for the first argument (requesting removal of the session cookie), or
- see <xref linkend="zend.session.global_session_management.headers_sent" />. Thus,
- <methodname>Zend_Session::destroy(true)</methodname> must either be called before <acronym>PHP</acronym> has sent <acronym>HTTP</acronym> headers, or output
- buffering must be enabled. Also, the total output sent must not exceed the set buffer size, in order to
- prevent triggering sending the output before the call to <methodname>destroy()</methodname>.
- </para>
- <note>
- <title>Throws</title>
- <para>
- By default, <varname>$readonly</varname> is enabled and further actions involving writing to the session data
- store will throw an exception.
- </para>
- </note>
- </sect2>
- <sect2 id="zend.session.global_session_management.stop">
- <title>stop()</title>
- <para>
- This method does absolutely nothing more than toggle a flag in <classname>Zend_Session</classname> to prevent further writing to
- the session data store. We are specifically requesting feedback on this feature. Potential uses/abuses might
- include temporarily disabling the use of <classname>Zend_Session_Namespace</classname> instances or
- <classname>Zend_Session</classname> methods to write to the session data store, while execution is transferred to view-
- related code. Attempts to perform actions involving writes via these instances or methods will throw an
- exception.
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.writeclose">
- <title>writeClose($readonly = true)</title>
- <para>
- Shutdown the session, close writing and detach <varname>$_SESSION</varname> from the back-end storage mechanism.
- This will complete the internal data transformation on this request. The optional <varname>$readonly</varname>
- boolean parameter can remove write access by throwing an exception upon any attempt to write to the session
- via <classname>Zend_Session</classname> or <classname>Zend_Session_Namespace</classname>.
- </para>
- <note>
- <title>Throws</title>
- <para>
- By default, <varname>$readonly</varname> is enabled and further actions involving writing to the session data
- store will throw an exception. However, some legacy application might expect <varname>$_SESSION</varname> to
- remain writable after ending the session via <methodname>session_write_close()</methodname>. Although not considered
- "best practice", the <varname>$readonly</varname> option is available for those who need it.
- </para>
- </note>
- </sect2>
- <sect2 id="zend.session.global_session_management.expiresessioncookie">
- <title>expireSessionCookie()</title>
- <para>
- This method sends an expired session id cookie, causing the client to delete the session cookie. Sometimes
- this technique is used to perform a client-side logout.
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.savehandler">
- <title>setSaveHandler(Zend_Session_SaveHandler_Interface $interface)</title>
- <para>
- Most developers will find the default save handler sufficient. This method provides an object-oriented
- wrapper for
- <ulink url="http://php.net/session_set_save_handler"><methodname>session_set_save_handler()</methodname></ulink>.
- </para>
- </sect2>
- <sect2 id="zend.session.global_session_management.namespaceisset">
- <title>namespaceIsset($namespace)</title>
- <para>
- Use this method to determine if a session namespace exists, or if a particular index exists in a particular
- namespace.
- </para>
- <note>
- <title>Throws</title>
- <para>
- An exception will be thrown if <classname>Zend_Session</classname> is not marked as readable (e.g., before
- <classname>Zend_Session</classname> has been started).
- </para>
- </note>
- </sect2>
- <sect2 id="zend.session.global_session_management.namespaceunset">
- <title>namespaceUnset($namespace)</title>
- <para>
- Use <methodname>Zend_Session::namespaceUnset($namespace)</methodname> to efficiently remove an entire namespace and its
- contents. As with all arrays in <acronym>PHP</acronym>, if a variable containing an array is unset, and the array contains
- other objects, those objects will remain available, if they were also stored by reference in other
- array/objects that remain accessible via other variables. So <methodname>namespaceUnset()</methodname> does not perform
- a "deep" unsetting/deleting of the contents of the entries in the namespace. For a more detailed
- explanation, please see <ulink url="http://php.net/references">References Explained</ulink> in the <acronym>PHP</acronym>
- manual.
- </para>
- <note>
- <title>Throws</title>
- <para>
- An exception will be thrown if the namespace is not writable (e.g., after <methodname>destroy()</methodname>).
- </para>
- </note>
- </sect2>
- <sect2 id="zend.session.global_session_management.namespaceget">
- <title>namespaceGet($namespace)</title>
- <para>
- DEPRECATED: Use <methodname>getIterator()</methodname> in <classname>Zend_Session_Namespace</classname>. This method returns an
- array of the contents of <varname>$namespace</varname>. If you have logical reasons to keep this method publicly
- accessible, please provide feedback to the
- <ulink url="mailto:fw-auth@lists.zend.com">fw-auth@lists.zend.com</ulink> mail list. Actually, all
- participation on any relevant topic is welcome :)
- </para>
- <note>
- <title>Throws</title>
- <para>
- An exception will be thrown if <classname>Zend_Session</classname> is not marked as readable (e.g., before
- <classname>Zend_Session</classname> has been started).
- </para>
- </note>
- </sect2>
- <sect2 id="zend.session.global_session_management.getiterator">
- <title>getIterator()</title>
- <para>
- Use <methodname>getIterator()</methodname> to obtain an array containing the names of all namespaces.
- </para>
- <note>
- <title>Throws</title>
- <para>
- An exception will be thrown if <classname>Zend_Session</classname> is not marked as readable (e.g., before
- <classname>Zend_Session</classname> has been started).
- </para>
- </note>
- </sect2>
- </sect1>
|