『注入攻击』Injection
基本概念
-
原理
注入攻击的本质是把用户输入的数据当作代码执行,它有三个关键条件:
-
用户能够控制输入
-
原本程序要执行的代码,拼接了用户输入的数据
-
变量不存在过滤或者过滤不严谨
-
-
靶场
- sqli-lab
SQL注入
当web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
在这一模块我们将介绍如何检测网站中是否存在SQL注入漏洞
盲注
-
概念
如果服务器开启了错误回显,可能会披露一些敏感信息以更正确的构造SQL注入语句。
-
盲注(blind injection):在没有开启错误回显的情况下来进行的注入攻击。
-
缺少了回显的调试信息,攻击者必须找到一个方法来验证注入的SQL是否成功执行
-
-
方法
1)最常见的盲注:构造简单的条件语句,根据返回页面是否发生变化,来判断SQL语句是否得到执行。
假设页面URL:
1
http://newspaper.com/items.php?id=2
执行的SQL为
1
select title,description,bod from items where id=2
假设黑客构造条件语句如下
1
http://newspaper.com/items.php?id=2 and 1=2
此时执行的SQL如下
1
select title,description,bod from items where id=2 and 1=2
因为
1=2
永远是一个假命题,此时Web应用不会返回结果给用户,黑客看到的页面结果将为空或者是一个出错页面。如果黑客继续构造请求
1
http://newspaper.com/items.php?id=2 and 1=1
如果页面返回正常了,那么说明SQL语句的"and"成功执行。
此时可以判断“id”参数存在SQL注入漏洞。
2)延时注入:攻击者通过引入延迟来推断数据库中的信息。
利用 mysql 数据库中的 benchmark() 函数,用于测试函数性能,将表达式expr执行count次。
1
benchmark(count,expr)
构造payload如下:
1
21170 UNION SELECT IF(SUBSTRING(CURRENT,1,1) =
CHAR(119),BENCHMARK(5000000,ENCODE('MSG','by 5 seconds')),null) FROM (Select Database() as current) as tbl;IF(condition, true_value, false_value)
:这是一个条件判断语句。如果条件为真,则执行true_value
,否则执行false_value
。SUBSTRING(CURRENT, 1, 1)
:SUBSTRING提取出CURRENT (从后面可以看出表示当前数据库名称)的第一个字符,判断它是不是CHAR(119),即字母wBENCHMARK(5000000, ENCODE('MSG', 'by 5 seconds'))
:如果条件为真的话,重复执行BENCHMARK
函数造成延时,如果没有的话就会返回null
这样可以根据页面回显的变化时间来判断数据库名称的第一个字符是不是 w,这样重复遍历可以将整个数据库名全部验证完成。
类似情况还可以获得有用信息的函数包含
- database()
- system_user():数据库系统用户
- current_user():登录数据库的当前用户
- last_insert_id()
数据库攻击技巧
找到SQL注入漏洞后,根据不同数据库,进行后续攻击的技巧也有所不同。
常见攻击技巧
下面一系列的操作比较繁琐,所以一般使用sqlmap进行自动化注入
-
猜解数据库版本:
因为知道了数据库的具体版本后,攻击者可以针对该版本已知的漏洞进行定向攻击。
MySQL版本如果是4,返回True
1
http://www.site.com/news.php?id=5 and substring(@@version,1,1)=4
-
判断表名是否存在
1
id=5 union all select 1,2,3 from admin
-
判断列名是否存在
1
id=5 union all select 1,2,passwd from admin
-
猜测username和password具体值
-
读写文件
如果当前数据库用户拥有读写系统相应文件或目录的权限
-
在mysql中,可以通过Load_file()读取系统文件,通过into dumpfile写入系统文件,最后通过 LOAD DATA INFILE 将文件导入创建的表中,最后就可以通过一般的注入技巧直接操作表数据了。
既然都可以读取系统文件了,为什么不直接读取或写入文件,而是导入表再操作呢:
- 导入表中可以结构化处理,更加灵活
- 直接操作可能被安全防护机制如IDS检测到
-
写入文件技巧,通常被用于直接在服务器上写入一个Webshell,为进一步攻击做铺垫。因此,设计数据库安全方案时,可以禁止普通数据库用户具备操作文件的权限。
-
命令执行
-
命令执行方式
- 通过导出webshell间接地执行命令
- 利用用户自定义函数,即UDF(User-Defined Function)来执行命令。
-
UDF
-
概念
流行的数据库一般都支持从本地文件系统中导入一个共享库文件作为自定义函数
UDF 命令执行是一种利用数据库自定义函数进行代码执行的攻击方法。攻击者可以通过SQL注入或文件上传漏洞等手段将恶意的自定义函数(通常是用C语言编写的共享库)上传到服务器的数据库中。
一旦自定义函数被成功注册,用户就可以通过SQL语句调用这些函数来执行任意操作,包括在操作系统级别执行命令。
-
具体例子
这一整体的流程也已经被集成在了sqlmap上面
-
1)编写恶意UDF
首先,攻击者需要编写一个恶意的共享库(通常使用C语言),该共享库实现自定义函数,这些函数可以执行操作系统命令。以下是一个简单的C语言UDF示例,展示了如何在Linux上执行系统命令
1
2
3
4
5
6
void sys_exec(char *cmd) {
system(cmd);
}编译此代码生成一个共享库,例如
libudf.so
:1
gcc -shared -o libudf.so -fPIC udf.c
-
2)将UDF加载到数据库中
攻击者需要将编译好的共享库文件上传到数据库服务器上,这通常可以通过SQL注入或文件上传漏洞实现。
-
3)在数据库中创建和使用UDF
接下来,攻击者需要在数据库中注册自定义函数。例如在MySQL中:
1
2
3
4
5-- 创建一个存放UDF库文件的目录(如果没有权限,可以尝试其他可写目录)
CREATE FUNCTION sys_exec RETURNS STRING SONAME 'libudf.so';
-- 调用自定义函数执行命令
SELECT sys_exec('id'); -- 在Linux系统上,这将执行'id'命令并返回结果 -
4)执行系统命令
一旦自定义函数被成功注册,攻击者可以通过SQL语句调用该函数来执行任意系统命令。
-
-
攻击存储过程
-
什么是数据库存储过程(Stored Procedure)
-
SQL语句需要先编译再执行。而存储过程是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
-
其实就是将sql查询语句封装成一个函数/对象的形式,可以直接调用该函数进行相应的查询。这样可以封装复杂的业务逻辑、提高性能、简化代码管理和提高安全性。
详细可见:数据库存储过程讲解与实例
-
-
利用存储过程进行攻击
在 MS SQL Server中,可以利用存储过程 xp_cmdshell 执行系统命令
1
2EXEC master.dbo.xp_cmdshell 'ping ';
EXEC master.dbo.xp_cmdshell 'cmd.exe dir c:';也可以利用 xp_regread 操作注册表
可以被利用的存储过程包括:
- xp_servicecontrol,允许用户启动、停止服务
- xp_availablemedia,显示机器上有用的驱动器
- xp_dirtree,允许获得一个目录树
- xp_enumdsn,例句服务器上的ODBC数据源
- xp_loginconfig,获取服务器安全信息
- xp_makecab,允许用户在服务器上创建一个压缩文件
- xp_ntsec_enumdomains,列举服务器可以进入的域
- xp_terminate_process,提供进程ID,终止该进程
-
存储过程本身也可能存在漏洞
有些自定义的存储过程也可能有注入漏洞,可能对外部传入的字段没有进行处理,造成SQL注入问题
编码问题
当Web应用、数据库和操作系统使用不相同的字符集和编码,由于各层对字符的理解存在差异,可能会导致不同编码解释从而产生一些安全漏洞。
具体例子:
-
如果Web应用使用PHP处理用户输入,并且使用
addslashes()
函数来转义特殊字符(如单引号'
等注入常用的闭合符号),则这些转义字符在存储到数据库之前会被加上反斜杠\
。 -
如果数据库使用GBK编码(双字节字符集),某些字节序列会被解释为一个字符。例如,
0xBF27
被解释为一个双字节字符。 -
攻击者可以输入
0xBF27 or 1=1
,经过addslashes()
处理后,变成0xBF\27 or 1=1
。在GBK编码中,0xBF5C
(\
的ASCII码是0x5C
)被解释为一个合法的双字节字符,从而吃掉了反斜杠,绕过了转义机制。
SQL Column Truncation(列截断)
-
基本概念
SQL Column Truncation(列截断)是一种利用数据库列长度限制来进行攻击的技术。攻击者可以通过提供特定长度的输入,使数据库在插入或更新数据时对输入进行截断,从而引发潜在的安全问题。以下是对这种攻击方式的详细解释和示例。
-
具体例子
当 MYSQL 的 sql-mode 设置为 default 时,即没有开启 STRICT_ALL_TABLES 选项时,MYSQL对于用户插入的超长值只会提示 warning 而不是 error(error即插入不成功),仍然会插入数据,如果插入了两个相同的数据就可能会产生鉴权方面的问题。
-
正常业务
假设有一个用户注册系统,数据库表定义如下:
1
2
3
4CREATE TABLE users (
username VARCHAR(20),
password VARCHAR(20)
);一个正常的注册请求可能是:
1
INSERT INTO users (username, password) VALUES ('alice', 'securepassword');
在这种情况下,数据库会将用户名
alice
和密码securepassword
存储在表中。 -
攻击方法
如果攻击者发现用户名字段的最大长度是20个字符,他们可以构造一个长度为20个字符的用户名,并在最后添加一个空格:
1
INSERT INTO users (username, password) VALUES ('admin ', 'anypassword');
数据库接收到上述请求后,由于
username
字段的长度限制是20个字符,数据库会截断用户名,存储为:1
'admin ' -- 实际存储的用户名
当攻击者尝试登录时,他们可以仅输入前缀匹配的用户名(例如,
admin
),数据库在处理查询时可能会忽略后面的空格,从而允许攻击者绕过验证。1
SELECT * FROM users WHERE username = 'admin' AND password = 'anypassword';
如果后续授权过程中,系统仅仅通过用户名来进行授权
1
SELECT * FROM users WHERE username = ?
我们注册的账号就直接拥有了管理员admin权限,产生了越权访问
-
SQL 注入防御
转义(escape)
escape的局限性:
仅仅对用户输入进行 escape(转义) 处理是不够的,escape采用的是黑名单机制,黑名单无法覆盖所有的过滤字符,用户输入的自然语言中也可能存在HAVING、ORDER BY等SQL保留字,盲目过滤可能导致误杀。
基于黑名单的过滤方法并不合适
预编译
-
基本概念
通过将SQL查询与参数分离来确保用户输入不会被当作SQL代码执行。预编译的核心思想是将SQL查询的结构固定下来,而将用户输入的数据作为参数处理,从而避免恶意输入影响SQL查询的结构。
- 一般来说,使用预编译语句是防御SQL注入的最佳方式,绑定变量保证了SQL语句的语义不会改变。
-
具体例子
-
PHP中使用预编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 创建数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');
// 准备SQL查询
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
// 绑定参数
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
// 设置参数值
$username = $_POST['username'];
$password = $_POST['password'];
// 执行查询
$stmt->execute();
// 获取查询结果
$result = $stmt->fetchAll(PDO::FETCH_ASSOC); -
Java中使用预编译
1
2String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
pstmt = conn.prepareStatement(sql);使用
?
表示变量
-
使用安全的存储过程
除了使用预编译语句外,我们还可以使用安全的存储过程对抗SQL注入。
使用存储过程的效果于使用预编译语句的效果类似,其区别就是存储过程需要先将SQL语句定义在数据库中。
但需要注意的是,
- 存储过程中也可能会存在注入问题,因此应该尽量避免在存储过程内使用动态的SQL语句。
- 如果无法避免,则应该使用严格的过滤或者是编码函数来处理用户的输入数据。
检查数据类型
对于输入的数据的数据类型进行检查,在很大程度上可以对抗SQL注入。
例如可以在代码中限制为 integer 类型,所以无法注入
但是如果需要用户提交的是一段字符串,单纯的数据类型检查就不够用了。
使用安全函数
一般来说,各种Web语言都实现了一些编码函数,可以帮助对抗SQL注入。
数据库自身
从数据库自身的就角度来说呢,应该使用最小权限原则,避免Web应用直接使用root,dbowner等高权限账户直接连接数据库。
如果有多个不同的应用在使用同一数据库,则也应该为每个应用分配不同的账户。
Web应用使用的数据库账户,不应该有创建自定义函数、操作本地文件的权限。
其他注入攻击
除了SQL注入以外,还有其他的注入攻击,这些攻击都是违背了“数据与代码分离”的原则。
XML注入
XML是一种标准通用标记语言,通过标签对数据进行结构化表示,其注入方法也与HTML比较类似,主要都是通过闭合标签或者其他符号来完成注入的。
防御方法也与HTML注入类似,对语言本身的保留字符进行转义即可
代码注入
代码注入和命令注入往往都是由一些不安全的函数或者方法引起的,其中的典型代表就是eval()和systrm()。
具体例子
-
PHP
下面这段php代码,从URL的查询参数中获取
arg
的值,并将其赋值给变量$x
,传递给 eval 函数执行1
2
3$myvar="varname";
$x=$_GET['arg'];
eval("\$myvar=$x;");假设用户访问的URL是
1
/index.php?arg=1;phpinfo()
服务器会执行
1
eval("\$myvar=1;phpinfo();");
输出php的配置信息
-
动态包含(Dynamic Include)
动态包含是一种在程序运行时动态地包含和执行代码文件的技术。在PHP、JSP等编程语言中,动态包含通常用于在运行时根据条件或配置文件来加载不同的代码文件。然而,这种技术也可能导致代码注入或远程文件包含漏洞,从而使得攻击者能够执行恶意代码。
PHP、JSP的动态include导致的代码执行,都可以算是一种代码注入。
例如在JSP中,可以使用
<jsp:include>
标签来动态包含文件,或者使用RequestDispatcher
类来实现。1
<jsp:include page="<%= request.getParameter("page") %>.jsp" />
CRLF注入
-
基本概念
CR是指回车符 Carriage Return,即
\r
。 LF是指换行符 Lined Feed,即\n
。CRLF常被用作不同语义之间的分隔符,因此通过“注入CRLF字符”就有可能改变原有的语义。- 所有使用CRLF作为分隔符的地方都可能存在这种注入。
-
具体例子
-
日志:登录失败用户名写入日志文件
正常情况下的记录如下
1
2Username login failed for: guest
Username login failed for: admin如果没有处理"\r\n",使用如下payload插入一条日志记录
1
guest\nUsername login succeed for: admin
结果日志变成。显然第二条记录是伪造的。
1
2Username login failed for: guest
Username login succeed for: admin -
http头部注入
在HTTP协议中,HTTP头是通过“\r\n”来分隔的。
因此如果服务器端没有过滤“\r\n”,而又把用户输入的数据放在HTTP头中,则有可能导致安全隐患。这种在HTTP头中的CRLF注入,又可以称为“Http Response Splitting”。这种注入最常见的情况就是把用户的输入拼接到http response的头部中了。
-
-
防御
对抗CRLF的方法非常简单。只需要处理好"\r"和"\n"这2个保留字,尤其是使用“换行符”作为分隔符的应用。