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.javaindex_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"

全局过滤器篇

审计时,得先看是否含有全局过滤器。切勿看到ServletJSP中的漏洞点就妄下定论,Servlet前面很有可能存在一个全局安全过滤的Filter。当然每种框架的写法也有差别。个人认为Filter主要是用在

  1. 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>
    
  2. jar包

    首先添加一个 jar :commons-lang-2.5.jar 然后在后台调用这些函数:
    StringEscapeUtils.escapeHtml(string); 
    StringEscapeUtils.escapeJavaScript(string); 
    StringEscapeUtils.escapeSql(string);
    
  3. 转义

    String string = HtmlUtils.htmlEscape(userinput); //转义
    String s2 = HtmlUtils.htmlUnescape(string); //转成原来的
    

常见漏洞篇

分为业务安全问题、代码实现服务架构安全问题

代码实现,查看对应代码与全局过滤器:

  1. 任意文件读写(文件上传、文件下载)、文件遍历文件删除文件重命名等漏洞
  2. SQL注入漏洞
  3. XXE(XML实体注入攻击)
  4. 表达式执行(SpEL、OGNL、MVEL2、EL等)
  5. 系统命令执行漏洞(ProcessBuilder)
  6. 反序列化攻击(ObjectInputStream、JSON、XML等)
  7. Java反射攻击
  8. SSRF攻击
  9. XSS

业务安全,主要理解该系统的逻辑:

  1. 用户登陆、用户注册、找回密码等功能中密码信息未采用加密算法。
  2. 用户登陆、用户注册、找回密码等功能中未采用验证码验证码未做安全刷新(未刷新Session中验证码的值)导致的撞库、密码爆破漏洞。
  3. 找回密码逻辑问题(如:可直接跳过验证逻辑直接发包修改)。
  4. 手机、邮箱验证、找回密码等涉及到动态验证码未限制验证码失败次数验证码有效期验证码长度过短导致的验证码爆破问题。
  5. 充值、付款等功能调用了第三方支付系统未正确校验接口(与第三方的交互、与客户的交互,主要查看逻辑问题)。
  6. 后端采用了ORM框架更新操作时因处理不当导致可以更新用户表任意字段(如:用户注册、用户个人资料修改时可以直接创建管理员账号或其他越权修改操作)。
  7. 后端采用了ORM框架查询数据时因处理不当导致可以接收任何参数导致的越权查询、敏感信息查询等安全问题。
  8. 用户中心转账、修改个人资料、密码、退出登陆等功能未采用验证码或Token机制导致存在CSRF漏洞
  9. 后端服务过于信任前端,重要的参数和业务逻辑只做了前端验证(如:文件上传功能的文件类型只在JS中验证、后端不从Session中获取用户ID、用户名而是直接接收客户端请求的参数导致的越权问题)。
  10. 用户身份信息认证逻辑问题(如:后台系统自动登陆时直接读取Cookie中的用户名、用户权限不做验证)。
  11. 重要接口采用ID自增、ID可预测并且云端未验证参数有效性导致的越权访问、信息泄漏问题(如:任意用户订单越权访问)。
  12. 条件竞争问题,某些关键业务(如:用户转账)不支持并发、分布式部署时不支持锁的操作等。
  13. 重要接口未限制请求频率,导致短信、邮件、电话、私信等信息轰炸。
  14. 敏感信息未保护,如Cookie中直接存储用户密码等重要信息,跟踪cookie中的变量最终到了哪。
  15. 弱加密算法、弱密钥,如勿把Base64当成数据加密方式、重要算法密钥采用弱口令如123456
  16. 后端无异常处理机制、未自定义50X错误页面,服务器异常导致敏感信息泄漏(如:数据库信息、网站绝对路径等)。
  17. 使用DWR框架开发时前后端不分漏洞(如:DWR直接调用数据库信息把用户登陆逻辑直接放到了前端来做)。

SQL注入篇

  1. 直接拼接,未进行过滤

    request.getParameter("")直接放在SQL语句。

    全局搜索查看:String sql等。

  2. 预编译使用有误

    • 在使用占位符后未进行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();

  1. %和_

    没有手动过滤%。预编译是不能处理这个符号的, 所以需要手动过滤,否则会造成慢查询,造成 dos。

  2. Order by、from等无法预编译

    如以下示例,需要手动过滤,否则存在sql注入。

    String sql = "Select * from news where title =?" + "order by '" + time + "' asc"

  3. 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}。修复是用户自行过滤。
  4. :=和和此处的${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

审计:

  1. 判断使用哪种XML解析器
  2. 搜索是否有禁用外部实体配置(修护部分有具体代码)
  3. 是否有外部输入点进行解析

修护:

  1. 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);
  1. 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));
  1. 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
  1. 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);
  1. xmlInputFactory
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory

xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
  1. 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);
  1. 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())) );
  1. transformerFactory
TransformerFactory tf = TransformerFactory.newInstance(); 
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
  1. 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, "");
  1. 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 文件

修护:

  1. 白名单。只能包含字母、数字、下划线
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();
}
  1. 使用 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 端口。

修护:

修护方案参考链接

  1. 白名单。只允许某些类被反序列化。

    以下例子通过重写ObjectInputSream中的resolveClass方法,读取需要反序列化的类名与SerialObject.class对比,判断是否合法。SerialKiller就是利用这种原理而写的jar包。

  2. /**只允许反序列化 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();
    
  3. 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
        }
      }
     }
   }
}

审计:

对于已经被确定为敏感的数据搜索示例一中相关的关键字。

或者查看进行序列化的类,是否含有敏感数据。

修护:

  1. 将敏感数据加上transient
private transient double x; // transient field will not be serialized
private transient double y; // transient field will not be serialized
  1. 将能序列化的加入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文件提取篇

简单描述:

两个危害:一个是提取出的文件标准路径落在解压的目标目录之外,另一个是提取出的文件消耗过多的系统资源。

示例:

  1. 解压后的文件名未作过滤(直接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();
}
  1. 解压后的文件名未作过滤;

    使用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 基本上就需要特别注意了。

修护:

  1. 防止解压至任何目录,使用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
{
  ...
}
  1. 防止过大
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
while (total + BUFFER <= TOOBIG && (count = zis.read(data, 0, BUFFER)) != -1)

文件上传篇

JDK<1.7.40的版本存在空字节问题。

文件读取篇

简单描述:

可读取用户输入的文件路径并回显在响应中。

审计:

快速发现这类漏洞的方式其实也是非常简单的,在IDEA中的项目中重点搜下如下文件读取的类。

  1. JDK原始的java.io.FileInputStream类

  2. JDK原始的java.io.RandomAccessFile类

  3. Apache Commons IO提供的org.apache.commons.io.FileUtils类

  4. JDK1.7新增的基于NIO非阻塞异步读取文件的java.nio.channels.AsynchronousFileChannel

  5. JDK1.7新增的基于NIO读取文件的java.nio.file.Files

    常用方法如:Files.readAllBytesFiles.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请求参数绑定到程序代码变量或对象中。逻辑型漏洞。

  1. @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类一定要有没有参数的构造函数。

  2. @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 ServiceFileService服务加上?wsdl参数可以看到FileService提供的服务方法和具体的参数信息。

一般扫描目录时可扫出,后带?wsdl的是接口总的说明文档

此类漏洞可使用burpsuite的wsdl插件,直接进行解析生成不同接口的request,再发送到repeater

接口可能是查询,可能是添加等等操作

根据报错信息,可能存在注入点

  • sql注入
  • 信息泄漏
  • 通过信息泄漏引起的组件

第三方组件安全篇

比如struts2、不安全的编辑控件、fastjson等等。

posted @ 2020-07-16 18:04  Luminous~  阅读(3288)  评论(0编辑  收藏  举报