Program이 secure하다는 것은 무엇을 의미할까요? Software가 의도한 대로 동작할 때, program이 secure하다고 볼 수 있습니다. 그런 의미에서 SW는 보안에서 중요한 역할을 합니다. 또한 insecurity의 대상이 될 수 있습니다.

 

SW는 공격자로부터 input을 받을 때 문제가 될 수 있습니다. 취약점이 노출되면, 공격자는 program의 availability, integrity, confidentiality에 피해를 줄 수 있습니다. 또한 다른 user의 권한을 사용해 access control policy를 위반하는 행위를 할 수 있습니다.

 

Attacks exploiting SW vulnerabilities

취약점은 Entry points로 이용될 수 있습니다. 다음의 피해가 발생할 수 있습니다.

  • Drive-by download malware on victim system
  • Steal/manipulate information
  • Send spam emails from victim
  • Privilege escalation
    - root 권한 가져오기

 

Common SW vulnerabilities

Input validation

여러 형태의 input이 존재하는 만큼, input을 검증하는 과정은 필요합니다. 일반적인 어플리케이션에 가능한 input은 다음과 같습니다.

  • Commnad line arguments
  • Environment variables
  • Configuration files
  • Inter-Process commnunication call arguments
  • Network packets
  • Web form input
  • Scripting languages with string input

 

Environment Variables

환경변수 검증이 필요한 이유를 살펴보겠습니다. 환경변수란 프로그램을 실행하기 위한 경로를 의미하며 대표적으로 PATH, LD_LIBRARY_PATH, IFS 등이 있습니다. 

 

환경변수는 user에 의해 임의로 추가, 수정, 삭제될 수 있습니다.

 

Resetting PATH attack

공격자가 PATH를 현대 directory로 바꾸고 ls라는 이름의 arbitrary code를 설치했다고 가정해봅시다. User가 shell에 ls를 치는 순간, 공격이 수행되게 됩니다. 

 

Solution. PATH를 standard form으로 재설정합니다.

 

Resetting Internal Field Separator (IFS)

IFS는 word splitter입니다. Default는 whitespace로 공백을 기준으로 단어를 split하는 역할을 수행합니다. 만약 공격자가 IFS를 s로 바꾼다면 ls 명령어는 l로 인식될 것입니다. 이런 식으로 input이 unexpected하게 바뀔 수 있어 문제가 됩니다.

 

Solution. 이 역시 whitespace로 IFS를 재설정합니다.

 

Resetting LD_LIBRARY_PATH

UNIX에서 library를 사용하기 위해 참조하는 PATH입니다. 공격자는 이 PATH를 /tmp/attack으로 바꿈으로써 user가 C code를 실행하면 load되는 library가 attack code로 바뀌게 됩니다.

 

Solution. Modern C에서는 "EUID===RUID" 와 "EGID===RGID"를 확인함으로써 이를 방지합니다.

 

Command Line Argument

Example user code

예제의 코드처럼 execve system call을 사용함으로써 user는 거의 모든 일을 수행할 수 있습니다. 그리고 만약 input으로 "a; ls" 등의 공격이 수행된다면, 문제가 될 수 있습니다.

 

Web from attack

앞서 살펴본 SQLi 등의 공격으로 인해서, Directory Traversal vulnerabilities나 Data 유출 등의 문제가 발생할 수 있다. 이를 방지하기 위해서는 input validation을 수행해야 한다.

 

Input validation을 위해 '\'을 검증해서 확인하는 단계를 구현했지만, Unicode vulnerabilities에 의해서 실패한 역사가 있다. 공격자가 '\' 대신, '%c0%af'를 사용해 공격을 수행했기 때문이다.

 

대표적인 공격으로는 SQL injection과  Cross Site Scripting이 있다.

  • SQLi는 input때문에 malicious query가 생성되는 경우이다.
  • XSS는 input에 있는 script때문에 발생하는 공격이다.

 

정리: Input validation

Malicious input은 code로 동작할 수 있다. Input validation은 어렵지만, systematic way로 해결할 수 있다. 

  • Blacklisting
  • Whitelisting
  • Systematic rewriting

 

Race conditions

TOCTTOU

Time-Of-Check-To-Time-Of-Use를 말한다. 줄여서 TOCTTOU라고 한다.

TOCTTOU

"시스템이 권한을 확인하는 시간"과 "프로그램이 권한을 사용하는 시간"이 다르기 때문에 발생하는 공격이다. 이 순간을 Race condition이라고 하는데, 공격자가 이 순간을 파고들어 code를 바꾸면 다른 code가 실행될 수 있다.

 

보통은 거절된다. P나 X가 바뀌었기 때문이다. 공격이 성공하려면 P가 실행하려는 정확히 그 순간에 X를 바꿔야하기 때문이다. 구현은 매우 어렵다.

 

Solution. Concurrency control: Atomic operation / Lock object

 

Buffer overflow

Buffer overflow

Buffer가 가지고 있는 공간보다 더 많은 공간에 data를 저장하는 경우를 말한다. 다른 공간에 있는 정보를 침해할 수 있기 때문에 매우 위험한 상황이다.

 

Buffer overflow는 bound 확인을 안하거나, C나 C++같이 메모리 할당에 protection이 구현되어 있지 않은 언어에서 주로 발생한다. Buffer overflow에는 다음의 종류가 있다.

 

  • Stack overflow
    • Shell code
    • Return-to-libc
      Return address을 libc function으로 바꾸는 공격
    • Off-by-one
    • Overflow function pointers & longjmp buffers
  • Heap overfliow

 

Process memory

Process memory

Stack에는 local variables과 return address가 저장된다.

Heap는 malloc과 같은 함수에 의한 메모리 동적 할당 영역이다.

 

Stack frame

Stack frame

함수를 실행하게 되면, Stack에 PC를 저장하고 FP를 설정합니다.

그리고 나서 함수를 실행하기 위한 local var를 저장하기 위해 SP를 감소시킵니다.

 

Stack buffer overflow

Stack buffer overflow example function

위와 같은 함수를 실행하는 경우 stack은 다음과 같이 설정될 것입니다.

Stack

만약 *str에 136바이트 짜리 shell program "exec("/bin/sh")"을 저장하면 어떻게 될까요?

Buffer overflowed

Ret를 실행하였더니 shell을 실행하게 되어 user는 shell을 얻게 됩니다.

 

이 공격이 성공하기 위해서는 함수가 실행될 때, code for P의 position을 결정할 수 있어야 합니다. 그래야 return address를 수정해서 code for P를 실행할 수 있기 때문입니다. 물론 이 때의 위치는 stack 대비 code for P의 상대위치입니다. 또한 함수가 존재하는 동안에 overflow로 인해 program이 죽으면 안되는 조건이 있습니다.

 

Return address를 수정하는 공격을 Stack smashing attack이라고 합니다. Return address만 공격 대상일까요?또 다른 공격 가능 대상으로 Function pointer가 있습니다. 이런 공격 유형을 control hijecking이라고 합니다.

 

이런 공격은 어떻게 방어할까요?

 

Data Execution Prevention (DEP)

Hardware에서 stack이나 heap을 non-executable하게 만드는 방법입니다. Linux나 XP SP2이후의 Window에서 지원하는 기능입니다.

 

HW에서 막아버리니 답이 없어 보이지만, DEP를 bypass함으로 공격이 가능합니다. 

  • Code Reuse Attacks
    • Return-to-libc attack
    • Return-oriented programming
  • Break JIT(just-in-time) compiling

 

Return-to-libc attack

Return-to-libc attack

Shellcode를 stack에 넣는 것이 아니라, return address를 잘 알려진 library function으로 overwrite하는 것입니다. 이렇게 하면, return하면서 system()을 실행하게 되고 argument들로 프로그램을 실행할 수 있게 됩니다.

 

Return-oriented programming

Create arbitrary program

Code injection이나 return-to-libc 없이 aribtrary code를 실행하는 것이 목표입니다. 방법은 process의 address space에 이미 존재하는 instruction을 잘 조합해서 arbitrary program을 생성하는 것입니다.

 

Heap buffer overflow

Heap에는 stack처럼 다른 data가 저장되어 있습니다. Statically initialized var(Data)과 uninitialized buffer(Bss)가 있습니다. Stack과 마찬가지로 buffer overflow가 발생하면, 이 데이터들이 영향을 받을 수 있습니다.

Sample code

위 샘플 코드는 왜 문제가 될까요? Buffer overflow를 의식해서 길이 확인도 했는데 말입니다. 그 이유는 바로 strlen의 문제입니다. strlen같은 경우 문자열 종료 문자 '\0'전까지만 글자수를 인지해 길이를 return합니다. 이 경우 input으로 "Tis\0tory"로 들어오면 길이가 3밖에 안되는 거죠. 만약 LEN이 4였다면 실제로 buffer에 할당하는 길이는 4 이상이 되어서 buffer overflow가 발생하게 됩니다.

 

Preventing Buffer Overflow attack

  • Data execution prevention
    non-executable stack
  • Safe language
    Java, Rust
  • Safe library
    strncpy
  • Static code analysis
  • Dynamic code analysis
  • Run time checking
    StackGuard
  • Address Space Layout Randomization (ASLR)

위와 같은 방법을 사용해 예방할 수 있습니다.

 

Static analysis

Potential buffer overflow

소스 코드를 보면서 potential buffer overflow를 감지하는 방법입니다. 자동화할 수 있지만 exponential cost를 보여줍니다. 지금은 tool이 존재하기 때문에 해당 tool을 사용해 검사합니다.

 

Dynamic analysis

실제로 프로그램을 돌리면서 actual buffer overflow가 발생하는지 검사합니다. Static analysis보다 더 expensive합니다.

 

Run time checking: StackGuard

Canary inserted

Run time에 stack integrity를 유지하는지 검사합니다. 검사 대상은 canaries입니다. Fuction return하기 전에 검사합니다.

 

Canary Types

Random canary Terminator canary
Random string
every stack frame에 삽입
return 하기 전에 verify

공격자는 현재의 random canary를 알아야 공격 가능
canary = 0, newline, linefeed, EOF 로 설정
string copy가 canary까지만 실행됨, 뒤에는 copy 안됨
string function을 이용해 stack corrupt 불가능

공격자에게 canary가 노출됨

 

이를 통해서 canary는 완벽한 보호는 아님을 알 수 있다. 이 외에도 LibSafe(strcpy를 intercept해서 공간을 적절하게 할당하는지 확인함), StackShield(RA를 backup해두고 function return할 때, 비교검사를 수행함)가 있다.

 

Address Space Layout Randomization (ASLR)

앞선 공격들 모두 virtual address를 알아야 할 수 있는 공격이었다. 이게 가능한 이유는 대부분의 머신에서 비슷한 address를 사용하기 때문이다. 그렇다면 이 address를 unpredictable하게 만들면 공격이 불가능하지 않을까?라는 생각이 들 수 있다. ASLR은 여기서 시작해서 randomization을 이용해 address를 관리한다. 현재 대부분의 OS에서 사용하고 있는 방법이다.

 

물론 공격은 가능하다. 게속해서 random address를 추측하는 방법이나 injected attack code를 spraying하는 방법을 사용해 공격할 수 있다.

 

Format string problems

Format string problem

user="%s%s%s"이면 어떻게 될까요? 검증 단계가 존재한다면 program이 crash할 것(Denial of Service, DoS)입니다.  만약 그렇지 않다면 %s format string에 의해서 memory에 존재하는 내용이 출력될 것입니다. 이는 보안상의 문제가 발생할 수 있습니다.

 

또 만약 %n을 사용하면 어떻게 될까요? 마찬가지로 검증 단계가 존재하지 않는다면 %n 이전까지 출력된 문자열의 수(바이트)를 출력할 것입니다. 

 

데이터가 유출되는 만큼 위 코드는 다음과 같이 수정해서 사용해야합니다.

Fixed

 

"%n" format string attack

printf("%n", &x)

 

위 함수는 x의 값을 변경할 것입니다. %n(=이전까지 쓰인 값) &x(=x의 주소)이기 때문입니다.

 

Integer overflows

데이터 타입 캐스팅을 수행하면서 발생하는 Buffer overflow이다. 우선 대표적인 data type은 다음과 같다. C 기준이다.

Type Bits Range
short int 16 -$2^{15}$~$2^{15}-1$
unsigned short int 16 0~$2^{16}-1$
unsigned int 16 0~$2^{32}-1$
int 32 -$2^{31}$~$2^{31}-1$
long int 32 -$2^{31}$~$2^{31}-1$
signed char 8 -$2^{7}$~$2^{7}-1$
unsigned char 8 0~$2^{8}-1$

 

Phrack example

Sample code1

주어진 코드는 argv[1]을 통해 메모리를 관리하고 있다. 그래서 다음의 코드 실행결과가 나타난다.

>./program 5 hello
s = 5
hello

>./program 80 hello
Input too long!

>./program 65536 hello
Segmentation Fault

 

여기서 s는 unsigned short로 MAX 범위가 0xFFFF이다. 반면 i는 int로 0xFFFFFFFF이다. 따라서 만약 input으로 0x10000이 들어온다면 int가 unsigned short로 캐스팅되면서 0x0000이 되어버려(truncated) s=0이 된다. 결과적으로 s>80의 검증 체계를 bypass할 수 있게 되는 것이다.

 

Sample code2

문제에 있는 필기는 무시합니다. 틀림

여기서 문제는 memcpy에 음수가 사용될 수 있다는 것입니다. [1]에서 검사를 수행하지만, signed int인 len과 unsigned int인 sizeof를  비교하고 있기 때문에 만약 [1]에서 len이 음수라면 검사 과정을 통과하게 됩니다.

 

Sample code3

이 경우에는 만약 pos가 음수인 경우 엉뚱한 곳에 데이터를 할당하게 됩니다. table[pos]는 사실 *(table+(pos×sizeof(int)))에 할당하는 것이기 때문입니다.

 

Sample code4

x가 unsigned shor로 선언되지만 if문에서 그냥 사용시 int로 캐스팅됩니다. 따라서 주어진 함수는 다음처럼 컴파일러가 인식하게 됩니다.

if(int(x)+int(y) < int(x)) return false
return true

이 함수의 결과는 사실상 true임으로 컴파일 과정에서 함수 호출 시 ture가 반환되도록 최적화될 수 있습니다. 함수가 제 역할을 수행하지 못하게 되는 것입니다. 이 코드를 올바르게 수행하기 위해서는 다음처럼 수정해야 합니다.

if( unsigned short(x+y) < x ) return false
return true

'Study > Computer Security' 카테고리의 다른 글

10. Malware  (0) 2023.06.14
9. Program Analysis  (0) 2023.06.14
7. Database Security  (0) 2023.06.13
MIDTERM 정리  (0) 2023.04.18
6. Web security  (0) 2023.04.17