赞
踩
CWE(Common Weakness Enumeration,通用缺陷枚举)。是由美国国土安全部国家计算机安全部门资助的软件安全战略性项目。
CVE (Common Vulnerabilities & Exposures,常用漏洞和风险)。 CVE 是国际著名的安全漏洞库,也是对已知漏洞和安全缺陷的标准化名称的列表,它是一个由企业界、政府界和学术界综合参与的国际性组织,采取一种非盈利的组织形式,其使命是为了能更加快速而有效地鉴别、发现和修复软件产品的安全漏洞。
用面向对象的话说,CWE就好像定义了一个类(漏洞类型),而CVE属于具体的对象。
软件接收输入,但不会验证或者有效的验证输入是否安全。
输入验证会应用于:
本示例要求用户获得最大尺寸为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
均设为负数,并且绝对值非常大,负负得正来分配更多内存。
public static final double price = 20.00;
int quantity = currentUser.getAttribute("quantity");
double total = price * quantity;
chargeUser(total);
代码规定了物品得价格为20。却没有对购买物品得数量进行校验,如果数量为负数,那么商家还会亏钱。
软件使用外部输入来构造一个路径名,该路径名用于访问受限制的父目录下的文件或目录,但软件不会正确地处理路径名中的特殊元素(比如 ../
),这些元素会导致路径名解析到受限制目录外的位置。
String path = getInputPath();
if (path.startsWith("/safe_dir/"))
{
File f = new File(path);
f.delete()
}
攻击者可输入/safe_dir/../important.dat
来删除和 /safe_dir/
同目录下的 important.dat
文件。
当软件用外部输入构造要执行的系统命令时,没有处理外部输入中的特殊元素导致恶意命令被执行。
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
的值进行检查,导致恶意用户可以输入&
来执行其它的系统命令。
软件对内存缓冲区执行操作,但它可以读取或写入缓冲区预期边界之外的内存位置。
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。
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。
软件读取数组之外的数据
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) {
...
}
当算术运算中出现增加一个非常大的数字或其它情况时容易触发。加法运算后值变负或者变小。
#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);
...
}
软件将敏感信息暴露给没有明确授权访问该信息的参与者。
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
注入。
当一个用户声称拥有一个给定的身份时,软件不能证明或者不能充分证明这个声明是正确的。
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(); }
软件不能正确地控制有限资源的分配和维护,从而使参与者能够影响所消耗的资源量,最终导致可用资源的耗尽。
资源包括:内存,系统存储空间,数据库连接等等。
/* 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)) {
...
}
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) { ... } }
释放内存后引用内存可能会导致程序崩溃、使用意外值或执行代码,这也是CTF中的热点。
#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); }
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;
操作。
当应用程序引用预期有效但为NULL
的指针时,就会发生空指针引用,通常会导致崩溃或退出。
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)。
软件在预期缓冲区的末尾或开头之前写入数据。也是CTF热点,strcpy
出现的地方几乎必有它。
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个字符。当输入字符串全部为&
时会导致缓冲区溢出。
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
数组以外的内存。
将密码或者密钥等重要信息硬编码在代码里。
int VerifyAdmin(char *password) {
if (strcmp(password, "Mew!")) {
printf("Incorrect Password!\n");
return(0)
}
printf("Entering Diagnostic Mode...\n");
return(1);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。