C/C++安全編碼-字符串

1 字符串 c++

   

1.1 字符串基礎 編程

字符串提供命令行參數、環境變量、控制檯輸入、文本文件及網絡連 數組

接,提供外部輸入方法來影響程序的行爲和輸出,這也是程序容易出錯的地方。字符串是一個概念,並非C/C++內置類型,標準C語言庫支持類型爲char的字符串和類型爲wchar_t的寬字符串。 安全

字符串由一個以第一個空(null)字符做爲結束的連續字符序列組成, 網絡

包含此空字符(因此sizeofstrlen會差1)。一個指向字符串的指針實際指向該字符串的起始字符。目標大小,指sizeof(array)大小,注意與元素個數區分。 ide

   

數組大小。數組帶來的問題之一是肯定其元素數量,例以下面的例子: 函數

void clear(int array[]) spa

{ 命令行

for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i) 指針

{

array[i] = 0;

}

}

   

void dowork()

{

int dis[12];

 

clear(dis);

/* ... */

}

array是一個參數,因此它的類型是指針。所以,sizeof(array)等於sizeof(int*),在x86 32機中,sizeof(array) / sizeof(array[0])計算結果都是1

   

字符串字面值:簡而言之就是在雙引號中的值,在C中,字符串字面值的類型是一個char數組,但在C++中,它是一個const char數組。因此在C中能夠修改字面值,可是程序若是試圖去修改,該行爲是未定義的。不要試圖修改字符串字面值,編譯器有時會把多個相同的字符串字面值存儲在相同位置,例如只讀存儲器(ROM)中,看下面例子:

const char *s1 = "abc";

const char *s2 = "abc";

   

char *s3 = "abc";

char *s4 = "abc";

   

char s5[] = "abc";

char s6[] = "abc";

比較地址會發現s1,s2,s3,s4相同,用這4個指針去改變字符串字面值是會出問題的。s5,s6值不一樣

字符數組初始化不要指定一個用字符串字面值初始化的字符數組的界限

const char s[3] = "abc"; //不安全寫法,少一個'\0'

const char s[] = "abc"; //推薦初始化方式

   

1.2 C++中的字符串

C++標準類模板std::basic_string。簡單來講就是string(basic_string<char>)

wstring(basic_string<wchar_t>)basic_string的類的模版特化更不容易出現錯誤和安全漏洞,須要強調的是大多數C++字符串對象被視爲不可分割的總體(一般按值傳遞和引用傳遞),內部字符串不必定是以空字符結束(大多數實現是以空字符結尾)C的庫函數都接受以空字符結尾的字符序列指針。

   

1.3 字符類型

char 是 signed char 仍是 unsigned char 可由編譯器的配置項設定

char有符號時,由unsigned char[]轉換爲const char *

char無符號時,由singned char[] 轉換爲const char *

若是不強制轉換會有警告,建議使用普通的char

   

1.4 字符串的長度

混淆概念容易在CC++中致使嚴重的錯誤,

wchar_t wide_str1[] = L"0123456789";

wchar_t *wide_str2 = (wchar_t*)malloc(strlen(wide_str1) + 1);

if(wide_str2 == NULL)

{

/*處理錯誤*/

}

free(wide_str2);

wide_str2 = NULL;

對一個以空字符結尾的字節字符串,strlen()統計終止空字節前面的字符數量。然而,寬字符能夠包含空字節,因此計算結果會出問題。

使用wcslen能夠計算寬字符串的大小

wchar_t wide_str1[] = L"0123456789";

wchar_t *wide_str2 = (wchar_t*)malloc(wcslen(wide_str1) + 1);

if(wide_str2 == NULL)

{

/*處理錯誤*/

}

free(wide_str2);

wide_str2 = NULL;

注意此長度沒有乘sizeof(wchar_t),因此仍是不對,下面值最終正確寫法:

wchar_t wide_str1[] = L"0123456789";

wchar_t *wide_str2 = (wchar_t*)malloc((wcslen(wide_str1)+1)*sizeof(wchar_t));

if(wide_str2 == NULL)

{

/*處理錯誤*/

}

free(wide_str2);

wide_str2 = NULL;

   

2 常見的字符串操做錯誤

   

2.1 無界字符串複製

void get_y_or_n()

{

char response[8];

puts("Continue? [y] n:");

gets(response);

if(response[0] == 'n')

exit(0);

 

return;

}

其實gets()函數在C99中以廢棄並在C11中淘汰。它沒有提供方法指定讀入的字符數的限制。這種限制在此函數的以下一致實現中是顯而易見的:

char *gets(char *dest)

{

int c = getchar();

char *p = dest;

 

while(c != EOF && c != '\n')

{

*p++ = c;

c = getchar();

}

*p = '\0';

 

return dest;

}

若是輸入超出8個字符,那麼會致使未定義的行爲。不要從一個無界源複製數據到定長數組中,禁止這種方法。

2.1.1 複製和鏈接字符串

例如strcpy(), strcat(), sprintf(), 容易執行無界操做。例如:

int main(int argc, char *argv[])

{

/*argc參數個數,argv參數數組*/

}

argc大於0,按照慣例,argv[0]指向的字符串是程序名。若argc > 1,argv[0]~argv[argc-1]引用的就是實際程序參數。

當分配的空間不足以複製一個程序的輸入,就會產生漏洞。攻擊者能夠控制argv[0]的內容

int main(int argc, char *argv[])

{

/*argc參數個數,argv參數數組*/

char prog_name[128];

strcpy(prog_name, argv[0]);

/* ... */

}

輸入一個大於128個字節的字符,棧溢出,即緩衝區溢出漏洞。

標準的寫法應該是:

int main(int argc, char *argv[])

{

/* 不要假設argv[0]不準爲空 */

const char *const name = argv[0]? argv[0] : "";

char *prog_name = (char*)malloc(strlen(name)+1);

if(prog_name != NULL)

{

strcpy(prog_name, name);

}

else

{

/* 復原 */

}

}

其實還有一種方法能夠避免溢出,經過設置域寬能夠消除gets()的缺陷

char buf[12];

std::cin::width(12);

std::cin >> buf;

std::cout << buf << std::endl;

   

2.2 差一錯誤

簡而言之就是從源字符串拷貝內容到目的字符串,恰好最後的'\0'沒有

拷貝到目的字符串中,在這以後對目的串調用C語言庫的函數可能會出問題,即空字符結尾錯誤,其他的還有字符串階截斷偏差,越界操做等。

   

2.3 字符串漏洞及其利用

大致上就是緩衝區溢出(詳細的能夠本身網上查,有不少資料詳細介

),棧溢出的話,能夠把目標代碼或者數據覆蓋到棧裏面,關於棧爲何會溢出,實際上是由於在編譯後,棧的大小就固定了。這種攻擊方式也稱注入,這裏涉及到彙編以及底層的結構,不作詳細解釋,不過解決方法也有不少,要麼作邊界檢查,要麼動態的分配內存,還有更簡單的那就是直接使用std::basic_string。固然使用string也會出問題,例如迭代器失效。

char input[];

string email;

string::iterator loc = email.begin();

//複製到string對象,同時把";" 轉換成" "

for (size_t i = 0; i < strlen(input); ++i)

{

if(input[i] != ";")

email.insert(loc++, input[i]);

else

email.insert(loc++, ' ');

}

第一次insert以後,loc就已經失效,後面的insert都將產生未定義行爲。正確的寫法應該是

char input[];

string email;

string::iterator loc = email.begin();

//複製到string對象,同時把";" 轉換成" "

for (size_t i = 0; i < strlen(input); ++i)

{

if(input[i] != ";")

loc = email.insert(loc, input[i]);

else

loc = email.insert(loc, ' ');

++loc;

}

固然在編程的時候引用邊界以外的元素會拋出一個異常std::out_of _range。另外std::string.c_str()函數能夠返回一個以空字符結尾的字符,const值,因此調用free()或者delete()會出錯,須要修改則只能修改副本。

相關文章
相關標籤/搜索