java代码审计
java编译篇
java编译过程:
Java源代码 ——(编译)——> Java字节码 ——(解释器)——> 机器码
Java源代码 ——(编译器 )——> jvm可执行的Java字节码 ——(jvm解释器) ——> 机器可执行的二进制机器码 ——>程序运行
采用字节码的好处:高效、可移植性高
以下示例为.java文件:
以下是.class文件:
反编译工具篇
- fernflower
-
jad
-
jd-gui
-
idea自带插件
jar包本质上是将所有class文件、资源文件压缩打成一个包。
Servlet与jsp篇
Servlet:
-
类似小程序,处理较复杂的服务端业务逻辑
-
含有HttpServlet类,可进行重写
- servlet3.0后使用注解方式描述servlet,使用doGet和doPost为默认命名
- servlet3.0版本之前必须在web.xml中配置
jsp:
会被编译成一个java类文件
,如index.jsp在Tomcat中Jasper编译后会生成index_jsp.java
和index_jsp.class
两个文件。是特殊的servlet。
全局控制器篇
使用idea,全局搜索command+shift+f(或者a)
find ~/cms/ -type f -name "*.class" |xargs grep -E "Controller|@RestController|RepositoryRestController"
find ~/cms/ -type f -name "*.class" |xargs grep -E "RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RepositoryRestResource"
全局过滤器篇
审计时,得先看是否含有全局过滤器。切勿看到Servlet
、JSP
中的漏洞点就妄下定论,Servlet
前面很有可能存在一个全局安全过滤的Filter
。当然每种框架的写法也有差别。个人认为Filter主要是用在
-
web.xml全局过滤
<filter> <filter-name>YytSecurityUrlFilter</filter-name> <filter-class>com.yytcloud.core.spring.pub.filter.YytSecurityUrlFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>sqlInjIgnoreUrls</param-name> <param-value>.*/itf/.*</param-value> </init-param> <init-param> <param-name>ignoreXSSUrls</param-name> <param-value>.*/itf/.*</param-value> </init-param> </filter> <filter-mapping> <filter-name>YytSecurityUrlFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
jar包
首先添加一个 jar 包:commons-lang-2.5.jar ,然后在后台调用这些函数: StringEscapeUtils.escapeHtml(string); StringEscapeUtils.escapeJavaScript(string); StringEscapeUtils.escapeSql(string);
-
转义
String string = HtmlUtils.htmlEscape(userinput); //转义 String s2 = HtmlUtils.htmlUnescape(string); //转成原来的
常见漏洞篇
分为业务安全
问题、代码实现
和服务架构
安全问题
代码实现,查看对应代码与全局过滤器:
- 任意
文件读写
(文件上传、文件下载)、文件遍历
、文件删除
、文件重命名
等漏洞 - SQL注入漏洞
- XXE(XML实体注入攻击)
- 表达式执行(SpEL、OGNL、MVEL2、EL等)
- 系统命令执行漏洞(ProcessBuilder)
- 反序列化攻击(ObjectInputStream、JSON、XML等)
- Java反射攻击
- SSRF攻击
- XSS
业务安全,主要理解该系统的逻辑:
- 用户登陆、用户注册、找回密码等功能中密码信息未采用加密算法。
- 用户登陆、用户注册、找回密码等功能中
未采用验证码
或验证码未做安全刷新
(未刷新Session中验证码的值)导致的撞库、密码爆破漏洞。 - 找回密码逻辑问题(如:可直接跳过验证逻辑直接发包修改)。
- 手机、邮箱验证、找回密码等涉及到动态验证码
未限制验证码失败次数
、验证码有效期
、验证码长度过短
导致的验证码爆破问题。 - 充值、付款等功能调用了第三方支付系统未正确校验接口(与第三方的交互、与客户的交互,主要查看逻辑问题)。
- 后端采用了
ORM框架
更新操作时因处理不当导致可以更新用户表任意字段(如:用户注册、用户个人资料修改时可以直接创建管理员账号
或其他越权修改操作)。 - 后端采用了
ORM框架
查询数据时因处理不当导致可以接收任何参数导致的越权查询、敏感信息查询等安全问题。 - 用户中心转账、修改个人资料、密码、退出登陆等功能未采用验证码或
Token机制
导致存在CSRF漏洞
。 - 后端服务过于信任前端,重要的参数和业务逻辑只做了前端验证(如:文件上传功能的文件类型只在JS中验证、后端不从Session中获取用户ID、用户名而是直接接收客户端请求的参数导致的
越权问题
)。 - 用户身份信息认证逻辑问题(如:后台系统自动登陆时直接读取Cookie中的用户名、用户权限不做验证)。
- 重要接口采用
ID自增、ID可预测并且云端未验证参数有效性
导致的越权访问、信息泄漏问题(如:任意用户订单越权访问)。 条件竞争问题
,某些关键业务(如:用户转账)不支持并发、分布式部署时不支持锁的操作等。- 重要接口
未限制请求频率
,导致短信、邮件、电话、私信等信息轰炸。 - 敏感信息未保护,如
Cookie中直接存储用户密码等重要信息
,跟踪cookie中的变量最终到了哪。 - 弱加密算法、弱密钥,如勿把Base64当成数据加密方式、重要算法密钥采用弱口令如
123456
。 - 后端无异常处理机制、未自定义50X错误页面,服务器异常导致敏感信息泄漏(如:数据库信息、网站绝对路径等)。
- 使用
DWR框架
开发时前后端不分漏洞(如:DWR直接调用数据库信息把用户登陆逻辑直接放到了前端来做)。
SQL注入篇
-
直接拼接,未进行过滤
将
request.getParameter("")
直接放在SQL语句。全局搜索查看:
String sql
等。 -
预编译使用有误
-
在使用占位符后未进行setObject或者setInt或者setString。
-
有些会使用SQLparameter函数,参数化查询SQL,能有效避免SQL注入。
-
使用setProperties函数。
-
占位符
这种在渗透中出现的情况是:当输入1' or '1'='1,不会有什么回显。
因为这个引号已经无法起到闭合作用了,只相当于是一个字符,由于对特殊符号的转义。
如图所知,在setString那个函数那里对引号等一些特殊符号做了转义。
// 执行查询
System.out.println(" 实例化Statement对象...");
PreparedStatement st=conn.prepareStatement("select * from " +
"springmysql1 where name=?");
st.setString(1,request.getParameter("name"));
ResultSet rs=st.executeQuery();
-
%和_
没有手动过滤%。预编译是不能处理这个符号的, 所以需要手动过滤,否则会造成慢查询,造成 dos。
-
Order by、from等无法预编译
如以下示例,需要手动过滤,否则存在sql注入。
String sql = "Select * from news where title =?" + "order by '" + time + "' asc"
-
Mybatis 框架
使用注解或者xml将java对象与数据库sql操作对应。
在注解中或者 Mybatis 相关的配置文件中搜索 $ 。然后查看相关 sql 语句上下文环境。
mybatis简单示例
- mybatis的maven配置
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency>
-
目录结构
java文件
配置文件
-
各文件功能(左下角是我的水印哈哈哈)
-
config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="dev"> <environment id="dev"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatistest"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
-
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.UserMapper"> <select id="getUser" resultType="org.User1"> select * from user where name=#{name} </select> </mapper>
-
UserMapper.java
package org; public interface UserMapper{ public User1 getUser(String name); }
-
MybatisUtil.java
package org; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.Reader; public class MybatisUtil{ public static SqlSessionFactory sessionFactory; static{ try{ Reader reader = Resources.getResourceAsReader("config.xml"); sessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e){ System.out.println(e); } } public static SqlSession getSession(){ return sessionFactory.openSession(); } }
-
User1.java
package org; import lombok.Data; @Data public class User1 { String name; int age; }
-
test.java
package org; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; import org.junit.Test; public class test { @Test public void test1() { SqlSession session=MybatisUtil.getSession(); UserMapper userMapper=session.getMapper(UserMapper.class); User1 user1=userMapper.getUser("wy"); System.out.println(user1.getAge()); } }
-
在UserMapper.xml使用
#{}
的结果 -
在UserMapper.xml使用
${}
的结果使用该符号需要手动写上引号拼接,不然会报错,
User1 user1=userMapper.getUser("'"+"wy' or '1'='1"+"'");
-
容易触发sql注入的条件与修复
- 模糊查询,需要加入特殊符号,不单单加入引号的那种。如
like '%${xxx}%'
,修复自然是将xxx拎出来,比如使用concat函数。 - 无需加引号处。比如
in(${xxx})
或者order by ${xxx}
。修复是用户自行过滤。
- 模糊查询,需要加入特殊符号,不单单加入引号的那种。如
-
:=
和和此处的${ids}
可防止SQL注入@Arguments("id") @Sql("select count(1) from cgform_head where physice_id=:id ") public int getByphysiceId(String id); @Arguments("ids") @Sql("select count(1) as hasPeizhi,physice_id id from cgform_head where 1=1 and physice_id in (${ids}) group by physice_id") public List<Map<String, Object>> getPeizhiCountByIds(String ids);
像字符型SQL语句的渗透利用在现实中无非三种,可能还需试一下时间盲注等等,视情况而定:
- 1') or 1=1 or ('1(括号那里可能会有1至多个)
- 1%' or '%'='
- 1' or '1'='1
SPel注入篇
简单描述:
使用el表达式且el表达式可控。如CVE-2018-1260就是spring-security-oauth2的一个SPel注入导致的RCE。
示例:
String el="T(java.lang.Runtime).getRuntime().exec(\"open /tmp\")";
ExpressionParser PARSER = new SpelExpressionParser();
Expression exp = PARSER.parseExpression(el);
System.out.println(exp.getValue());
在getValue那里执行命令,调用栈如下
审计:
查看使用SpelExpressionParser的地方有没有用户可控的。
XSS篇
示例
@RequestMapping("/xss")
public ModelAndView xss(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
String name = request.getParameter("name");
ModelAndView mav = new ModelAndView("mmc");
mav.getModel().put("uname", name);
return mav;
}
如果想要返回json格式,将mmc
替换为new MappingJackson2JsonView()
。
SSRF篇
代码中提供了从其他服务器应用获取数据的功能但没有对目标地址做过滤与限制。比如从指定URL链接获取图片、下载等。主要可能存在于在线文档编辑器之类。
示例
String url = request.getParameter("picurl");
StringBuffer response = new StringBuffer();
URL pic = new URL(url);
HttpURLConnection con = (HttpURLConnection) pic.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
审计
支持的协议
- file
- ftp
- http
- https
- jar
- mailto
- netdoc
常用的函数
HttpClient.execute
HttpClient.executeMethod
HttpURLConnection.connect
HttpURLConnection.getInputStream
URL.openStream
修复
- 使用白名单校验HTTP请求url地址
- 避免将请求响应及错误信息返回给用户
- 禁用不需要的协议及限制请求端口,仅仅允许http和https请求等(这点待研究)
CSRF篇
简单描述:
跨站请求伪造是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作
,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件
等操作;但是如果受害者是一个具有管理员权限的用户
时CSRF则可能威胁到整个Web系统的安全。
审计:
一些增删改查方法,是否进行Referer头检验
、token检验
无法构造的随机数参数
、验证码密码
。
搜索session["token"]
修护:
Referer头检验、token检验。
XXE篇
简单描述:
当允许引用外部实体且存在输入点
时,恶意攻击者即可构造恶意内容访问服务器资源,如读取 passwd 文件
https://www.cnblogs.com/r00tuser/p/7255939.html
示例:
@RequestMapping("/xxetest")
public String xxetest(HttpServletRequest request) throws DocumentException {
String xmldata = request.getParameter("data");
SAXReader sax=new SAXReader();
Document document=sax.read(new ByteArrayInputStream(xmldata.getBytes()));
Element root= ((org.dom4j.Document) document).getRootElement();
List rowList = root.selectNodes("//msg");
Iterator<?> iter1 = rowList.iterator();
if (iter1.hasNext()) {
Element beanNode = (Element) iter1.next();
return beanNode.getTextTrim();
}
return "error";
}
root.selectNodes("//msg")
获取根目录下的所有<msg>标签</msg>
利用:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE replace [
<!ENTITY test SYSTEM "file:///tmp/flag">]>
<msg>&test;</msg>
渗透的话可以结合burpsuite的插件:collaborator https://blog.csdn.net/fageweiketang/article/details/89073662
审计:
- 判断使用哪种XML解析器
- 搜索是否有禁用外部实体配置(修护部分有具体代码)
- 是否有外部输入点进行解析
修护:
- saxReader
saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- saxBuilder
SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Document doc = builder.build(new File(fileName));
- saxTransformerFactory
SAXTransformerFactory sf = SAXTransformerFactory.newInstance(); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
sf.newXMLFilter(Source);
Note: Use of the following XMLConstants requires JAXP 1.5, which was added to Java in 7u40 and Java 8:
javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET
- schemaFactory
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
Schema schema = factory.newSchema(Source);
- xmlInputFactory
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
- xmlReader
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); // This may not be strictly required as DTDs shouldn't be allowed at all, per previous line.
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- XPathExpression
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = df.newDocumentBuilder();
String result = new XPathExpression().evaluate( builder.parse(new ByteArrayInputStream(xml.getBytes())) );
- transformerFactory
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
- Validator
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
- Unmarshaller
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(new StringReader(xml)));
JAXBContext jc = JAXBContext.newInstance(Object.class);
Unmarshaller um = jc.createUnmarshaller();
um.unmarshal(xmlSource);
XML篇
简单描述:
一个用户,如果他被允许输入结构化的XML片段,则他可以在 XML 的数据域中注入 XML 标签
来改写目标 XML 文档的结构与内容。
示例:
private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException
{
String xmlString;
xmlString = "<user><role>operator</role><id>" + user.getUserId()
+"</id><description>" + user.getDescription() +
"</description></user>";
outStream.write(xmlString.getBytes());
outStream.flush();
}
输入以下恶意代码
hhh</id><role>administrator</role><id>hhh
由于 SAX 解析器(org.xml.sax and javax.xml.parsers.SAXParser
)在解释 XML 文档时会将第二个role 域的值覆盖前一个 role 域的值
,因此导致此用户角色由操作员提升为了管理员。
审计方法:
全局搜索如下字符串
- xml
- StreamSource
- XMLConstants
- StringReader
- xmlString
在项目中搜索. Xsd 文件
修护:
- 白名单。只能包含
字母、数字、下划线
。
private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException
{
if (!Pattern.matches("[_a-bA-B0-9]+", user.getUserId()))
{
...
}
if (!Pattern.matches("[_a-bA-B0-9]+", user.getDescription()))
{
...
}
String xmlString = "<user><id>" + user.getUserId()
\+ "</id><role>operator</role><description>"
\+ user.getDescription() + "</description></user>";
outStream.write(xmlString.getBytes());
outStream.flush();
}
-
使用
dom4j
来构建 XML。dom4j 是一个良好定义的、开源的 XML 工具库。Dom4j将会
对文本数据域进行 XML 编码
,从而使得 XML 的原始结构和格式免受破坏。
public static void buidlXML(FileWriter writer, User user) throws IOException
{
Document userDoc = DocumentHelper.createDocument();
Element userElem = userDoc.addElement("user");
Element idElem = userElem.addElement("id");
idElem.setText(user.getUserId());
Element roleElem = userElem.addElement("role");
roleElem.setText("operator");
Element descrElem = userElem.addElement("description");
descrElem.setText(user.getDescription());
XMLWriter output = null;
try{
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");
output = new XMLWriter(writer, format);
output.write(userDoc);
output.flush();
}
finally{
try{
output.close();
}
catch (Exception e){}
}
}
越权篇
水平越权和垂直越权。
审计:
在每个request.getParameter("userid");
之后查看是否有检验当前用户与要进行增删改查的用户。
修护:
获取当前登陆用户并校验该用户是否具有当前操作权限,并校验请求操作数据是否属于当前登陆用户,当前登陆用户标识不能从用户可控的请求参数中获取。
批量请求篇
简单描述:
在部分接口,没有进行验证码等防护,导致可以无限制重发接口,结果是浪费了系统资源的才算。比如一直发短信验证码,但是可以不断查询就不算批量请求漏洞。批量请求与csrf的修护建议类似,但由于使用场景不同,因此漏洞不同。
修护:
- 验证码
- token
- 对同一个用户发起这类请求的频率、每小时及每天发送量在服务端做限制,不可在前端实现限制
- 对参数使用不可预测的随机数
命令执行篇
简单描述:
执行的命令用户可控。
示例:
String cmd=request.getParameter("cmd");
Runtime.getRuntime.exec(cmd);
审计:
查找是否有使用如下方法,且其中的内容用户可控。
Runtime.exec
ProcessBuilder.start
GroovyShell.evaluate
反序列化-代码执行篇
简单描述:
Java 程序使用 ObjectInputStream 对象的readObject
方法将反序列化数据转换为 java 对象。但当输入的反序列化的数据可被用户控制
,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码
。
示例:
//读取输入流,并转换对象
InputStream in=request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in); //恢复对象
ois.readObject();
ois.close();
审计:
java 序列化的数据一般会以标记(ac ed 00 05
)开头,base64 编码后的特征为rO0AB
。
找出反序列化函数调用点:
- ObjectInputStream.readObject
- ObjectInputStream.readUnshared
- XMLDecoder.readObject
- Yaml.load
- XStream.fromXML
- ObjectMapper.readValue
- JSON.parseObject
RMI:是 Java 的一组拥护开发分布式应用程序的 API,实现了不同操作系统之间程序的方法调用。RMI 的传输 100%基于反序列化,Java RMI 的默认端口是 1099 端口。
修护:
-
白名单。只允许某些类被反序列化。
以下例子通过重写ObjectInputSream中的resolveClass方法,读取需要反序列化的类名与SerialObject.class对比,判断是否合法。SerialKiller就是利用这种原理而写的jar包。
-
/**只允许反序列化 SerialObject class */ @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!desc.getName().equals(SerialObject.class.getName())) { throw new InvalidClassException( "Unauthorized deserialization attempt", desc.getName()); } return super.resolveClass(desc); } }
SerialKiller简单用法
ObjectInputStream ois = new SerialKiller(is, "/etc/serialkiller.conf"); String msg = (String) ois.readObject();
-
Apache Commons IO Serialization 包中的
ValidatingObjectInputStream
类的accept
方法来实现反序列化类白/黑名单控制Object obj; ByteArrayInputStream bais = new ByteArrayInputStream(buffer); // Use ValidatingObjectInputStream instead of InputStream ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais); //只允许反序列化SerialObject class ois.accept(SerialObject.class); obj = ois.readObject();
反序列化-权限过高篇
简单描述:
没懂所以不想写
审计:
手工搜索以下文本
- public * writeObject
- public * readObject
- public readResolve public writeReplace
修护:
- private void writeObject
- private void readObject
- protected Object readResolve
- protected Object writeReplace
敏感数据序列化篇
简单描述:
将敏感数据连着实例方法一起序列化,导致敏感数据泄漏。
示例:
假设x和y
是敏感数据,序列化后面临坐标泄漏危险
public class GPSLocation implements Serializable
{
private double x; // sensitive field
private double y; // sensitive field
private String id;// non-sensitive field
// other content
}
public class Coordinates
{
public static void main(String[] args)
{
FileOutputStream fout = null;
try{
GPSLocation p = new GPSLocation(5, 2, "northeast");
fout = new FileOutputStream("location.ser");
ObjectOutputStream oout = new ObjectOutputStream(fout);
oout.writeObject(p);
oout.close();
}
catch (Throwable t){
// Forward to handler
}
finally{
if (fout != null){
try{
fout.close();
}
catch (IOException x){
// handle error
}
}
}
}
}
审计:
对于已经被确定为敏感的数据搜索示例一中相关的关键字。
或者查看进行序列化的类,是否含有敏感数据。
修护:
- 将敏感数据加上transient
private transient double x; // transient field will not be serialized
private transient double y; // transient field will not be serialized
- 将能序列化的加入
serialPersistentFields
,那么其余将不会被序列化
public class GPSLocation implements Serializable
{
private double x;
private double y;
private String id;
// sensitive fields x and y are not content in serialPersistentFields
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("id", String.class)};// other content
}
静态内部类的序列化篇
简单描述:
没懂
审计:
人工查找 implements Serializable 的所有内部类
修护:
class \${InnerSer} {}
去除内部类的序列化。
static class \${InnerSer} implements Serializable {}把内部类声明为静态从而被序列化。但是要注意遵循示例三中的敏感信息问题
路径安全篇
简单描述:
攻击者利用../
可以上传至任意指定目录。
服务端使用getAbsolutePath()
的话无法检测出攻击者真正上传的文件路径,因此即使做了过滤也将可被绕过。
示例:
当前目录E:\workspace\myTestPathPrj(windows系统)
public static void testPath() throws Exception{
File file = new File("..\\src\\ testPath.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
file.getAbsolutePath()
打印出E:\workspace\myTestPathPrj\..\src\testpath.txt
file.getCanonicalPath()
打印出E:\workspace\src\testPath.txt
审计:
-
查找
permission Java.io.FilePermission
字样和grant
字样,看是否已经做出防御。 -
查找
getAbsolutePath()
和getPath()
,找到后看有没有用户输入的。
ZIP文件提取篇
简单描述:
两个危害:一个是提取出的文件标准路径落在解压的目标目录之外,另一个是提取出的文件消耗过多的系统资源。
示例:
-
解压后的文件名未作过滤(直接
entry.getName()
);未对上传的压缩包大小作限制(
zis.read
后直接dest.write
)
static final int BUFFER = 512;
// ...
public final void unzip(String fileName) throws java.io.IOException
{
FileInputStream fis = new FileInputStream(fileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null)
{
System.out.println("Extracting: " + entry);
int count;
byte data[] = new byte[BUFFER];
// Write the files to the disk
FileOutputStream fos = new FileOutputStream(entry.getName());
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1)
{
dest.write(data, 0, count);
}
dest.flush();
dest.close();
zis.closeEntry();
}
zis.close();
}
-
解压后的文件名未作过滤;
使用
getSize()函数不能准确判断压缩包大小
,攻击者可以修改压缩包的16进制编码进行绕过。恶意攻击者可以伪造 ZIP 文件中用来描述解压条目大小的字段,因此,getSize()方法的返回值是不可靠的。
public static final int BUFFER = 512;
public static final int TOOBIG = 0x6400000; // 100MB
// ...
public final void unzip(String filename) throws java.io.IOException
{
FileInputStream fis = new FileInputStream(filename);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
try{
while ((entry = zis.getNextEntry()) != null)
{
System.out.println("Extracting: " + entry);
int count;
byte data[] = new byte[BUFFER];
// Write the files to the disk, but only if the file is not insanely
if (entry.getSize() > TOOBIG)
{
throw new IllegalStateException("File to be unzipped is huge.");
}
if (entry.getSize() == -1)
{
throw new IllegalStateException("File to be unzipped might be huge.");
}
FileOutputStream fos = new FileOutputStream(entry.getName());
BufferedOutputStream dest = new BufferedOutputStream(fos,BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1)
{
dest.write(data, 0, count);
}
dest.flush();
dest.close();
zis.closeEntry();
}
}
finally{
zis.close();
}
}
审计:
搜索以下函数,看是否有使用到:
- FileInputStream
- ZipInputStream
- getSize()
- ZipEntry
如果出现 getSize 基本上就需要特别注意了。
修护:
- 防止解压至任何目录,使用
getCanonicalPath()
,过滤。
File f = new File(intendedDir, entryName);
String canonicalPath = f.getCanonicalPath();
File iD = new File(intendedDir);
String canonicalID = iD.getCanonicalPath();
if (canonicalPath.startsWith(canonicalID))
{
return canonicalPath;
}
else
{
...
}
- 防止过大
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
while (total + BUFFER <= TOOBIG && (count = zis.read(data, 0, BUFFER)) != -1)
文件上传篇
JDK<1.7.40的版本存在空字节问题。
文件读取篇
简单描述:
可读取用户输入的文件路径并回显在响应中。
审计:
快速发现这类漏洞的方式其实也是非常简单的,在IDEA中的项目中重点搜下如下文件读取的类。
-
JDK原始的java.io.FileInputStream类
-
JDK原始的java.io.RandomAccessFile类
-
Apache Commons IO提供的org.apache.commons.io.FileUtils类
-
JDK1.7新增的基于NIO非阻塞异步读取文件的
java.nio.channels.AsynchronousFileChannel
类 -
JDK1.7新增的基于NIO读取文件的
java.nio.file.Files
类常用方法如:
Files.readAllBytes
、Files.readAllLines
如果仍没有什么发现可以搜索一下FileUtil
,很有可能用户会封装文件操作的工具类。(参考)
URL重定向篇
简单描述:
接口从host头或者参数中取值,直接跳转到用户自定义的url,导致url重定向。
示例:
访问不存在的资源,将host改成自定义url,页面302跳转,跳转地址为host头中的自定义url。
@RequestMapping("/urltest")
@ResponseBody
public String urltest(HttpServletRequest request,HttpServletResponse response) throws ServletException,
IOException {
String site = request.getParameter("url");
if(!site.isEmpty()){
response.sendRedirect(site);
}
return response.toString();
}
审计:
查找sendRedirect
,跳转的url是否用户可控,如果可控是否有进行过滤判断。
特别是在删掉某个资源的斜杠,有可能就进行了302跳转,该处时常出现url重定向漏洞。
Autobinding篇
简单描述:
将HTTP请求参数绑定到程序代码变量或对象中。逻辑型漏洞。
-
@ModelAttribute注解
- 运用在参数上,会将
客户端传递过来的参数
按名称注入到指定对象
中,并且会将这个对象自动加入ModelMap
中,便于View层使用 - 运用在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中
@RequestMapping(value = "/home", method = RequestMethod.GET) public String home(@ModelAttribute User user, Model model) { if (showSecret){ model.addAttribute("firstSecret", firstSecret); } return "home"; }
前端jsp中可使用
${user.name}
访问对象user中的name成员。注意这时候这个User类一定要有没有参数的构造函数。 - 运用在参数上,会将
-
@SessionAttributes注解
- 将ModelMap 中的属性转存到 session 中
- 只要不去调用SessionStatus的
setComplete()
方法,这个对象就会一直保留在 Session 中
示例:
在/resetQuestion
接口,从客户端传入user的成员answer=hhd
,因为代码@ModelAttribute User user
,answer将会注入到user对象,并自动加入ModelMap中。
在/reset
接口,因为代码@SessionAttributes("user")
,将user对象从ModelMap中读出并放入session中,因此user中的answer=hhd
也加入了session中。那么只需要输入问题的答案为hhd
则与session中的hhd
匹配,因此可绕过。
@Controller
@SessionAttributes("user")
public class ResetPasswordController {
private UserService userService;
...
@RequestMapping(value = "/reset", method = RequestMethod.POST)
public String resetHandler(@RequestParam String username, Model model) {
User user = userService.findByName(username);
if (user == null) {
return "reset";
}
model.addAttribute("user", user);
return "redirect: resetQuestion";
}
@RequestMapping(value = "/resetQuestion", method = RequestMethod.GET)
public String resetViewQuestionHandler(@ModelAttribute User user) {
logger.info("Welcome resetQuestion ! " + user);
return "resetQuestion";
}
修护:
Spring MVC中可以使用@InitBinder注解,通过WebDataBinder的方法setAllowedFields、setDisallowedFields设置允许或不允许绑定的参数。
Webservice篇
Web Service
是一种基于SOAP协议
实现的跨语言Web服务调用。配置web.xml,配置server-config.wsdd
文件注册Web Service
服务类和方法。
访问Web Service
的FileService
服务加上?wsdl
参数可以看到FileService
提供的服务方法和具体的参数信息。
一般扫描目录时可扫出,后带?wsdl的是接口总的说明文档
此类漏洞可使用burpsuite的wsdl插件,直接进行解析生成不同接口的request,再发送到repeater
接口可能是查询,可能是添加等等操作
根据报错信息,可能存在注入点
- sql注入
- 信息泄漏
- 通过信息泄漏引起的组件
第三方组件安全篇
比如struts2、不安全的编辑控件、fastjson等等。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】