反射型XSS攻击【高危】 详细信息 应用程序通过Web请求获取不可信的数据,在未检验数据是否存在恶意代码的情况下,便将其传送给了Web用户,应用程序将容易受到反射型XSS攻击。 例如,下面PHP代码中,将用户输入的参数直接输出到页面上: $param = $_GET['param']; //Attackers may input code such as “alert(document.cookie);” echo " ".$param." "; ?> 如果param里有包含恶意代码,那么Web浏览器就会执行该代码,应用程序将受到反射型XSS攻击。 部分现代浏览器已经能较好的识别并阻止反射型XSS攻击,不过我们不应该依赖这种机制,毕竟不是所有的浏览器都能很好的识别这种攻击。 修复建议 为了避免反射型XSS攻击,建议采用以下方式进行防御: (1)输入验证:对用户的输入进行合理验证(比如只允许输入字母或数字)。 (2)输出编码:根据数据在HTML文档中出现的位置(HTML标签、HTML属性、JavaScript脚本、CSS、URL等),对所有不可信数据进行恰当的输出编码。例如,在HTML标签或者HTML属性中输出不可信的数据,可以采用htmlentities()和htmlspecialchars()进行HTML编码。 (3)为Cookie设置HTTPOnly属性,浏览器将禁止页面的JavaScript访问带有HTTPOnly属性的Cookie,从而避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击。给Cookie添加HTTPOnly属性的代码如下: setcookie("abc", "test", 0, "abc", "app.abc.com", true, true); //最后一个参数用来设置HTTPOnly属性。 ?> SQL注入【高危】 详细信息 SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。 例如,下面代码使用用户提交的数据来构造SQL查询: $name = $_POST['name']; mysql_query("SELECT * FROM tbl_users WHERE userLogin = '$name'"); ?> 如果攻击者提供如下的name字符串进行SQL注入: validuser' OR '1'='1 当其注入到命令时,命令就会变成: SELECT * FROM tbl_users WHERE userLogin = 'validuser' or '1' = '1' 这样就导致tbl_users表的所有信息被泄露。 更坏的情况是,攻击者可以输入如下的字符串: validuser'; DELETE FROM tbl_users; SELECT * FROM tbl_users WHERE '1'='1 这样,SQL语句就变成了三条,导致整个tbl_users表被删除。 修复建议 SQL注入的根本在于本应是数据的部分,被当作了SQL命令来执行。防止SQL注入的方法如下: (1)正确使用预编译SQL语句,绑定变量。例如,可以将描述的例子改写为使用参数化SQL查询的方式: $mysqli = new mysqli($host,$user, $password, $db); $name = $_POST['name']; $query = "SELECT * FROM tbl_users WHERE userLogin = ?"; $stmt = $mysqli->prepare($query); $stmt->bind_param('s',$name); $stmt->execute(); ?> (2)如果构造SQL指令时需要动态加入约束条件,可以通过创建一份白名单,从中选择需要约束的条件字段,来避免SQL注入攻击。 路径遍历【高危】 详细信息 应用程序对用户输入未经合理校验,就作为路径传送给一个文件API,将导致路径遍历攻击。攻击者可能会使用一些特殊的字符(如“..”和“/”)绕过限制,访问一些受保护的文件或目录。 例如,下面的代码使用一个$_GET参数作为文件名: $filename = $_GET['filename']; unlink("/safe_dir/" . $filename); ?> 如果攻击者传入类似于“../../../../../../etc/passwd”的文件名,将可能导致系统的重要文件被删除。 修复建议 防止路径遍历的方法包括: (1)避免让用户控制需要操作的路径。 (2)确实需要用户提供路径的,应该使用白名单来限制用户的输入范围。 (3)如果白名单无法满足业务逻辑的需要,则需要限制用户的输入(比如只允许输入字母数字等)来降低危害。 以下例子采用白名单来限制用户的输入: $fileindex = intval($_GET["fileindex"]); $files = array("a.txt", "b.txt", "c.txt"); unlink("/tmp/". $files[$fileindex]); ?> 硬编码密码【高危】、有风险的硬编码【低危】 详细信息 硬编码密码容易导致系统的安全性降低。 例如,以下代码使用硬编码密码,用于连接SQL服务器: $password = "mypassword"; $conn = mysql_connect("server", "root", $password); ?> 任何对该代码具有访问权限的人都能获取到这个密码。另外,如果以后需要修改这个密码,则必须要修改源代码,在生产环境中修改源代码并不是一个好的选择。 在某些情况下,一些恶意开发者在代码中留下的后门,也会在代码中留下一段硬编码密码,以便攻击者利用该密码攻击系统。 修复建议 程序中不应采用硬编码密码,对于重要的密码信息,应该采取人工输入或其他安全的外部渠道来获取。 不安全的哈希算法【中危】 详细信息 在安全性要求较高的系统中,不应使用被业界公认的不安全的哈希算法(如MD5、SHA-1等)来保证数据的完整性。 例如,以下代码使用MD5算法来加密密码: $password = $_GET['password']; if(md5($password) === $db_pass){ //auth success } ?> 修复建议 在安全性要求较高的系统中,应采用散列值>=224比特的SHA系列算法(如SHA-224、SHA-256、SHA-384和SHA-512)来保证敏感数据的完整性。 例如,以下是修改过的代码: $password = $_GET['password']; if(hash("sha384", $password) === $db_pass){ //auth success } ?> 系统信息泄露:外部【中危】 详细信息 系统数据或调试信息通过网络连接或其他方式发送到远程机器,被攻击者查看到,将有助于攻击者了解系统并制定相应的攻击计划。 例如,下面代码片段中,堆栈信息被输出到浏览器: debug_print_backtrace(); ?> 修复建议 时刻对调试信息的处理保持警惕。特别是在生产环境中,避免使用调试函数。即使最简短的调试信息,也有可能给攻击者以提示。 侵犯隐私【中危】 详细信息 由于应用程序没有正确的处理一些私人数据(例如信用卡号),导致这些数据被不相关、未授权的账户获取到,这样就发生了隐私泄露。 例如,下面的例子演示了私人密码被记录到了日志中: ... $pwd = get_password($user) ... if($pwd != $input_pwd) { log("Login failed:" . $user . "," . $input_pwd); } ... 密码等隐私信息绝不允许直接存储到任何地方,包括开发者认为很安全的地方。 修复建议 当保护隐私和其他方面的问题发生冲突时,优先考虑保护隐私。 有风险的SQL查询【中危】 详细信息 SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。 例如,下面代码使用用户提交的数据来构造SQL查询: $name = $_POST['name']; mysql_query("SELECT * FROM tbl_users WHERE userLogin = '$name'"); ?> 如果攻击者提供如下的name字符串进行SQL注入: validuser' OR '1'='1 当其注入到命令时,命令就会变成: SELECT * FROM tbl_users WHERE userLogin = 'validuser' or '1' = '1' 这样就导致tbl_users表的所有信息被泄露。 更坏的情况是,攻击者可以输入如下的字符串: validuser'; DELETE FROM tbl_users; SELECT * FROM tbl_users WHERE '1'='1 这样,SQL语句就变成了三条,导致整个tbl_users表被删除。 程序员常常会使用过滤函数来对用户输入进行过滤,试图避免SQL注入,但这经常无法完全避免SQL注入。 例如,下面代码使用mysqli_real_escape_string过滤用户输入: $id = $_POST[ 'id' ]; $id = mysqli_real_escape_string($conn, $id); $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; 可以看到,例子中的参数并未用单引号包围,所以这里即使使用了过滤函数,我们依然可以构造任意的查询语句。 修复建议 SQL注入的根本在于本应是数据的部分,被当作了SQL命令来执行。防止SQL注入的方法如下: (1)正确使用预编译SQL语句,绑定变量。例如,可以将描述的例子改写为使用参数化SQL查询的方式: $mysqli = new mysqli($host,$user, $password, $db); $name = $_POST['name']; $query = "SELECT * FROM tbl_users WHERE userLogin = ?"; $stmt = $mysqli->prepare($query); $stmt->bind_param('s',$name); $stmt->execute(); ?> (2)如果构造SQL指令时需要动态加入约束条件,可以通过创建一份白名单,从中选择需要约束的条件字段,来避免SQL注入攻击。 HTTP响应截断【中危】 详细信息 程序从一个不可信的数据源获取数据,未进行验证就置于HTTP头部中发给用户,可能会使HTTP头部被篡改,导致跨站脚本、网页劫持、Cookie篡改、重定向、缓存中毒、用户信息涂改等攻击。 例如:下列代码片段中,程序从HTTP请求中获取author的值,并将其设置到HTTP响应的头部中。 $author = $_GET['AUTHOR_PARAM']; header("author: $author"); ?> 如果请求中提交的是一个“Jane Smith”字符串,那么包含该头部的HTTP响应可能表现为以下形式: HTTP/1.1 200 OK ... Author: Jane Smith ... 那么如果攻击者提交的是一个恶意字符串,比如“Wiley Hacker\r\nHTTP/1.1 200 OK\r\n...”,那么HTTP响应就会被分割成以下形式的两个响应: HTTP/1.1 200 OK ... Author: Wiley Hacker HTTP/1.1 200 OK ... 这样第二个响应已完全由攻击者控制,攻击者可以用所需的HTTP头和正文内容构建该响应实施攻击。幸运的是,目前所有的主流Web服务器都已经对这种攻击做了妥善的处理。PHP在header的值中包含换行符时,会产生一个警告,并丢弃掉该HTTP头。尽管如此,对HTTP头部的篡改仍然会导致重定向、Cookie篡改等攻击。 修复建议 防止HTTP响应截断的方法: (1)根据业务逻辑对输入进行验证,如输入只允许是数字或字母。 (2)采取白名单的方式,只允许特定的字符串出现在HTTP头部中。 例如,下面代码片段中,能过滤掉非法输入: $author = intval($_GET['AUTHOR_PARAM']); $safe_list = array("john", "xiao ming"); If($author < 0 || $author > 1){ $author = 1; } header("author: ".$safe_list[$author]); ?> Cookie:未经过SSL加密【中危】 详细信息 程序创建Cookie时,未设置secure标记,存在安全风险。 现代浏览器支持为每个Cookie设置secure标记,用于通知浏览器,仅在HTTPS协议下传送Cookie。由于HTTP方式的通信安全性较差,存在窃听、篡改等风险,所以应该尽可能避免Cookie信息以HTTP方式传送,特别是当它包含敏感信息时。 例如,下面代码片段中,在生成Cookie时,未设置secure标记: setcookie("privateCookie", "privateMessage", 0, "/", "www.example.com"); ?> 由于没有设置secure标记,这个包含敏感信息的cookie就有可能通过HTTP方式发送。而由于HTTP的不安全特性,这些信息很容易被攻击者截获。 修复建议 为Cookie设置secure标记,浏览器将只能通过较安全的HTTPS方式发送Cookie,私密Cookie被窃取的风险大大降低。 PHP函数setcookie的第6个参数用于设置这个标记,例如,以下代码将Cookie的secure标记设置为 true: setcookie("privateCookie", "privateMessage", 0, "/", "www.example.com", true); ?> 不安全的随机数【低危】 详细信息 伪随机数是由数学算法实现的,它真正随机的地方在于种子(seed)。种子一旦确定后,再通过同一伪随机数算法计算出来的随机数,其值是固定的,对此计算所得值的顺序也是固定的。如果使用rand()生成的随机数用于一些重要的地方,会比较危险。 例如,以下程序用rand()函数产生的随机数来作为重要的ID标识: $id_check = rand(); ?> 这个标识用来校验某ID是否登录,假如攻击者能猜出这个标识,那么就能获取到敏感信息: $check = $_GET['check']; if($check == $id_check){ //do some important things. } ?> 修复建议 在安全性要求较高的应用中,应使用更安全的随机数生成器。 例如:在PHP5.3.0及其之后的版本中,若是支持openSSL扩展, 可以直接使用以下函数来生成随机数: string openssl_random_pseudo_bytes ( int $length [, bool &$crypto_strong ] ) 在PHP7中,引入了两个更安全的随机数生成函数random_bytes和random_int,用于取代PHP5的rand和mt_rand函数。 除此之外,在算法上还可以通过将多个随机数的组合,以增加随机数的复杂性。 注释中的密码【低危】 详细信息 应用程序在注释中保留密码等敏感信息,存在安全隐患。如果攻击者能够获取到源代码,那么注释中的密码信息将有助于攻击者获取更多信息或权限。 例如,以下代码在注释中保留了默认密码: //Mysql username: root //Mysql password: somepass ?> 修复建议 应用程序不应在注释中保留密码等敏感信息。 文件上传【低危】 详细信息 文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本获得了执行服务器端命令的能力。 应用系统如果需要文件上传功能,在文件上传后,服务器的处理逻辑做得不够安全,就会出现安全问题。文件上传导致的安全问题一般有: (1)上传文件是Web脚本语言,服务器的脚本解释器解释并执行了用户上传了的脚本文件,导致代码执行。 (2)上传文件是Flash的策略文件crossdomain.xml, 攻击者可以控制Flash在该域下的行为。 (3)上传文件是病毒、木马文件,攻击者用以诱骗用户或者管理员下载执行。 (4)上传文件是钓鱼图片或者包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。 例如,在下面PHP代码片段中,通过“黑名单”的方式检测文件后缀,来确定上传的文件是否安全。 $Config['AllowedExtension']['File'] = array(); $Config['DeniedExtensions']['File'] = array('php','php3','php5','phtml','asp','aspx','ascx','jsp','cfm','cfc','pl','bat','exe','dll','reg','cgi'); 黑名单很难过滤掉所有不合法的类型,如果上传类型是php2、php4、inc、pwml、asa、cer等文件,可能导致安全问题。 修复建议 如果应用程序不需要文件上传功能,应该禁用文件上传功能。PHP中禁用file_uploads的方式: (1)在php.ini文件中配置:file_uploads = 'off' (2)也可以在Apache httpd.conf文件中配置:php_flag file_uploads off 如果应用程序需要文件上传功能,可以参考以下建议: (1)文件上传的目录设置为不可执行。 (2)采用白名单方式判断文件类型。在判断文件类型时,可采用MIME Type、 后缀检查等方式。 (3)使用随机数改写文件名和文件路径。 如果应用程序采用随机数改写了文件名和路径,可以很大程度上增加攻击的成本。 (4)对于图片的处理,可以采用压缩函数或者resize函数,在处理图片的同时,破坏图片中可能包含的脚本代码。 Cookie:路径范围过大【低危】 详细信息 开发者常常将Cookie路径设置为根路径。有些情况下,在同一个域下面部署了多个应用程序,这时,如果某应用程序将Cookie路径设置为”/“,那么同一域下的所有应用程序都将能访问到这个Cookie。 例如,某Web应用部署在http://www.phpcms.com/app上,当用户访问时,应用会给用户发送路径为”/“的Cookie: setcookie("email", $email, 0, "/", "www.phpcms.com", true, true); ?> 如果攻击者控制了同一域下的另一个不太安全的Web应用http://www.phpcms.com/insecureapp,那么用户在访问这个不太安全的Web应用时,就有可能将自己的电子邮箱泄露给攻击者。 修复建议 尽可能缩小Cookie路径的范围,坚持权限最小原则。 例如,下面的代码片段中,将Cookie路径设置为“/app”: setcookie("email", $email, 0, "/app", "www.phpcms.com", true, true); ?> Cookie:未设置HttpOnly标记【低危】 详细信息 程序创建Cookie时,未设置HTTPOnly标记,存在安全风险。 现代浏览器支持为每个Cookie设置HTTPOnly标记,用于通知浏览器,不允许客户端脚本访问Cookie。使用客户端脚本访问Cookie,是攻击者利用XSS漏洞窃取用户隐私数据的主要方式。 例如,下面代码片段中,在生成Cookie时,未设置HTTPOnly标记: setcookie("privateCookie", "privateMessage", 0, "/", "www.example.com", true); ?> 修复建议 为Cookie设置HTTPOnly标记,浏览器将不允许客户端脚本访问Cookie,可有效缓解XSS漏洞造成的威胁。 PHP函数setcookie的第7个参数用于设置这个标记,例如,以下代码将Cookie的HTTPOnly标记设置为 true: setcookie("privateCookie", "privateMessage", 0, "/", "www.example.com", true, true); ?> Cookie:持久【低危】 详细信息 默认情况下,PHP所创建的Cookie是非持久的,即关闭浏览器后就自动清除。可以通过调用PHP函数或配置ini文件来使浏览器保留这些Cookie,直到函数或ini配置所指定的日期。 例如,下列代码片段中,指定保存电子邮箱的Cookie在1年后过期: setcookie("email", $email, time()+60*60*24*365); ?> 修复建议 避免在持久性Cookie中保存敏感信息。对于持久性Cookie中的数据,设置一个合理的过期时间。 例如,以下代码设置Cookie在关闭浏览器后过期: setcookie("email", $email, 0, "/app", "www.phpcms.com", true, true); ?>