CWE
- CWE-20: Improper Input Validation
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
- CWE-125: Out-of-bounds Read
- CWE-190: Integer Overflow or Wraparound
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
- CWE-287: Improper Authentication
- CWE-416: Use After Free
- CWE-476: NULL Pointer Dereference
- CWE-787: Out-of-bounds Write
CWE(Common Weakness Enumeration,通用缺陷枚举)。是由美国国土安全部国家计算机安全部门资助的软件安全战略性项目。
CVE (Common Vulnerabilities & Exposures,常用漏洞和风险)。 CVE 是国际著名的安全漏洞库,也是对已知漏洞和安全缺陷的标准化名称的列表,它是一个由企业界、政府界和学术界综合参与的国际性组织,采取一种非盈利的组织形式,其使命是为了能更加快速而有效地鉴别、发现和修复软件产品的安全漏洞。
用面向对象的话说,CWE就好像定义了一个类(漏洞类型),而CVE属于具体的对象。
CWE-20: Improper Input Validation
软件接收输入,但不会验证或者有效的验证输入是否安全。
输入验证会应用于:
- raw data: strings, numbers, parameters, file contents
- metadata:关于raw data的信息,比如headers或者大小
示例代码1
本示例要求用户获得最大尺寸为100平方的 m × n m \times n m×n 游戏板的高度和宽度。
...
#define MAX_DIM 100
...
/* board dimensions */
int m,n, error;
board_square_t *board;
printf("Please specify the board height: \n");
error = scanf("%d", &m);
if ( EOF == error ){
die("No integer passed: Die evil hacker!\n");
}
printf("Please specify the board width: \n");
error = scanf("%d", &n);
if ( EOF == error ){
die("No integer passed: Die evil hacker!\n");
}
if ( m > MAX_DIM || n > MAX_DIM ) {
die("Value too large: Die evil hacker!\n");
}
board = (board_square_t*) malloc( m * n * sizeof(board_square_t));
malloc
函数原型为 void* malloc (size_t size);
该示例中代码会检验输入数据是否超过上限,但没有检查是否出现负数。攻击者可以将 m
和n
均设为负数,并且绝对值非常大,负负得正来分配更多内存。
示例代码2
public static final double price = 20.00;
int quantity = currentUser.getAttribute("quantity");
double total = price * quantity;
chargeUser(total);
代码规定了物品得价格为20。却没有对购买物品得数量进行校验,如果数量为负数,那么商家还会亏钱。
CWE-22: Improper Limitation of a Pathname to a Restricted Directory
软件使用外部输入来构造一个路径名,该路径名用于访问受限制的父目录下的文件或目录,但软件不会正确地处理路径名中的特殊元素(比如 ../
),这些元素会导致路径名解析到受限制目录外的位置。
示例代码1
String path = getInputPath();
if (path.startsWith("/safe_dir/"))
{
File f = new File(path);
f.delete()
}
攻击者可输入/safe_dir/../important.dat
来删除和 /safe_dir/
同目录下的 important.dat
文件。
CWE-78: Improper Neutralization of Special Elements used in an OS Command
当软件用外部输入构造要执行的系统命令时,没有处理外部输入中的特殊元素导致恶意命令被执行。
示例代码1
public String coordinateTransformLatLonToUTM(String coordinates)
{
String utmCoords = null;
try {
String latlonCoords = coordinates;
Runtime rt = Runtime.getRuntime();
Process exec = rt.exec("cmd.exe /C latlon2utm.exe -" + latlonCoords);
// process results of coordinate transform
// ...
}
catch(Exception e) {
...
}
return utmCoords;
}
示例代码中没有对coordinates
的值进行检查,导致恶意用户可以输入&
来执行其它的系统命令。
CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
软件对内存缓冲区执行操作,但它可以读取或写入缓冲区预期边界之外的内存位置。
示例代码1
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/*routine that ensures user_supplied_addr is in the right format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr( addr, sizeof(struct in_addr), AF_INET);
strcpy(hostname, hp->h_name);
}
示例代码分配了一个64字节的数组来存储hostname
,但是并没有校验hp->name
的值是否超过64。
示例代码2
int main (int argc, char **argv) {
char *items[] = {"boat", "car", "truck", "train"};
int index = GetUntrustedOffset();
printf("You selected %s\n", items[index-1]);
return 0;
}
同样,代码并没有对index
进行校验,可能导致buffer over-read。
CWE-125: Out-of-bounds Read
软件读取数组之外的数据
示例代码1
int getValueFromArray(int *array, int len, int index) {
int value;
if (index < len) {
value = array[index];
}
else {
printf("Value is: %d\n", array[index]);
value = -1;
}
return value;
}
代码对index
长度进行了校验,不得超过数组最大长度,但是没有确保index
不能为负数,这样会导致out of bounds read。
正确的做法为
// range of values for the array
if (index >= 0 && index < len) {
...
}
CWE-190: Integer Overflow or Wraparound
当算术运算中出现增加一个非常大的数字或其它情况时容易触发。加法运算后值变负或者变小。
示例代码1
#define JAN 1
#define FEB 2
#define MAR 3
short getMonthlySales(int month) {...}
float calculateRevenueForQuarter(short quarterSold) {...}
int determineFirstQuarterRevenue() {
// Variable for sales revenue for the quarter
float quarterRevenue = 0.0f;
short JanSold = getMonthlySales(JAN); /* Get sales in January */
short FebSold = getMonthlySales(FEB); /* Get sales in February */
short MarSold = getMonthlySales(MAR); /* Get sales in March */
// Calculate quarterly total
short quarterSold = JanSold + FebSold + MarSold;
// Calculate the total revenue for the quarter
quarterRevenue = calculateRevenueForQuarter(quarterSold);
saveFirstQuarterRevenue(quarterRevenue);
return 0;
}
determineFirstQuarterRevenue
方法用来计算第一个季度的财政收入,这个方法会接收前3个月的销售额作为输入,然后根据3个月的销售额总和来计算第一个季度的财政收入并存入数据库,然而它们均用short
类型的变量来存储数据,short
最大值32768
。使得计算3个月的总和时容易造成整数溢出。
正确代码
float calculateRevenueForQuarter(long quarterSold) {...}
int determineFirstQuarterRevenue() {
...
// Calculate quarterly total
long quarterSold = JanSold + FebSold + MarSold;
// Calculate the total revenue for the quarter
quarterRevenue = calculateRevenueForQuarter(quarterSold);
...
}
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
软件将敏感信息暴露给没有明确授权访问该信息的参与者。
示例代码1
public BankAccount getUserBankAccount(String username, String accountNumber) {
BankAccount userAccount = null;
String query = null;
try {
if (isAuthorizedUser(username)) {
query = "SELECT * FROM accounts WHERE owner = "
+ username + " AND accountID = " + accountNumber;
DatabaseManager dbManager = new DatabaseManager();
Connection conn = dbManager.getConnection();
Statement stmt = conn.createStatement();
ResultSet queryResult = stmt.executeQuery(query);
userAccount = (BankAccount)queryResult.getObject(accountNumber);
}
} catch (SQLException ex) {
String logMessage = "Unable to retrieve account information from database,\nquery: " + query;
Logger.getLogger(BankManager.class.getName()).log(Level.SEVERE, logMessage, ex);
}
return userAccount;
}
getUserBankAccount
方法使用提供的用户名和帐号从数据库检索银行帐户对象以查询数据库。如果查询数据库时引发SQLException
,则会创建一条错误消息并输出到日志文件。创建的错误消息包含有关数据库查询的信息,这些信息可能包含有关数据库或查询逻辑的敏感信息。在这种情况下,错误消息将公开数据库中使用的表名和列名。这些数据可以用来简化其他攻击,例如直接访问数据库的SQL
注入。
CWE-287: Improper Authentication
当一个用户声称拥有一个给定的身份时,软件不能证明或者不能充分证明这个声明是正确的。
my $q = new CGI;
if ($q->cookie('loggedin') ne "true") {
if (! AuthenticateUser($q->param('username'), $q->param('password'))) {
ExitError("Error: you need to log in first");
}
else {
# Set loggedin and user cookies.
$q->cookie(
-name => 'loggedin',
-value => 'true'
);
$q->cookie(
-name => 'user',
-value => $q->param('username')
);
}
}
if ($q->cookie('user') eq "Administrator") {
DoAdministratorTasks();
}
CWE-400: Uncontrolled Resource Consumption
软件不能正确地控制有限资源的分配和维护,从而使参与者能够影响所消耗的资源量,最终导致可用资源的耗尽。
资源包括:内存,系统存储空间,数据库连接等等。
示例代码1
/* process message accepts a two-dimensional character array of the form [length][body] containing the message to be processed */
int processMessage(char **message)
{
char *body;
int length = getMessageLength(message[0]);
if (length > 0) {
body = &message[1][0];
processMessageBody(body);
return(SUCCESS);
}
else {
printf("Unable to process message; invalid message length");
return(FAIL);
}
}
这里示例代码处理一个二维数组,二维数组第一行表示数组有多少个消息,这里并没有对消息数量进行限制,如果message
非常大,那么非常消耗系统资源。
正确示范
unsigned int length = getMessageLength(message[0]);
if ((length > 0) && (length < MAX_LENGTH)) {
...
}
示例代码2
public void acceptConnections() {
try {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
int counter = 0;
boolean hasConnections = true;
while (hasConnections) {
Socket client = serverSocket.accept();
Thread t = new Thread(new ClientSocketThread(client));
t.setName(client.getInetAddress().getHostName() + ":" + counter++);
t.start();
}
serverSocket.close();
} catch (IOException ex) {
...
}
}
示例代码并没有对connection
作出限制。可以通过线程池来限制创建线程的数量。
正确示范
public static final int SERVER_PORT = 4444;
public static final int MAX_CONNECTIONS = 10;
...
public void acceptConnections() {
try {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
int counter = 0;
boolean hasConnections = true;
while (hasConnections) {
hasConnections = checkForMoreConnections();
Socket client = serverSocket.accept();
Thread t = new Thread(new ClientSocketThread(client));
t.setName(client.getInetAddress().getHostName() + ":" + counter++);
ExecutorService pool = Executors.newFixedThreadPool(MAX_CONNECTIONS);
pool.execute(t);
}
serverSocket.close();
} catch (IOException ex) {
...
}
}
CWE-416: Use After Free
释放内存后引用内存可能会导致程序崩溃、使用意外值或执行代码,这也是CTF中的热点。
示例代码1
#include <stdio.h>
#include <unistd.h>
#define BUFSIZER1 512
#define BUFSIZER2 ((BUFSIZER1/2) - 8)
int main(int argc, char **argv) {
char *buf1R1;
char *buf2R1;
char *buf2R2;
char *buf3R2;
buf1R1 = (char *) malloc(BUFSIZER1);
buf2R1 = (char *) malloc(BUFSIZER1);
free(buf2R1);
buf2R2 = (char *) malloc(BUFSIZER2);
buf3R2 = (char *) malloc(BUFSIZER2);
strncpy(buf2R1, argv[1], BUFSIZER1-1);
free(buf1R1);
free(buf2R2);
free(buf3R2);
}
示例代码2
char* ptr = (char*)malloc (SIZE);
if (err) {
abrt = 1;
free(ptr);
}
...
if (abrt) {
logError("operation aborted before commit", ptr);
}
ptr
在free
之后其中的内容被清空了。但是中间一系列操作过后,原ptr
指向的那片堆内存可能被其它的指针指向(设为ptr1
),在之后的logError操作中再次引用了ptr
,实际上记录的是ptr1
的值。当free(ptr);
之后应有ptr=NULL;
操作。
CWE-476: NULL Pointer Dereference
当应用程序引用预期有效但为NULL
的指针时,就会发生空指针引用,通常会导致崩溃或退出。
示例代码1
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/*routine that ensures user_supplied_addr is in the right format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr( addr, sizeof(struct in_addr), AF_INET);
strcpy(hostname, hp->h_name);
}
在 gethostbyaddr( addr, sizeof(struct in_addr), AF_INET);
中,由于用户提供的host ip无效,导致 gethostbyaddr
返回 NULL
,而strcpy
之前没有对hp
进行校验。这段代码同样有buffer overflow(CWE-119)。
CWE-787: Out-of-bounds Write
软件在预期缓冲区的末尾或开头之前写入数据。也是CTF热点,strcpy
出现的地方几乎必有它。
示例代码1
char * copy_input(char *user_supplied_string){
int i, dst_index;
char *dst_buf = (char*)malloc(4*sizeof(char) * MAX_SIZE);
if ( MAX_SIZE <= strlen(user_supplied_string) ){
die("user string too long, die evil hacker!");
}
dst_index = 0;
for ( i = 0; i < strlen(user_supplied_string); i++ ){
if( '&' == user_supplied_string[i] ){
dst_buf[dst_index++] = '&';
dst_buf[dst_index++] = 'a';
dst_buf[dst_index++] = 'm';
dst_buf[dst_index++] = 'p';
dst_buf[dst_index++] = ';';
}
else if ('<' == user_supplied_string[i] ){
/* encode to < */
}
else
dst_buf[dst_index++] = user_supplied_string[i];
}
return dst_buf;
}
dst_index
的长度4倍于用户输入的字符串,而&
字符的加密用了5个字符。当输入字符串全部为&
时会导致缓冲区溢出。
示例代码2
char* trimTrailingWhitespace(char *strMessage, int length) {
char *retMessage;
char *message = malloc(sizeof(char)*(length+1));
// copy input string to a temporary string
char message[length+1];
int index;
for (index = 0; index < length; index++) {
message[index] = strMessage[index];
}
message[index] = '\0';
// trim trailing whitespace
int len = index-1;
while (isspace(message[len])) {
message[len] = '\0';
len--;
}
// return string without trailing whitespace
retMessage = message;
return retMessage;
}
这段代码中当输入全空格时会导致len
的值小于0,从而写入到message
数组以外的内存。
CWE-798: Use of Hard-coded Credentials
示例代码1
将密码或者密钥等重要信息硬编码在代码里。
int VerifyAdmin(char *password) {
if (strcmp(password, "Mew!")) {
printf("Incorrect Password!\n");
return(0)
}
printf("Entering Diagnostic Mode...\n");
return(1);
}