其它类型的软件漏洞
[TOC]
格式化漏洞
printf 中的缺陷
printf 的参数:格式控制符和待输出的数据列表
#include "stdio.h"
main()
{
int a=44,b=77;
printf("a=%d,b=%d\n",a,b);
printf("a=%d,b=%d\n");
}
对于上述的代码,第二个 printf 缺少了待输出的数据列表,它运行后会输出什么呢?
推荐使用的环境 | 备 注 | |
---|---|---|
操作系统 | Windows XP SP3 | 其他 Win32 操作系统也可进行本实验 |
编译器 | Visual C++ 6.0 | |
编译选项 | 默认编译选项 | |
build 版本 | release 版本 | debug 版本的实验过程将和本实验指导有所差异 |
说明: 推荐使用 VC 加载程序,在程序关闭前能自动暂停程序以观察输出结果。
输出结果:
第二次 printf 没有引起异常,程序正常执行,只是输出数据有些出乎意料。
用OD调试一下,发现在第一次调用 printf 时,参数按照从左到右的顺序入栈,栈中的状态如下图:
第二次调用 printf 时,由于缺少待输出参数列表,所以只压入格式控制符参数。输出时,系统把格式控制符后面的两个 DWORD 当成了待输出参数列表,自动把十六进制转换成 %d 的有符号整型。
用 printf 读取内存数据
如果 printf 函数参数中的 ”格式控制符“ 可以被外界输入影响,那就是格式化串漏洞了。
#include "stdio.h"
int main(int argc, char ** argv)
{
printf(argv[1]);
}
实验环境:
推荐使用的环境 | 备 注 | |
---|---|---|
操作系统 | Windows XP SP3 | 其他 Win32 操作系统也可进行本实验 |
编译器 | Visual C++ 6.0 | |
编译选项 | 默认编译选项 | |
build 版本 | release 版本 | debug 版本的实验过程将和本实验指导有所差异 |
说明:请使用命令行传入方式加载,并传入适当的参数配合。
打开 cmd
进入程序目录,输入:name.exe %p %p…..
通过传入格式控制符,printf 就会打印出栈中的数据。
运行结果:
用 printf 向内存写数据
格式控制符 %n
能把当前输出的所有数据的长度写回到一个变量中去。
#include "stdio.h"
int main(int argc, char ** argv)
{
int len_print=0;
printf("before write: length=%d\n",len_print);
printf("failwest:%d%n\n",len_print,&len_print);
printf("after write: length=%d\n",len_print);
}
字符串 “failwest:0” 长度为10,所以第二个 printf 中的 len_print 将会被赋值为 10。 实验环境:
推荐使用的环境 | 备 注 | |
---|---|---|
操作系统 | Windows XP SP3 | 其他 Win32 操作系统也可进行本实验 |
编译器 | Visual C++ 6.0 | |
编译选项 | 默认编译选项 | |
build 版本 | release 版本 | debug 版本的实验过程将和本实验指导有所差异 |
说明:推荐使用 VC 加载程序,再程序关闭前能自动暂停程序以观察输出结果。
结果如下图:
格式化串漏洞的检测与防范
当输入输出函数的格式化控制符能够被外界影响时,攻击者可以综合利用前面介绍的读内存和写内存的方法修改函数返回地址,劫持进程,从而使 shellcode 得到执行。
格式化串漏洞的起因非常简单,只要检测相关的函数的参数配置是否恰当就行。通常能引起格式化串漏洞的函数包括:
int printf( const char* format [, argument]... );
int wprintf( const wchar_t* format [, argument]... );
int fprintf( FILE* stream, const char* format [, argument ]...);
int fwprintf( FILE* stream, const wchar_t* format [, argument ]...);
int sprintf( char *buffer, const char *format [, argument] ... );
int swprintf( wchar_t *buffer, const wchar_t *format [, argument] ... );
int vprintf( const char *format, va_list argptr );
int vwprintf( const wchar_t *format, va_list argptr );
int vfprintf( FILE *stream, const char *format, va_list argptr );
int vfwprintf( FILE *stream, const wchar_t *format, va_list argptr );
int vsprintf( char *buffer, const char *format, va_list argptr );
int vswprintf( wchar_t *buffer, const wchar_t *format, va_list argptr );
通过简单的静态代码扫描,一般可以比较容易地发现这类漏洞。此外, VS2005 中在编译级别对参数做了更好的检查,而且默认情况下关闭了对 “%n” 控制符的使用。
SQL注入攻击(脚本漏洞)
SQL 注入原理
SQL 命令注入的漏洞是 Web 系统特有的一类漏洞,它源于 PHP 、ASP等脚本语言对用户输入数据和解析时的缺陷。
SQL 命令注入的精髓是构造巧妙的注入命令串,从服务器不同的反馈结果中,逐步分析出数据库中各个表项之间的关系,直到攻破数据库。
以 PHP 语言为例,如果用户的输入能够影响到脚本中 SQL 命令串的生成,那么很可能在添加了单引号、 #号等转义命令字符后,能够改变数据库最终执行的 SQL 命令。
攻击 PHP + MySQL 网站
首先介绍一下 PHP 配置文件 php.ini 中与注入攻击相关的重要选项,如下表
选 项 | 安全配置 | 说 明 |
---|---|---|
safe_mode | on | 安全模式 |
display_errors | off | 是否向客户端返回错误信息。错误信息能够帮助攻击者摸 清数据库的表结构和变量类型等重要信息 |
magic_quotes_gpc | on | 自动将提交变量中的单引号、双引号、反斜线等特殊符号 替换为转义字符的形式。例如, ’ 将被转换为 \’ |
在 display_errors 关闭的情况下,攻击者可以利用盲注的方法通过服务器的不同反馈进行分析,获得表结构和列名等信息;
在 magic_quotes_gpc 打开的情况下,攻击者仍然可以通过 MySQL 提供的 char()和 ascii()等函数引用敏感字符。
联合查询(MySQL 4.x及以上的版本)
利用联合查询往往可以直接把得到的数据返回到某个变量中,从而在网页中显示出来。
failwest' union select 1#
failwest' union select 1,2#
failwest' union select 1,2,3#
failwest' union select 1,2,3,4,…#
按照这种方式试探脚本中共有几个变量接收数据。当返回的页面不再出错时,证明变量的数量正好,观察页面中显示出来的数字,可以确定出能够用于显示结果的变量位置。
攻击者常用的注入命令串,如下表。这些攻击串可以跟在 URL 后边,其中 ”failwest“ 代表提交的变量值。
SQL 注入攻击测试用例 | 说 明 |
---|---|
failwest failwest’ and 1=1# failwest’ and 1=2# | 判断注入点。第一次是正常请求,如果存在注入漏洞,那 么第二次请求得到结果应该与第一次一样,并且第三次请求得到的结果应该与前两次不同 |
failwest’ or 1=1# | 返回所有数据,常用于有搜索功能的页面 |
failwest’ union select version()# | 返回数据库版本信息 |
failwest’ union select database()# | 返回当前的库名 |
failwest’ union select user()# | 返回数据库的用户名信息 |
failwest’ union select session_user()# | 返回数据库的用户名信息 |
failwest’ union select system_user()# | 返回数据库的用户名信息 |
failwest’ union select load_file(’/etc/passwd’)# | 读取系统文件 |
failwest’ select user,password from mysql.user# | 返回数据库用户的密码信息,密码一般以 MD5 的方式存放 |
盲注入(MySQL 3.x 版本)
对于 MySQL 3.x 版本,不支持联合查询语言,无法插入整句的检索语言,因此通常采用盲注入:通过服务器对请求的反馈不同,一个字节一个字节地获得数据。
盲注入需要用到以下几个 MySQL 的函数:
mid( string , offset , len )
这个 API 用于取出字符串中的一部分。第一个参数是所要操作的字符串,第二个参数指明要截取字符串的偏移位置,第三个参数代表字符串的长度。
当攻击者想获得 etc/hosts 文件的内容时,将先从这个文件的第一个字节开始尝试注入:
failwest'and ascii(mid((load_file('/etc/hosts'),1,1))=1#
failwest'and ascii(mid((load_file('/etc/hosts'),1,1))=2#
failwest'and ascii(mid((load_file('/etc/hosts'),1,1))=3#
……
当尝试的 ASCII 码与/etc/hosts 文件第一个字符的 ASCII 一样的时候, 服务器将返回正常的页面,其余的尝试都将获得错误的页面。由于反馈的不同,最多进行 255 次尝试就能得到/etc/hosts 文件的第一个字节的值。
在获得了第一个字节之后,可以通过
failwest'and ascii(mid((load_file('/etc/hosts'),2,1))=1#
failwest'and ascii(mid((load_file('/etc/hosts'),2,1))=2#
failwest'and ascii(mid((load_file('/etc/hosts'),2,1))=3#
……
获得第二个字节、第三个字节……的内容。
这里应该使用折半查找法。
最后,当 php.ini 中的 magic_quotes_gpc 配置选项被打开时,我们不能在攻击串中使用单引号,因为这对字符变量的攻击将不再可行,但如果是数字型变量,则仍然能够实现注入攻击。
对应于上例, load_file(’/etc/hosts’)调用中的单引号和斜杠可以用 MySQL 提供的另一个函数 char() 进行转换,如下表。
char | / e | t | c | / | H | o | s | t | s |
---|---|---|---|---|---|---|---|---|---|
ASCII | 47 101 | 116 | 99 | 47 | 104 | 111 | 115 | 116 | 115 |
failwest'and ascii(mid((load_file('etc/host'),1,1))=1#
可以转换为
failwest’ and ascii(mid((load_file(char(47,101,116,99,47,104,111,115,116,115),1,1))=1#
注入攻击的检测与防范
针对 SQL 注入漏洞的传统防范方式:对用户输入的数据进行限制,过滤掉可能引起攻击的敏感字符。
注意:数据库对大小写不敏感。使用正则表达式,同时过滤掉 select、SELECT、Select、sElect 等所有形式的保留字。
SQL 注入产生的根源:查询语句采用了拼接字符串的形式。
string sql = “select * from users where user=’” + username + “’ and psw=’” + password + “’”;
//username 和 password 两个变量的值是由用户输入的
**有效防范方式:**使用参数化查询的方法。
参数化查询就是在访问数据库时,将查询语句中要填入的数据通过参数的方式传递,这样数据库不会将参数的内容视为 SQL 语句的一部分,因此即便参数中含有攻击者构造的查询指令,也不会被执行。 典型的参数化查询语句:
string sql = "select * from users where username=? and password=?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1,username);
pstmt.setString(2,password);//username 和 password 两个变量的值是由用户输入的
防治和检测 SQL 注入漏洞的主要方法:
- 实时的入侵检测。处理问题的位置位于脚本程序和数据库之间。很有效,但是会对 Web服务器 带来额外的负担
- 代码分析。如使用数据流分析( Data FlowAnalysis)、类型验证系统( Type S ystem)、模型检测系统( Model C hecking)等查找程序高级逻辑错误的方法来对脚本代码进行漏洞挖掘。
其它注入方式
Cookie 注入,绕过马奇诺防线
程序员们把常用的过滤、编码函数组织成库,最终制作成通用的防注入过滤库。通过防注入库来过滤用户输入的敏感字。然而,防注入系统忽略了用户除了用 Get 和 Post 提交数据外,还能用 Cookie 提交数据。
在动态服务页面(ASP)中,程序员常常使用下面两种语句来获取用户提交的数据:
ID = Request.QueryString(“id”) //获取用户通过 GET 方式提交的 id 数据
ID = Request.Form(“id”) //获取用户通过 POST 方式提交的 id 数据
但为了同时支持 GET 和 POST 方式,常常使用下面这条语句:
ID = Request(“id”)
这条语句会先读取 GET 中的数据,如果没有再读取 POST 中的数据,如果还没有则会去读取 Cookie 中的数据。很多防注入系统会检测 GET 和 POST 数据中是否存在敏感字符,却忽略了对 Cookie 数据的检测。这样,攻击者就可以利用 Cookie 提交精心构造的注入命令串来进行 SQL 注入。
如何检测一个站点是否存在 Cookie 注入漏洞呢?
http://www.testsite.com/news.asp?id=169
我们先输入以下地址来测试该站点是否存在 SQL 注入:
http://www.testsite.com/news.asp?id=169 and 1=1
如果浏览器跳出请不要在参数中包含非法字符尝试注入
对话框或者其它类似提示,则说明该站点使用了敏感字过滤的防注入手段。
如果我们在浏览器中只输入:
http://www.testsite.com/news.asp?id=16
—— 未完待续 ——-