1. HTML injection

HTML injection 이란?
코드 인젝션 공격의 하위 개념으로 취약한 매개변수에 악의적인 HTML 코드를 삽입하는 공격

 

반사기법

: URL에 악의적인 HTML 태그를 삽입하여 링크를 클릭한 사용자의 PC에서 HTML 태그가 실행되게 하는 공격

- GET 방식으로 데이터를 전송할 경우, URL에 변수 이름과 입력 값을 노출한다.

 

② 저장기법

: 악의적인 HTML 태그를 데이터베이스에 저장하여 저장된 태그 내용을 확인한 사용자의 PC에 HTML 태그가 실행되게 하는 공격

 

  반사기법 저장기법
차이점 URL에 HTML 태그 삽입 서버의 DB에 저장

2. 아래 화면 출력하기

 

 

 

먼저 실습 환경 구축을 해주었다.

 

[ 난이도 low ]
img src: http://[본인IP]/bWAPP/images/bee_1.png
hint1. 페이지 소스 코드를 확인해봅시다.

 

First name에 hello, Last name에 world를 입력해보았더니 Welcome hello world가 출력되는걸 볼 수 있었다.

그리고 URL이 bWAPP/htmli_get.php?firstname=hello&lastname=world&form=submit 로 변경되었다.

이렇게 URL에 데이터가 노출된다는 것을 확인할 수 있었다.

 

그리고 페이지 소스에도 Welcome hello world가 추가되었다.

 

 

이번엔 h1태그를 이용해 hello를 써주고 h2태그를 이용해 world를 입력해보았더니 위와 같이 나타났다.

 

그리고 이와 같은 소스코드가 생겼다.

 

URL은 ?firstname=<h1>hello<%2Fh1>&lastname=<h2>world<%2Fh2> 이러한 형식으로 바뀌었다.

 

※ 출력해야 하는 화면 중 First name에 해당하는 부분이 /SUCCESS/ 이므로 <h1>SUCCESS</h1>이 들어가야 한다고 생각했다. 그리고 Last name 부분에는 이미지가 들어가야 하므로 문제에서 준 img src: http://[본인IP]/bWAPP/images/bee_1.png 코드를 활용하고자 하였다.

 

이미지 태그를 삽입하기 위해 <img src="http://[본인IP]/bWAPP/images/bee_1.png">이라고 작성해주었더니

다음과 같은 결과가 나왔다.

 

[난이도 medium]

hint1. low일 때 성공한 페이지와 비교해봅시다. 소스코드 차이점이 반드시 있습니다.

hint2. URL Encoding

먼저 위에서 한 것과 동일하게 First name에 hello, Last name에 world를 입력해주었더니 동일한 결과가 나왔다

 

하지만 h태그를 이용해 작성해주었더니

위에서 본 결과와 다르게 h태그가 적용된 결과가 아닌 태그 그대로를 출력함을 확인할 수 있었다.

URL은 ?firstname=hello<%2Fh1>&lastname=world<%2Fh2>의 형태로 동일하게 나타났다.

 

페이지 소스 코드에서 low와의 다른점을 찾을 수 있었다.

좌측은 low의 코드, 우측은 medium의 코드이다.

우측 코드를 보면 <이 &lt;로 변환되고, >이 &gt;로 변환된다는 것을 알 수 있었다.

&lt와 &gt에 대해 검색해보니 마크업기반 언어에서 수식 속 부등호를 사용하기 위한 코드라고 한다.

따라서 우측 코드에서는 <와 >을 코드가 아닌 부등호로 인식한다는 것을 알 수 있었다.

 

firstname=hello<%2Fh1>&lastname=world<%2Fh2>에서도 볼 수 있듯이 인코딩이 필요하다고 생각했다.

그래서 인코딩 변환기를 사용하여 위에서 사용한 코드를 인코딩해주었다.

<h1>SUCCESS</h1> → %3Ch1%3ESUCCESS%3C%2Fh1%3E
<img src="http://192.168.204.128/bWAPP/images/bee_1.png">
→ %3Cimg%20src%3D%22http%3A%2F%2F%2FbWAPP%2F192.168.204.128images%2Fbee_1.png%22%3E

이를 각각 입력해주었더니

다음과 같은 결과가 나타났다.

 

+) bee-box에서 cd /var/www/bWAPP 해주고 htmli_get.php파일을 열어주면 다음과 같은 화면이 나온다.

case 0, 1, 2는 각각 low, medium, high에 해당된다고 한다.

 

functions_external.php 파일을 열어보면

해당 함수들을 볼 수 있다.

특히, xss_check_2(medium)함수에서 <을 &lt로, >을 &gt로 변환하도록 코드가 작성되어 있는 것을 확인할 수 있다.

 

[난이도 high]

풀 수 없습니다. 풀 수 없는 이유를 정리해주세요.

hint1. include는 외부 파일을 포함하는 함수입니다. 확인해야 할 함수가 있는데 외부 파일에 있는 듯 합니다.

hint2. function_external.php 파일에 xss_check2 함수를 확인해봅시다.

 

위에서 찾은 xss_check_3을 활용해보자

(hight에 해당하는 함수는 xss_check_3이다)

코드를 보니 htmlspecialchars함수를 사용하고 있다.

htmlspecialchars 함수
: HTML에서 사용하는 특수문자를 UTF-8로 반환하는 함수

- & 는 &amp; 로 변환
- " 는 &quot; 로 변환
- ' 는 &#039; 로 변환
- < 는 &lt; 로 변환
- > 는 &gt;로 변환

구조 : string htmlspecialchars ( string $string , int $quote_style , string $charset , bool $double_encode )
string $string : HTML 엔티티로 변환할 문자열
int $quote_style 
- ENT_COMPAT : 기본모드, 큰따옴표만 변환
- ENT_QUOTES : 큰따옴표와 작은따옴표 둘다 변환
- ENT_NOQUOTES : 큰 따옴표와 작은따옴표 둘다 변환하지 않음
string $charset : 변환에 사용할 문자셋 지정
bool $double_encode : 이미 존재하는 HTML 엔티티를 encode 할지 여부 지정, 생략하는 true를 기본값으로 가진다.
return htmlspecialchars($data, ENT_QUOTES, $encoding);

이 코드에서 data로 받은 문자열을 UTF-8로 반환하는데 ", ' 둘 다 변환됨을 알 수 있다.

이 때문에 문제를 풀 수 없다.

 

★ 이를 통해 HTML Injection 공격을 방지하기 위해서는 htmlspecialchars함수를 사용하면 된다는 것을 알 수 있다.

https://jini00.tistory.com/123

4주차 내용은 위의 내용을 참고하면 된다.

 

https://github.com/jini-coding/ott_review_project

[ DB ]

contents_review라는 DB를 생성하였다.

 

id는 자동으로 1씩 증가시켜 중복되지 않는 식별자를 갖도록 하였다.

title, ott, category는 공백을 허용하지 않도록 하였고 score은 총 5점 만점으로 매길 수 있도록 하려고 한다.

comments는 공백을 허용하며 작성 시간도 나타나도록 하였다.

 

INSERT INTO contents(title, ott, category, score, comments, created) VALUES('오징어 게임', 'netflix', '영화', '5', 
'잔인함 속에 가려진 인간의 추악한 면, 그리고 다양성', NOW());

위와 같은 코드를 작성하여 밑에와 같은 데이터를 넣었다.

이는 나중에 웹페이지를 통해 작성받아 DB에 저장받도록 할 예정이다.

 

 

[ 메인 페이지 ]

 

메인페이지 코드는 다음과 같다. 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      h1{
        text-align: center;
        padding: 35px;
        color :#A50000;
        border-bottom:1px solid black;
        font-size: 5em;
        font-family : sans-serif;
      }
      img{
        box-shadow : 5px 5px 5px #BDBDBD;
        border-radius : 30px;
      }
      body{
        background-color:#FFD8D8;
        height : 2000px;
      }
      nav, section{
        display : flex;
        justify-content: center;
      }
      nav a{
        text-decoration: none;
        color : black;
        margin : 2em;
      }
      #underline{
        position : absolute;
        width:0;
        background-color: black;
        top:340px;
        left:0;
        height:4px;
        transition:0.5s;
      }
    </style>
    <title></title>
  </head>
  <body>
    <h1>OTT별 콘텐츠 추천</h1>
    <nav>
      <div id="underline"></div>
      <a href="About.php">About</a>
      <a href="Board.php">Board</a>
      <a href="Search.php">Search</a>
    </nav>
    <script>
      let under = document.getElementById("underline");
      let menu = document.querySelectorAll("nav a");
      menu.forEach((menu)=>
        menu.addEventListener("mouseover",(e)=>indicator(e))
      );
      function indicator(e){
        under.style.left=e.currentTarget.offsetLeft+"px";
        under.style.width=e.currentTarget.offsetWidth+"px";
        under.style.top=
           e.currentTarget.offsetTop+e.currentTarget.offsetheight+"px";
      }
    </script>
    <section>
      <a href="netflix.php"><img class="netflix_icon" src="netflix_img.PNG"></a>
      <a href="tving.php"><img class="tving_icon" src="tving_img.PNG"></a>
      <a href="watcha.php"><img class="watcha_icon" src="watcha_img.PNG"></a>
      <a href="disney.php"><img class="disney_icon" src="disney+_img.PNG"></a>
  </section>
  </body>
</html>

 

저번주와 크게 달라진 부분

  1. About, Board, Search 버튼
  2. 버튼 아래의 underline 
<nav>
  <div id="underline"></div>
  <a href="About.php">About</a>
  <a href="Board.php">Board</a>
  <a href="Search.php">Search</a>
</nav>

<script>
  let under = document.getElementById("underline");
  let menu = document.querySelectorAll("nav a");
  menu.forEach((menu)=>menu.addEventListener("mouseover",(e)=>indicator(e)));
  
  function indicator(e){
    under.style.left=e.currentTarget.offsetLeft+"px";
    under.style.width=e.currentTarget.offsetWidth+"px";
    under.style.top=e.currentTarget.offsetTop+e.currentTarget.offsetheight+"px";
  }
</script>

a태그로 각 버튼에 알맞는 페이지를 연결시켰다. 이를 nav태그로 한번에 감싸주었다. 이렇게 각자 버튼을 만들었다.

여기서 각 버튼에 마우스를 가져갔을 때 밑에 밑줄을 생기도록 하고 싶었다. 그래서 자바스크립트를 이용해주었다.

 

먼저 underline과 nav a태그인 메뉴들을 가져와주었고 각 메뉴에다가 mouseover이벤트가 일어날때마다 indicator 함수를 실행하도록 하였다. 그리고 addEventListener을 통해 자동으로 event(e)를 넘겨주었다. 이 event 안에 뭘 선택했는지가 나오게 된다.

 

indicator함수에서는 인자를 e로 받아왔다. 

div태그를 직사각형으로 보았을 때 아래쪽 변은 offsetWidth, 높이는 offsetheight, 위쪽 변과 상단 브라우저의 높이 차이는 offsetTop, 왼쪽 변과 좌측 브라우저의 간격 길이는 offsetLeft이다. 

underline을 그리는 시작점은 (offsetLeft, offsetTop+offsetheight)이다. 

따라서 left시작값인 x좌표는 offsetleft, width는 해당 메뉴(직사각형)의 너비만큼만 그려주면 되므로 offsetWidth, y좌표는 offsetTop+offsetheight로 지정해주었다.

 

<style>
  nav, section{
    display : flex;
    justify-content: center;
  }
  nav a{
    text-decoration: none;
    color : black;
    margin : 2em;
  }
  #underline{
    position : absolute;
    width:0;
    background-color: black;
    top:340px;
    left:0;
    height:4px;
    transition:0.5s;
  }
</style>

 

nav와 section부분의 위치를 가운데로 flex속성을 이용해 지정해주었다.

nav 중 a태그에 밑줄을 없애고 color과 margin을 지정해주었다.

그리고 div태그의 id인 underline에 위치, 색상 등을 지정해주며 transition을 이용해 underline이 더욱 부드럽게 이동하는 것처럼 보이도록 해주었다.

 

 

[ 세부 페이지 ]

각 페이지는 동일한 형태를 가지므로 NETFLIX페이지만 설명하도록 하겠다.

<div id="board"><h3>리뷰 작성</h3>
    <form action="create.php" method="POST">
      <p>제목 : <select name="title" required>
           <option value="none" selected disabled>==선택==</option>
           <option value="1">제목1</option>
           <option value="2">제목2</option>
           <option value="3">제목3</option>
         </select></p>
      <p>OTT : <select name="ott" required>
           <option value="none" selected disabled>==선택==</option>
           <option value="netflix">Netflix</option>
           <option value="watcha">Watcha</option>
           <option value="tving">Tving</option>
           <option value="disney+">Disney+</option>
         </select>
         &nbsp; &nbsp;
         카테고리 : <select name="category" required>
           <option value="none" selected disabled>==선택==</option>
           <option value="movie">영화</option>
           <option value="drama">드라마</option>
           <option value="entertain">예능</option>
         </select></p>

      <p>별점 :  (구현예정)</p>
      <p>내용 : <textarea name="review" rows="10" cols="50" placeholder="내용을 입력해주세요"></textarea></p>
      <p style="padding-left:440px;"><input type="submit" value="작성" style=""></p></div>

이번주에는 리뷰 작성 페이지에 OTT와 카테고리를 선택하는 부분을 추가했다. 둘 다 제목 선택하는 것과 마찬가지로 select로 내용을 입력받도록 했다. 무조건 입력해야 하는 내용이므로 required 속성을 적용했고 초기의 상태인 ==선택== 은 disabled를 이용해 선택이 불가능하도록 설정했다. 나머지 다른 선택지들은 각각의 내용에 따라 적당한 value 값을 설정하여 구분하도록 하였다.

 

Level 05

 

Sign up을 눌러보았다.

url에 /signup?next=confirm이 추가된 것을 확인할 수 있었다.

이는 GET방식으로 데이터를 보내는 것이다.

GET 방식
: 클라이언트의 데이터를 URL 뒤에 붙여보내는 방식

- ?를 통해 URL의 끝임을 알려주며, 동시에 데이터 표현의 시작점임을 알 수 있다.
- URL에 데이터가 노출되어 보안에 취약하다.

 

Next를 눌러보았다.

url 뒷부분이 confirm으로 바뀌었고 몇초 후에 다시 원래 페이지로 돌아간다.

 

 

<a href="{{ next }}">Next >></a>

이 a 태그를 통해 Next를 누르면 next url로 넘어간다.

그럼 Next를 누를 때 원래의 next 페이지가 아닌 다른 페이지로 넘어가도록 하면 될 것이다.

 

힌트 4에서 이러한 코드를 발견할 수 있었다.

따라서 ?next=javascript:alert(); 라고 입력해주었다.

Go를 누르고 Next까지 눌렀더니 다음단계로 넘어갈 수 있었다.

 


Level 06

 문제에 있는 XMLHttpRequest에 대해 찾아보았다.

XMLHttpRequest
:  서버와 통신을 하려면 서버에 데이터를 요청하고 결과를 받아와야 하는데 이때 서버와 주고받는 데이터를 쉽게 다룰 수 있는 방법

<사용법>
1. XMLHttpRequest 객체 생성
2. onreadystatechange에 함수 설정
3. open()함수 통해 요청 초기화
4. send()함수 통해 요청

 

이처럼 url #뒤의 문자를 조작하면 아래 Loaded gadget from 뒤의 문자도 바뀌게 된다.

 

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
 
    <script>
    function setInnerText(element, value) {
      if (element.innerText) {
        element.innerText = value;
      } else {
        element.textContent = value;
      }
    }
 
    function includeGadget(url) {
      var scriptEl = document.createElement('script');
 
      // This will totally prevent us from loading evil URLs!
      if (url.match(/^https?:\/\//)) {
        setInnerText(document.getElementById("log"),
          "Sorry, cannot load a URL containing \"http\".");
        return;
      }
 
      // Load this awesome gadget
      scriptEl.src = url;
 
      // Show log messages
      scriptEl.onload = function() { 
        setInnerText(document.getElementById("log"),  
          "Loaded gadget from " + url);
      }
      scriptEl.onerror = function() { 
        setInnerText(document.getElementById("log"),  
          "Couldn't load gadget from " + url);
      }
 
      document.head.appendChild(scriptEl);
    }
 
    // Take the value after # and use it as the gadget filename.
    function getGadgetName() { 
      return window.location.hash.substr(1) || "/static/gadget.js";
    }
 
    includeGadget(getGadgetName());
 
    // Extra code so that we can communicate with the parent page
    window.addEventListener("message", function(event){
      if (event.source == parent) {
        includeGadget(getGadgetName());
      }
    }, false);
 
    </script>
  </head>
 
  <body id="level6">
    <img src="/static/logos/level6.png">
    <img id="cube" src="/static/level6_cube.png">
    <div id="log">Loading gadget...</div>
  </body>
</html>

코드에서 볼 수 있듯이 https가 url에 존재하는지 여부만 판단하고 있다. http://가 포함되면 로드가 되지 않는다.

 

찾아보니 Data URL Scheme 방법이 있다고 한다.

Data URL Scheme
data:[자료타입],[데이터] 방식으로 데이터를 URL형태로 표현

이 문제에서는 data:javascript,alert(); 라고 입력하면 될 것 같아 url에 # 뒷부분에 추가해주었다.

 

그랬더니 성공했다.

Level 03

URL을 이용해 풀이해야 하는 문제이다. 

각 image를 눌러본 결과, URL의 #뒤에 숫자가 바뀌는 것을 볼 수 있었다.

 

소스코드를 보면 변수 html에 <img src='/static/level3/cloud" + num + ".jpg' />의 이미지 태그가 들어가있다. 

 

image가 3까지 밖에 없기 때문에 #뒤의 숫자를 1, 2, 3이 아닌 숫자로 바꿔줄 경우 아무 그림도 뜨지 않는다.

level 2에서처럼 잘못된 이미지 경로를 넣어주고 onerror을 실행시켜보기로 하였다. 

 

onerror="alert('error');"를 입력해주었다.

다음과 같은 창이 뜨며 다음단계로 넘어갈 수 있었다.

 

 

+) 사실 굳이 1, 2, 3이 아닌 수를 넣을 필요가 없었다. onerror 코드를 넣어주는 것만으로도 잘못된 이미지 경로를 주는 것과 마찬가지였다. 


Level 04

먼저 프로그램이 어떤식으로 작동하는 지 확인해보았다.

입력 칸에 3이라 쓰고 Create timer이라는 버튼을 눌렀을 때 URL에 ?timer=3가 추가되며 3초 타이머가 만들어졌다.

타이머가 종료됨과 동시에 Time is up!이라는 문구의 창이 떴다.

 

힌트 1에서 알려준 것 처럼 startTimer가 어디서 쓰이는 지 소스코드에서 확인해보았다.

startTimer은 onload속성에 의해 호출되고 있었다.

onload는 이미지가 로드된 직후에 실행된다.

 

https://xss-game.appspot.com/level4/frame?timer=('{{timer}}');의 형태로 전달되기 때문에 {{timer}}부분에 alert창을 띄우도록 해야한다.

');alert('error 라고 작성해주어 timer 뒷부분에 alert창이 들어가도록 하였다.

하지만 실행되지 않았다.

 

검색해 본 결과 힌트2에서 말하는 것처럼 세미콜론을 인코딩해서 넣어야 했던 것이다.

세미콜론을 인코딩하면 %3B가 나왔다.

세미콜론을 대신해 %3B를 넣어주었다.

 

실행해보았더니 %3B가 세미콜론으로 변하였고 다음과 같은 창이 떴다.

'Web Hacking > WEB Hacking 기초' 카테고리의 다른 글

[SISS] 웹 프로젝트 5주차  (0) 2022.01.26
[SISS] XSS Game 05, 06  (0) 2022.01.22
[SISS] 생활코딩 DATABASE-MySQL 정리  (0) 2022.01.08
[SISS] XSS Game 01, 02  (0) 2022.01.03
[SISS] XSS 공부  (0) 2021.12.31

+ Recent posts