纸牌屋弗兰克

纸牌屋弗兰克 查看完整档案

深圳编辑武汉科技大学  |  计算机科学与技术 编辑xunlei  |  后台开发 编辑 tanteng.me 编辑
编辑

The journey is the reward

个人动态

纸牌屋弗兰克 赞了回答 · 2019-11-01

解决Git如何删除版本库中的一个提交?

  1. git reset --hard HEAD~1
  2. git push --force

关注 4 回答 4

纸牌屋弗兰克 赞了文章 · 2019-10-27

更加顺手的用好 Laravel 的多态关联

前言

在业务中,关联是我们最常用到的场景。在开发时我们始终都在强调对数据库设计选择可解耦,简洁化,最小化。在这种开发环境下,往往都会将传统的一个大表拆分成多个小表,这时候关联就显得很重要。

MySQL 为我们提供了像 inner joinleft joinright join 这些关联方式,满足了绝大部分需求。但是在实际开发中,我们还是会去选择一些程序上的关联关系,让代码去处理关联,这些关联从简单的一对一,一对多,再到复杂的多态关联、中间表关联,等等,下面主要从源码的角度去讲解一下 Laravel 中的多态关联

官方文档

从 Laravel 官方文档的中文翻译中,我们可以找到关于多态关联的内容。

一对一多态关联与简单的一对一关联类似;不过,目标模型能够在一个关联上从属于多个模型。例如,博客 Post 和 User 可能共享一个关联到 Image 模型的关系。使用一对一多态关联允许使用一个唯一图片列表同时用于博客文章和用户账户。

官网的文档可能不是那么的直观,这里推荐一个 文章 可以帮助你加深理解,这里就不展开了。

image.png

单从文档来说,如果你的设计或者你之前的设计符合官方的要求以及要求。 *_type 的值必须为被关联的模型的类名

开始操作

很多时候,我们的设计中 type 都不一定会那样设计,基本都是以数字为主,虽然 Laravel 为我们提供了自定义 type 的解决办法 ,但是也不能很好的解决关于数字作为 type 的问题,我还搜索到了一个一样的问题。那么我们就来解决一下,现在有三张表。

  • shopping_cart (购物车表)
字段类型介绍
idint主键
product_typetinyint(1)关联的产品类型 1 表示 Tool、2 表示 Food
product_idint关联的产品的ID
  • tool (工具表)
字段类型介绍
idint主键ID
namevarchar(20)名字
  • food (食品表)
字段类型介绍
idint主键ID
namevarchar(20)名字

现在我们有了这三张表,购物车表中根据 product_type 的不同值去关联不同的模型,这里就要用到 多态关联,现在如果我们直接按照官方的文档来编写我们的 Model ,那么,应该是下面这样的。

class ShoppingCart extends Model
{
    const TABLE = 'shopping_cart';
    protected $table = self::TABLE;

    public function product()
    {
        return $this->morphTo();
    }
    
}
class Tool extends Model
{
    const TABLE = 'tool';
    protected $table = self::TABLE;

    public function product()
    {
        return $this->morphOne(ShoppingCart::class, 'product');
    }

}
class Food extends Model
{
    const TABLE = 'food';
    protected $table = self::TABLE;

    public function product()
    {
        return $this->morphOne(ShoppingCart::class, 'product');
    }
    
}

根据官方的文档:模型关联 |《Laravel 5.8 中文文档》| Laravel China 社区。我们的代码应该可以运行,但是可能不符合预期。

image-20191027150453093.png

不出意外的看到了错误信息 “类名必须是有效的对象或者字符”,源码中也是一个 new $class,到 IDE 中打开并断点调试。

image-20191027150847436.png

此时 $class 为 1 ,根据调用栈一路往上找,发现了一个有价值的方法。

image-20191027151420811.png

可以看到,这个 $type 是从这里 $this->dictionary取出来的,按住 Ctrl + 点击 后到了属性定义的位置,然后再按住 Ctrl + 点击 ,选择上面的筛选赋值操作,可以看到只有一处有赋值的操作,点击转到。

image-20191027151751484.png

转到赋值的位置后,打一个断点。

image-20191027151949597.png

看到这里调用栈,源码 过多,就不展开讲解。下面讲重点。
image-20191027152116492.png

看到这个属性,$model->{$this->morphType},先打印它的值$this->morphType,结果是 product_type,然后外层还有 点击进入按钮,我们进入到了模型实例中的 __get 魔术方法。

image-20191027152533661.png

image-20191027152622940.png

在官方手册中,关于 __get 的定义为:

读取不可访问属性的值时,__get() 会被调用。

首先,对于 Model 而言,是没有 product_type 属性的,所以触发了它,方法内部调用了 getAttribute

image-20191027152924822.png

看到 getAttribute 方法内部,第 321 行,使用了一个属性 $this->attribute ,执行表达式可以看到,这就是我们的数据结果。而根据 array_key_exists 的判断可以确定这个 if 是成立的,因为 后面的是 || 运算,即使后面是 false ,这个表达式也是成立,但是我们这里还是希望来看一下这个方法。

image-20191027153530439.png

这个方法只做了一件事,就是判断一个 getter 方法是否存在,这里的 Str::studly() 的作用是把 字符串从下划线命名规则转为大驼峰。也就是说,在这里会检查访问器 ,当然,现在我们是没有这个方法的,继续往下。
image-20191027153637551.png

果然,在 349 ~ 351 行,有着这样的一个逻辑,那么我们回过来看一下 Laravel 文档中关于 修改器 & 访问器 的介绍。
image-20191027153847195.png

简而言之就是,当在访问这个字段的值时,我们可以自己根据获取器的规则定一个名为 getProductTypeAttribute 的访问器方法,在这个方法中,我们可以修改其返回值,作为最终的结果返回给访问者。这样看来,我们就可以在访问器中修改我们原本的 product_type1 为对应的需要实例化的类名称,即可,现在开始定义一下。

public function getProductTypeAttribute($val)
{
    $map = [
        1 => Tool::class,
        2 => Food::class,
    ];
    return $map[$val] ?? Tool::class;
}

根据文档我们可以得知,在对一个已存在的字段添加访问器时,访问器方法可以接受一个参数,其值为原本值,在这个方法中,我们编写了一个 $map ,其 key 为 product_type 字段的原值$val,如果这个字段原值 ($val) ,对应的 key 不存在,就返回默认为 App\Models\Tool模型类,现在这样就够了吗?我们可以来试试。

image-20191027155028728.png

果然,代码可以工作了 ,不再报错,而且,在 relations 属性中我们还可以看到 product 分别是两个不同的模型,接下来我们 toArray 看一下结果。

image-20191027155216334.png

果然,结果已经达到了我们的预期,但是我们却发现 product_type 字段值变成了字符串,而不是原来的数字 1、2,该怎么办?两个办法。

  • 利用获取器添加一个辅助字段,来存储原来的 product_type 。
  • 遍历重新赋值。

下面来展示一下第二种方法,从上面的截图中可以了解到,查询结果给我们返回的是一个Eloquent 集合,现在我们使用其中的 transform,方法来转换原集合。

$list = $cart->with(['product'])->get();
$list->transform(function (ShoppingCart $item) {
    $item->product_type_origin = $item->getOriginal('product_type');
    return $item;
});
dump($list->toArray());

通过模型的 getOriginal 方法拿到了原有的值。
image-20191027160107439.png

到这里,问题已经解决了,那么我们可以自定义 productproduct_typeproduct_id 这三个的名字吗?这一点在 Laravel 文档中鲜有提到,在这里答案是可以的。

我们通过 ShoppingCart 模型的 product 方法,这里我们调用 morphTo 方法没有传递 任何的值。

public function product()
{
    return $this->morphTo();
}

接下来我们进入进入 morphTo 方法,一探究竟。

image-20191027161339955.png

首先映入眼帘的是一段注释,这段注释的 大概意思就是,如果没有指定 $name 那么就从调用栈中取第一条的 function名字作为 $name 也就是最终挂载的模型上的字段名字 方法实现如下

protected function guessBelongsToRelation()
{
    [$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);

    return $caller['function'];
}

接着往下看$type$id

protected function getMorphs($name, $type, $id)
{
    return [$type ?: $name.'_type', $id ?: $name.'_id'];
}

可以看到,当我们没有自己给定 $type$id 时,那么默认值即为 $name 分别加上 _type_id 后缀。

后期补充

2019-10-29

发现经过上面一番操作后,使用 whereHasMorph 方法进行筛选时,type 的值变成了 getAttribute 的值。这时候只需要在被关联的模型 、「Food」和「Tool」 中重写 getMorphClass ,返回值分别为其 type 映射前的值 1、2 即可。

// Tool
public function getMorphClass()
{
    return 1;
}

结束

至此,文章内容结束了。本文主要涉及 Laravel 中关于 多态关联获取器 两个知识点的了解。

文中所使用的调试工具为 PHPStorm 和 Xdebug 。

文中如有纰漏,请不吝赐教,如文中内容涉及到你的利益,请与我联系。

参考资料

查看原文

赞 5 收藏 2 评论 0

纸牌屋弗兰克 赞了文章 · 2019-04-09

PHP 安全问题入门:10 个常见安全问题 + 实例讲解

file

文章转自:https://learnku.com/php/t/24930

更多文章:https://learnku.com/laravel/c...

相对于其他几种语言来说, PHP 在 web 建站方面有更大的优势,即使是新手,也能很容易搭建一个网站出来。但这种优势也容易带来一些负面影响,因为很多的 PHP 教程没有涉及到安全方面的知识。

此帖子分为几部分,每部分会涵盖不同的安全威胁和应对策略。但是,这并不是说你做到这几点以后,就一定能避免你的网站出现任何问题。如果你想提高你的网站安全性的话,你应该继续通过阅读书籍或者文章,来研究如何提高你的网站安全性

出于演示需要,代码可能不是很完美。日常开发过程中,很多代码都包含在了框架跟各种库里面。作为一个后台开发,你不仅要熟练基本的CURD,更要知道如何保护你的数据。

1. SQL 注入

我赌一包辣条,你肯定会看到这里。 SQL 注入是对您网站最大的威胁之一,如果您的数据库受到别人的 SQL 注入的攻击的话,别人可以转出你的数据库,也许还会产生更严重的后果。

网站要从数据库中获取动态数据,就必须执行 SQL 语句,举例如下:

<?php

$username = $_GET['username'];
$query = "SELECT * FROM users WHERE username = '$username'";

攻击者控制通过 GET 和 POST 发送的查询(或者例如 UA 的一些其他查询)。一般情况下,你希望查询户名为「 peter 」的用户产生的 SQL 语句如下:

SELECT * FROM users WHERE username = 'peter'

但是,攻击者发送了特定的用户名参数,例如:' OR '1'='1

这就会导致 SQL 语句变成这样:

SELECT * FROM users WHERE username = 'peter' OR '1' = '1'

这样,他就能在不需要密码的情况下导出你的整个用户表的数据了。

那么,我们如何防止这类事故的发生呢?主流的解决方法有两种。转义用户输入的数据或者使用封装好的语句。转义的方法是封装好一个函数,用来对用户提交的数据进行过滤,去掉有害的标签。但是,我不太推荐使用这个方法,因为比较容易忘记在每个地方都做此处理。

下面,我来介绍如何使用 PDO 执行封装好的语句( mysqi 也一样):

$username = $_GET['username'];
$query = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$query->execute(['username' => $username]);
$data = $query->fetch();

动态数据的每个部分都以:做前缀。然后将所有参数作为数组传递给执行函数,看起来就像 PDO 为你转义了有害数据一样。

几乎所有的数据库驱动程序都支持封装好的语句,没有理由不使用它们!养成使用他们的习惯,以后就不会忘记了。

你也可以参考 phpdelusions 中的一篇关于动态构建 SQL 查询时处理安全问题的文章。链接:  https://phpdelusions.net/pdo/...

2. XSS

XSS 又叫 CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往 Web 页面里插入恶意 html 代码,当用户浏览该页之时,嵌入其中 Web 里面的 html 代码会被执行,从而达到恶意攻击用户的特殊目的。

下面以一个搜索页面为例子:

<body>
<?php
$searchQuery = $_GET['q'];
/* some search magic here */
?>
<h1>You searched for: <?php echo $searchQuery; ?></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>

因为我们把用户的内容直接打印出来,不经过任何过滤,非法用户可以拼接 URL:

search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E

PHP 渲染出来的内容如下,可以看到 Javascript 代码会被直接执行:

<body>
<h1>You searched for: <script>alert(1);</script></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>

问:JS 代码被执行有什么大不了的?

Javascript 可以:

  • 偷走你用户浏览器里的 Cookie;
  • 通过浏览器的记住密码功能获取到你的站点登录账号和密码;
  • 盗取用户的机密信息;
  • 你的用户在站点上能做到的事情,有了 JS 权限执行权限就都能做,也就是说 A 用户可以模拟成为任何用户;
  • 在你的网页中嵌入恶意代码;
  • ...

问:如何防范此问题呢?

好消息是比较先进的浏览器现在已经具备了一些基础的 XSS 防范功能,不过请不要依赖与此。

正确的做法是坚决不要相信用户的任何输入,并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的 XSS 攻击:

<?php

$searchQuery = htmlentities($searchQuery, ENT_QUOTES);

或者你可以使用模板引擎 Twig ,一般的模板引擎都会默认为输出加上 htmlentities 防范。

如果你保持了用户的输入内容,在输出时也要特别注意,在以下的例子中,我们允许用户填写自己的博客链接:

<body>
  <a href="<?php echo $homepageUrl; ?>">Visit Users homepage</a>
</body>

以上代码可能第一眼看不出来有问题,但是假设用户填入以下内容:

#" onclick="alert(1)

会被渲染为:

<body>
  <a href="#" onclick="alert(1)">Visit Users homepage</a>
</body>

永远永远不要相信用户输入的数据,或者,永远都假设用户的内容是有攻击性的,态度端正了,然后小心地处理好每一次的用户输入和输出。

另一个控制 XSS 攻击的方法是提供一个 CSP Meta 标签,或者标头信息,更多详情请见: https://www.html5rocks.com/en...

另外种 Cookie 时,如果无需 JS 读取的话,请必须设置为 "HTTP ONLY"。这个设置可以令 JavaScript 无法读取 PHP 端种的 Cookie。

3. XSRF/CSRF

CSRF 是跨站请求伪造的缩写,它是攻击者通过一些技术手段欺骗用户去访问曾经认证过的网站并运行一些操作。

虽然此处展示的例子是 GET 请求,但只是相较于 POST 更容易理解,并非防护手段,两者都不是私密的 Cookies 或者多步表单。

假如你有一个允许用户删除账户的页面,如下所示:

<?php
//delete-account.php

$confirm = $_GET['confirm'];

if($confirm === 'yes') {
  //goodbye
}

攻击者可以在他的站点上构建一个触发这个 URL 的表单(同样适用于 POST 的表单),或者将 URL 加载为图片诱惑用户点击:

<img data-original="https://example.com/delete-account.php?confirm=yes" />

用户一旦触发,就会执行删除账户的指令,眨眼你的账户就消失了。

防御这样的攻击比防御 XSS 与 SQL 注入更复杂一些。

最常用的防御方法是生成一个 CSRF 令牌加密安全字符串,一般称其为 Token,并将 Token 存储于 Cookie 或者 Session 中。

每次你在网页构造表单时,将 Token 令牌放在表单中的隐藏字段,表单请求服务器以后会根据用户的 Cookie 或者 Session 里的 Token 令牌比对,校验成功才给予通过。

由于攻击者无法知道 Token 令牌的内容(每个表单的 Token 令牌都是随机的),因此无法冒充用户。

<?php /* 你嵌入表单的页面 */ ?>

<form action="/delete-account.php" method="post">
  <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf']; ?>">
  <input type="hidden" name="confirm" value="yes" />
  <input type="submit" value="Delete my account" />
</form>
## 

<?php
//delete-account.php

$confirm = $_POST['confirm'];
$csrf = $_POST['csrf'];
$knownGoodToken = $_SESSION['csrf'];

if($csrf !== $knownGoodToken) {
  die('Invalid request');
}

if($confirm === 'yes') {
  //goodbye
}

请注意,这是个非常简单的示例,你可以加入更多的代码。如果你使用的是像 Symfony 这样的 PHP 框架,那么自带了 CSRF 令牌的功能。

你还可以查看关于 OWASP 更详细的问题和更多防御机制的文章: https://github.com/OWASP/CheatS....

4. LFI

LFI (本地文件包含) 是一个用户未经验证从磁盘读取文件的漏洞。

我经常遇到编程不规范的路由代码示例,它们不验证过滤用户的输入。我们用以下文件为例,将它要渲染的模板文件用 GET 请求加载。

<body>
<?php
  $page = $_GET['page'];
  if(!$page) {
    $page = 'main.php';
  }
  include($page);
?>
</body>

由于 Include 可以加载任何文件,不仅仅是PHP,攻击者可以将系统上的任何文件作为包含目标传递。

index.php?page=../../etc/passwd

这将导致 /etc/passwd 文件被读取并展示在浏览器上。

要防御此类攻击,你必须仔细考虑允许用户输入的类型,并删除可能有害的字符,如输入字符中的“.” “/” “”。

如果你真的想使用像这样的路由系统(我不建议以任何方式),你可以自动附加 PHP 扩展,删除任何非 [a-zA-Z0-9-_] 的字符,并指定从专用的模板文件夹中加载,以免被包含任何非模板文件。

我在不同的开发文档中,多次看到造成此类漏洞的 PHP 代码。从一开始就要有清晰的设计思路,允许所需要包含的文件类型,并删除掉多余的内容。你还可以构造要读取文件的绝对路径,并验证文件是否存在来作为保护,而不是任何位置都给予读取。

5. 不充分的密码哈希

大部分的 Web 应用需要保存用户的认证信息。如果密码哈希做的足够好,在你的网站被攻破时,即可保护用户的密码不被非法读取。

首先,最不应该做的事情,就是把用户密码明文储存起来。大部分的用户会在多个网站上使用同一个密码,这是不可改变的事实。当你的网站被攻破,意味着用户的其他网站的账号也被攻破了。

其次,你不应该使用简单的哈希算法,事实上所有没有专门为密码哈希优化的算法都不应使用。哈希算法如 MD5 或者 SHA 设计初衷就是执行起来非常快。这不是你需要的,密码哈希的终极目标就是让黑客花费无穷尽的时间和精力都无法破解出来密码。

另外一个比较重要的点是你应该为密码哈希加盐(Salt),加盐处理避免了两个同样的密码会产生同样哈希的问题。

以下使用 MD5 来做例子,所以请千万不要使用 MD5 来哈希你的密码, MD5 是不安全的。

假如我们的用户 user1user315 都有相同的密码 ilovecats123,这个密码虽然看起来是强密码,有字母有数字,但是在数据库里,两个用户的密码哈希数据将会是相同的:5e2b4d823db9d044ecd5e084b6d33ea5

如果一个如果黑客拿下了你的网站,获取到了这些哈希数据,他将不需要去暴力破解用户 user315 的密码。我们要尽量让他花大精力来破解你的密码,所以我们对数据进行加盐处理:

<?php
//warning: !!这是一个很不安全的密码哈希例子,请不要使用!!

$password = 'cat123';
$salt = random_bytes(20);

$hash = md5($password . $salt);

最后在保存你的唯一密码哈希数据时,请不要忘记连 $salt 也已经保存,否则你将无法验证用户。

在当下,最好的密码哈希选项是 bcrypt,这是专门为哈希密码而设计的哈希算法,同时这套哈希算法里还允许你配置一些参数来加大破解的难度。

新版的 PHP 中也自带了安全的密码哈希函数 password_hash ,此函数已经包含了加盐处理。对应的密码验证函数为 password_verify 用来检测密码是否正确。password_verify 还可有效防止 时序攻击.

以下是使用的例子:

<?php

//user signup
$password = $_POST['password'];
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

//login
$password = $_POST['password'];
$hash = '1234'; //load this value from your db

if(password_verify($password, $hash)) {
  echo 'Password is valid!';
} else {
  echo 'Invalid password.';
}

需要澄清的一点是:密码哈希并不是密码加密。哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做消息摘要),而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。显然他们之间最大的区别是可逆性,在储存密码时,我们要的就是哈希这种不可逆的属性。

6. 中间人攻击

MITM (中间人) 攻击不是针对服务器直接攻击,而是针对用户进行,攻击者作为中间人欺骗服务器他是用户,欺骗用户他是服务器,从而来拦截用户与网站的流量,并从中注入恶意内容或者读取私密信息,通常发生在公共 WiFi 网络中,也有可能发生在其他流量通过的地方,例如ISP运营商。

对此的唯一防御是使用 HTTPS,使用 HTTPS 可以将你的连接加密,并且无法读取或者篡改流量。你可以从 Let's Encrypt 获取免费的 SSL 证书,或从其他供应商处购买,这里不详细介绍如何正确配置 WEB 服务器,因为这与应用程序安全性无关,且在很大程度上取决于你的设置。

你还可以采取一些措施使 HTTPS 更安全,在 WEB 服务器配置加上 Strict-Transport-Security 标示头,此头部信息告诉浏览器,你的网站始终通过 HTTPS 访问,如果未通过 HTTPS 将返回错误报告提示浏览器不应显示该页面。

然而,这里有个明显的问题,如果浏览器之前从未访问过你的网站,则无法知道你使用此标示头,这时候就需要用到 Hstspreload。

可以在此注册你的网站: https://hstspreload.org/

你在此处提交的所有网站都将被标记为仅 HTTPS,并硬编码到 Google Chrome、FireFox、Opera、Safari、IE11 和 Edge 的源代码中。

你还可以在 DNS 配置中添加 Certification Authority Authorization (CAA) record ,可以仅允许一个证书颁发机构(例如: Let's encrypt)发布你的域名证书,这进一步提高了用户的安全性。

7. 命令注入

这可能是服务器遇到的最严重的攻击,命令注入的目标是欺骗服务器执行任意 Shell 命令

你如果使用 shell_exec 或是 exec 函数。让我们做一个小例子,允许用户简单的从服务器 Ping 不同的主机。

<?php

$targetIp = $_GET['ip'];
$output = shell_exec("ping -c 5 $targetIp");

输出将包括对目标主机 Ping 5次。除非采用 sh 命令执行 Shell 脚本,否则攻击者可以执行想要的任何操作。

ping.php?ip=8.8.8.8;ls -l /etc

Shell 将执行 Ping 和由攻击者拼接的第二个命令,这显然是非常危险的。

感谢 PHP 提供了一个函数来转义 Shell 参数。

escapeshellarg 转义用户的输入并将其封装成单引号。

<?php

$targetIp = escapeshellarg($_GET['ip']);
$output = shell_exec("ping -c 5 $targetIp");

现在你的命令应该是相当安全的,就个人而言,我仍然避免使用 PHP 调用外部命令,但这完全取决于你自己的喜好。

另外,我建议进一步验证用户输入是否符合你期望的形式。

8. XXE

XXE (XML 外部实体) 是一种应用程序使用配置不正确的 XML 解析器解析外部 XML 时,导致的本地文件包含攻击,甚至可以远程代码执行。

XML 有一个鲜为人知的特性,它允许文档作者将远程和本地文件作为实体包含在其 XML 文件中。

<?xml version="1.0" encoding="ISO-8859-1"?>
 <!DOCTYPE foo [
   <!ELEMENT foo ANY >
   <!ENTITY passwd SYSTEM "file:///etc/passwd" >]>
   <foo>&passwd;</foo>

就像这样, /etc/passwd 文件内容被转储到 XML 文件中。

如果你使用 libxml 可以调用 libxml_disable_entity_loader 来保护自己免受此类攻击。使用前请仔细检查 XML 库的默认配置,以确保配置成功。

9. 在生产环境中不正确的错误报告暴露敏感数据

[](https://secure.php.net/manual...,可能会在生产环境中因为不正确的错误报告泄露了敏感信息,例如:文件夹结构、数据库结构、连接信息与用户信息。
file

你是不希望用户看到这个的吧?

一般根据你使用的框架或者 CMS ,配置方法会有不同的变化。通常框架具有允许你将站点更改为某种生产环境的设置。这样会将所有用户可见的错误消息重定向到日志文件中,并向用户显示非描述性的 500 错误,同时允许你根据错误代码检查。

但是你应该根据你的 PHP 环境设置: error_reporting 与 display_errors.

10. 登录限制

像登录这样的敏感表单应该有一个严格的速率限制,以防止暴力攻击。保存每个用户在过去几分钟内失败的登录尝试次数,如果该速率超过你定义的阈值,则拒绝进一步登录尝试,直到冷却期结束。还可通过电子邮件通知用户登录失败,以便他们知道自己的账户被成为目标。

一些其他补充

  • 不要信任从用户传递给你的对象 ID ,始终验证用户对请求对象的访问权限
  • 服务器与使用的库时刻保持最新
  • 订阅关注安全相关的博客,了解最新的解决方案
  • 从不在日志中保存用户的密码
  • 不要将整个代码库存储在 WEB 根目录中
  • 永远不要在 WEB 根目录创建 Git 存储库,除非你希望泄露整个代码库
  • 始终假设用户的输入是不安全的
  • 设置系统禁止可疑行为的 IP 显示,例如:工具对 URL 随机扫描、爬虫
  • 不要过分信任第三方代码是安全的
  • 不要用 Composer 直接从 Github 获取代码
  • 如果不希望站点被第三方跨域 iframe,请设置反 iframe 标示头
  • 含糊是不安全的
  • 如果你是缺乏实践经验的运营商或合作开发人员,请确保尽可能时常检查代码
  • 当你不了解安全功能应该如何工作,或者为什么会安装,请询问知道的人,不要忽视它
  • 永远不要自己写加密方式,这可能是个坏的方法
  • 如果你没有足够的熵,请正确播种你的伪随机数生成并舍弃
  • 如果在互联网上不安全,并有可能被窃取信息,请为这种情况做好准备并制定事件响应计划
  • 禁用 WEB 根目录列表显示,很多 WEB 服务器配置默认都会列出目录内容,这可能导致数据泄露
  • 客户端验证是不够的,需要再次验证 PHP 中的所有内容
  • 不惜一切代价避免反序列化用户内容,这可能导致远程代码执行,有关此问题的详细信息,请参阅此文章: https://paragonie.com/blog/20...

小贴士

我不是一个安全专家,恐无法做到事无巨细。尽管编写安全软件是一个非常痛苦的过程,但还是可以通过遵循一些基本规则,编写合理安全的应用程序。其实,很多框架在这方面也帮我们做了很多工作。

在问题发生之前,安全性问题并不像语法错误等可以在开发阶段追踪到。因此,在编写代码的过程中,应该时刻有规避安全风险的意识。如果你迫于业务需求的压力而不得不暂时忽略一些安全防范的工作,我想你有必要事先告知大家这样做的潜在风险。

如果你从这篇文章有所收益,也请把它分享给你的朋友们把,让我们共建安全网站。

文章转自:https://learnku.com/php/t/24930

更多文章:https://learnku.com/laravel/c...
查看原文

赞 148 收藏 110 评论 4

纸牌屋弗兰克 提出了问题 · 2019-04-04

Laravel 队列 sleep 改成 0 会有什么问题吗?

artisan queue:listen --sleep=0 --queue=high --tries=1 --timeout=600 --memory=512

如代码所示,这里 sleep 改成 0 会有什么问题吗?会不会导致 CPU 负载高?

关注 1 回答 0

纸牌屋弗兰克 赞了回答 · 2019-03-21

PHP对象到底是值传递还是引用传递

除了加上 clone 关键字,否则都是传引用。

关注 7 回答 7

纸牌屋弗兰克 赞了回答 · 2019-03-21

PHP对象到底是值传递还是引用传递

  1. 对象在函数中是引用传递
  2. 即使赋值给其它变量,也是引用
  3. 但是改变了$a的类型,准确的说是zval.value指针都变化了,所以此时产生了分裂。

所以:

尽量避免函数内操作外部对象,否则有可能造成致命性的逻辑错误,特别是改变对象数据的时候。
或者在做对象数据传递的时候要注意数据的严谨性。

关注 7 回答 7

纸牌屋弗兰克 赞了回答 · 2019-01-31

“通道写完后,必须关闭通道,否则range遍历会出现死锁”这句话对吗?

通道写完后,必须关闭通道,否则range遍历会出现死锁,请问这句话对吗?

没错, 关闭 channel 会导致 for range 退出循环.

但, 用简单关闭 channel 的方式控制读写 routine 是危险的, 因为写入已经关闭的 channel 会直接崩溃.
所以你得给 channel 加个状态值, 防止这种情况发生. 当然了, 你也可以不用 channel, 自己用锁实现.

下面举两个例子, 分别是利用 channel 和不用 channel 实现的 生产/消费 模型.
注意: 例子只提供思路, 并不完善.

用 channel

package main

import (
    "sync"
)

type Store struct {
    sync.Mutex // 写\关闭 channel 时用
    ch         chan int
}

func NewStore() *Store {
    return &Store{ch: make(chan int, 128)}
}

func (store *Store) Read() int {
    return <-store.ch
}

func (store *Store) Write(value int) {
    store.Lock()
    defer store.Unlock()
    store.ch <- value
}

func (store *Store) Close() {
    store.Lock()
    defer store.Unlock()
    close(store.ch)
}

不用 channel

package main

import (
    "sync"
)

type Store2 struct {
    sync.Mutex
    link   []int
    closed bool
}

func (store *Store2) Read() int {
    store.Lock()
    defer store.Unlock()
    if len(store.link) == 0 {
        return -1
    }
    value := store.link[0]
    store.link = store.link[1:]
    return value
}

func (store *Store2) Write(value int) {
    store.Lock()
    defer store.Unlock()
    if !store.closed {
        store.link = append(store.link, value)
    }
}

func (store *Store2) Close() {
    store.Lock()
    defer store.Unlock()
    store.closed = true
}

关注 5 回答 4

纸牌屋弗兰克 关注了用户 · 2019-01-31

大彬 @lessisbetter

公众号:Go语言充电站
二维码:https://segmentfault.com/img/...

关注 857

纸牌屋弗兰克 提出了问题 · 2019-01-30

“通道写完后,必须关闭通道,否则range遍历会出现死锁”这句话对吗?

通道写完后,必须关闭通道,否则range遍历会出现死锁,请问这句话对吗?

那么如果我有一个场景,一个goroutine往通道写数据,一个或多个goroutine从通道读数据,这种情况是没有关闭通道的,那应该怎么写?


补充:

我提的问题有点没说清楚,这个往通道写是个死循环的不断写的过程,读也是不断的读,所以你怎么关闭通道?目前我是开一个goroutine不断写,开5个goroutine用range读,尚没有问题,但是看书有这么一句所以问问。

clipboard.png

这个通道是不断写数据的,所以不存在“通道写完后”,所以也无法关闭,那么另外的goroutine就只管range读取数据就好了可以吗?,问题改成这个,理解对吗,目前程序运行没有报错,只是请教一下这样写是否规范。

关注 5 回答 4

认证与成就

  • SegmentFault 讲师
  • 获得 274 次点赞
  • 获得 151 枚徽章 获得 4 枚金徽章, 获得 54 枚银徽章, 获得 93 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-02-27
个人主页被 4.2k 人浏览