除了基本的棧溢出利用,還有堆溢出、off by one、虛函數、格式化串等漏洞利用技術,下面進行簡單介紹。
1.off by one的利用
只溢出一個字節,,配合上特定的溢出場景, off by one 就有可能演化為安全漏洞。
void off_by_one(char * input)
{
char buf[200];
int i=0,len=0;
len=sizeof(buf);
for(i=0; input[i]&&(i<=len); i++)
{
buf[i]=input[i];
}
}
“ i<=len”正確的使用應該是“ i<len”,給了一個字節的攻擊機會。當緩沖區后面緊跟著 EBP 和返回地址時,溢出數組的那一個字節正好“部分”地破壞了EBP。
當能夠讓 EBP 恰好植入可控制的緩沖區時,是有可能做到劫持進程的。此外, off by one問題有可能破壞重要的鄰接變量,從而導致程序流程改變或者整數溢出等更深層次的問題。
2.攻擊C++虛函數
C++虛函數的入口地址被統一保存在虛表( Vtable)中,虛表指針保存在對象的內存空間中,緊接著虛表指針的是其他成員變量。
#include "windows.h"
#include "iostream.h"
char shellcode[]="xxx";////set fake virtual function pointer
class Hacktest
{
public:
char buf[200];
virtual void test(void)
{
cout<<"Class Vtable::test()"<<endl;
}
};
Hacktest overflow, *p;
void main(void)
{
char * p_vtable;
p_vtable=overflow.buf-4;//point to virtual table
//reset fake virtual table to 0x004088cc
//the address may need to ajusted via runtime debug
p_vtable[0]=0xCC;
p_vtable[1]=0x88;
p_vtable[2]=0x40;
p_vtable[3]=0x00;
strcpy(overflow.buf,shellcode);//set fake virtual function pointer
p=&overflow;
p->test();
}
( 1)虛表指針位于成員變量 char buf[200]之前,程序中通過 p_vtable=overflow.buf-4 定位到這個指針。
( 2)修改虛表指針指向緩沖區的 0x004088CC 處。
( 3)程序執行到 p->test()時,將按照偽造的虛函數指針去 0x004088CC 尋找虛表,這里正好是緩沖區里 shellcode 的末尾。在這里填上 shellcode 的起始位置 0x0040881C 作為偽造的虛函數入口地址,程序將最終跳去執行 shellcode,如下圖所示。
由于虛表指針位于成員變量之前,溢出只能向后覆蓋數據,所以有一定局限性。對象的內存空間位于堆中,如果內存中存在多個對象且能夠溢出到下一個對象空間中去,“連續性覆蓋”還是有攻擊的機會的,如下圖所示。
3.Heap Spray:堆噴射
在針對瀏覽器的攻擊中,常常會結合使用堆和棧協同利用漏洞。
(1)當瀏覽器或其使用的 ActiveX 控件中存在溢出漏洞時,攻擊者就可以生成一個特殊的 HTML文件來觸發這個漏洞。
( 2)不管是堆溢出還是棧溢出,漏洞觸發后最終能夠獲得 EIP。
( 3)有時我們可能很難在瀏覽器中復雜的內存環境下布置完整的 shellcode。
( 4)頁面中的 JavaScript 可以申請堆內存,因此,把 shellcode 通過 JavaScript 布置在堆中成為可能。
在使用 Heap Spray 的時候,一般會將 EIP 指向堆區的 0x0C0C0C0C 位置,然后用 JavaScript申請大量堆內存,并用包含著 0x90 和 shellcode 的“內存片”覆蓋這些內存。
通常, JavaScript 會從內存低址向高址分配內存, 因此申請的內存超過 200MB( 200MB=200 × 1024× 1024 = 0x0C800000 > 0x0C0C0C0C)后, 0x0C0C0C0C 將被含有 shellcode 的內存片覆蓋。只要內存片中的 0x90 能夠命中 0x0C0C0C0C 的位置, shellcode 就能最終得到執行。
var nop=unescape("%u9090%u9090");
while (nop.length<= 0x100000/2)
{
nop+=nop;
}//生成一個 1MB 大小充滿 0x90 的數據塊
nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2 );
var slide = new Arrary();
for (var i=0; i<200; i++)
{
slide[i] = nop + shellcode
}
Java 會為申請到的內存填上一些額外的信息,為了保證內存片恰好是 1MB,我們將這些額外信息所占的空間減去。nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2 )將一個內存片恰好湊成 1MB 大小。
4.格式化串漏洞
4.1 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并不會編譯報錯,運行輸出如下:
雖然函數調用時沒有給出“輸出數據列表”,但系統仍然按照“格式控制符”所指明的方式輸出了棧中緊隨其后的兩個 DWORD。現在應該明白輸出“ a=4218928,b=44”的原因了:4218928 的十六進制形式為 0x00406030,是指向格式控制符“ a=%d,b=%d\n”的指針。
4.2 用printf讀取內存數據
如果傳入的字符串中帶有格式控制符時, printf 就會打印出棧中“莫須有”的數據。
例如,輸入“ %p,%p,%p……”,實際上可以讀出棧中的數據。
#include "stdio.h"
int main(int argc, char ** argv)
{
printf(argv[1]);
}
4.3 用printf向內存寫數據
在格式化控制符中,有一種鮮為人知的控制符%n。這個控制符用于把當前輸出的所有數據的長度寫回一個變量中去。
#include "stdio.h"
int main(int argc, char ** argv)
{
int len_print=0;
printf("before write: length=%d\n",len_print);
printf("hacktest:%d%n\n",len_print,&len_print);
printf("after write: length=%d\n",len_print);
}
第二次 printf 調用中使用了%n 控制符,它會將這次調用最終輸出的字符串長度寫入變量len_print 中。“hacktest:0”長度為 10,所以這次調用后 len_print 將被修改為 10。
4.4 格式化串漏洞的檢測
當輸入輸出函數的格式化控制符能夠被外界影響時,攻擊者可以綜合利用前面介紹的讀內存和寫內存的方法修改函數返回地址,劫持進程,從而使 shellcode 得到執行。
比起大量使用命令和腳本的 UNIX 系統, Windows 操作系統中命令解析和文本解析的操作并不是很多,再加上這種類型的漏洞發生的條件比較苛刻,使得格式化串漏洞的實際案例非常罕見。
格式化串漏洞的起因非常簡單,只要檢測相關函數的參數配置是否恰當就行。通過簡單的靜態代碼掃描,一般可以比較容易地發現這類漏洞。
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 );
5.SQL 注入原理
SQL 命令注入的漏洞是 Web 系統特有的一類漏洞,它源于 PHP、 ASP 等腳本語言對用戶輸入數據和解析時的缺陷。
當攻擊者把用戶名輸入為 admin’#的時候,輸入字串中的單引號將和腳本中的變量的單引號形成配對,而輸入字串中的“ #”號對于 My SQL 的語言解釋器來說是一行注釋符。通過這樣的輸入,攻擊者可以輕易繞過身份驗證機制,沒有正確的密碼也能看到管理員的信息。
注入攻擊的檢測與防范:
- 檢測:
SQL注入掃描:NGSSQuirreL - 防范:
一種十分有效的防止 SQL 注入的方法是使用參數化查詢( Parameterized Query)的方法。參數化查詢就是在訪問數據庫時,將查詢語句中要填入的數據通過參數的方式傳遞,這樣數據庫不會將參數的內容視為 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 兩個變量的值是由用戶輸入的
6.XSS攻擊
XSS 是跨站腳本(Cross Site Script)的意思,由于網站技術中的 Cascading Style Sheets 縮寫為 CSS,為了不至于產生概念混淆,故一般用 XSS 來簡稱跨站腳本。
在很多 Web 應用中,服務器都將客戶端輸入或請求的數據“經過簡單的加工”后,再以頁面文本的形式返回給客戶端。
當用戶進行正常的請求,"http://testapp.com/test.php?input=this is a test",服務器將簡單地把“ this is a test”返回給客戶端的瀏覽器。
當請求"http://testapp.com/test.php?input=<script>alert(‘xss’);</script>",服務器也會把“<script>alert(‘xss’);</script>” 當字符串返回給客戶端瀏覽器,瀏覽器在解析這次反饋的頁面時,發現頁面中的是腳本命令,而不是數據,因此會把“ <script>alert(‘xss’);</script>”當做腳本命令進行解析,進而執行,彈出一個警告消息框。
<?php
echo $input
?>
XSS 攻擊的目標是客戶端的瀏覽器,因此受影響的范圍要遠遠大于攻擊服務器的 SQL 注入攻擊;獨立的 XSS 漏洞攻擊并不是非常嚴重,但是配合上其他攻擊技術往往能產生非常嚴重的后果。
7.路徑回溯漏洞
windows中“ ../”或者“ ..\”,Linux中的“ ../”,在路徑中表示“上一級”,當足夠多的“ ..”使得路徑跳轉到根目錄時,多余的“ ..”將被忽略掉。
例如有這樣一個URL:http://www.testsite.com/download.asp?file=document.pdf
如果沒有回溯檢查,可構造URL獲取敏感文件:http://www.testsite.com/download.asp?file=../../../../etc/passwd