Page-1(Basic Challenges)

Less 1-4

Less-(1-4)是最常规的SQL查询,分别采用单引号闭合、无引号、括号单引号闭合、括号双引号闭合,没有过滤;可以采用and '1'='1的方式闭合引号,或注释掉引号来执行SQL语句。也可以使用报错注入。

Less 5-6

Less-5为盲注,即我们不能直接看到查询的返回结果,只能看到“查询成功”或“查询失败”,在sqli-labs中是以“You are in...”或无回显的形式来表明查询成功和失败,通常遇到的盲注形式是正常返回页面/错误显示页面。对于有回显的盲注,使用报错函数来注入也是一个思路,这就需要积累一些报错公式。

@@version MySQL的版本信息
@@datadir MySQL的安装路径

and extractvalue(1,concat(0x7e,(select @@version),0x7e))
and (updatexml(1,concat(0x7e, (select @@version),0x7e),1)) 
and (extractvalue(1,concat(0x7e,(select @@version),0x7e)))
and (select 1 from (select count(*),concat(floor(rand(0)*2),(select (select(select @@version)) from information_schema.tables limit 0,1))x from information_schema.tables group by x)a)

继续说盲注,我们无法直接看到查询结果,那么我们的思路便是传入一个判断语句,询问服务器某某字段的某个字母是否等于a?是否等于b?是否等于c?……以此来判断字段的名称,所以我们还需要截断字符串的函数,可以选择的有:left(a, b)从左侧截取a的前b位、substr(a,b,c)从b开始截取字符串a,长度为c、mid(a,b,c)从b位置 开始截取a字符串,长度为c。仅仅知道如何截取也是不够的,还需要知道如何查询字段的长度length(database())。可以是left(database(),1)>'s'的形式,也可以是ascii(left(database(),1))>115的形式。对于盲注,手工注入是很繁琐的,因此大多使用Python脚本来进行盲注。

payload:?id=1' and substr(database(),1)>'s' and '1'='1

YrK2Y6.png

上面的盲注思路是构造判断,观察页面的返回异同。SQL中有一个sleep()函数,可以让服务器“沉睡”一段时间,构造判断语句执行sleep,根据页面返回时间的长短来确定语句是否为真。

Less-6与Less-5的区别在于前者采用双引号闭合,后者采用单引号闭合。

Less 7

本关需要用到SQL语句中的select XX into outfile '路径'将查询结果导出为一个文件。一般用来写入一句话木马,再连接shell获取服务器的权限。

payload:?id=0')) union select 1,2,'shell' into outfile 'C:\phpStudy_new\WWW\1.php' --+

这里的shell,可以是一句话木马,也可以是要查询的内容,这样访问生成的文件就可以获得查询的结果了。

Less 8

布尔盲注,与Less-5的区别是不会显示SQL查询的错误信息。

payload:http://192.168.137.130/sqli-labs/Less-8/?id=1%27%20and%20substr(database(),1)%3E%27s%27%20and%20%271%27=%271

Less 9-10

这一关无论什么样的查询,什么页面返回都不会报错,起初很好奇这是如何做到的呢?看过源码后发现在判断查询成功失败的if语句中,if和else里面都是“You are in...”,因此不管是否查询成功,返回的都是成功。于是布尔盲注不能用了,可以考虑基于时间的盲注。下面的payload中正确的时候会直接返回,错误则会等待5秒。

Less-10将单引号闭合改为了双引号闭合。

payload:?id=1'and If(ascii(substr(database(),1,1))=115,1,sleep(5))--+

Less 11-12

开始进入POST类型的注入,这一关中的用户名和密码均存在注入点,单引号闭合的布尔注入。由于存在报错回显,也可以使用报错注入。

关于“万能密码”的原理,万能密码1' or '1'='1,用户名可以存在或者随意,查看此时的SQL查询语句SELECT username, password FROM users WHERE username='2' and password='2' or '1'='1' LIMIT 0,1,or连接的1=1永远为真,故该查询将直接返回users表中的第一条记录。

同时,采用--+的注释是因为在方式为GET的参数传递过程中,--+为注释符--的URL编码。在POST类型的注入中不能使用,应该使用#注释掉后续语句或采用闭合引号的方式。

Less-12与Less-11的区别在于闭合方式为")

Less 13-14

在Less-13中,即使登录成功也无法看到回显信息,意味着需要进行盲注,因为成功与失败返回的是图片是不一样的,因此布尔盲注可以行得通。存在数据库查询报错信息回显,因此也可以使用报错注入。

Less-14与13的区别在于Less-14使用双引号闭合。

Less 15-16

单引号闭合,没有报错回显,没有查询回显,因此报错注入不能使用,可以采用布尔盲注和延时注入。

Less-16与15的处理方法类似,闭合方式改为")

Less-17

这一关是一个修改密码的界面,跟前面的不太一样了。前面的关往往都是查询用户的相关信息,用的是SQL的查询语句select ... from ... ,查看源码,这里首先执行了查询语句@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";,如果查询成功,再执行更新表中密码的语句$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";

对传入的用户名参数经过了check_input()函数的处理。

function check_input($value)
	{
	if(!empty($value))
		{
		// truncation (see comments)
		$value = substr($value,0,15);
		}

		// Stripslashes if magic quotes enabled
		if (get_magic_quotes_gpc())
			{
			$value = stripslashes($value);
			}

		// Quote if not a number
		if (!ctype_digit($value))
			{
			$value = "'" . mysql_real_escape_string($value) . "'";
			}
		
	else
		{
		$value = intval($value);
		}
	return $value;
	}

check_input首先判断参数是否为空,若为空将其转换为整型,即若为空返回0。若非空,则只保留参数的前15位。然后检测magic_quotes_gpc是否开启,若开启,则返回去除转义符的字符串。接着进行一个纯数字检测,若果不是纯数字,则将SQL语句中使用的特殊字符转义并在两端添加一个单引号。

magic_quotes_gpc若开启,则会将GET、POST、COOKIE传递来的特殊字符添加转义符“”。check_input()中,有一步检测该参数是否开启,若开启,将转义符去掉,起初很是疑惑为什么要将用来转义的去掉,后面看到了mysql_real_escape_string()才明白,该函数针对在SQL语句中将引起歧义的特殊字符进行过滤,“”的存在反而会引起歧义。

虽然对用户名进行了过滤,但是密码并没有做任何处理。新密码以单引号闭合,报错信息有回显,因此可以采用报错注入,但是要注意需要输入正确的用户名才可以进行注入。

除了报错注入,堆叠注入理论上也是行得通的,不过在实际测试中没有成功,将更新的SQL语句显示出来后,在命令行里执行该SQL语句却可以将shell导出。堆叠注入,即是利用分号将SQL语句分隔开,从而执行不同的SQL语句。

tpcTo9.png

Less 18-19

18关主要告诉我们http的请求头也可以存在注入点,抓包后修改user-agent,与前面的用户名、密码的注入并无二致。19关注入点在referer上,类同。

Less 20-22

在20关这里,开启了一个新的环境。这里首先需要登录,在登录时用户名和密码都经过了check_input()的处理,登录后获取cookie,若cookie未删除且未过期时,服务器会首先检查cookie中的uname是否设置,然后执行查询,于是cookie中的uname字段便可以进行注入。

$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
$result=mysql_query($sql);

另外cookie中的uname以明文显示且与用户名相同,刷新页面修改uname字段的值为其他用户可导致水平越权。

在21关中,将uname进行了base64加密,处理方式类同。
22关在21关的基础上修改了uname参数的闭合方式,在21关中以')闭合,22关中以双引号闭合。

tpXqf0.png

Page-2(Advanced Injections)

Less 23

这是一个有过滤的报错注入,将--和#进行了过滤,因此无法使用注释来注释掉用来闭合的单引号,可以使用and '1'='1来闭合。

前面提到报错注入,只是把一些常见的报错公式积累了,后来学习了一下报错的原理,补充一下。首先是应用在mysql数据库报错注入的count()、rand与order by一起使用的问题。count()用来统计每一行记录相同的的个数,rand()生成随机数,order by意味按某字段排序当执行select count(*) form Table group by key时,mysql会建立一个虚拟表,其中key为主键。取出数据库中的一条数据,然后查看虚拟表中是否存在该条记录,若不存在,则插入新纪录,若存在,则使其count(*)字段+1。mysql官方给过提示,当查询时使用rand()时,rand()可能会被计算多次。例如在查询中使用到了rand和order by时,查询数据时rand会被执行一次,若虚表不存在记录,插入虚表时会被再执行一次。因此当key重复时会引起主键重复将会产生报错,当报错语句与查询的语句在一个聚合函数中时,查询的一部分就会以错误的形式显现出来。extractvalue()、updatexml()、第二个参数要求为xpath格式的字符串,当我们输入的格式不是时将产生报错。

Less 24

该关用到了二次注入,所谓二次注入,即当我们第一次尝试注入时,我们的可能引起注入的语句被后端代码进行了转义因此未能注入成功,但却原封不动地插入到了数据库中,当该段有害的代码再次被调用时产生注入,因此被称为二次注入。在本关中,我们首先注册一个名为admin' #的用户,登录成功后使用其更改密码功能更改密码,此时执行的数据库语句是这样的:update users set password='a' where username='admin' #',因此实际上我们将一个原本正常的用户admin的密码给更改为了a。

Less 25

该关标题为“你所有的and&or都属于我们”,将and和or进行过滤,由于其使用的是preg_replace函数进行替换,因此使用双写即可绕过,即把使用到and的地方改为aandnd、使用到or的地方改为oorr。25a中没有使用引号闭合,除此之外没有什么区别。很疑惑,25a的标题中明明写着blind based,基于盲注,但实测是有回显的。

Less 26

本关中过不仅过滤了and和or,还将空格和用来注释的:--、#、进行了过滤。对于and和or使用双写进行绕过;无法注释就使用23关中的技巧使用and '1'='1的方式闭合引号;空格被过滤就使用一些特殊符号如tab换行等,因为get型要使用URL进行传参,故特殊符号要进行URL编码。

符号 说明
%09 TAB
%0a 新建一行
%0c 新的一页
%0d return功能
%a0 空格

在实际操作中,使用这些特殊符号替换空格后,会出现乱码,因此尝试注入并不成功,好在26关存在错误回显,因此报错注入行得通。网上有文章说是window的Apache解析问题切换到Linux环境下可以,但是在我搭了一个Linux环境后跟发现Windows下情况一样,也失败了。最终将PHP版本切换到5.2后,即便是window server 2003服务器下,也可以使用如上特殊符号绕过对空格的过滤,由此可见并非是所谓Windows下Apache解析问题。PHP5.4版本的则无法正常绕过。在解决这个

由于26a关闭了报错回显,因此在解决环境问题之前一直无从下手,现在PHP5.2下可以使用特殊符号绕过空格后,就跟前面的方法无异了。

Less 27-28

27关对union和select也进行了过滤,一般可以通过大写小混合或双写进行尝试绕过,本关可以使用大小写混合进行绕过。不过我百思不得其解的是前面使用双写绕过对and和or的过滤都可行,在这里对select进行双写依旧会被过滤掉,而union却可以绕过

最终我在源码中找到了答案,如下图所示,源码中对小写的select进行了两次过滤,因此使用“三写”即可绕过。


27a为盲注,无引号闭合,绕过方法相同。

28与27方法相同,区别在于28关使用单引号加括弧闭合。28a为28基础上的盲注。

Less 29-31

29关标题表示本关配有waf,真正含有waf的界面是在login.php,若直接在index.php下则与第一关无异。直接对login.php的源码进行代码审计:

if(isset($_GET['id']))
{
	$qs = $_SERVER['QUERY_STRING'];
	$hint=$qs;
	$id1=java_implimentation($qs);
	$id=$_GET['id'];
	//echo $id1;
	whitelist($id1);
...

服务器以get方式获取到参数字符串后,传递给java_implimentation()函数,后者对参数字符串进行某种处理后赋值给id1,再由whitelist函数对id1进行处理,下面是java_implimentation()和whitelist()两个函数的情况:

//WAF implimentation with a whitelist approach..... only allows input to be Numeric.
function whitelist($input)
{
	$match = preg_match("/^d+$/", $input);
	if($match)
	{
		//echo "you are good";
		//return $match;
	}
	else
	{	
		header('Location: hacked.php');
		//echo "you are bad";
	}
}



// The function below immitates the behavior of parameters when subject to HPP (HTTP Parameter Pollution).
function java_implimentation($query_string)
{
	$q_s = $query_string;
	$qs_array= explode("&",$q_s);


	foreach($qs_array as $key => $value)
	{
		$val=substr($value,0,2);
		if($val=="id")
		{
			$id_value=substr($value,3,30); 
			return $id_value;
			echo "<br>";
			break;
		}

	}

}

java_implimentation()函数首先以&符号将参数字符串进行分割,得到参数组成的数组,而后对该数组进行遍历,判断各个参数前两个字母是否为“id”,若是,则从下标为3的字符起截取30个字符作为返回值直接返回。例如:id=123,i下标为0,d下标为1,=下标为2,以此类推。该返回值将被交到whitelist函数手中,可以看到whitelist对id的值是相当严格的,对该值进行正则匹配,若不为纯数字,则直接重定向到hacked.php页面。

由于其在第一次匹配到头两个字母是id后便直接返回,而传递给数据库进行查询的id则是通过$id=$_GET['id]方式进行获取,这里它默认了截取到的id与数据库查询的id为同一个。漏洞还是比较明显的,例如我们可以构造:idd1&id=<payload>如下图:

30关将单引号闭合改为了双引号。

31关为盲注,闭合方式改为("")

Less 32-33

32关下面的hint告诉了我们输入的查询语句的十六进制,容易想到宽字节注入。当我们输入单引号时,发现没有引起报错,原因是我们输入的单引号反斜线所转义,不会闭合sql语句中的单引号,其对应的十六进制为5c27,所谓宽字节注入,即gbk中以两个字符表示一个汉字,因此我们可以输入某个字符来将反斜线“吃掉”,使得其的转义失效,从而是我们输入的单引号逃逸(或其他可能被转义的字符)。如下图所示:

查看源码发现这里添加反斜线的方式是通过自定义的函数进行正则匹配替换,PHP中的addslashes()函数可以对包括单引号、双引号、反斜线和NUL字符前添加反斜线进行转义。当magic_quotes_gpc为on的时候会自动对GET、POST、COOKIE数据执行addslashes(),前面就曾遇到有一关中检查magic_quotes_gpc是否为on,若是则执行去除反斜线,因为后面有对执行SQL查询的的语句进行特殊字符过滤,“”的存在反而会引起歧义。

33关就使用了addslashes()函数,同样的payload可以直接使用。

Less 34-36

34关同样是宽字节注入,但是使用的是POST方式,URL编码的%df将失效,可以通过burp进行抓包,直接修改Hex。如下图:

35关标题提示了数字型,直接报错注入可解。

36关同样是宽字节注入,貌似没啥区别,同样的payload可解,另外在注释掉引号的过程中,#不可行,换成其URL编码%23就可行了,有点梦幻,--+可行。看了源码才知道,这次使用的是mysql_real_escape_string()函数,功能与addslashes()类似,不过该函数自PHP5.5.0起废弃,从PHP7.0.0起移除。

37关为36关的POST型兄弟,同理。

Less 38

38关目录中写了一个“Future Editions”,未来版本,很好奇,点开发现原来是堆叠注入。SQL中将分号作为一条SQL语句的结束,堆叠注入正是分号结束一条查询语句后再构造另一条SQL语句例如对数据库的增删改查,从而执行目标SQL语句。

前前后后花了一周的时间把之前写的重新复现、完善;SQL注入时很基础的攻击手段,危害也很大,这篇文章很简单,就像摘要里说的,主要记录一些思路和技巧,以后有机会还会继续复现sqli-labs的后面关卡。

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/iamblackcat/p/15905733.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!