취약점 뒤에 숨겨진 버그 5부
5부작으로 구성된 시리즈의 마지막 편에서는 취약점과 익스플로잇의 원인이 되는 코드 버그에 대해 살펴봅니다. 이번 파트에서는 마지막 다섯 가지 항목에 대해 살펴보겠습니다. Mitre CWE 상위 25개에서 5위부터 1위까지 최종 5개 항목을 살펴보겠습니다.
여기에서 찾을 수 있습니다. 1부는 여기에서, 파트 2는 여기에서, 3부 여기, 4부 여기를 참조하세요.
5. 범위를 벗어난 읽기
범위를 벗어난 읽기는 제품이 의도한 버퍼의 끝이나 시작 전에 데이터를 읽을 때 발생합니다. 이 경우 공격자가 다른 메모리 위치에서 민감한 정보를 읽거나 충돌을 일으킬 수 있습니다.
예를 들어, 사용자 제어 입력을 사용하여 배열에서 읽는 다음 C 코드 스니펫을 생각해 보겠습니다:
#include <stdio.h> int get_element(int *array, int len, int index) { int result; if (index < len) { result = array[index]; } else { printf("Invalid index: %d\n", index); result = -1; } return result; } int main() { int my_array[5] = {10, 20, 30, 40, 50}; int index = -1; // Simulating user-controlled input int value = get_element(my_array, 5, index); printf("Value at index %d: %d\n", index, value); return 0; }
이 예제에서 get_element 함수는 제공된 인덱스가 배열의 최대 바운드 내에 있는지 확인하지만 최소 바운드는 확인하지 않습니다. 이로 인해 음수 값이 유효한 배열 인덱스로 허용되어 범위를 벗어난 읽기가 발생할 수 있습니다. 이 문제를 완화하려면 최소 바운드에 대한 검사를 추가하세요:
if (index >= 0 && index < len) { result = array[index]; }
4. 부적절한 입력 유효성 검사
부적절한 입력 유효성 검사는 제품이 입력 또는 데이터를 수신하지만 유효성을 검사하지 않거나 잘못 검사하여 제어 흐름이 변경되거나 리소스를 임의로 제어하거나 임의의 코드가 실행되는 경우 발생합니다.
private void buildList(int untrustedListSize) { if (0 > untrustedListSize) { die("Negative value supplied for list size, die evil hacker!"); } Widget[] list = new Widget[untrustedListSize]; list[0] = new Widget(); }
이 예제에서 코드는 untrustedListSize가 음수인지 확인하지만 0인지는 확인하지 않습니다. 0 값이 제공되면 코드가 크기 0의 배열을 만든 다음 첫 번째 위치에 새 위젯을 저장하려고 시도하여 예외가 발생합니다. 이 문제를 방지하려면 입력의 잠재적으로 관련성이 있는 모든 속성을 고려하여 포괄적인 입력 유효성 검사 기법을 사용하고 악의적이거나 잘못된 입력을 찾는 데만 의존하지 마세요. 이 경우 대신 불신 목록 크기가 0보다 큰지 확인해야 합니다.
3. SQL 명령에 사용되는 특수 요소의 부적절한 무력화(무서운 "SQL 인젝션") 3.
SQL 인젝션은 애플리케이션이 의도한 SQL 명령을 수정할 수 있는 특수 요소를 무력화하거나 잘못 무력화하지 않고 업스트림 구성 요소의 외부 영향을 받는 입력을 사용하여 SQL 명령의 전체 또는 일부를 구성할 때 발생합니다.
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char query[1024]; char *user_input = argv[1]; snprintf(query, sizeof(query), "SELECT * FROM users WHERE username='%s'", user_input); printf("Generated query: %s\n", query); // Execute the query... return 0; }
이 예에서는 사용자 입력이 SQL 쿼리에 직접 연결되므로 공격자가 악성 SQL 코드를 삽입할 수 있습니다. SQL 인젝션을 방지하려면 매개변수화된 쿼리, 준비된 문 또는 저장 프로시저를 사용하여 사용자 입력과 SQL 문을 분리해야 합니다. 이 경우 준비된 문을 사용해야 합니다:
#include <stdio.h> #include <stdlib.h> #include <mysql/mysql.h> (...) MYSQL_STMT *stmt = mysql_stmt_init(con); if (!stmt) { fprintf(stderr, "mysql_stmt_init() failed\n"); exit(1); } const char *query = "SELECT * FROM users WHERE username=?"; if (mysql_stmt_prepare(stmt, query, strlen(query)) != 0) { fprintf(stderr, "mysql_stmt_prepare() failed: %s\n", mysql_stmt_error(stmt)); exit(1); }
준비된 문을 사용하면 사용자 입력이 SQL 쿼리의 일부가 아닌 데이터로 취급되므로 SQL 인젝션을 효과적으로 방지할 수 있습니다.
2. 웹 페이지 생성 중 입력의 부적절한 무력화(또는 "교차 사이트 스크립팅") 2.
크로스 사이트 스크립팅(XSS) 취약점은 애플리케이션이 다른 사용자에게 제공되는 웹 페이지로 사용되는 출력에 배치되기 전에 사용자가 제어할 수 있는 입력을 무력화하지 않거나 잘못 무력화할 때 발생합니다.
다음 PHP 코드 스니펫을 고려하세요:
사용자 이름 = $_GET['username']; echo "Welcome, " . 사용자 이름 . "!";
이 예시에서는 사용자 입력이 적절한 위생 처리 없이 웹 페이지에 직접 삽입되어 공격자가 악성 HTML 또는 JavaScript 코드를 삽입할 수 있습니다. XSS를 방지하려면 사용자 입력을 유효성 검사 및 살균하고, 웹 페이지에 사용자 입력을 표시할 때 보안 출력 인코딩 기술을 사용하며, 콘텐츠 보안 정책을 적용하여 XSS 공격의 영향을 줄이세요. 이 경우 사용자 입력을 표시하기 전에 살균 처리해야 합니다:
사용자 이름 = htmlspecialchars($_GET['username'], ENT_QUOTES, 'UTF-8'); echo "Welcome, " . 사용자 이름 . "!";
1. 범위를 벗어난 쓰기
범위 초과 쓰기는 제품이 의도한 버퍼의 끝이나 시작 전에 데이터를 쓸 때 발생하며, 이로 인해 데이터 손상, 충돌 또는 코드 실행이 발생할 수 있습니다.
예를 들어 배열에 쓰는 다음 C 코드 스니펫을 생각해 보세요:
#include <stdio.h> void fill_array(int *array, int len) { for (int i = 0; i <= len; i++) { array[i] = i * 10; } } int main() { int my_array[5]; fill_array(my_array, 5); for (int i = 0; i < 5; i++) { printf("Element at index %d: %d\n", i, my_array[i]); } return 0; }
In this example, the function `fill_array` writes to the array `my_array` using the loop variable `i`. However, the loop condition is `i <= len`, which allows the loop to run one iteration beyond the intended bounds of the array, causing an out-of-bounds write.
To avoid this issue, ensure that you use proper boundary checks when writing to buffers. In this case, the loop condition should be `i < len`:
for (int i = 0; i < len; i++) { array[i] = i * 10; }
이렇게 수정하면 코드가 해당 범위 내에서 배열에 올바르게 쓰기 때문에 범위를 벗어난 쓰기 취약점을 방지할 수 있습니다.
결론
결론적으로, 개발자는 애플리케이션의 취약점을 초래할 수 있는 일반적인 코드 버그를 인식하는 것이 중요합니다. 이러한 문제를 이해하고 안전한 코딩 관행을 구현하면 공격자에 의한 익스플로잇 위험을 크게 줄일 수 있습니다.
다음은 명심해야 할 몇 가지 일반적인 보안 코딩 원칙입니다:
- 최소 권한
애플리케이션이 작업을 완료하는 데 필요한 최소한의 권한으로 실행되도록 하세요. 이렇게 하면 공격자가 권한을 상승시키거나 민감한 데이터 또는 시스템 리소스에 무단으로 액세스할 수 있는 능력을 줄여 보안 침해의 잠재적 영향을 제한할 수 있습니다.
- 보안 기본값
보안 설정이 기본적으로 활성화된 상태로 애플리케이션을 설계하세요. 사용자가 보안 기능을 활성화하지 않고 명시적으로 비활성화해야 합니다. 이렇게 하면 보다 안전한 사용자 환경을 조성하고 안전하지 않은 구성의 가능성을 줄일 수 있습니다.
- 심층 방어
애플리케이션에 여러 계층의 보안을 구현하여 한 계층이 침해되더라도 다른 계층이 보호 기능을 제공할 수 있도록 하세요. 여기에는 입력 유효성 검사, 출력 인코딩, 액세스 제어를 함께 사용하여 다양한 공격에 대한 포괄적인 방어를 제공하는 것이 포함될 수 있습니다.
- 안전한 실패
오류 또는 예기치 않은 입력이 발생할 경우 애플리케이션이 안전하게 실패하도록 설계하세요. 예를 들어, 애플리케이션이 사용자의 입력을 처리하는 동안 오류가 발생하는 경우 민감한 정보를 노출하거나 의도하지 않은 액세스 권한을 부여해서는 안 됩니다. 대신 오류를 정상적으로 처리하고 사용자에게 유용한 오류 메시지를 제공해야 합니다.
- 단순하게 유지
복잡성은 보안의 적입니다. 코드를 단순화하기 위해 노력하고 이해와 유지 보수가 어려운 복잡한 구조의 사용을 최소화하세요. 이렇게 하면 잠재적인 보안 문제를 더 쉽게 발견할 수 있고 개발 또는 유지 보수 중에 새로운 취약점이 발생할 가능성을 줄일 수 있습니다.
- 정기적인 종속성 업데이트
오래된 라이브러리와 프레임워크는 보안 취약성을 초래할 수 있으므로 애플리케이션의 종속성을 최신 상태로 유지하세요. 업데이트와 패치를 정기적으로 확인하고 개발 및 배포 프로세스에 통합하세요.
이러한 보안 코딩 원칙을 통합하고 이 시리즈에서 언급한 일반적인 코드 버그를 해결하면 더욱 안전한 애플리케이션을 개발하고 잠재적인 보안 위협으로부터 사용자를 보호하는 데 큰 도움이 될 것입니다.