2.1 图形验证码
图形验证码一般是防止使用程序恶意注册、暴力破解用户名密码或者批量发帖而设置的。在页面初试化时服务器向页面发送一个随机字符串,同时在 session 里也保存一份,当用户提交时将随机数一起 post 到后台,通过与 session 中保存的值对比,如果不相同,则有可能是恶意攻击。
示例代码
用户登陆过程中是否启用了验证码
验证码复杂度是否符合要求
验证码的有效性是否只有一次
代码示例:
//从 session 中获取验证码
String validateCode=(String)request.getSession().getAttribute("rand");
//获取用户输入的验证码
String userInput=request.getParameter("validateCode");
if(userInput==null||(!validateCode.equalsIgnoreCase(userInput)))
{
//提示验证码错误
……
return;
}
……
//从以上代码中可以看到在验证码校验时,只是校验了验证码是否正确并未清除session的验证码
验证码是否设置了超时机制
代码示例:
protected boolean checkTimeout(EMPSession session)
{
return System.currentTimeMillis() - session.getLastAccessTime() > this.sessionTimeOut;
}
//从上面的代码中可以看到验证码校验机制
验证顺序
代码示例:
//从session中获取验证码
String validateCode=(String)request.getSession().getAttribute("rand");
//获取用户输入的验证码
String userInput=request.getParameter("validateCode");
String username=request.getParameter(“userName”);
String userpwd=request.getParameter(“userPwd”);
if(LoginSuccess(username,userpwd)) {
if(validateCode.equalsIgnoreCase(userInput)) {
……
} else {
//提示验证码错误
request.getSession().removeAttribute("rand");
return;
}
} else {
//提示用户名或密码错误
request.getSession().removeAttribute("rand");
return;
}
//正确的验证过程,应将验证码的验证放在首位,验证码验证通过后,再验证登陆或者其他操作。
图形验证码安全防范编码
String userName = request.getParameter("adminname");
String userPwd = request.getParameter("adminpwd");
//从session中取得验证码
String validateCode=(String)request.getSession().getAttribute("rand");
//获取用户输入的验证码
String userInput=request.getParameter("validateCode");
if(userName==null||userPwd==null)
return;
if(validateCode!=null) {
if(!validateCode.equalsIgnoreCase(userInput)) {
out.print("<script>alert('验证错误! ');window.location='AdminLogin.jsp'</script>");
//移除使用过的验证码
request.getSession().removeAttribute("rand");
return;
} else {
request.setAttribute("message", "验证正确");
}
boolean loginValid=dao.LoginStatus(userName, userPwd);
if(loginValid) {
request.getSession().invalidate();
request.getSession().setAttribute("AdminName", userName);
out.print("<script>alert('登录成功,跳转到首页');window.location='AdminIndex.jsp'</script>");
} else {
out.print("<script>alert(' 用 户 名 或 密 码 错 误 !');window.location='AdminLogin.jsp'</script>");
}
//移除使用过的验证码
request.getSession().removeAttribute("rand");
}
2.2 用户登陆认证
用户登陆认证应检查用户名和密码的合法性。
对于用户名错误和密码错误的提示信息应统一,降低账号、密码被猜解的风险。
用户唯一性(用户注册时检查)。
日志信息
登录日志中记录客户访问系统的远程 IP 地址和时间等详细信息,可以统计客户访问系统的次数;除了正常的登陆和操作日志,还应记录异常的日志信息并严格控制系统日志的访问权限。
用户登录认证安全编码规范:
public boolean checkUser(String username,String password) {
try {
//使用预编译的方式防止产生注入
pstmt=ct.prepareStatement("select * from userTable where username=? and password=?");
pstmt.setString(1, username);
//密码加密
pstmt.setString(2, Entrypt. string2MD5(password,salt));
ResultSet rs=pstmt.executeQuery();
User user=new User();
while(rs.next()) {
user.setId(rs.getInt(1));
user.setUsername(rs.getString(2));
user.setPassword(rs.getString(3));
//更新session数据
HttpSession session=dhUtil.changeSessionIdentifier(request);
session. .setAttribute("username", username);
return user;
}
return null;
} catch(Exception e) {
return null;
}
}
2.3 注销
系统应为用户提供退出系统功能,在退出系统后,清理会话信息。
注销安全编码规范:
public void logout() {
ESAPI.httpUtilities().killCookie( ESAPI.currentRequest(),ESAPI.currentResponse(),HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME );
HttpSession session = ESAPI.currentRequest().getSession(false);
if (session != null) {
removeSession(session);
session.invalidate();
}
ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(),ESAPI.currentResponse(),"JSESSIONID");
loggedIn = false;
logger.info(Logger.SECURITY_SUCCESS, "Logout successful" );
ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
ESAPI.httpUtilities().sendRedirect(“登录页面”);
}
2.4 认证失败次数限制
系统应对客户登录密码输入次数进行记录,如果客户密码输入次数累计达到3次,系统应自动将此客户冻结,防止恶意攻击
2.5 重定向认证
Web应用中经常需要指定完成当前页面操作之后下一个页面的请求地址。常见的例如:登录操作完成之后,返回指定页面或者返回登录操作前页面。如果返回地址可被攻击者控制,可能导致受害者访问恶意网站、钓鱼网站的链接。
重定向认证安全编码规范
对需要重定向的参数进行判断,如果重定向的URL不符合"Redirect"正则表达式的要求,则触发异常,并重定向回主页。
注:ESAPI是通过ESAPI.properties文件中的Redirect正则表达式进行过滤判断的,根据具体需要编辑ESAPI.properties
<%
String redirect=request.getParameter("redirect");
if(redirect!=null)
{
ESAPI.httpUtilities().sendRedirect(redirect);
}
%>
2.6 SSO认证
SSO(Single Sign On,单点登录)是目前比较流行的企业业务整合方案之一。在SSO定义的众多应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。在实现过程中应避免在客户端中明文传输账号密码信息,建议使用Token交换认证信息或者将用户名密码加密处理后再进行传输。
SSO认证安全编码规范
在Token认证中,将SSO分为SSO-Server和SSO-Client两个部分,SSO-Server主要负责用户的登陆注销、为SSO-Client分配Token和验证Token工作,SSO-Client作为一个受信系统,没有自己的认证系统,通过SSO-Server完成身份认证工作。
if (Domain.Security.SmartAuthenticate.LoginUser != null)
{
//生成Token,并持久化Token
Domain.SSO.Entity.SSOToken token = new Entity.SSOToken();
token.User = new Entity.SSOUser();
token.User.UserName=Domain.Security.SmartAuthenticate.LoginUser.UserName;
token.LoginID = Session.SessionID;
Domain.SSO.Entity.SSOToken.SSOTokenList.Add(token);
//拼接返回的url,参数中带Token
string spliter = returnUrl.Contains('?') ? "&" : "?";
returnUrl = returnUrl + spliter + "token=" + token.ID;
Response.Redirect(returnUrl);
}
当完成Token分配之后,页面将带有TokenID的参数跳转到SSO-Client页面,并在SSO-Client的Cookie中添加Token值,在以后的每次请求中,SSO-Client通过调用SSO-Server的服务来验证Token的合法性。
public Entity.SSOToken ValidateToken(string tokenID) //验证token的有效性
{
if (!KeepToken(tokenID))
return null;
var token = Domain.SSO.Entity.SSOToken.SSOTokenList.Find(m => m.ID ==tokenID);
return token;
}
public bool KeepToken(string tokenID) //保持token不过期
{
var token = Domain.SSO.Entity.SSOToken.SSOTokenList.Find(m => m.ID ==tokenID);
if (token == null)
return false;
if (token.IsTimeOut())
return false;
token.AuthTime = DateTime.Now;
return true;
}
当用户请求SSO-Client的受保护资源时,SSO-Client会首先是否有TokenID,如果存在TokenID,则调用SSO-Server的WebService来验证这个TokenID是否合法;验证成功以后将会返回SSOToken的实例,里面包含已登录的用户信息。
if (!string.IsNullOrEmpty(tokenID)) {
AuthTokenService.AuthTokenServiceSoapClient client = new AuthTokenService.AuthTokenServiceSoapClient();
var token = client.ValidateToken(tokenID);
if (token != null) {
//登录成功!跳转
} else {
//获取不到用户信息!跳转到登录页面
}
} else {
//tokenID无效
}
2.7 session 建立
JavaWeb系统会在客户登录成功之后为其在应用服务器内存中建立Session,在客户后续的交易请求中,系统不断检查内存中Session的有效性,如果Session失效(没有、超时或被人窜改),则交易请求是非法的,系统不予接受;
为了防止攻击者暴力猜解session会话ID标识,应至少包含128位安全随机数标识符;
为了防止session会话ID劫持,应当在每次认证完成后开启全新的用户ID,保证会话安全。
session建立安全编码规范
//将当前session数据拷贝到新session,并使当前session失效,并将新session绑定到当前user对象中,并返回新的session。
HttpSession ESAPI.httpUtilities().changeSessionIdentifier(HttpServletRequest request);
2.8 Session超时处理
Session超时处理包括两部分:Session时间戳重置,Session超时检查。
Session时间戳重置是指在有新的交易请求提交到JavaWeb系统时,系统首先检查Session是否超时,如果未超时,则重置Session的时间戳,继续后续操作;否则,执行Session超时处理,向客户返回超时信息。
Session超时检查是指为防止垃圾Session的在内存中堆积而占用系统资源,系统通过后台线程定时检查超时Session,并将其从内存中清除,从而释放系统资源。
session超时处理安全编码规范
DefaultUser user = (DefaultUser) getUserFromSession(); //检查用户是否已登录
if(!user .isSessionTimeout()) {
user. setLastLoginTime(new Date())
} else {
//注销
Logout();
}
2.9 Session清理
应当允许用户安全的结束自己的会话。
一个会话结束后,应该安全的从硬盘和内存中清除相关数据。
session清理安全编码规范:与《注销》安全编码规范相同
2.10 Session实时检查
JavaWeb系统每个交易的定义中,第一个步骤必须是Session检查交易步骤,来检验Session有效性。
session实时检查安全编码规范
DefaultUser user = (DefaultUser) getUserFromSession();
//检查用户是否已登录
if (user.isAnonymous()||!user.isEnabled()||user.isLocked()||user.isExpired()||user.isSessionTimeout()||user.isSessionAbsoluteTimeout())
//各种用户状态条件判断
{
//交易处理
}
2.11 cookie管理
是否会在Cookie中存储明文或简单编码/加密(base64编码)过的密码
是否会在Cookie中存储应用的特权标识
是否设置了Cookie的有效域和有效路径
是否设置了合适的Cookie有效时间 |