我赌一包辣条,你肯定会看到这里。 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 为你转义了有害数据一样。
险些所有的数据库驱动程序都支持封装好的语句,没有情由不该用它们!
养成利用他们的习气,往后就不会忘却了。
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>
永久永久不要相信用户输入的数据,或者,永久都假设用户的内容是有攻击性的,态度端正了,然后小心地处理好每一次的用户输入和输出。
其余设置 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 src=\"大众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 令牌的功能。
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 是不屈安的。
如果我们的用户 user1 和 user315 都有相同的密码 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)是将目标文本转换成具有不同长度的、可逆的密文。显然他们之间最大的差异是可逆性,在储存密码时,我们要的便是哈希这种不可逆的属性。