다른 여러 언어에 비해 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가 잘못된 데이터를 이스케이프 처리한 것처럼 보입니다.
거의 모든 데이터베이스 드라이버는 캡슐화된 명령문을 지원하므로 이를 사용하지 않을 이유가 없습니다! 사용하는 습관을 들이면 나중에 잊지 않을 것입니다.
동적으로 SQL 쿼리를 작성할 때 보안 문제를 처리하는 방법에 대한 phpdelusions의 기사를 참조할 수도 있습니다. 링크:
https://phpdelusions.net/pdo/sql_injection_example
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
사용자가 입력한 데이터를 절대 신뢰하지 않거나 항상 사용자의 콘텐츠가 공격적이라고 가정하고 좋은 태도를 갖고 신중하게 처리하십시오. 입력과 출력.
XSS 공격을 제어하는 또 다른 방법은 자세한 내용을 위해 CSP 메타 태그 또는 헤더 정보를 제공하는 것입니다:
https://phpdelusions.net/pdo/sql_injection_example另外设置 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" / alt="10가지 일반적인 PHP 보안 문제(예제를 통한 설명)" >
用户一旦触发,就会执行删除账户的指令,眨眼你的账户就消失了。
防御这样的攻击比防御 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/CheatSheetSeries/blob/master/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md
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 표시를 금지하도록 시스템을 설정하세요
● 하지 마세요 타사 코드가 안전하다고 지나치게 신뢰하지 마세요
● Composer를 사용하여 Github에서 직접 코드를 가져오지 마세요
● 사이트가 타사에 의해 크로스 도메인 iframe되는 것을 원하지 않는 경우 anti- iframe 헤더
● 모호하면 안전하지 않습니다
● 실무 경험이 부족한 운영자 또는 파트너라면 개발자는 가능한 한 자주 코드를 확인하세요
● 보안 기능이 어떻게 작동하는지 이해하지 못할 때 작동해야 하는지, 왜 설치되었는지 아는 사람에게 물어보세요. 무시하지 마세요.
● 암호화를 직접 작성하지 마세요. 버그일 수 있습니다. 나쁜 접근 방식
● 엔트로피가 충분하지 않으면 시드하세요. 의사 난수를 정확하게 생성하고 버리세요
● 인터넷이 안전하지 않고 정보가 유출될 가능성이 있다면, 이 시나리오에 대비하고 사고 대응 계획을 세우세요
● WEB 루트 디렉터리 목록 표시를 비활성화하고, 많은 웹 서버 구성은 기본적으로 디렉토리 내용을 나열하므로 데이터 유출이 발생할 수 있습니다.
● 클라이언트 확인만으로는 충분하지 않으며 PHP의 모든 콘텐츠를 다시 확인해야 합니다.
● 사용자 콘텐츠를 역직렬화하지 마세요. 이로 인해 다음이 발생할 수 있습니다. 원격 코드 실행, 이 문제에 대한 자세한 내용은 다음 문서를 참조하세요.
https://paragonie.com/blog/2016/04/securely-implementing -de-serialization-in-php
팁
저는 보안 전문가가 아니기 때문에 모든 것을 자세하게 할 수는 없습니다. 보안 소프트웨어를 작성하는 것은 고통스러운 과정일 수 있지만 몇 가지 기본 규칙을 따르면 합리적으로 안전한 애플리케이션을 작성하는 것이 가능합니다. 실제로 많은 프레임워크가 이와 관련하여 많은 작업을 수행하는 데 도움이 되었습니다.
보안 문제는 문제가 발생하기 전 개발 단계에서 추적할 수 있는 문법 오류와는 다릅니다. 따라서 코드를 작성할 때는 항상 보안 위험을 피하도록 주의해야 합니다. 비즈니스 요구로 인한 압박으로 인해 일부 보안 예방 조치를 일시적으로 무시해야 한다면 그렇게 할 때 발생할 수 있는 잠재적인 위험에 대해 모든 사람에게 미리 알려야 한다고 생각합니다.
이 기사를 통해 혜택을 받으셨다면 친구들과 공유해 주시고 안전한 웹사이트를 함께 만들어 가세요.
위 내용은 10가지 일반적인 PHP 보안 문제(예제를 통한 설명)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!