原创

Web开发安全测试指南

1.常见WEB潜在问题

输入验证:嵌入到查询字符串、表单字段、cookie 和 HTTP 头中的恶意字符串的攻击。这些攻击包括命令执行、跨站点脚本(XSS)、SQL 注入和缓冲区溢出攻击。
身份验证:标识欺骗、密码破解、特权提升和未经授权的访问。
授权验证:访问保密数据或受限数据、篡改数据以及执行未经授权的操作。
配置管理:对管理界面进行未经授权的访问、具有更新配置数据的能力以及对用户帐户和帐户配置文件进行未经授权的访问。
敏感数据:泄露保密信息以及篡改数据。
会话管理:捕捉会话标识符,从而导致会话劫持及标识欺骗。
加密管理:访问保密数据或帐户凭据,或二者均能访问。
参数操作:路径遍历攻击、命令执行以及绕过访问控制机制,从而导致信息泄漏、特权提升和拒绝服务。
异常管理:拒绝服务和敏感的系统级详细信息的泄漏。
审核和记录:不能发现入侵迹象、不能验证用户操作,以及在诊断时出现困难。

2. 开发安全规范

2.1. 跨站点脚本(XSS)防范

2.1.1.漏洞描述
Cross Site Script(缩写:XSS),跨站脚本攻击。
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。XSS攻击可以分为两种类型:非持久型攻击和持久型攻击。
非持久型XSS攻击顾名思义,非持久型XSS攻击是一次性的,仅对当次的页面访问产生影响。非持久型XSS攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。
持久型XSS攻击:持久型XSS,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。
XSS漏洞按照攻击利用手法的不同,也可以分为以下三种类型:
反射型:经过后端,不经过数据库;
存储型:经过后端,经过数据库;
DOM型:不经过后端,DOM—based XSS漏洞是基于文档对象模型Document Objeet Model,DOM)的一种漏洞,dom - xss是通过url传入参数去控制触发的。
2.1.2.代码示例

反射型:xss.php

\\XSS反射型
 <form action="" method="get">
<input type="text" name="xss"/>
<input type="submit" value="test"/>
</form>
<?php $xss = @$_GET['xss'];
if($xss!==null){
echo $xss;
}?>

存储型:xss.php

\\XSS存储型
 <form action="" method="post">
<input type="text" name="xss"/>
<input type="submit" value="test"/>
</form>
<?php $xss=@$_POST['xss'];
mysql_connect("localhost","root","123");
mysql_select_db("xss");
 if($xss!==null){
$sql="insert into temp(id,payload) values('1','$xss')"$result=mysql_query($sql);
echo $result;
}?>

新建show.php文件:

 <?php 
mysql_connect("localhost","root","root");
mysql_select_db("xss");
$sql="select payload from temp where id=1";
$result=mysql_query($sql);
while($row=mysql_fetch_array($result)){
echo $row['payload']; }
?>

DOM型:xss.php

\\DOM
 <?php error_reporting(0); //禁用错误报告
$name = $_GET["name"];
?> 
<input id="text" type="text" value="<?php echo $name;?>" />
<div id="print"></div>
<script type="text/javascript">
var text = document.getElementById("text");
var print = document.getElementById("print");
print.innerHTML = text.value;
 </script>

其中print.innerHTML = text.value;  获取 text的值,并且输出在print内。这里是导致xss的主要原因。

2.1.3.解决方案

应对XSS攻击的主要手段是编码与过滤两种,编码用于将特殊的符号 "<、>、&、'、""进行转义,而过滤则是阻止特定的标记、属性、事件。

反射型:
反射型这段代码中首先包含一个表单,用于向页面自己发送GET请求,带一个名为xss的参数。然后PHP会读取该参数,如果不为空,则直接打印出来,这里不存在任何过滤。也就是说,如果xss中存在HTML结构性的内容,打印之后会直接解释为HTML元素。
直接输入一个js代码,比如<script>alert('hack')</script>即可触发
防御方案:
1.过滤用户输入的内容中是否有非法内容。如:<>(尖括号)、”(引号)、 ‘(单引号)、%(百分比符号)、;(分号)、()(括号)、&符号)、+(加号)等。
存储型:
存储型的XSS.php代码中用户输入的内容没有过滤,但是不直接显示在页面中,而是插入到了数据库。Show.php代码从数据库读取了之前插入的内容,并将其显示出来。先创建一个数据库xss,创建temp表。然后访问xss.php,像之前一样输入HTML代码(<script>alert('hack')</script>),点击test之后发现没有任何动静,但事实上,数据已经插入到了数据库中。当我们访问show.php查询这个值的时候,代码就会被执行。
防御方案:
1.过滤用户输入的内容中是否有非法内容。如:<>(尖括号)、”(引号)、 ‘(单引号)、%(百分比符号)、;(分号)、()(括号)、&符号)、+(加号)等。
2.对输出进行编码
function encodeHtml(html){
return html && html.replace ?
( html.replace(/&/g"&"//转换&符号 
.replace(/ /g" "// 转换空格 
.replace(/\b +/g" "// 转换多个空格为单个空格 
.replace(/</g"<"// 转换小于符号 
.replace(/>/g">"// 转换大于符号 
.replace(/\\/g"\"// 转换斜杠符号 
.replace(/\'/g"'"// 转换单引号 
.replace(/\"/g"""// 转换双引号 
.replace(/\n/g"<br/>"// 转换换行符号 
.replace(/\r/g""//转换回车符号 
)
: html;
}

代码的作用是把对应的特殊符号,转换为转义字符,而不是直接插入html中。这样,不管用户输入什么内容,都可以直接转换成字符串输出在html中。

DOM型:
DOM型的XSS主要是由客户端的脚本通过DOM动态地输出数据到页面上,它不依赖于提交数据到服务器,而是从客户端获得DOM中的数据在本地执行。
容易导致DOM型的XSS的输入源包括:
Document.URL、Location(.pathname|.href|.search|.hash)、
Document.referrer、Window.name、Document.cookie、localStorage/globalStorage;
防御方案:
1.把变量输出到页面时要做好相关的编码转义工作,如要输出到 <script>中,可以进行JS编码;要输出到HTML内容或属性,则进行HTML编码处理。根据不同的语境采用不同的编码处理方式。

其他注意事项:

不论是反射型还是存储型,攻击者总需要找到两个要点,即“输入点”与"输出点",也只有这两者都满足,XSS攻击才会生效。
1.对重要的 cookie设置 httpOnly, 防止客户端通过document.cookie读取 cookie,此 HTTP头由服务端设置。
2.将不可信的值输出 URL参数之前,进行 URLEncode操作,而对于从 URL参数中获取值一定要进行格式检测(比如你需要的时URL,就判读是否满足URL格式)。
3.不要使用 Eval来解析并运行不确定的数据或代码,对于 JSON解析请使用 JSON.parse() 方法。

2.2. 防SQL注入规范

2.2.1.漏洞描述

SQL injection,SQL注入攻击。

当应用程序将用户输入的内容,拼接到SQL语句中,一起提交给数据库执行时,就会产生SQL注入威胁。

由于用户的输入,也是SQL语句的一部分,所以攻击者可以利用这部分可以控制的内容,注入自己定义的语句,改变SQL语句执行逻辑,让数据库执行任意自己需要的指令。通过控制部分SQL语句,攻击者可以查询数据库中任何自己需要的数据,利用数据库的一些特性,可以直接获取数据库服务器的系统权限。

2.2.2.代码示例
比如在一个登录界面,要求输入用户名和密码:
可以这样输入实现免帐号登录:
用户名: or 1 = 1 
密 码:
点登陆,如若没有做特殊处理,那么这个非法用户就登陆进去了.
后台认证程序中会有如下的SQL语句:
String sql = "select * from user_table where username=
' "+userName+" ' and password=' "+password+" '";
当输入了上面的用户名和密码,上面的SQL语句变成:
SELECT * FROM user_table WHERE username=
'or 1 = 1 -- and password='
分析SQL语句:
条件后面username=or 1=1 用户名等于 ” 或1=1 那么这个条件一定会成功;
然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都能正确执行,用户轻易骗过系统,获取合法身份。
2.2.3.解决方案

1.PreparedStatement

采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
使用好处:
(1).代码的可读性和可维护性.
(2).PreparedStatement尽最大可能提高性能.
(3).最重要的一点是极大地提高了安全性.
原理:
sql注入只对sql语句的准备(编译)过程有破坏作用
PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,
而不再对sql语句进行解析,准备,因此也就避免了sql注入问题

2. 使用正则表达式过滤传入的参数

要引入的包:
import java.util.regex.*;
正则表达式:
private String CHECKSQL = ^(.+)\\sand\\s(.+)|(.+)\\sor(.+)\\s$;
判断是否匹配:
Pattern.matches(CHECKSQL,targerStr);
下面是具体的正则表达式:
检测SQL meta-characters的正则表达式 :
/(\%27)|(\)|(\-\-)|(\%23)|(#)/ix
检测SQL meta-characters的正则表达:/((\%3D)|(=))[^\n]*((\%27)|(\)|(\-\-)|(\%3B)|(:))/i
典型的SQL 注入攻击的正则表达式 :/\w*((\%27)|(\))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix
检测SQL注入,UNION查询关键字的正则表达式 :/((\%27)|(\))union/ix(\%27)|(\)
检测MS SQL Server SQL注入攻击的正则表达式:
/exec(\s|\+)+(s|x)p\w+/ix

3. 字符串过滤

||之间的参数可以根据自己程序的需要添加)
public static boolean sql_inj(String str){
String inj_str = "'|and|exec|insert|select|delete|update|
count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
String inj_stra[] = split(inj_str,"|");
for (int i=0 ; i < inj_stra.length ; i++ ){
if (str.indexOf(inj_stra[i])>=0){
return true;
}
}
return false;
}

4. jsp中调用该函数检查是否包含非法字符

防止SQLURL注入:
sql_inj.java代码:
package sql_inj;
import java.net.*;
import java.io.*;
import java.sql.*;
import java.text.*;
import java.lang.String;
public class sql_inj{
public static boolean sql_inj(String str){
String inj_str = "'|and|exec|insert|select|delete|update|
count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
//这里的东西还可以自己添加
String[] inj_stra=inj_str.split("\\|");
for (int i=0 ; i < inj_stra.length ; i++ ){
if (str.indexOf(inj_stra[i])>=0){
return true;
}
}
return false;
}
}

5. JSP页面判断代码:

使用javascript在客户端进行不安全字符屏蔽
功能介绍:检查是否含有”‘”,\\,/
参数说明:要检查的字符串
返回值:0:是1:不是
函数名是
function check(a){
return 1;
fibdn = new Array (”‘” ,\\,/);
i=fibdn.length;
j=a.length;
for (ii=0; iii; ii++)
{ for (jj=0; jjj; jj++)
{ temp1=a.charAt(jj);
temp2=fibdn[ii];
if (tem; p1==temp2)
{ return 0; }
}
}
return 1;
}

2.3.文件上传

2.3.1.漏洞描述

File upload,任意文件上传攻击。

Web应用程序在处理用户上传的文件时,没有判断文件的扩展名是否在允许的范围内,就把文件保存在服务器上,导致恶意用户可以上传任意文件,甚至上传脚本木马到web服务器上,直接控制web服务器。

2.3.2.代码示例
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(
                     request.getRealPath("/")+getFileName(request))));
ServletInputStream in = request.getInputStream();
int i = in.read();
while (i != -1) {
pw.print((char) i);
i = in.read();
}
// 缺少文件扩展名判断
pw.close();
2.3.3.解决方案

处理用户上传文件,要做以下检查:

1. 检查上传文件扩展名白名单,不属于白名单内,不允许上传;
2. 上传文件的存储目录必须是http请求无法直接访问到的。如果需要访问的,必须上传到其他(和web服务器不同的)域名下,并设置该目录为不解析jsp等脚本语言的目录;
3. 上传文件要保存的文件名和目录名由系统根据时间生成,不允许用户自定义;
4. 图片上传,要通过处理(缩略图、水印等),无异常后才能保存到服务器;
5. 上传文件需要做日志记录,请参照“Error Handing and Logging章节”。
6. 通过限定接口的并发数以及单位时间内的调用频率限制接口的上传次数。

2.4.文件下载&目录遍历

2.4.1.漏洞描述

File download and Directory traversal,任意文件下载攻击和目录遍历攻击。

处理用户请求下载文件时,允许用户提交任意文件路径,并把服务器上对应的文件直接发送给用户,这将造成任意文件下载威胁。如果让用户提交文件目录地址,就把目录下的文件列表发给用户,会造成目录遍历安全威胁。恶意用户会变换目录或文件地址,下载服务器上的敏感文件、数据库链接配置文件、网站源代码等。

2.4.2.代码示例
2.4.3.解决方案

对文件操作功能,做到以下几点:

String path = request.getParameter("path");
java.io.OutputStream os = response.getOutputStream();
java.io.FileInputStream fis = new java.io.FileInputStream(path);
byte[] b = new byte[1024];
int i = 0;
while ((i = fis.read(b)) > 0 ){
os.write(b, 0, i);
}
fis.close();
os.flush();
os.close();

目录遍历防护方案
1.要下载的文件地址保存至数据库中;
2. 文件路径保存至数据库,让用户提交文件对应ID下载文件;
3. 下载文件之前做权限判断;
4. 文件放在用户端浏览器无法直接访问的目录;
5. 记录文件下载日志(内容见日志章节);
6. 不允许提供目录遍历服务。
防范目录遍历攻击漏洞,最有效的办法就是权限控制,可使用下面两条进行:
1. 数据净化,对网站用户提交过来的文件名进行硬编码或者统一编码,对文件后缀进行白名单控制,对包含了恶意的符号或者空字节进行拒绝。
2. Web应用程序可以使用绝对路径+参数来控制访问目录,使其即使是越权或者跨越目录也是在指定的目录下
   针对OSS的安全防护
1. 数据在客户端和服务器之间传输时有可能会出错。OSS现在支持对各种方式上传的Object返回其crc64值,客户端可以和本地计算的crc64值做对比,从而完成数据完整性的验证。
2. 使用服务器端加密方式保护静态数据,即OSS将用户数据写入数据中心内的磁盘时,会在对象(Object)级别加密数据,并且在访问这些数据时自动解密,用户只需要在请求时验证是否拥有访问权限。
3. 防盗链
(1).Referer白名单。仅允许指定的域名访问OSS资源。
(2).是否允许空Referer。如果不允许空Referer,则只有HTTP或HTTPS header中包含Referer字段的请求才能访问OSS资源。
4. OSS异常流量排查及防护
5. 根据用户应用场景而设置Bucket的权限:私有、公有读、公共读写三种权限。
具体配置示例详见官网:
https://help.aliyun.com/document_detail/43394.html?spm=a2c4g.11186623.6.1303.798f3ef7saDDA6
https://help.aliyun.com/knowledge_list/51633.html?spm=a2c4g.11186623.6.1429.29325e21fHPLir

下载文件前的权限判断 

1. 依赖于jwt(JSON Web Token)的机制,使得文件下载的url需要在query中添加token参数进行权限验证;
       例如下载地址为 /download/fileA

(1).后端需要额外提供一个接口供用户获取下载用的token

(2).客户端使用ajax请求该接口获取token

(3).客户端模拟打开拼接了token的url:/download/fileA?token=xxx

(4).后端解析该token判断用户是否有权限下载该文件

// 服务器端
const jwt = require('jsonwebtoken');
...
router.get('/download/token', (req, res) => {
  const secret = 'xxxxxx'
  const token = jwt.sign({}, secret, { expiresIn: 2 }); // 2s内有效
  return res.json({ token });
});
// 客户端
const download = async (url) => {
const { data: { token } } = await axios.get('/download/token');
let finalUrl = url;
if (url.includes('?')) {
 finalUrl += `&token=${token}`;
} else {
 finalUrl += `?token=${token}`;
}
window.open(finalUrl);
}
// 服务器端具体解析token下载文件的代码就省略了。
// jwt的使用参考jsonwebtoken库的使用
 2. 利用 HTML5 File API。使用createObjectURLrevokeObjectURL方法来创建和下载文件,从而可以利用ajax来模拟实现文件下载。

(1).后端提供文件下载的接口与其他接口保证同样的权限验证策略,例如在header中验证Authorization头

(2).前端使用ajax请求文件下载的接口

(3).前端利用window.URL.createObjectURL将请求得到的内容转化为objectUrl

(4).前端模拟点击,使用window.URL.revokeObjectURL(url)实现文件下载

// 客户端
const download = async (url) => {
  const response = await axios.get('/download/fileA', {headers: {Authorization: 'Token xxxxxx'}});
  const blob = new Blob([response], { type: 'text/plain;charset=utf-8' });
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = [fileName];
  a.click();
  window.URL.revokeObjectURL(url);
}

2.5.访问控制

2.5.1.Vertical Access Control
2.5.1.1漏洞描述

Vertical Access Control,纵向权限安全攻击,也就是权限提升攻击。

由于web应用程序没有做权限控制,或仅仅在菜单上做了权限控制,导致的恶意用户只要猜测其他管理页面的URL,就可以访问或控制其他角色拥有的数据或页面,达到权限提升目的。

这个威胁可能导致普通用户变成管理员权限。

2.5.1.2.代码示例

一个仅仅做了菜单控制的代码:

<tr>
<td><a href="/user.jsp">管理个人信息</a></td>
</tr>
<%if (power.indexOf("administrators")>-1){%>
<tr><td><a href="/userlist.jsp">管理所有用户</a></td></tr>
<%}%>
25.1.3.解决方案

在打开管理页面URL时,首先判断当前用户是否拥有该页面的权限,如果没有权限,就判定为“权限提升”攻击,同时记录安全日志。

前端解决方案:

在项目的widget/Common/Bundle.js 全局顶级父组件中判断路由权限,否则就直接转去404。

//在此处判断路由权限
ComponentWillMount(e){
let location = this.props.location.pathname;
//拥有权限的路由列表,在正式环境中应该是从数据库中获取
let jurisdictionList = ['/timeZone','/hello'];
//通过location来判断当前路由是否拥有权限
jurisdictionList.push('/404'); //默认拥有404页面权限
if(!jurisdictionList.includes(location)){
//如果没有权限,跳转到404页面
this.props.history.push('/404');
}

后端解决方案:

后端使用RBAC的权限模型对操作和资源进行控制。即使用户猜测出管理地址,由于其没有对应的操作权限,操作被拒绝,浏览器接收到403响应。

2.5.2.Horizontal Access Control
2.5.2.1漏洞描述

Horizontal Access Control,访问控制攻击,也就是横向权限安全攻击。

Web应用程序接收到用户请求,修改某条数据时,没有判断数据的所属人,或判断数据所属人时,从用户提交的request参数(用户可控数据)中,获取了数据所属人id,导致恶意攻击者可以通过变换数据ID,或变换所属人id,修改不属于自己的数据。

恶意用户可以删除或修改其他人数据。

2.5.2.2代码示例

访问数据层(dao),所有的更新语句操作,都可能产生这个漏洞。

以下代码存在这个漏洞,web应用在修改用户个人信息时,从从用户提交的request参数(用户可控数据)中,获取了userid,执行修改操作。

修改用户个人信息页面:

<form action="/struts1/edituser.htm" method="post">
<input name="userid" type="hidden" value="<%=userid%>">
   <table border="1">
<tr>
<td>username:</td>
<td><%=rs.getString("name")%></td>
</tr>
<tr>
<td>passwd:</td>
<td> <input name="pass" value="<%=rs.getString("pass")%>"></td>
</tr>
<tr>
<td>type:</td>
<td><%=rs.getString("type")%></td>
</tr>
<tr>
<td>realname:</td>
<td><input name="realname" value="<%=rs.getString("realname")%>"></td>
</tr>
<tr>
<td>email:</td>
<td> <input name="email" value="<%=rs.getString("email")%>"></td>
</tr>
<tr>
<td>tel:</td>
<td> <input name="tel" value="<%=rs.getString("tel")%>"></td>
</tr>
</table>
<html:submit/>
</form>

表单中,将用户的useird作为隐藏字段,提交给处理修改个人信息的应用。

下面代码是修改个人信息的应用

int userid=Integer.valueOf( request.getParameter("userid"));
String email=request.getParameter("email");
String tel=request.getParameter("tel");
String realname=request.getParameter("realname");
String pass=request.getParameter("pass");
JdbcConnection conn = null;
try {
conn = new JdbcConnection();
Object[] params = new Object[5];
params[0] = email;
params[1] = tel;
params[2] = realname;
params[3] = pass;
params[4] = userid;
final String sql = "update user set email=?,tel=?,realname=?,pass=? where userid=?";
conn.execUpdate(sql,params);
conn.closeConn();

这段代码是从request的参数列表中,获取userid,也就是表单提交上来的userid,之后修改userid对应的用户数据。

而表单中的userid是可以让用户随意修改的。

2.5.2.3解决方案

1.从用户的加密认证cookie中,获取当前用户的id,并且需要在执行的SQL语句中,加入当前用户id作为条件语句。由于是web应用控制的加密算法,所以恶意用户无法修改加密信息。

2.后端使用RBAC的权限模型对操作和资源进行控制。对于类似的url,增加“只允许本人操作”的访问策略。

2.5.3防止非授权访问
2.5.3.1漏洞描述

WEB服务或微服务对外暴露的接口,应该防止非授权的访问。因为报文在传输的过程中会被劫持、篡改。

2.5.3.2解决方案

对于浏览器发起的请求,必须使用https 协议。

对于应用发起的访问,发起方对请求进行签名, 接收方对请求验证签名,确保请求是由信任的应用发起,且数据没有被篡改。

签名验证方案:

    (1).服务提供方发布应用标识和密钥给服务使用方

  (2).服务使用方使用密钥对请求签名,参与签名的字段通常要包含:应用标识,请求uri(包括query参数部分),报文body(可选)。 使用方请求时把签名也要发给提供方。

(3).服务提供方接收到请求后,找到应用标识和密钥,对请求进行验签,通过则认为是合法请求,否则拒绝服务。

2.6.Session会话

Cookie的安全原则:

1.secure指定Cookie是否只能通过https协议访问
2. Domain,可以指定子域使用
3. Expire 指定过期时间

2.6.1Cookie httponly flag
2.6.1.1漏洞描述

Cookie http only,是设置COOKIE时,可以设置的一个属性,如果COOKIE没有设置这个属性,该COOKIE值可以被页面脚本读取。

当攻击者发现一个XSS漏洞时,通常会写一段页面脚本,窃取用户的COOKIE,为了增加攻击者的门槛,防止出现因为XSS漏洞导致大面积用户COOKIE被盗,所以应该在设置认证COOKIE时,增加这个属性。

2.6.1.2代码示例

设置cookie的代码

response.setHeader("SET-COOKIE", "user=" + request.getParameter("cookie"));

这段代码没有设置http only属性

2.6.1.3解决方案

设置cookie时,加入属性即可

response.setHeader("SET-COOKIE", "user=" + request.getParameter("cookie") + "; HttpOnly");
2.6.2Cookie Secure flag
2.6.2.1漏洞描述

Cookie Secure,是设置COOKIE时,可以设置的一个属性,设置了这个属性后,只有在https访问时,浏览器才会发送该COOKIE。

浏览器默认只要使用http请求一个站点,就会发送明文cookie,如果网络中有监控,可能被截获。

如果web应用网站全站是https的,可以设置cookie加上Secure属性,这样浏览器就只会在https访问时,发送cookie。

2.6.2.2代码示例

设置cookie的代码

response.setHeader("SET-COOKIE", "user=" + request.getParameter("cookie") + "; HttpOnly");

这段代码没有设置Secure属性

2.6.2.3解决方案

在设置认证COOKIE时,加入Secure。

response.setHeader("SET-COOKIE", "user=" + request.getParameter("cookie") + "; HttpOnly ; Secure ");

再次访问http网站,抓数据包可以看到,已经不再发送这个COOKIE了。

2.6.3Session Expires
2.6.3.1漏洞描述

Session Expires,Session有效期安全攻击。

由于Session没有在web应用中设置强制超时时间,攻击者一旦曾经获取过用户的Session,就可以一直使用。

2.6.3.2代码示例

设置cookie的代码

response.setHeader("SET-COOKIE", "user=" + request.getParameter("cookie") + "; HttpOnly ; Secure ");

这段代码没有在服务器中设置强制超时时间。

2.6.3.3解决方案

在设置认证cookie中,加入两个时间,一个是“即使一直在活动,也要失效”的时间,一个是“长时间不活动的失效时间”。并在web应用中,首先判断两个时间是否已超时,再执行其他操作。

判断会员的cookie是否过期
if (isLogin) {
String timeStampStr = (String)
map.get(UserAuthenticationContext.TIMESTAMP);
    long loginTime = 0;
    try {
         loginTime = Long.parseLong(timeStampStr);
    } catch (NumberFormatException e) {
         if (logger.isInfoEnabled()) {
             logger.info(" loginId: " + usr.getLoginId() + " timestamp has exception " + timeStampStr);
             }
         }
         long now = System.currentTimeMillis() / 1000;
         if (now - loginTime > UserAuthenticationContext.COOKIE_VALIDITY) {
                                                  usr.setAuthenticated(false, true);
             if (logger.isInfoEnabled()) {
                 logger.info("loginId: " + usr.getLoginId() + " loginTime: " + loginTime + " nowTime: " + now);
             }
         }
}

2.7.异常管理

2.7.1日志logging)
2.7.1.1日志记录

web应用运行的过程中,必须开启安全日志。当有异常发生时,对用户的当前请求,记录日志。在所有安全方案中需要记录日志的地方,都应该按照本章节的要求记录日志,以便回溯攻击场景。Web应用程序中涉及的日志大概有如下几种:错误日志、访问日志、调试日志。

2.7.1.2日志存储

日志文件要单独放在服务器上,不能和web容器的log放在同一个文件中,并且是http无法直接访问到的地方,例如 /home/admin/tomcat/logs/security(date).log。

日志存储要预留http接口,以便需要时将日志通过http,发送到统一的服务器上。

2.7.1.3日志字段

字段按照以下要求记录,http request包可配置,平时可以不打开,当受到攻击频繁时,临时开启。

字段 说明
IP 访问者IP地址
用户id 如果用户已登录,可以记录
用户名 如果用户已登录,可以记录
cookie 当前http request中的cookie
method POST/GET
时间 访问时间,要以服务器时间为准
访问资源对象 URL、页面编号等
操作类型 查看、修改、删除等
操作对象 资源ID等
操作结果 成功或失败、处理记录数量等

2.8数据安全

32.8.1原则&要求
1. 禁止未经授权的数据访问(读&写);
2. 授权操作账号密钥需要定期更换
3. 授权账号需要设立有效期及时回收
4. 不允许粗粒度授权
5. 建立数据审计机制
6. 对公司外部进行数据发布需要对敏感信息脱敏(身份证号、手机号、姓名、银行卡等)。
7. 数据库关键数据,应当防止直接使用sql修改。如订单金额,可用积分余额等。
32.8.2敏感数据使用
1. 不能任意在Cookie、Session、ServletContext中存放数据,对于这些对象中存放的数据,必须有统一定义、说明、Lifecycle的管理等。
2. 对于敏感信息都必须保障其私密性。在页面显示、操作交互中不可避免的会有一些如用户的标识信息、密码、帐号信息、涉及的金额信息等敏感数据(保密性的数据)的存在。
3. 所有的密码、key、帐号等机密信息都不能在URL中传递。
4. 所有的密码、key、银行账号等机密信息都不能存储到Cookie,Session、ServletContext中。
尽量减少不必要的信息传递。在信息的传递过程中,如果当前Step中的对象、对象的属性、参数  等在下一个Step中不需要,则不要再做传递,甚至可以显式的销毁。这样可以有效的减少信息被截获的机率,特别是敏感数据(例如用户密码、账户信息等等)。
32.8.3数据库数据防篡改

数据库中金额,余额,用户的密码等敏感数据应当防止在程序外部修改,如直接通过sql脚本修改。

1. 密码加盐: 每个用户的密码入库前,随机生成自定盐值,盐值和密码一起加密,写入数据库。

2. 数字签名或数据摘要: 对金额或需要防改的高敏感数据,在数据表中增加签名字段,通过RSA算法或摘要算法加密,数据使用前通过签名验证数据是否被改动过。

3. 数据库访问审计: 需要对DBA等可以直接操作数据库的运维人员加入操作审计,以便事后追责。如使用阿里RDS可以同时开通数据库操作审计,这样管理员的所有操作都会被记录下来,一旦出现事故,可以恢复并追责。

2.9暴力破解

2.9.1漏洞描述

暴力破解的产生是由于服务器端没有做限制,导致攻击者可以通过暴力的手段破解所需信息,如用户名、密码、验证码等。

2.9.2代码示例
<?php 
if(isset($_GET['Login'])){
//Getusername
$user=$_GET['username'];
//Getpassword
$pass=$_GET['password'];
$pass=md5($pass);
//Checkthedatabase $query="SELECT*FROM`users`WHEREuser='$user'ANDpassword='$pass';"; $result=mysql_query($query)ordie('<pre>'.mysql_error().'</pre>'); if($result&&mysql_num_rows($result)==1){
//Getusersdetails
$avatar=mysql_result($result,0,"avatar");
//Loginsuccessful
echo"<p>Welcometothepasswordprotectedarea{$user}</p>"; echo"<imgsrc="{$avatar}"/>";
}
else{
//Loginfailed
echo"<pre><br/>Usernameand/orpasswordincorrect.</pre>";
 }
mysql_close();
}
?>

以上代码只验证了参数Login是否被设置(isset函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false),没有任何的防爆破机制。

2.9.3解决方案

1. 加入可靠的防爆破机制,当检测到频繁的错误登录后,系统会将账户锁定;

2. 使用验证码(验证码的生存周期应为一次性,即无论校验成功或失败,验证码在一次校验后就应该失效。),增加暴力破解成本;

3. 所有的人机识别措施和校验都应在服务端进行;
    4. 验证过程中不应返回有助于推测正确验证答案的信息,比如:返回验证码的内容客户端。

第三方工具防爆破方案:

1. 防护软件/Waf/Web服务器限制单IP固定时间段的登陆频率

  • 通过WAF可以实现某一个IP访问频率过高时则将此IP加入黑名单一段时间;
  • .通过Nginx等Web服务器可以实现限制单IP固定时间段的登陆频率,也就是限制流量。

2.10 Command Execution

2.10.1漏洞描述

应用程序有时需要调用一些执行系统外部命令的函数,如在PHP中,使用system、exec、shell_exec、passthru等函数执行系统命令。当攻击者能控制这些函数中的参数时,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行攻击。

2.10.2代码示例
<?php
if( isset( $_POST[ 'submit' ] ) ) {
    $target = $_REQUEST[ 'ip' ];
    // Determine OS and execute the ping command.
    if (stristr(php_uname('s'), 'Windows NT')) { 
        $cmd = shell_exec( 'ping  ' . $target );
        echo '<pre>'.$cmd.'</pre>';
    } else { 
        $cmd = shell_exec( 'ping  -c 3 ' . $target );
        echo '<pre>'.$cmd.'</pre>'; 
    }
}
?>

页面通过request获取传入的ip参数,并获取当前系统类型之后拼接相应命令"ping + target IP"并执行,在此过程中IP参数可控,所以在IP可拼接命令。

127.0.0.1&&whoami
127.0.0.1;whoami
127.0.0.1||whoami

2.10.3解决方案

1. 严格过滤关键字符;

$substitutions = array(
    '&&' => '',
    ';' => '',
    '||' => '',
);
$target = str_replace(array_keys($substitutions), $substitution, $target);

2. 尽量少用执行命令的函数或者直接禁;

3. 参数值尽量使用引号包括

4. 在进入执行命令的函数/方法之前,对参数进行过滤,对敏感字符进行转义

<?php
    $arg = $_GET['cmd'];
    // $arg = addslashes($arg);
    $arg = escapeshellcmd($arg);  //拼接前就处理
    if ($arg) {
        system("ls -al '$arg'");
    }
?>
// escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者执行操作符之前进行转义。

2.11跨站请求伪造(CSRF)防范

2.11.1.漏洞描述

CSRF攻击的全称是跨站请求伪造( cross site request forgery),是一种对网站的恶意利用。你可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。 CRSF能做的事情包括利用你的身份发邮件、发短信、进行交易转账等,甚至盗取你的账号。

2.11.2.攻击示例
//GET方式
假设某银行网站A以GET请求来发起转账操作,转账的地址为www.xxx.com/transfer.do?accountNum=l000l&money=10000
其中参数accountNum表示转账的账户,参数money表示转账金额。
而某大型论坛B上,一个恶意用户上传了一张图片,而图片的地址栏中填的并不是图片的地址,而是前所说的账地址:
<img src="http://www.xxx.com/transfer.do?accountNum=l000l&money=10000">
当你登录网站A后,没有及时登出,这时你访问了论坛B,不幸的事情发生了,你会发现你的账号里面少了10000块
为什么会这样呢
在你登录银行A时,你的浏览器端会生成银行A的cookie,而当你访问论坛B的时候,页面上的<img>标签需要浏览器发起一个新的HTTP请求,以获得图片资源,当浏览器发起请求时,请求的却是银行A的转账地址
www.xxx.com/transfer.do?accountNum=l000l&money=10000
并且会带上银行A的cookie信息,结果银行的服务器收到这个请求后,会以为是你发起的一次转账操作,因此你的账号里边便少了10000块。

//POST方式
假设银行将其转账方式改成POST提交,而论坛B恰好又存在一个XSS漏洞,恶意用户在它的页面上植入如下代码:
<form id="aaa" action="http://www.xxx.com/transfer.do" metdod="POST" display="none">
    <input type="text" name="accountNum" value="10001"/>
    <input type="text" name="money" value="10000"/>
</form>
<script>
    var form = document.forms('aaa');
    form.submit();
</script>
如果你此时恰好登录了银行A,且没有登出,当你打开上述页面后,脚本会将表单aaa提交,把accountNum和money参数传递给银行的转账地址http://www.xxx.com/transfer.do,同样的,银行以为是你发起的一次转账会从你的账户中扣除10000块。
2.11.3.解决方案

1. 尽量使用POST,限制GET

GET接口太容易被拿来做CSRF攻击,看上面示例就知道,只要构造一个img标签,而img标签又是不能过滤的数据。接口最好限制为POST使用,GET则无效,降低攻击风险。当然POST并不是万无一失,攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。

2. cookie设置为HttpOnly

CRSF攻击很大程度上是利用了浏览器的cookie,为了防止站内的XSS漏洞盗取cookie,需要在cookie中设置“HttpOnly”属性,这样通过程序(如JavaScript脚本、Applet等)就无法读取到cookie信息,避免了攻击者伪造cookie的情况出现。在Java的Servlet的API中设置cookie为HttpOnly的代码如下:

 response.setHeader( "Set-Cookie", "cookiename=cookievalue;HttpOnly");

3. 增加token

CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于cookie中,因此攻击者可以在不知道用户验证信息的情况下直接利用用户的cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信总不存在于cookie之中。鉴于此,系统开发人员可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求。

假设请求通过POST方式提交,则可以在相应的表单中增加一个隐藏域:

 <input type="hidden" name="_toicen" value="tokenvalue"/>

token的值通过服务端生成,表单提交后token的值通过POST请求与参数一同带到服务端,每次会话可以使用相同的token,会话过期,则token失效,攻击者因无法获取到token,也就无法伪造请求。在session中添加token的实现代码:

HttpSession session = request.getSession();
Object token = session.getAttribute("_token");
if(token == null I I "".equals(token)) {
    session.setAttribute("_token", UUID.randomUUIDO .toString());
}

4. 通过Referer识别

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限的页面的请求都来自于同一个网站。比如某银行的转账是通过用户访问http://www.xxx.com/transfer.do页面完成的,用户必须先登录www.xxx.com,然后通过单击页面上的提交按钮来触发转账事件。当用户提交请求时,该转账请求的Referer值就会是

提交按钮所在页面的URL(本例为www.xxx. com/transfer.do)。如果攻击者要对银行网站实施CSRF攻击,他只能在其他网站构造请求,当用户通过其他网站发送请求到银行时,该请求的Referer的值是其他网站的地址,而不是银行转账页面的地址。因此,要防御CSRF攻击,银行网站只需要对于每一个转账请求验证其Referer值即可,如果是以www.xx.om域名开头的地址,则说明该请求是来自银行网站自己的请求,是合法的;如果Referer是其他网站,就有可能是CSRF攻击,则拒绝该请求。取得HTTP请求Referer:

 String referer = request.getHeader("Referer");

正文到此结束
本文目录