magento SUPEE 5344

Analyzing the Magento Vulnerability (Updated)

Check Point researchers recently discovered a critical RCE (remote code execution) vulnerability in the Magento web e-commerce platform that can lead to the complete compromise of any Magento-based store, including credit card information as well as other
financial and personal data, affecting nearly two hundred thousand online shops.


Check Point privately disclosed the vulnerabilities together with a list of suggested fixes to eBay prior to public disclosure. A patch to address the flaws was released on February 9, 2015 (SUPEE-5344

available here
). Store owners and administrators are urged to apply the patch immediately if they haven’t done so already.
For a visual demonstration of one way the vulnerability can be exploited, please see our video

What kind of attack is it?

The vulnerability is actually comprised of a chain of several vulnerabilities that ultimately allow an unauthenticated attacker to execute PHP code on the web server. The attacker bypasses all security mechanisms and gains control of the store and its complete
database, allowing credit card theft or any other administrative access into the system.

This attack is not limited to any particular plugin or theme. All the vulnerabilities are present in the Magento core, and affects any default installation of both Community and Enterprise Editions. Check Point customers are already protected from exploitation
attempts of this vulnerability through the IPS software blade.


How did Check Point discover this vulnerability?

These vulnerabilities were discovered by Netanel Rubin of the Check Point Malware & Vulnerability Research Group. Check Point researchers often function as “white hat hackers”, discovering and protecting against security vulnerabilities before malicious
actors are able to exploit them. This publication is part of Check Point’s ongoing efforts toward public security awareness and education of consumers and enterprises.


How can I protect against the vulnerability?

Magento-based e-Commerce businesses are advised to apply the designated patch SUPEE-5344 released by Magento. Although Check Point did not witness any exploitation attempts of this vulnerability in the wild, administrators are advised to monitor logs for
patterns matching the technical description.

Check Point IPS currently protects against exploitation attempts of this vulnerability.


Vulnerable Versions

Confirmed vulnerable: CE and EE (Latest releases as of this writing).



Magento is a popular eCommerce platform purchased by eBay in 2011. It has 2 versions:
• A community version, which is open-sourced and contains code contributed from the community.
• An enterprise version, which offers more features as well as customer support and other premium benefits.

We discovered a vulnerability-chain which ultimately allows an unauthenticated attacker to execute PHP code in the vulnerable server. This chain consists of a number of vulnerabilities, which are described further in the technical description.

These vulnerabilities have been assigned CVE-2015-1397, CVE-2015-1398, CVE-2015-1399.


Disclosure Timeline

January 14, 2015 – First contact with Magento Security
January 15, 2015 – Provided complete vulnerability report including suggested fixes
February 9, 2015 – Patch Released by Magento (SUPEE-5344 available here)
April 22, 2015 – Public Disclosure


Technical Description

Before we begin the technical walkthrough resulting in the discovery of each vulnerability, we need to briefly explain how Magento extensively uses reflection-style code as a dynamic way of providing content.

Magento uses modules, which are directories that contain different pieces of functionality. Each module contains controllers, which are PHP class files that define actions, public methods in that class.

Each incoming request is parsed to understand which module, controller and action is requested by the user. The PATH_INFO variable (the section of the URI after the resource/script file name) contains the requested model, controller, and action name, in
this format:


The dynamic controller loading logic uses this algorithm:
1. Determine if [MODULE_NAME] exists in the modules white list.
2. If it exists, construct a class name in this format: Mage_[MODULE_NAME]_[CONTROLLER_NAME]Controller
3. Find a class file by replacing every ‘_’ with ‘/’ and appending a ‘php’ extension.
4. If the file is found, include it.

For example, if we send this request:

GET /index.php/downloadable/file/ HTTP/1.1

The following class is loaded:


Controlled Injections

When an administrator user attempts to load a controller, the system appends the string ‘Adminhtml’ after the module name.

Therefore, if we add the ‘admin’ prefix to our request:

GET /index.php/admin/downloadable/file/ HTTP/1.1

We get the following class name (Note: the reflection loader repeats the module name after the ‘Adminhtml’ prefix):


Therefore, in addition to the obvious LFI of any file with a ‘Controller.php’ suffix (‘/index.php/downloadable/……file’), we can bypass admin authentication if we send this request:

GET /index.php/downloadable/Adminhtml_Downloadable_File/ HTTP/1.1

That will try to load this class:


The weakness exposed here is that Magento’s code validates admin sessions only if a request contains the ‘/admin/’ prefix, and fails to detect this controller injection technique.

However, as mentioned earlier, a predefined module white list constrains the module names we can attempt to load. This list differs between user and admin contexts. Therefore, the controller injection technique (loading an admin-context module while in user-context)
will only work for modules that exist in both white lists (controllers containing user and admin functionality). This greatly limits our attack surface.

A second drawback for attackers is that some modules implement additional checks for specific privileges. As we don’t have any privileges, these checks will fail.

These well-implemented safeguards really constrain our attack surface, and we could not find any critical (RCE, LFI with no suffix) vulnerabilities with this approach.


Best Foot ‘Forwarded’

Continuing in attacker mode, we next examined how the system determines if we are administrators when a controller is loaded.

For authentication-required controllers, we discovered that the system does not block access (using exit(), die; or similar) for unauthenticated users. Instead, the requested controller is replaced with a ‘login’ controller.

This code can be viewed here:

$requestedActionName = $request->getActionName();
$openActions = array(
    LIST_OF_OPEN_ACTIONS (login, reset password, etc.)

if (in_array($requestedActionName, $openActions)) {
    $request->setDispatched(true); // An open actions
} else { // Should check authentication
    if($user) { // A user exist
        $user->reload(); // Check validity of user
    if (!$user || !$user->getId()) { // No user or no user ID
        if ($request->getPost('login')) { // Try to login
	     // No active user session AND no user login - access denied
        if (!$request->getParam('forwarded')) {
            if ($request->getParam('isIframe')) {
            } elseif($request->getParam('isAjax')) {
            } else {
            return false;


Note the highlighted ‘if’ statement that occurs after the system has detected that we are not authenticated and we are not attempting to log in.

• If the statement is true, the system changes our controller to one of the ‘denied access’ controllers, blocking us from the admin panel.
• If the statement is false, our controller remains unchanged and the system continues normally.

This is a simple control flow logic flaw, and we can bypass the checks by the simple addition of a ‘forwarded’ parameter to our request.

We believe this flaw resulted from an attempt to allow controllers to authenticate users bypassing the built-in login mechanism. The fact that this parameter can be controlled externally may have been overlooked.

This finding vastly widens our attack surface, as we can now access any admin module regardless of whether or not it offers functionality to regular users.

However, we are left with the restriction of using modules that do not implement additional privilege checking.

Mage: Level 90

There are very few modules that never check for privileges, and those modules do not usually do anything that would be of interest to an attacker. However, let’s take a look at the ‘Cms_Wysiwyg’ controller in the ‘Adminhtml’ module. This
controller has only one action named ‘directive’, which loads an image using a given path.

This is the relevant code from the controller:

// Get the ___directive parameter
$directive = $this->getRequest()->getParam('___directive');

// base64_decodes the input
$directive = Mage::helper('core')->urlDecode($directive);

// Filter the input
$url = Mage::getModel('cms/adminhtml_template_filter')->filter($directive);

// Try to load the image
try {
    $image = Varien_Image_Adapter::factory('GD2');


Excerpt from the “What You See is What You Gain access to” controller


Any attempt to load a file that is not a valid image (such as a configuration file, typically sought after by attackers) results in an exception being raised, and a default image is displayed instead. As a result, we can’t use this controller for a local
file disclosure attack.

However, let’s examine the ‘filter()’ function. In this controller, ‘filter()’ is defined as a method in ‘cms/adminhtml_template_filter.’

The ‘filter()’ function reviews the input and performs these steps:
1. Locate blocks with this format: ‘{{FUNC_NAME PARAM_NAME=PARAM_VALUE}}’
2. If found, attempt to call the function ‘[FUNC_NAME]Directive’ in this class.
3. If called successfully, replace the block with the function output.

As part of an attack, we can attempt to use any filter that admin templates use, and call any class method named ‘[FUNC]Directive.’ Unfortunately, many of these methods operate only if there are template parameters. As our template parsing instance doesn’t
contain any, most of these methods are useless for our purposes.

Luckily, one function turned out very valuable: ‘blockDirective()’, which creates a Magento-“block” class under our control.

The class name is defined:


where ‘X’ and ‘Y’ are both under our current control.

Apart from the LFI this causes (triggered by the auto loading mechanism), we can thus create any block class we want.

Note: The LFI is no use to us on its own; most of the system files are class files, while other files do not contain code that we can exploit. As in the previous LFI, a null byte can be useful for this situation, but we don’t want our exploit
to work only on old versions of PHP (null byte injections were fixed since 5.3.4).

After the system initializes the requested block, it continues to set its parameters using the function ‘setDataUsingMethod()’:

public function setDataUsingMethod($key, $args=array())
 $method = 'set'.$this->_camelize($key); // Parse the method name
 $this->$method($args); // Call the method
    return $this; // Return (Wow, such dynamics, very $this)

Mage_Core_Model_Email_Template_Filter::blockDirective ()

Where ‘$key’ is the name of one of the filter parameters we have entered and ‘$args’ is its value.

At this point, an attacker can call any method that starts with the ‘set’ prefix (e.g. ‘setData()’), passing an argument that he controls.

After the block is initialized and has its parameters set, ‘blockDirective()’ lets us call any method we want (from the block class)with no arguments.

We can now create an instance of any block class we want, set some of its properties, and then call any class method without arguments. The drawback for attackers is that blocks are usually responsible for the GUI parts of the system, and the majority of
them can’t be used for anything potentially dangerous.


A needle in a Magestack

Determined to discover an exploitable function, we set out on another code-wide search. We found the ‘getCsvFile()’ method from the ‘Mage_Adminhtml_Block_Widget_Grid’ class, which exports the data contained in the block (data extracted
from the DB) and saves it into a CSV file.Unfortunately for attackers, neither the file name nor its extension can be controlled.

Magento offers the option of filtering what data is extracted, by adding different conditions to the actual SQL query.

This is the routine logic:

1. Retrieve the ‘filter’ parameter from the request.
2. Parse it using ‘parse_str()’.
3. Loop on each parameter name and check for a matching condition name.
4. If a match is found, parse and escape the parameter so it will be safe to use in the query based on the condition type used.
5. Add the parsed condition to the SQL query.

For a regular text string used as a filter in an SQL condition, the condition parsing is performed by inserting

the string into a ‘LIKE’ statement with two surrounding ’%’ chars. The resulting ‘WHERE’ statement is similar to:

WHERE `attr` LIKE ‘%escaped_text_string%’

Other filtering conditions (‘id’, ‘range’) are processed differently. For example, the class ‘Mage_Adminhtml_Block_Report_Search_Grid’ displays every search query that is performed. One of its condition parameters, ‘popularity’,
is considered a ‘range’ condition. This means we can insert 2 parameters, ‘from’ and ‘to’, to create a range operation on the ‘popularity’ column of the search term.

The condition parsing is seen here (where we control ‘$condition’):

$conditionKeyMap = array(
    // A dictionary containing operation and their matching SQL

$query = '';
// If the condition is an array
if (is_array($condition)) { 
    // If there's a 'field_expr' field, assign it to $fieldName
    if (isset($condition['field_expr'])) { 
        $fieldName = str_replace('#?', $this->quoteIdentifier($fieldName), $condition['field_expr']);
    // Add the start condition
    if (isset($condition['from'])) { 
        $from = $this->_prepareSqlDateCondition($condition, 'from');
        $query = $this->_prepareQuotedSqlCondition($conditionKeyMap['from'], $from, $fieldName);
    // Add the end condition
    if (isset($condition['to'])) { 
        $query .= empty($query) ? '' : ' AND ';
        $to = $this->_prepareSqlDateCondition($condition, 'to');
        $query = $this->_prepareQuotedSqlCondition($query . $conditionKeyMap['to'], $to, $fieldName);

Varien_Db_Adapter_Pdo_Mysql::prepareSqlCondition ()

Note the highlighted line where ‘$fieldname’ is replaced with a value arriving from ‘$condition.’ This means we can control the field name on which the condition is performed if we can assign a ‘field_expr’ key in the condition
dictionary. Since the field name is considered secure, it’s not escaped.

As attackers, we want to control the entire dictionary. The system attempts to validate our input by passing it into a validator function as seen here:

// This is the actual array from the request parameter
$value = $this->getData('value');

// Checks for a 'from' and 'to' keys (I edited the ‘if’ statement a little bit so it’ll be convenient to read)
if (strlen($value['from']) > 0) || strlen($value['to']) > 0) {
return $value; // Just return the dictionary as it!


Therefore, if there are ‘from’ and ‘to’ keys in the dictionary, the function returns the dictionary as is, without unsetting any of its elements.

Now that we control the entire dictionary, we can inject any SQL we want. Fortunately for attackers, Magento uses PHP PDO,
which by default supports multiple queries in a single statement. In our case, Magento assigns ‘PDO::ATTR_EMULATE_PREPARES’ to ‘true’, which forces PDO to enable multiple queries support. This means we can go beyond ‘union select’ statement
injections, and inject any query we want.

There is no shortage of effective SQLi exploitation ideas. For example, we could add another administrator account in the DB and could completely compromise the system.
Such a change is too visible, however, and is still not as deep as an RCE could potentially go. We decided to keep looking for vectors to gain access to the RCE holy grail.

We can use the SQLi to add another file to the server by inserting our data to the ‘core_file_storage’ table which stores media files locally. When we access the ‘get.php’ page and request the file we just inserted, the files are created in the actual file
system under the ‘media’ directory.


Dodging Detection

A drawback for attackers is that the system disables CGI execution in this directory using an ‘.htaccess’ file explicitly declaring ‘Options –ExecCGI.’ In order to bypass that safeguard, we can create another ‘.htaccess’ file in a subdirectory
and override the parent declaration. However, this is a common exploitation trick, easily detected when looking for suspicious red flags in the server.
Similarly, we will avoid using the previously mentioned LFI as we don’t want to introduce the very suspicious artifact of a ‘.php’ file in an image directory.We therefore need to find a creative way to execute a ‘.jpg’ file without using another ‘.htaccess.’

The ‘Mage_Core_Block_Template_Zend’ class has 2 methods: ‘setScriptPath()’ and ‘fetchView().’ ‘fetchView()’ takes a template file as an argument and calls the ‘render()’ method at ‘Zend_View_Abstract’.
It then parses the template file’s full path and includes it without forcing another extension.

Examine the following code:

// Check for any path traversal in the template name
if ($this->isLfiProtectionOn() && preg_match('#..[\/]#', $name)) {
// Check that we have at least one include directory
if (0 == count($this->_path['script'])) {
// Try to include the file by concating each directory with the file name
foreach ($this->_path['script'] as $dir) {
// If the file is readable, return it (and later include it without further checks)
   if (is_readable($dir . $name)) {
       return $dir . $name;


At first glance, this appears to be a great opportunity for us to LFI our way to the file we have created. However, even though we can call any method we want, we are still limited to calling it without any arguments. This means we have to rely on another
method to call ‘fetchView()’ with an argument we can control.

Fortunately for the attackers, such a method does exist: ‘renderView()’, which calls ‘fetchView()’ with a template file. We can control the template name property because of the ‘setDataUsingMethod()’ behavior we examined earlier.
The problem is that this function calls ‘setScriptPath()’ before the ‘fetchView()’ call, which adds the ‘./app/design’ directory to the ‘$this->_path’ array. This means that path is now prepended to our file path; due to
the LFI protection at the start of the function, we cannot insert any path traversal strings and escape the ‘design’ directory.

If we call ‘setScriptPath()’ ourselves with our directory before the call to ‘renderView()’ (using ‘setDataUsingMethod()’), ‘renderView()’ will override us and won’t let us use our directory.

However, if we call ‘setScriptPath()’ with our directory name and ‘fetchView()’ directly (without going through ‘renderView()’), ‘fetchView()’ is called with a blank file name. When the system appends the file name to
the directory, the resulting include path contains only our directory. WIN.

Unfortunately, ‘setScriptPath()’ appends a forward slash (‘/’) to our directory name, forcing our path to be treated as a directory in the include statement. This is a problem, as directories cannot be included.

However, as we control anything prior to that slash, we also control the stream wrapper (e.g. ‘http://’). We can therefore potentially RFI our way out of this situation. However, RFI is disabled by default in PHP versions greater than 5.2, so we need to
find a different, unique wrapper to exploit, one that will allow us to treat a directory path as a valid file and is not affected by PHP’s ‘allow_url_include.’


Phar Fetched Exploit

The ‘phar://’ wrapper handles PHP archive files. Similar to JAR files, these are archives containing compressed PHP files. The unique feature of the Phar wrapper for our purposes is that
it allows us to include a file despite an ending forward slash in the file path. That file will be properly included due to the fact the wrapper interprets the last forward slash as the root directory inside the Phar archive.

Therefore, if we try to include a path similar to ‘phar://lolz.file/’, the Phar parser will execute the file’s ‘bootstrap stub’, an executable PHP code that is loaded whenever the phar file is included directly (without trying to access the files
it contains). Additionally, the Phar wrapper supports different stat functions such as ‘is_readable()’, allowing us to pass the validity checks of the included file. Finally, the Phar parser disregards bytes prior to its header, as it considers them
part of the bootstrap stub. We can therefore append our code to a valid image file – a very stealthy approach that is hard to detect. If we want to go the extra mile, Phar supports GZ and BZ2 compressions, as well as ZIP and TAR archives.

Using the authentication bypass, and combining both the file upload/SQLi vulnerability and the LFI/RFI vulnerability, we can ultimately execute any PHP code on the system, without any authentication.


此文章通过 python 爬虫创建,原文是自己的csdn 地址: magento SUPEE 5344

php pdo 简单使用 (二)

通过上一节内容 php pdo 简单使用(一)了解了链接的创建和简单的操作。
通过 pdo 有三种方式执行sql语句: exec, query, prepared statement
1. exec() 函数适用于执行一次sql 操作,并且不适用于select语句 参见:
2. query() 函数适用于执行一次sql操作,并且在下一次query之前要fetch 出所有的结果否则执行将会失败 参见:
3. 如果一类查询(查询结构相似而具体的参数不一)需要一次解析而执行使用很多次,可以先用prepared statement,这样可以为具体的查询的执行做好准备,避免了分析、编译、优化的循环,将减少资源占用率,从而提高运行效率。通过对数据库进行prepare操作,便会返回PDOStatement数据类型,从而在其基础上展开execute、fetch等进一步的操作。
使用prepared statement还可以防止SQL注入。查询语句里可以使用包含命名(:name)和问号(?)的参数占位符,分别将用associated array 和indexed array传入数值。

header("Content-type: text/html; charset=utf-8");

$dbType = 'mysql';
$dbUser = 'root';
$dbPass = 'simael';
$dbhost = 'localhost';
$dbName = 'pdotest';
    $pdo = new PDO($dsn, $dbUser, $dbPass,array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); 
    echo "PDO成功连接MySQL数据库!<br>";
}catch(PDOException $exception){
    echo $exception->getMessage();
$pdo->query('set names utf8');

$name = '';
$text = '';

// 用问号占位符传入参数 
$stmt = $pdo->prepare("INSERT INTO users (name, text) VALUES (?, ?)");
//$stmt->bindParam(1, $name); // 这里只能传入变量,不能是常量例如 $stmt->bindParam(1, 'simael'); 报错
//$stmt->bindParam(2, $text);

$stmt->bindValue(1, 'bindValue');
$stmt->bindValue(2, 'bindValue 's text');

$name = 'again name';
$text = 'again text text';
//$stmt->execute(array('mike','mike text'));
// 用命名占位符传入参数
$stmt2 = $pdo->prepare("SELECT * FROM users WHERE name like :name");

/*while($row = $stmt2->fetch(PDO::FETCH_ASSOC)){       
 echo '<br>';


当然我们也可以直接在 execute()函数中直接传入参数。
上面的例子还包含了使用Pdo 的插入和查询操作,其它操作类似。


$row=$dbh->fetchAll(PDO::FETCH_BOTH); //FETCH_BOTH是默认的,可省,返回关联和索引。
$row=$dbh->fetchAll(PDO::FETCH_ASSOC); //FETCH_ASSOC参数决定返回的只有关联数组。
$row=$dbh->fetchAll(PDO::FETCH_NUM); //返回索引数组
$row=$dbh->fetchAll(PDO::FETCH_OBJ); //如果fetch()则返回对象,如果是fetchall(),返回由对象组成的二维数组


$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT); //不显示错误

$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);//显示警告错误,并继续执行



$dbh->exec("Insert INTO `test`.`table` (`name` ,`text`)VALUES ('mick', 'text1');");    
$dbh->exec("Insert INTO `test`.`table` (`name` ,`text`)VALUES ('lily', 'text2');");   
$dbh->exec("Insert INTO `test`.`table` (`name` ,`text`)VALUES ('susan', 'text3');");    
 } catch (Exception $e) {    
echo "Failed: " . $e->getMessage();    

使用pdo 建立一个持久连接:

$db = new PDO('mysql:host=localhost;dbname=testpdo', $user, $pass, array(


$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('n').length;
var $numbering = $('

    for (i = 1; i <= lines; i++) {
  • ').text(i));

此文章通过 python 爬虫创建,原文是自己的csdn 地址: php pdo 简单使用 (二)


HTTP (HyperText Transfe Protocol)超文本传输协议,是一种基于 TCP/IP 的网络协议。
HTTP 是一种无状态的协议,即 客户端向服务器发请求的时候服务器不会存储关于客户的任何信息,这样即使这个客户很快的再次请求同一个资源的时候服务器是不会认识他的,而是再次重新发送这个资源。所以为了支持客户端和服务器的交互,引入了cookie 和 session 技术。
cookie 保存在客户端,session 保存在服务器端。
HTTP 协议现在有两个版本,HTTP/1.0 和 HTTP/1.1
1 缓存处理
2 带宽优化及网络连接的使用
3 错误通知的管理
4 消息在网络中的发送
5 互联网地址的维护
6 安全性及完整性

这里可以说比较主要的一点区别就是 HTTP/1.1 默认开启了 keep-alive .
当使用 HTTP/1.0 访问一个有很多图片资源 css js 的网页时,客户端的每一个请求都需要重复建立连接和关闭连接,既造成了消耗有造成了延迟。
但是使用 HTTP/1.1(开启 keep-alive) 就可以在设定的时间内重复使用之前建立好的连接。即一次连接可以包含多次请求和响应。





可以看到设置了 connection: keep-alive ,同时可以看到 timeout设置为20秒
其实这里的 keep-alive 设置的就是 http 长连接(持久连接),通过长连接我们可以使得访问一个网页的速度提升,减少服务器的cpu消耗。
其实在使用浏览器访问 web 页面的时候请求资源是并发的操作。
RFC 文档对此连接数限制是两个,但是不同的浏览器对此的限制并相同。


所以我们通常会需要另外的服务器存放我们的 图片 js css 等资源。
长连接也存在缺点, 那就是每次建立一个长连接服务器都要维护这个连接,从而消耗内存,所以 keep-alive 的timeout 时间也很重要
Tcp 的keep alive 和 http 的keep alive 是不一样的。 Http 的keep alive 作用如上所述(让连接活久一点,在一定时间内可重复使用)。
而Tcp 的keep alive 是用来检查当前的TCP连接是否还活着,例如当一个TCP连接长时间没有通信的时候,一方会发送

例如 使用php 的pdo 连接数据库

$pdo = new PDO($dsn, $dbUser, $dbPass);

建立 长连接

$db = new PDO($dsn, $user, $pass, array(

连接 -> 数据传输 -> 关闭连接
连接 -> 数据传输 -> 保持连接 -> 数据传输 -> 保持连接 -> … -> 关闭连接

长连接,消耗内存。网上的一种说法是 用户少通讯频繁可以使用长连接。



$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('n').length;
var $numbering = $('

    for (i = 1; i <= lines; i++) {
  • ').text(i));

此文章通过 python 爬虫创建,原文是自己的csdn 地址: 从HTTP说起,肆意漫谈

Headers already sent

的前半部分,后半部分 magento 中出现这个问题的解决办法
这次的才是正解 ╮(╯3╰)╭

写 magento 前端样式的时候有时候需要用到 ajax,发送请求到controller 然后controller 直接返回数据
例如 echo ‘Simael’;
这个时候通常会在log文件中看到日志提示:DEBUG (7): HEADERS ALREADY SENT: …
这是因为在 magento中response 都是通过response object 这个对象输出到浏览器,你虽然只是 echo 一个字符串
但是系统在这之后仍有去设置 headers 的操作导致错误发生。
最简单的解决办法就是添加 exit;

echo 'Simael';


$output = 'Simael';
$this->getResponse()->setBody($output );


->setHeader('Content-Type', 'text/html')
->setBody('Some Response');


$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('n').length;
var $numbering = $('

    for (i = 1; i <= lines; i++) {
  • ').text(i));

此文章通过 python 爬虫创建,原文是自己的csdn 地址: Headers already sent

php 开发记住我功能



其实记住我功能就是利用了 cookie 并设置保存时间,那么每次用户访问网站只要判断这个cookie是否存在以及值是否正确就可以了。


1. 切不可以把 用户名和密码即使加密后 直接存到cookie 中
2. 认证保存时间不可以太长一般例如记住我一周
3. 设置 http only
4. 用户注销不要忘记同时销毁这个凭证 cookie
5. 保存记住我功能的cookie 的 name最好也不是个让人一眼看到就知道他的用途的
6. cookie 值最好是随机的,即每次使用记住我的得到的cookie 不一样,例如如果你只是 hash(name+password) 那么每次都一样

还有什么需要补充的,望不吝赐教。 o(∩_∩)o

此文章通过 python 爬虫创建,原文是自己的csdn 地址: php 开发记住我功能