다른 여러 언어에 비해 PHP는 웹 사이트 구축에 있어 더 큰 장점을 가지고 있습니다. 초보자도 쉽게 웹 사이트를 구축할 수 있습니다. 그러나 많은 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가 잘못된 데이터를 이스케이프 처리한 것처럼 보입니다.
거의 모든 데이터베이스 드라이버는 캡슐화된 명령문을 지원하므로 이를 사용하지 않을 이유가 없습니다! 사용하는 습관을 들이면 나중에 잊지 않을 것입니다.
2. XSS
XSS는 크로스 사이트 스크립팅 공격인 CSS(Cross Site Script)라고도 합니다. 악의적인 공격자가 웹 페이지에 악성 html 코드를 삽입하면 사용자가 해당 페이지를 탐색할 때 웹에 포함된 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>
Q: JS 코드가 실행되는 데 있어 가장 큰 문제는 무엇입니까?
Javascript는 다음을 수행할 수 있습니다.
브라우저의 비밀번호 기억 기능을 통해 사이트 로그인 계정과 비밀번호를 얻습니다.
사용자의 기밀 정보를 훔칩니다. 이는 JS 권한 및 실행 권한으로 수행될 수 있습니다. 즉, 사용자 A는 모든 사용자를 가장할 수 있습니다.
웹 페이지에 악성 코드를 삽입할 수 있습니다.
...
Q: 이 문제를 방지하는 방법은 무엇입니까?
좋은 소식은 이제 고급 브라우저에 몇 가지 기본 XSS 방지 기능이 있다는 것입니다. 하지만 이에 의존하지 마세요.
올바른 접근 방식은 사용자의 입력을 절대 신뢰하지 않고 입력에서 모든 특수 문자를 필터링하는 것입니다. 이렇게 하면 대부분의 XSS 공격이 제거됩니다.
<?php $searchQuery = htmlentities($searchQuery, ENT_QUOTES);
또는 템플릿 엔진 Twig를 사용할 수 있습니다. 일반 템플릿 엔진은 기본적으로 출력에 htmlentities 방지를 추가합니다.
사용자의 입력 내용을 유지하는 경우 출력할 때 특별한 주의를 기울이십시오. 다음 예에서는 사용자가 자신의 블로그 링크를 채울 수 있도록 허용합니다.
<body> <a href="<?php echo $homepageUrl; ?>">Visit Users homepage</a> </body>
위 코드는 언뜻 보기에는 문제가 없어 보일 수 있지만 사용자는 다음 콘텐츠를 입력합니다.
#" onclick="alert(1)
는 다음과 같이 렌더링됩니다:
Visit Users homepage
사용자가 입력한 데이터를 절대 신뢰하지 않거나 항상 사용자의 콘텐츠가 공격적이라고 가정하고 좋은 태도를 갖고 신중하게 처리하십시오. 입력과 출력.
또한, 쿠키 설정시 JS를 읽을 필요가 없다면 "HTTP ONLY"로 설정해주세요. 이 설정은 JavaScript가 PHP 측에서 쿠키를 읽는 것을 방지합니다.
3.虽然此处展示的例子是 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 令牌的功能。
你还可以查看关于 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 是不安全的。
假如我们的用户 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)是将目标文本转换成具有不同长度的、可逆的密文。显然他们之间最大的区别是可逆性,在储存密码时,我们要的就是哈希这种不可逆的属性。
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. 在生产环境中不正确的错误报告暴露敏感数据
如果你不小心,可能会在生产环境中因为不正确的错误报告泄露了敏感信息,例如:文件夹结构、数据库结构、连接信息与用户信息。
你是不希望用户看到这个的吧?
一般根据你使用的框架或者 CMS ,配置方法会有不同的变化。通常框架具有允许你将站点更改为某种生产环境的设置。这样会将所有用户可见的错误消息重定向到日志文件中,并向用户显示非描述性的 500 错误,同时允许你根据错误代码检查。
但是你应该根据你的 PHP 环境设置: error_reporting 与 display_errors.
10. 登录限制
像登录这样的敏感表单应该有一个严格的速率限制,以防止暴力攻击。保存每个用户在过去几分钟内失败的登录尝试次数,如果该速率超过你定义的阈值,则拒绝进一步登录尝试,直到冷却期结束。还可通过电子邮件通知用户登录失败,以便他们知道自己的账户被成为目标。
一些其他补充
사용자가 전달한 개체 ID를 신뢰하지 마세요. 항상 요청한 개체에 대한 사용자의 액세스를 확인하세요.
서버와 라이브러리를 최신 상태로 유지하세요.
보안 관련 팔로우를 구독하세요. 최신 솔루션에 대해 알아보려면 블로그를 방문하세요. 솔루션
사용자의 비밀번호를 로그에 저장하지 마세요.
전체 코드 베이스를 WEB 루트 디렉터리에 저장하지 마세요.
절대로 WEB 루트 디렉터리에 Git 저장소를 만들지 마세요. 전체 코드 베이스를 유출하려는 경우
항상 사용자 입력이 안전하지 않다고 가정하세요
도구, 크롤러에 의한 URL 무작위 스캔
과 같은 의심스러운 동작의 IP 표시를 금지하도록 시스템을 설정하세요. 타사 코드가 안전하다고 지나치게 신뢰하지 마세요
Github에서 직접 코드를 가져오기 위해 Composer를 사용하지 마세요
사이트가 타사에 의해 도메인 간 iframe되는 것을 원하지 않는 경우, 안티 iframe 헤더를 설정해주세요
모호하면 안전하지 않습니다
실무경험이 부족하신 분들은 운영자나 공동개발자분들은 꼭 코드를 자주 확인해주세요
보안 기능이 어떻게 작동해야 하는지, 왜 설치되었는지 이해하지 못한다면 아는 사람에게 물어보세요. 무시하지 마세요
절대로 직접 수행하지 마세요. 암호화를 작성하는 것은 아마도 나쁜 접근 방식일 것입니다
엔트로피가 부족하면 의사 난수 생성을 올바르게 시드하고 버리십시오
인터넷이 안전하지 않고 정보가 도난당할 위험이 있다면 이러한 상황에 대비하고 사고 대응 계획을 세우십시오
웹 루트 디렉터리 목록을 비활성화합니다. 많은 웹 서버 구성은 기본적으로 디렉터리 내용을 나열하므로 데이터 유출이 발생할 수 있습니다.
클라이언트 확인만으로는 충분하지 않습니다. PHP에서 모든 것을 다시 확인해야 합니다
팁
저는 보안 전문가가 아니기 때문에 모든 것을 자세히 할 수는 없습니다. 보안 소프트웨어를 작성하는 것은 고통스러운 과정일 수 있지만 몇 가지 기본 규칙을 따르면 합리적으로 안전한 애플리케이션을 작성하는 것이 가능합니다. 실제로 많은 프레임워크가 이와 관련하여 많은 작업을 수행하는 데 도움이 되었습니다.
보안 문제는 문제가 발생하기 전 개발 단계에서 추적할 수 있는 문법 오류와는 다릅니다. 따라서 코드를 작성할 때는 항상 보안 위험을 피하도록 주의해야 합니다. 비즈니스 요구로 인한 압박으로 인해 일부 보안 예방 조치를 일시적으로 무시해야 한다면 그렇게 할 때 발생할 수 있는 잠재적인 위험에 대해 모든 사람에게 미리 알려야 한다고 생각합니다.
이 기사를 통해 혜택을 받으셨다면 친구들과 공유해 주시고 안전한 웹사이트를 함께 만들어 가세요.
추천 튜토리얼: "PHP 튜토리얼"
위 내용은 PHP의 10가지 일반적인 보안 문제(예제를 통한 설명)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!