<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://zevvoo.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://zevvoo.github.io/" rel="alternate" type="text/html" hreflang="ko" /><updated>2026-07-05T21:59:35+09:00</updated><id>https://zevvoo.github.io/feed.xml</id><title type="html">zevvoo</title><subtitle>보안 늦입문 컴공러의 스터디 아카이브입니다.
</subtitle><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><entry><title type="html">Studying Web Hacking (CSRF)</title><link href="https://zevvoo.github.io/2026/03/29/Web-CSRF/" rel="alternate" type="text/html" title="Studying Web Hacking (CSRF)" /><published>2026-03-29T00:00:00+09:00</published><updated>2026-03-29T00:00:00+09:00</updated><id>https://zevvoo.github.io/2026/03/29/Web-CSRF</id><content type="html" xml:base="https://zevvoo.github.io/2026/03/29/Web-CSRF/"><![CDATA[<h1 id="csrf">CSRF</h1>

<h1 id="csrf란">CSRF란?</h1>

<p>CSRF(Cross-Site Request Forgery) : 사이트 간 요청 위조</p>

<p><strong>공격자가 피해자의 브라우저를 속여, 피해자가 로그인된 사이트에 원하지 않는 요청을 보내게 만드는 공격</strong></p>

<ul>
  <li>공격자는 직접 로그인하지 않고 이미 로그인된 사용자를 이용</li>
  <li>브라우저가 자동으로 쿠키를 포함해서 요청 보내는 점을 악용</li>
</ul>

<p>발생하는 이유</p>

<p>브라우저의 기본 동작 때문에 발생</p>

<ul>
  <li>같은 사이트 요청이면 자동으로 쿠키 포함</li>
  <li>서버는 <strong>쿠키</strong>만 보고 “정상 사용자”라고 판단</li>
</ul>

<p>문제상황</p>

<ol>
  <li>사용자가 은행 사이트 로그인 상태</li>
  <li>공격자가 악성 페이지 유도</li>
  <li>해당 페이지가 은행 서버로 요청 전송</li>
  <li>브라우저가 자동으로 쿠키 포함</li>
  <li>서버는 정상 요청으로 착각</li>
</ol>

<p>성립조건</p>

<ul>
  <li>사용자가 대상 사이트에 로그인되어 있어야 함</li>
  <li>인증이 주로 쿠키, 세션, 자동 전송 자격증명에 의존해야 함</li>
  <li>서버가 CSRF 토큰, Origin 검사, Fetch Metadata 같은 검증을 안하거나 부실해야 함</li>
  <li>상태 변경 요청이 존재해야 함</li>
</ul>

<p>위험이 줄어드는 경우</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Authorization: Bearer …</code> 같은 헤더 기반 인증만 사용</li>
  <li>민감 요청마다 CSRF 토큰 검증</li>
  <li>SamSite 쿠키 정책 적절히 사용</li>
  <li>Origin / Referer / Sec-Fetch-Site 검증</li>
</ul>

<hr />

<h1 id="공격이-되는-이유">공격이 되는 이유</h1>

<p>정상 요청</p>

<ol>
  <li>사용자가 사이트에 로그인</li>
  <li>서버가 세션 쿠키 발급</li>
  <li>사용자가 버튼 클릭</li>
  <li>브라우저가 쿠키 포함 요청 전송</li>
  <li>서버가 처리</li>
</ol>

<p>CSRF 요청</p>

<ol>
  <li>사용자는 이미 로그인된 상태</li>
  <li>공격자가 악성 페이지 열게 함</li>
  <li>악성 페이지가 대상 사이트로 요청 생성</li>
  <li>브라우저가 쿠키 자동 첨부</li>
  <li>서버가 “로그인된 사용자의 정상 요청”으로 착각</li>
</ol>

<p>“사용자가 정말 클릭했는지”가 중요한게 아니라 “쿠키가 있는 요청”이 중요</p>

<hr />

<h1 id="대표-공격-형태">대표 공격 형태</h1>

<ol>
  <li>
    <p>고전적인 Form POST 기반 CSRF</p>

    <p>가장 유명한 형태고, 악성 페이지에 숨겨진 form을 두고 자동 제출</p>

    <p>예)</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cp">&lt;!doctype html&gt;</span>
 <span class="nt">&lt;html&gt;</span>
 	<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"document.forms[0].submit()"</span><span class="nt">&gt;</span>
 		<span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">"http://localhost:3000/account/email"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">&gt;</span>
 			<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"attacker@lab.local"</span><span class="nt">&gt;</span>
 		<span class="nt">&lt;/form&gt;</span>
 	<span class="nt">&lt;/body&gt;</span>
 <span class="nt">&lt;/html&gt;</span>
</code></pre></div>    </div>

    <p>이게 먹히려면,</p>

    <ul>
      <li>대상 서버가 쿠키 세션 기반 인증</li>
      <li>CSRF 토큰 검사 없음</li>
      <li>쿠키가 cross-site 요청에 포함되는 상황이어야 함</li>
    </ul>
  </li>
  <li>
    <p>GET 요청 악용</p>

    <p>상태 변경을 <code class="language-plaintext highlighter-rouge">GET</code>으로 처리하면 매우 위험</p>

    <p>예)</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"http://localhost:3000/user/delete?id=10"</span><span class="nt">&gt;</span>
</code></pre></div>    </div>

    <p>또는</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"http://localhost:3000/account/logout"</span><span class="nt">&gt;</span>클릭<span class="nt">&lt;/a&gt;</span>
</code></pre></div>    </div>

    <p><code class="language-plaintext highlighter-rouge">GET</code> 은 원래 조회용</p>

    <p>상태 변경을 <code class="language-plaintext highlighter-rouge">GET</code> 으로 두는 것은 최신 기준에서도 여전히 매우 나쁜 설계</p>
  </li>
  <li>
    <p>최신 관점: SameSite 때문에 예전보다 달라진 점</p>

    <p>요즘은 브라우저가 예전처럼 아무 쿠키나 다 보내주지 않음</p>

    <p>현대 브라우저 기준 핵심</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">SameSite-=Lax</code> 가 사실상 기본 동작인 경우가 많음</li>
      <li><code class="language-plaintext highlighter-rouge">SameSite=Strict</code> 는 더 강하게 차단</li>
      <li><code class="language-plaintext highlighter-rouge">SameSite=None</code> 을 쓰려면 <code class="language-plaintext highlighter-rouge">Secure</code> 가 필요</li>
      <li>
        <p>SameSite 쿠키 속성 설명</p>

        <p>쿠키의 SameSite 속성은 CSRF 요청 시 쿠키를 전송할지 여부를 제어해서 CSRF 공격을 방어</p>

        <ul>
          <li>
            <p><code class="language-plaintext highlighter-rouge">SameSite-=Lax</code> : 기본값</p>

            <p>| 요청 유형 | 쿠키 전송 여부 |
  | — | — |
  | 같은 사이트 요청 | 전송 O |
  | 최상위 탐색 (링크 클릭, GET) | 전송 O |
  | 크로스 사이트 POST/PUT/PATCH | 차단 X  |
  | <code class="language-plaintext highlighter-rouge">&lt;img&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code> 등 서브리소스 | 차단 X  |</p>
            <ul>
              <li>외부 사이트에서 링크를 클릭해 이동할 때는 쿠키가 전송되지만, 폼 POST나 비동기 요청에서는 차단</li>
              <li>대부분의 일반 웹사이트에 적합한 균형잡힌 기본값이다</li>
            </ul>
          </li>
          <li>
            <p><code class="language-plaintext highlighter-rouge">SameSite=Strict</code> : 가장 엄격한 설정</p>

            <p>| 요청 유형 | 쿠키 전송 여부 |
  | — | — |
  | 같은 사이트 요청 | 전송 O |
  | 외부 사이트에서의 <strong>모든</strong> 요청 | 차단 X |</p>
            <ul>
              <li>외부 링크 클릭 후 이동해도 쿠키 미전송 → 로그인이 풀린 것처럼 보일 수 있다</li>
              <li>금융, 관리자 페이지 등 보안이 최우선인 서비스에 적합</li>
            </ul>
          </li>
          <li>
            <p><code class="language-plaintext highlighter-rouge">SameSite=None + Secure</code> : 크로스 사이트 요청에서도 쿠키를 항상 전송</p>

            <p>| 요청 유형 | 쿠키 전송 여부 |
  | — | — |
  | 같은 사이트 요청 | 전송 O |
  | 크로스 사이트 모든 요청 | 전송 O |</p>
            <ul>
              <li>반드시 <code class="language-plaintext highlighter-rouge">Secure</code> 속성과 함께 사용해야 함</li>
            </ul>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  Set-Cookie: token=abc123; SameSite=None; Secure
</code></pre></div>            </div>

            <ul>
              <li><code class="language-plaintext highlighter-rouge">Secure</code> 없이 <code class="language-plaintext highlighter-rouge">SameSite=None</code> 만 설정하면 브라우저가 쿠키를 거부</li>
              <li><code class="language-plaintext highlighter-rouge">Secure</code> 속성 자체는 HTTPS 연결에서만 쿠키를 전송하도록 강제</li>
              <li>사용 사례: 서드파티 위젯, OAuth/SSO, 결제 모듈, 임베드된 iframe 등
                <ul>
                  <li>
                    <p>서드파티 위젯: 내 사이트에 다른 회사의 기능을 붙여넣는 것</p>

                    <p>예) 내 블로그에 다른 사이트의 위젯(링크)을 걸어놓는 것</p>

                    <p><code class="language-plaintext highlighter-rouge">SameSite=None</code>이 없으면 → 쿠키 전송 안됨 → 항상 로그아웃 상태로 보임</p>
                  </li>
                  <li>
                    <p>OAuth / SSO (소셜 로그인) : 네이버 로그인, 구글 로그인 버튼</p>

                    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  예시
                    
  (1) 사용자가 coupang.com에서 "네이버로 로그인" 클릭
  (2) naver.com 로그인 페이지로 이동
  (3) 네이버에서 인증 완료
  (4) coupang.com으로 돌아옴 + "이 사람 인증됐어요" 토큰 전달
  (5) coupang.com이 그 토큰으로 로그인 처리
</code></pre></div>                    </div>

                    <p>(4)번 단계에서 네이버 → coupang.com 으로 쿠키/토큰이 넘어오는 순간이 크로스 사이트 요청</p>

                    <p><code class="language-plaintext highlighter-rouge">SameSite=Strict</code>면 이 순간 쿠키가 차단돼서 로그인이 실해할 수 있음</p>
                  </li>
                  <li>
                    <p>결제 모듈 : 내 쇼핑몰에서 토스/네이버페이 결제창을 띄우는 것</p>

                    <p>위 로그인과 비슷</p>
                  </li>
                  <li>
                    <p>임베드된 iframe : 내 페이지 안에 다른 사이트 페이지를 액자처럼 끼워 넣는 것</p>

                    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c">&lt;!-- 내 사이트 (mysite.com) --&gt;</span>
  <span class="nt">&lt;h1&gt;</span>강의 페이지<span class="nt">&lt;/h1&gt;</span>
                    
  <span class="nt">&lt;iframe</span> <span class="na">src=</span><span class="s">"https://youtube.com/embed/영상ID"</span><span class="nt">&gt;</span>
    <span class="c">&lt;!-- 여기 안은 youtube.com 의 세계 --&gt;</span>
    <span class="c">&lt;!-- 유튜브 로그인 쿠키가 있어야 --&gt;</span>
    <span class="c">&lt;!-- 광고 없이 보기, 저장 등이 가능 --&gt;</span>
  <span class="nt">&lt;/iframe&gt;</span>
</code></pre></div>                    </div>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
          <li>
            <p><code class="language-plaintext highlighter-rouge">SameSite</code> 속성 단독 사용</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  Set-Cookie: session=xyz; Secure; HttpOnly
</code></pre></div>            </div>

            <ul>
              <li>HTTPS 통신에서만 쿠키 전송</li>
              <li>네트워크 도청(Man-in-the-Middle) 공격 방어</li>
              <li><code class="language-plaintext highlighter-rouge">HttpOnly</code> 와 함께 쓰면 JS에서 쿠키 접근도 차단되어 <strong>XSS 방어까지 가능</strong></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>

    <p>의미</p>

    <ul>
      <li>예전의 단순 cross-site POST CSRF는 과거보다 성공 조건이 까다로워짐</li>
      <li>하지만 여전히 끝난 공격이 아님</li>
      <li>특히 아래 경우는 여전히 위험</li>
    </ul>

    <p>위험이 남는 경우</p>

    <ul>
      <li>쿠키가 <code class="language-plaintext highlighter-rouge">SameSite=None; Secure</code></li>
      <li>상태 변경이 <code class="language-plaintext highlighter-rouge">GET</code></li>
      <li>로그인 직후의 예외 동작</li>
      <li>클라이언트 사이드 CSRF</li>
      <li>잘못된 CORS/Origin 처리</li>
      <li>OAuth, SSO, 로그인 플로우의 <code class="language-plaintext highlighter-rouge">state</code> 검증 부재</li>
    </ul>
  </li>
</ol>

<hr />

<h1 id="csrf-유형-최신유형">CSRF 유형 (최신유형)</h1>

<h3 id="client-side-csrf">Client-Side CSRF</h3>

<ul>
  <li>기존 CSRF - 서버가 취약</li>
  <li>Client-Side CSRF - 프론트엔드 JS가 취약</li>
</ul>

<p>예를 들어 프론트 코드가 URL 파라미터를 읽어서 내부 API 요청을 만들어버리면, 공격자는 그 입력값을 조작해서 브라우저가 정상 토큰과 쿠키까지 붙인 요청을 보내게 만들 수 있음</p>

<p>취약 예시)</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">api</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">fetch</span><span class="p">(</span><span class="nx">api</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">X-CSRF-Token</span><span class="dl">"</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">csrfToken</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>위험한 이유</p>

<ul>
  <li>토큰도 정상적으로 붙음</li>
  <li>쿠키도 정상적으로 붙음</li>
  <li>서버 입장에서는 “정상 SPA 요청”처럼 보임</li>
</ul>

<p>즉, <strong>“CSRF 토큰이 있어도 프론트가 공격자 입력으로 요청을 만들면 뚫릴 수 있다”</strong>는 점이 중요</p>

<h3 id="login-csrf--oauth-csrf">Login CSRF / OAuth CSRF</h3>

<p>사용자를 공격자 계정으로 로그인시키는 형태</p>

<p>예)</p>

<ul>
  <li>피해자가 쇼핑몰에 접속</li>
  <li>공격자가 만든 계정으로 로그인되게 유도</li>
  <li>피해자가 자기 정보라고 생각하고 결제/개인정보 입력</li>
  <li>실제론 공격자 계정에 데이터가 쌓임</li>
</ul>

<p>OAuth/OIDC에선 state 검증 부재가 대표적</p>

<hr />

<h1 id="공격-페이로드를-볼-때-알아야할-것">공격 페이로드를 볼 때 알아야할 것</h1>

<ol>
  <li>인증 수단이 무엇인가
    <ul>
      <li>쿠키 기반인지, Authorization 헤더 기반인지
        <ul>
          <li>
            <p>쿠키 기반 (CSRF 취약)</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  로그인 시:
  서버 → 브라우저: Set-Cookie: session=abc123
            
  이후 모든 요청:
  브라우저 → 서버: Cookie: session=abc123  ← 자동으로 붙음!
</code></pre></div>            </div>

            <p>브라우저가 자동으로 쿠키를 보내기 때문에, 공격자가 만든 페이지에서 요청해도 쿠키가 딸려감 (전통적인 웹사이트 로그인 방식)</p>
          </li>
          <li>
            <p>Authorization 헤더 기반 (CSRF 안전)</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  로그인 시:
  서버 → 클라이언트: { token: "eyJhbG..." }  ← JS가 직접 저장
            
  이후 모든 요청:
  JS 코드가 직접: Authorization: Bearer eyJhbG...  ← 수동으로 붙여야 함
</code></pre></div>            </div>

            <p>JS가 직접 헤더를 붙여야 하므로, 공격자 사이트의 HTML form은 헤더를 임의로 추가 못함 → CSRF 공격 자체가 불가능 (현대적인 SPA + JWT 방식)</p>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>세션 쿠키의 SameSite 값은 무엇인가</li>
  <li>대상 요청이 GET 인가 POST 인가
    <ul>
      <li>GET vs POST 방식의 차이
        <ul>
          <li>
            <p>GET 요청 → 공격이 매우 쉬움</p>

            <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"https://......"</span><span class="nt">&gt;</span>
</code></pre></div>            </div>

            <p>이미지, 링크, scipt src 등 태그 하나로 공격이 가능</p>

            <p>피해자가 클릭을 안해도 페이지 로드만으로 실행</p>
          </li>
          <li>
            <p>POST 요청 → 공격이 복잡하지만 그래도 가능</p>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>서버가 Origin / Referer 를 확인하는가</p>

    <p>브라우저는 “어디에서 왔는지 정보”를 헤더에 자동으로 담는다</p>

    <p>예)</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 피해자가 evil.com 에서 mybank.com 으로 요청 시:
    
 Origin:  https://evil.com
 Referer: https://evil.com/hack.html
</code></pre></div>    </div>

    <ul>
      <li>Origin → 요청의 출처 도메인만</li>
      <li>Referer → 요청 직전 페이지의 전체 URL</li>
    </ul>
  </li>
  <li>
    <p>Sec-Fetch-Site를 확인하는가</p>

    <p>브라우저가 자동으로 붙여주는 헤더로, “이 요청이 어디서 시작됐는지”를 명확하게 알려줌</p>

    <p>Referer / Origin 보다 훨씬 정확하고 JS로 위조가 불가능</p>

    <table>
      <thead>
        <tr>
          <th>값</th>
          <th>의미</th>
          <th>예시</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">same-origin</code></td>
          <td>완전히 같은 도메인</td>
          <td>mybank.com → mybank.com/api</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">same-site</code></td>
          <td>같은 최상위 도메인</td>
          <td>app.mybank.com → api.mybank.com</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">cross-site</code></td>
          <td>완전히 다른 도메인 ← <strong>CSRF는 여기</strong></td>
          <td>evil.com → mybank.com</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">none</code></td>
          <td>주소창 직접 입력, 북마크</td>
          <td>-</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>프론트엔드가 공격자 입력으로 API 요청을 조립하는가</li>
</ol>

<hr />

<h1 id="실습용-예시">실습용 예시</h1>

<h3 id="1-form-post-기반-csrf">1. Form POST 기반 CSRF</h3>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!doctype html&gt;</span>
<span class="nt">&lt;html&gt;</span>
	<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"document.forms[0].submit()"</span><span class="nt">&gt;</span>
		<span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">"http://localhost:3000/account/email"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"attacker@lab.local"</span><span class="nt">&gt;</span>
		<span class="nt">&lt;/form&gt;</span>
	<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<ul>
  <li>사용자가 <code class="language-plaintext highlighter-rouge">localhost:3000</code> 에 이미 로그인되어 있음</li>
  <li>브라우저가 해당 사이트의 세션 쿠키를 자동으로 붙임</li>
  <li>서버가 <code class="language-plaintext highlighter-rouge">POST /account /email</code> 요청에 대해 CSRF 토큰을 검사하지 않음</li>
  <li>
    <p>서버는 “쿠키가 있으니 본인 요청이겠지”라고 잘못 판단</p>

    <p>→ 로그인된 <strong>“요청”</strong> 인지 확인만 하고, <strong>사용자가 직접 보낸 요청</strong> 인지 확인하지 않음</p>

    <p>→ 취약점 : <strong>자동 제출 form</strong> 자체가 아니라, <strong>세션 쿠키만 보고 요청을 신뢰</strong>하는 서버 로직</p>
  </li>
</ul>

<h3 id="2-get-요청-기반-csrf">2. GET 요청 기반 CSRF</h3>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"http://localhost:3000/user/delete?id=10"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<ul>
  <li>브라우저는 이미지 로드를 위해 자동으로 GET 요청을 보냄</li>
  <li>사용자가 로그인된 상태면 관련 쿠키도 함께 전송될 수 있음</li>
  <li>
    <p>서버가 <code class="language-plaintext highlighter-rouge">GET /user/delete?id=10</code> 같은 요청으로 실제 상태 변경을 수행함</p>

    <p>→ 상태를 바꾸는 기능을 GET 으로 만든 설계 실수</p>

    <p>→ GET 은 원래 조회용이어야 하는데, 삭제/변경 같은 동작을 넣어버렸기 때문</p>
  </li>
  <li>img, iframe, a, script 등 여러 HTML 요소가 GET 요청을 쉽게 발생시킴</li>
  <li>사용자는 클릭도 안했는데 요청이 나갈 수 있음</li>
</ul>

<h3 id="3-자동-로그아웃--설정-변경형-csrf">3. 자동 로그아웃 / 설정 변경형 CSRF</h3>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"http://localhost:3000/account/logout"</span><span class="nt">&gt;</span>클릭<span class="nt">&lt;/a&gt;</span>
</code></pre></div></div>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"http://localhost:3000/settings/toggle?darkmode=off"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<ul>
  <li>로그아웃이나 설정 변경이 단순 <strong>”URL 호출”</strong>만으로 처리됨</li>
  <li>요청 출처 검증이 없음</li>
  <li>
    <p>사용자가 해당 사이트에 로그인된 상태면 쿠키가 붙어서 정상 요청처럼 처리될 수 있음</p>

    <p>→ 민감 기능이 너무 쉽게 호출되도록 설계</p>

    <p>→ 서버가 “어디서 온 요청인지”, “사용자가 실제로 의도했는지” 확인하지 않음</p>
  </li>
</ul>

<h3 id="4-로그인-csrf">4. 로그인 CSRF</h3>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!doctype html&gt;</span>
<span class="nt">&lt;html&gt;</span>
	<span class="nt">&lt;body</span> <span class="na">onload=</span><span class="s">"document.forms[0].submit()"</span><span class="nt">&gt;</span>
		<span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">"http://localhost:3000/login"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"username"</span> <span class="na">vlaue=</span><span class="s">"attacker_demo"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">value=</span><span class="s">"demo1234"</span><span class="nt">&gt;</span>
		<span class="nt">&lt;/form&gt;</span>
	<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<ul>
  <li>로그인 요청에도 CSRF 방어가 없으면, 공격자가 자기 계정 정보로 피해자 브라우저에서 로그인 요청을 보낼 수 있음</li>
  <li>서버는 새 세션을 만들고 브라우저에 그 세션 쿠키를 심어줌</li>
  <li>
    <p>피해자는 자기 계정이 아니라 공격자 계정으로 로그인된 상태가 됨</p>

    <p>→ 개발자가 “로그인 전이니깐 CSRF 필요 없다”고 오해한 것</p>

    <p>→ 하지만 로그인도 인증 상태를 바꾸는 중요한 상태 변경 요청</p>
  </li>
</ul>

<h3 id="5-client-side-csrf">5. Client-Side CSRF</h3>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">api</span><span class="dl">"</span><span class="p">);</span>

<span class="nf">fetch</span><span class="p">(</span><span class="nx">api</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">X-CSRF-Token</span><span class="dl">"</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">csrfToken</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<ul>
  <li>프론트엔드 코드가 공격자 제어 입력값(api)을 그대로 요청 대상으로 사용함</li>
  <li>브라우저는 <code class="language-plaintext highlighter-rouge">credentials: “include”</code> 때문에 쿠키를 포함</li>
  <li>앱이 보유한 정상 CSRF 토큰도 함께 전송할 수 있음</li>
  <li>
    <p>서버 입장에서는 “정상 앱이 보낸 요청”처럼 보임</p>

    <p>→ 서버보다 프론트엔드가 요청 대상을 안전하게 통제하지 못한 것</p>

    <p>→ “요청 URL/동작은 앱 내부에서 고정되어야 하는데, 공격자 입력이 그 흐름에 끼어든 것”</p>
  </li>
  <li>고전적 CSRF 방어인 토큰이 있어도 우회될 수 있기 때문에 최신 환경에서는 프론트엔드 설계까지 같이 봐야함!</li>
</ul>

<h3 id="6-json-api">6. JSON API</h3>

<ul>
  <li>
    <p>예시 상황</p>

    <p>서버가 <code class="language-plaintext highlighter-rouge">/api/profile</code> 같은 JSON API를 제공</p>

    <p>인증은 여전히 세션 쿠키 기반</p>

    <p>서버가 <code class="language-plaintext highlighter-rouge">Origin</code> 검사나 CSRF 토큰 검증을 안 함</p>
  </li>
  <li>
    <p>가능한 이유</p>

    <p>많은 개발자가 “JSON이면 폼이 아니니 안전하다”고 착각함</p>

    <p>하지만 핵심은 요청 형식이 아니라 인증 방식</p>

    <p>쿠키 기반이면 브라우저가 자동 전송할 가능성이 있어 CSRF 관점 검토가 필요</p>
  </li>
</ul>

<hr />

<h1 id="방어-방법">방어 방법</h1>

<p>CSRF 방어의 핵심은 로그인된 요청인지 확인하는 것이 아니라, <strong>사용자가 실제로 의도한 요청</strong>인지 검증하는 것</p>

<p>위 공격 페이로드에서도 언급했지만 한번 더 정리하자면,</p>

<ol>
  <li>CSRF 토큰 검증
    <ul>
      <li>상태 변경 요청마다 예측 불가능한 토큰을 포함시키고 서버에서 검증</li>
      <li><code class="language-plaintext highlighter-rouge">POST</code>, <code class="language-plaintext highlighter-rouge">PUT</code>, <code class="language-plaintext highlighter-rouge">PATCH</code>, <code class="language-plaintext highlighter-rouge">DELETE</code> 같은 요청에 적용</li>
      <li>토큰이 있어도 프론트엔드가 공격자 입력으로 요청을 만들면 우회될 수 있으니, 토큰만 믿으면 안됨</li>
    </ul>
  </li>
  <li>SameSite 쿠키 설정
    <ul>
      <li>세션 쿠키에 <code class="language-plaintext highlighter-rouge">SameSite=Lax</code> 또는 엄격한 <code class="language-plaintext highlighter-rouge">Strict</code>를 적용</li>
      <li>외부 사이트에서 cross-site 요청 시 쿠키 전송을 줄여주지만 토큰을 대체하지는 못함</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Origin</code> / <code class="language-plaintext highlighter-rouge">Referer</code> 검증
    <ul>
      <li>민감 요청에서 출처가 자사 도메인인지 확인</li>
      <li>문자열 포함 검사 말고, 정확한 origin 비교를 해야함</li>
      <li>브라우저 환경에 따라 <code class="language-plaintext highlighter-rouge">Origin</code>이 없을 수 있어 <code class="language-plaintext highlighter-rouge">Referer</code>를 보조적으로 보는 경우도 많음</li>
    </ul>
  </li>
  <li>Fetch Metadata 검사
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Sec-Fetch-Site</code>, <code class="language-plaintext highlighter-rouge">Sec-Fetch-Mode</code> 같은 헤더를 활용해 cross-site 상태 변경 요청을 차단</li>
      <li>최신 브라우저 환경에서 실무적으로 매우 유용한 추가 방어</li>
    </ul>
  </li>
  <li>상태 변경 기능에 <code class="language-plaintext highlighter-rouge">GET</code> 금지
    <ul>
      <li><code class="language-plaintext highlighter-rouge">GET</code> 은 조회 전용으로만 사용</li>
      <li>삭제, 수정, 로그아웃, 결제 같은 동작은 모두 POST 계열로 분리해야 함</li>
    </ul>
  </li>
  <li>민감 기능에 재인증/추가 확인
    <ul>
      <li>비밀번호 변경, 결제, 계정 삭제는 비밀번호 재입력, OTP, WebAuthn 같은 추가 검증을 둠</li>
      <li>CSRF가 일부 우회되어도 피해를 줄일 수 있음</li>
    </ul>
  </li>
  <li>프론트엔드 요청 흐름 통제
    <ul>
      <li>URL 파라미터나 외부 입력으로 API 경로, 매서드, 액션을 직접 결정하지 않도록 설계</li>
      <li>SPA에서는 이 부분이 요즘 특히 중요</li>
    </ul>
  </li>
</ol>

<hr />

<h1 id="현재-실무에서-중요한-포인트">현재 실무에서 중요한 포인트</h1>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SameSite</code>만으로 끝났다고 생각하면 안됨
    <ul>
      <li>예전보다 고전적 CSRF는 줄었지만, 완전히 사라진게 아님</li>
      <li>쿠키 정책, 예외 흐름, 잘못된 서버 설계 때문에 여전히 발생</li>
    </ul>
  </li>
  <li>Client-Side CSRF 를 같이 봐야함
    <ul>
      <li>요즘은 서버만이 아니라 프론트엔드 코드가 공격자 입력으로 요청을 조립하는지가 매우 중요</li>
      <li>토큰이 있어도 프론트가 잘못 짜여 있으면 정상 토큰을 붙여 공격 요청이 나갈 수 있음</li>
    </ul>
  </li>
  <li>Login CSRF 와 OAuth/OIDC state 검증도 중요함
    <ul>
      <li>로그인 요청은 보호 안해도 된다고 생각하면 위험함</li>
      <li>SSO, 소셜 로그인, 연동 기능에서는 state 검증 누락이 큰 문제로 이어질 수 있음</li>
    </ul>
  </li>
  <li>CORS 와 CSRF 를 혼동하면 안됨
    <ul>
      <li>CORS는 응답 읽기 제어이고, CSRF는 요청 위조 문제</li>
      <li>CORS를 잘 설정해도 CSRF가 자동으로 해결되지는 않음</li>
    </ul>
  </li>
  <li>XSS 와 함께 봐야함
    <ul>
      <li>XSS가 있으면 CSRF 토큰 탈취나 정상 요청 위조가 가능해져 방어가 무너질 수 있음</li>
      <li>실무에서는 CSRF 방어와 XSS 방어를 같이 설계해야함</li>
    </ul>
  </li>
</ul>]]></content><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><category term="Security" /><category term="Web" /><category term="Web" /><category term="웹해킹" /><category term="해킹" /><category term="CSRF" /><summary type="html"><![CDATA[CSRF]]></summary></entry><entry><title type="html">LLM Neuroanatomy: How I Topped the LLM Leaderboard Without Changing a Single Weight</title><link href="https://zevvoo.github.io/2026/03/23/LLM-Neuroanatomy/" rel="alternate" type="text/html" title="LLM Neuroanatomy: How I Topped the LLM Leaderboard Without Changing a Single Weight" /><published>2026-03-23T00:00:00+09:00</published><updated>2026-03-23T00:00:00+09:00</updated><id>https://zevvoo.github.io/2026/03/23/LLM-Neuroanatomy</id><content type="html" xml:base="https://zevvoo.github.io/2026/03/23/LLM-Neuroanatomy/"><![CDATA[<p>2026-03-22
https://dnhkng.github.io/posts/rys/</p>

<p>위의 깃허브 링크는 David Noel Ng라는 개인 연구자가 LLM 레이어 복제 실험에 관하여 블로그에 올린 내용이다.</p>

<p>짧게 요약 정리해보자면,</p>

<h3 id="1-두-가지-힌트에서-시작">1. 두 가지 힌트에서 시작</h3>

<p>모델에게 Base64로 인코딩된 질문을 보내도 제대로 이해하고 Base64로 답한다는 것을 발견했다. 이것이 시사하는 바로는 초기 레이어가 번역기처럼 입력을 추상적 내부 표현으로 변환하고, 후기 레이어가 그걸 다시 출력 언어로 변환한다는 것이다. 그렇다면 중간 레이어는 순수하게 “사고”를 담당하는게 아닐까? 하는 것이 첫 번째 힌트였다.</p>

<p>두 번째 힌트는 Goliath-120B라는 모델이었는데, 두 개의 완전히 다른 70B 모델 레이어를 뒤섞어도 작동했다는 점이다.</p>

<p>레이어들이 생각보다 훨씬 유연하다는 증거였다.</p>

<h3 id="2-실험-방법-뇌-스캐너-만들기">2. 실험 방법: “뇌 스캐너” 만들기</h3>

<p>RTX 4090두 장으로 80개 레이어짜리 Qwen2-72B 모델에 대해 가능한 모든 레이어 쌍 조합 3,241가지 테스트를 했다.</p>

<p>각 조합마다</p>

<ul>
  <li>어려운 암산 문제 (7자리 수의 세제곱근을 직관으로 맞히기)</li>
  <li>감성 지능 테스트 (사회적 시나리오에서 감정 강도 예측)</li>
</ul>

<p>두 가지 완전히 다른 능력을 측정해서, 특정 레이어 구간이 진짜 범용 추론을 담당하는지 찾았다.</p>

<h3 id="3-핵심-발견-회로-단위로-작동한다">3. 핵심 발견: 회로 단위로 작동한다</h3>

<p>단일 레이어를 n번 반복해도 효과가 없었고, 7개 레이어 블록 단위로 복제해야만 효과가 있었다.</p>

<p>이는 중간 레이어들이 독립적인 작업을 하는 것이 아니라, 레시피처럼 여러 레이어가 하나의 완결된 “회로”로 작동하기 때문이다.</p>

<h3 id="4-결과">4. 결과</h3>

<p>45~51번 레이어(7개)를 한 번 더 통과시킨 것만으로 72B → 78B 모델이 됐고, 6개 벤치마크 중 5개에서 성능 향상 —MUST +17.72%, MATH +8.16% — 으로 HuggingFace 오픈 LLM 리더보드 1위를 달성했다.</p>

<h3 id="5-이후-영향">5. 이후 영향</h3>

<p>2026년 초 기준 오픈 LLM 리더보드 상위 4개 모델이 모두 RYS-XLarge를 기반으로 만들어진 모델이다.</p>

<p>다른 연구자들이 RYS 위에 파인튜닝을 더 쌓아서 성능을 계속 올린 결과이다.</p>

<p>이 실험으로, 트랜스포머 안에 기능적 해부학이 실제로 존재한다는 걸 보여줬다.</p>

<p>초기 레이어(번역), 중간 레이어(추론 회로), 후기 레이어(출력 변환)로 역할이 나뉘어 있고, 더 큰 모델일수록 이 구분이 더 명확해진다는 가설을 실험으로 뒷받침했다는 내용이다.</p>

<p>그리고 이 모든 걸 수백억짜리 클러스터 없이 지하실에서 RTX-4090 두 장으로 해냈다는 점이 인상적인 부분이다.</p>

<hr />

<h2 id="트랜스포머-구조">트랜스포머 구조</h2>

<p>LLM은 <strong>레이어(층)</strong>가 수십 개 쌓인 구조이다. 입력 텍스트가 맨 아래 레이어붙 들어가서, 위로 올라가며 처리되고, 맨 위에서 답이 나오는 구조다. 딥러닝에서 트랜스포머에 대해 배웠다면 쉽게 이해가 될 것이다.</p>

<ul>
  <li>
    <p>일반 트랜스포머 구조</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  입력 텍스트
     ↓
  [레이어 0]  ← 초기 해석
  [레이어 1]
  [레이어 2]
     ...
  [레이어 45] ← 중간: 추론/사고
  [레이어 46]
     ...
  [레이어 79] ← 후기: 출력 변환
     ↓
  출력 텍스트
</code></pre></div>    </div>
  </li>
  <li>
    <p>레이어 복제</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    입력
     ↓
  [레이어 0 ~ 44]
  [레이어 45 ~ 51]  ← 1회차 통과
  [레이어 45 ~ 51]  ← 2회차 통과 (동일한 레이어를 다시!)
  [레이어 52 ~ 79]
     ↓
    출력
</code></pre></div>    </div>
  </li>
</ul>

<hr />

<h3 id="궁금증">궁금증</h3>

<p>복제 레이어를 늘려서 원래 레이어 곧장 뒤에 붙이는지, 모든 레이어가 끝난 다음에 복제된 레이어를 마지막에 한번 더 돌리는건지 궁금했다. 찾아본 결과로는 원래 레이어의 바로 뒤에 복제 레이어가 붙는데,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[레이어 45] → [레이어 45] → [레이어 46] → …
</code></pre></div></div>

<p>이런 식이 아니라</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[레이어 45] → [레이어 46] → ... -&gt; [레이어 51]
-&gt; [레이어 45] → [레이어 46] → ... [레이어 51]
</code></pre></div></div>

<p>다음과 같이 크게 두 바퀴를 돈다고 생각하면 될 것 같다.</p>]]></content><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><category term="Issues" /><category term="AI/ML" /><category term="LLM" /><category term="AI" /><category term="ML" /><category term="DL" /><category term="LLM Layer" /><summary type="html"><![CDATA[2026-03-22 https://dnhkng.github.io/posts/rys/]]></summary></entry><entry><title type="html">LLM based Wordpress Plugin Web Vulnerability Scanner Agent Develop</title><link href="https://zevvoo.github.io/2026/03/23/Wordpress-Vuln-Scanner/" rel="alternate" type="text/html" title="LLM based Wordpress Plugin Web Vulnerability Scanner Agent Develop" /><published>2026-03-23T00:00:00+09:00</published><updated>2026-03-23T00:00:00+09:00</updated><id>https://zevvoo.github.io/2026/03/23/Wordpress-Vuln-Scanner</id><content type="html" xml:base="https://zevvoo.github.io/2026/03/23/Wordpress-Vuln-Scanner/"><![CDATA[<h1 id="llm-based-wordpress-plugin-web-vulnerability-scanner-agent-develop-1">LLM based Wordpress Plugin Web Vulnerability Scanner Agent Develop (1)</h1>

<p>작년에 동아리 팀 프로젝트로 진행하다 중단 되었던 <strong>“LLM 기반 워드프레스 플러그인의 웹 취약점을 자동으로 분석하고 찾아내어 취약한 코드 부분과 취약한 수준, 시큐어 코딩 방법 등까지 보고서로 만들어주는 스캐너 에이전트 개발”</strong>을 이어서 해보려고 한다.</p>

<h2 id="구상-및-계획">구상 및 계획</h2>]]></content><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><category term="Projects" /><category term="Agent" /><category term="LLM" /><category term="RAG" /><category term="Agent" /><category term="Wordpress" /><category term="Vulnerability" /><category term="Scanner" /><summary type="html"><![CDATA[LLM based Wordpress Plugin Web Vulnerability Scanner Agent Develop (1)]]></summary></entry><entry><title type="html">Studying Web Hacking (XSS)</title><link href="https://zevvoo.github.io/2026/03/22/Web-XSS/" rel="alternate" type="text/html" title="Studying Web Hacking (XSS)" /><published>2026-03-22T00:00:00+09:00</published><updated>2026-03-22T00:00:00+09:00</updated><id>https://zevvoo.github.io/2026/03/22/Web-XSS</id><content type="html" xml:base="https://zevvoo.github.io/2026/03/22/Web-XSS/"><![CDATA[<h1 id="xss">XSS</h1>

<h2 id="xss란">XSS란?</h2>

<p>XSS(Cross-Site Scripting)</p>

<ul>
  <li>공격자가 웹페이지에서 자신이 만든 스크립트나 실행 가능한 브라우저 동작을 끼워 넣어, 그 페이지를 보는 사용자의 브라우저에서 실행되게 만드는 취약점</li>
  <li>“서버가 털린다” 보단 사용자 세션/권한/화면/행동이 탈취 또는 조작된다</li>
</ul>

<p>위험한 이유</p>

<ul>
  <li>로그인된 사용자의 권한으로 요청을 보낼 수 있음</li>
  <li>페이지 내용을 바꿔 피싱 UI를 띄울 수 있음</li>
  <li>민감한 화면 정보, 토큰, 사용자 입력을 가로챌 수 있음</li>
  <li>관리자 페이지에 발생하면 피해가 훨씬 큼</li>
</ul>

<p>성립 조건</p>

<p>3단계가 연결되면 생김</p>

<ul>
  <li>공격자가 제어 가능한 입력값이 들어옴</li>
  <li>그 값이 적절히 인코딩/검증/정제되지 않는다</li>
  <li>브라우저가 그 값을 “문자열”이 아니라 “HTML/속성/스크립트”로 해석하는 위치에 넣는다</li>
</ul>

<p>→ XSS는 <strong>“사용자 입력이 위험한 컨텍스트로 들어가는 문제”</strong></p>

<hr />

<h2 id="컨텍스트란">컨텍스트란</h2>

<ol>
  <li>
    <p>HTML 본문 컨텍스트</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;div&gt;</span>USER_INPUT<span class="nt">&lt;/div&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>HTML 속성 컨텍스트</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;input</span> <span class="na">value=</span><span class="s">"USER_INPUT"</span><span class="nt">&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>URL 컨텍스트</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"USER_INPUT"</span><span class="nt">&gt;</span>link<span class="nt">&lt;/a&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>JavaScript 컨텍스트</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;script&gt;</span>
 	<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">USER_INPUT</span><span class="dl">"</span><span class="p">;</span>
 <span class="o">&lt;</span><span class="sr">/sciprt</span><span class="err">&gt;
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>DOM sink 컨텍스트</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">element</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">userInput</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
</ol>

<hr />

<h2 id="대표적-종류">대표적 종류</h2>

<h3 id="1-reflected-xss">1. Reflected XSS</h3>

<p>사용자 요청에 들어간 값이 바로 응답 HTML에 반사됨</p>

<p>예) 검색어, 에러 메시지, URL 파라미터</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 취약 예시 ##
<span class="nt">&lt;p&gt;</span>검색어: USER_INPUT<span class="nt">&lt;p/&gt;</span>

서버가 USER_INPUT을 그대로 넣으면 Reflected XSS
</code></pre></div></div>

<h3 id="2-stored-xss">2. Stored XSS</h3>

<p>공격 문자열이 DB 등에 저장됐다가 나중에 다른 사용자에게 보여질 때 실행</p>

<p>예) 게시글, 댓글, 닉네임, 관리자 메모</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 취약 예시 ##
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"comment"</span><span class="nt">&gt;</span>SUER_COMMENT<span class="nt">&lt;/div&gt;</span>

댓글 저장 시 필터링/정제가 부실하면 Stored XSS
</code></pre></div></div>

<h3 id="3-dom-based-xss">3. Dom-based XSS</h3>

<p>서버 응답 자체보다, 브라우저의 JS 코드가 URL,입력값,postMessage 같은 값을 읽어서 DOM에 위험하게 넣을 때 발생합니다.</p>

<ul>
  <li>
    <p>DOM : Document Object Model(문서 객체 모델)</p>

    <p>웹페이지(HTML)를 컴퓨터가 이해하기 쉽게 구조로 바꿔놓은 것</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;html&gt;</span>
    <span class="nt">&lt;body&gt;</span>
      <span class="nt">&lt;h1&gt;</span>안녕<span class="nt">&lt;/h1&gt;</span>
      <span class="nt">&lt;p&gt;</span>내용<span class="nt">&lt;/p&gt;</span>
    <span class="nt">&lt;/body&gt;</span>
  <span class="nt">&lt;/html&gt;</span>
</code></pre></div>    </div>

    <p>위 HTML을 브라우저는 아래 트리 구조와 같이 이해</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  html
   └── body
        ├── h1 ("안녕")
        └── p ("내용")
</code></pre></div>    </div>

    <p>이 구조 전체가 DOM</p>

    <ul>
      <li>
        <p>DOM은 JS가 웹페이지를 조작할 수 있게 해줌</p>

        <p>예) 글자 바꾸기, 버튼 클릭하면 내용 변경, 팝업</p>

        <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">h1</span><span class="dl">"</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">바뀐 제목</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">##</span> <span class="nx">취약</span> <span class="nx">예시</span> <span class="err">##</span>
<span class="kd">const</span> <span class="nx">q</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">).</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">q</span><span class="dl">"</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">result</span><span class="dl">"</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">q</span><span class="p">;</span>

<span class="nx">서버가</span> <span class="nx">아니라</span> <span class="nx">브라우저</span> <span class="nx">JS가</span> <span class="nx">직접</span> <span class="nx">취약</span>
</code></pre></div></div>

<hr />

<h2 id="poc-예시">PoC 예시</h2>

<ol>
  <li>
    <p>HTML 삽입 여부 확인</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;b&gt;</span>teset<span class="nt">&lt;/b&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>이벤트 핸들러 실행 여부 확인</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">x</span> <span class="na">onerror=</span><span class="s">alert(1)</span><span class="nt">&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>DOM XSS 기본 확인</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">&lt;img src=x onerror=alert(1)&gt;</span><span class="dl">'</span><span class="p">;</span>
 <span class="nx">target</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">payload</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="자주-보이는-위험-sink">자주 보이는 위험 Sink</h2>

<p>sink : 외부 입력값이 “실제로 실행되거나 반영되는 최종 지점”</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[사용자 입력] -&gt; (가공/처리) -&gt; [sink]
</code></pre></div></div>

<p>sink는 위험이 실제로 터지는 지점</p>

<h3 id="대표적인-sink">대표적인 sink</h3>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">innerHTML</code> : 요소 안쪽 HTML을 통째로 바꿔버림</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">div</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">userInput</span><span class="p">;</span>
</code></pre></div>    </div>

    <ul>
      <li>문자열을 HTML로 해석</li>
      <li><code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> 같은 것도 실행될 수 있음</li>
    </ul>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">outerHTML</code> : 요소 자기 자신까지 포함해서 바꿔버림</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">div</span><span class="p">.</span><span class="nx">outerHTML</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">&lt;p&gt;바뀜&lt;/p&gt;</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>    </div>

    <ul>
      <li>innerHTML보다 더 강력</li>
      <li>역시 HTML로 해석됨</li>
    </ul>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">insertAdjacentHTML</code> : 특정 위치에 HTML 삽입</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">div</span><span class="p">.</span><span class="nf">insertAdjacentHTML</span><span class="p">(</span><span class="dl">"</span><span class="s2">beforeend</span><span class="dl">"</span><span class="p">,</span> <span class="nx">userInput</span><span class="p">);</span>
</code></pre></div>    </div>

    <ul>
      <li>문자열을 그대로 HTML로 넣음</li>
      <li>위치만 다를 뿐 <code class="language-plaintext highlighter-rouge">innerHTML</code>과 동일하게 위험</li>
    </ul>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">document.write()</code> : 페이지에 직접 HTML을 써버림</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">document</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="nx">userInput</span><span class="p">);</span>
</code></pre></div>    </div>

    <ul>
      <li>옛날 방식이지만 여전히 위험</li>
      <li>HTML + 스크립트 실행 가능</li>
    </ul>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">eval()</code> : 문자열을 JavaScript 코드로 실행</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nf">eval</span><span class="p">(</span><span class="nx">userInput</span><span class="p">);</span>
</code></pre></div>    </div>

    <ul>
      <li>문자열 → 코드로 변환해서 실행</li>
      <li>XSS뿐 아니라 RCE까지 이어질 수 있음</li>
    </ul>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">new Function()</code> : 문자열로 함수 생성 후 실행</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">new</span> <span class="nc">Function</span><span class="p">(</span><span class="nx">userInput</span><span class="p">);</span>
</code></pre></div>    </div>

    <ul>
      <li>eval과 거의 동일한 위험성</li>
      <li>동적으로 코드 실행</li>
    </ul>
  </li>
  <li>
    <p>문자열 기반 <code class="language-plaintext highlighter-rouge">seTimeout(), setInterval()</code></p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nf">setTimeout</span><span class="p">(</span><span class="dl">"</span><span class="s2">alert(1)</span><span class="dl">"</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</code></pre></div>    </div>

    <ul>
      <li>문자열을 코드처럼 실행함</li>
      <li>함수 대신 문자열 쓰면 위험</li>
    </ul>

    <p>안전한 방식</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nf">alert</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="mi">1000</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>예)</p>

<p>취약한 코드</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">box</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">userInpurt</span><span class="p">;</span>
</code></pre></div></div>

<p>안전한 방식</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">box</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">userInput</span><span class="p">;</span>
</code></pre></div></div>

<p>또는</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">):</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">userInput</span><span class="p">;</span>
<span class="nx">box</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</code></pre></div></div>

<p>→ 실무 감각으로</p>

<ul>
  <li>HTML로 넣지 말고 text로 넣어라</li>
  <li>문자열을 실행하지 말고 DOM API로 조립해라</li>
</ul>

<hr />

<h2 id="방어-방법">방어 방법</h2>

<ol>
  <li>출력 시 컨텍스트 맞춤 인코딩
    <ul>
      <li>HTML 본문: HTML entity encoding</li>
      <li>속성 : 속성 컨텍스트에 맞는 인코딩 + 반드시 따옴표 사용</li>
      <li>URL: URL 검증 + 허용 스킴만 허용(<code class="language-plaintext highlighter-rouge">http</code>, <code class="language-plaintext highlighter-rouge">https</code>)</li>
      <li>JS 문자열: JS 컨텍스트 인코딩</li>
      <li>CSS/스크립트 컨텍스트 직접 삽입은 가능하면 피하기</li>
    </ul>
  </li>
  <li>안전한 DOM API 사용
    <ul>
      <li><code class="language-plaintext highlighter-rouge">tesxtContent</code></li>
      <li><code class="language-plaintext highlighter-rouge">createElement</code></li>
      <li><code class="language-plaintext highlighter-rouge">setAttribute</code></li>
      <li><code class="language-plaintext highlighter-rouge">appendChild</code></li>
    </ul>
  </li>
  <li>
    <p>HTML Sanitization</p>

    <p>사용작 HTML 입력을 정말 허용해야 할 때만 sanitizer를 씀</p>

    <p>OWASP는 대표적으로 <code class="language-plaintext highlighter-rouge">DOMPurify</code> 사용을 권장</p>

    <p>단, sanitizer도 지속 업데이트가 중요</p>
  </li>
  <li>프레임워크의 위험 escape hatch 주의
    <ul>
      <li>React: <code class="language-plaintext highlighter-rouge">dangerouslySetInnerHTML</code></li>
      <li>Angular: <code class="language-plaintext highlighter-rouge">bypassSecurityTrustAs*</code></li>
      <li>Lit: <code class="language-plaintext highlighter-rouge">unsafeHTML</code></li>
    </ul>
  </li>
  <li>
    <p>CSP는 보조 수단</p>

    <p>CSP는 좋지만 주 방어책이 아님 (OWASP도 언급)</p>
  </li>
  <li>
    <p>최신 방어 포인트: Trusted Types</p>

    <p>특히 DOM XSS 방어에 유용</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-policy;
</code></pre></div>    </div>

    <p>이렇게 두면 <code class="language-plaintext highlighter-rouge">innerHTML</code> 같은 위험 sink에 문자열을 바로 넣는 습관을 강하게 통제할 수 있음</p>
  </li>
</ol>

<h2 id="헷갈리기-쉬운-포인트">헷갈리기 쉬운 포인트</h2>

<ul>
  <li>입력 검증만으로 XSS 막을 수 없음</li>
  <li>“<code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> 차단 = XSS 방어 완료”가 아님</li>
  <li>서버가 안전해 보여도 클라이언트 JS 때문에 DOM XSS가 날 수 있음</li>
  <li>템플릿 엔진 auto-escape가 있어도 속성/URL/JS 컨텍스트는 별도 사고가 남</li>
  <li>sanitizer를 썼더라도, 이후 문자열을 다시 조작하면 방어가 무력화될 수 있음</li>
</ul>

<hr />

<h2 id="실전-분석-순서">실전 분석 순서</h2>

<ol>
  <li>입력 지점 찾기
    <ul>
      <li>URL 파라미터</li>
      <li>폼 입력</li>
      <li>저장되는 게시물/댓글</li>
      <li>해시(<code class="language-plaintext highlighter-rouge">#</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">postMessage</code></li>
      <li>로컬 스토리지/세션 스토리지</li>
    </ul>
  </li>
  <li>반영 위치 확인
    <ul>
      <li>HTML 본문인가</li>
      <li>속성인가</li>
      <li>JS 문자열인가</li>
      <li>DOM API인가</li>
    </ul>
  </li>
  <li>sink 확인
    <ul>
      <li><code class="language-plaintext highlighter-rouge">innerHTML</code> 계열인지</li>
      <li>실행함수(<code class="language-plaintext highlighter-rouge">eval</code>) 계열인지</li>
      <li>URL 이동/삽입인지</li>
    </ul>
  </li>
  <li>방어 확인
    <ul>
      <li>인코딩이 컨텍스트에 맞는지</li>
      <li>sanitizer를 쓰는지</li>
      <li>Trusted Types/CSP가 있는지</li>
      <li>프레임워크 escape hatch를 쓰는지</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="요즘-실무에서-특히-중요한-xss-포인트">요즘 실무에서 특히 중요한 XSS 포인트</h2>

<ul>
  <li>단순히 <code class="language-plaintext highlighter-rouge">&lt;script&gt;...&lt;/script&gt;</code> 만 막는다고 끝나지 않음</li>
  <li>요즘은 DOM XSS, 프레임워크의 escape hatch, sanitizer 우회, 위험한 sink 사용이 더 중요</li>
  <li>OWASP는 React의 <code class="language-plaintext highlighter-rouge">dangerouslySetInnerHTML</code>, Angular의 <code class="language-plaintext highlighter-rouge">bypassSecurityTrustAs*</code>, Lit의 <code class="language-plaintext highlighter-rouge">unsageHTML</code> 같은 지점을 대표 위험 지점으로 봄</li>
  <li>MDN 기준으로 Trusted Types 관련 CSP(require-trusted-types-for, trusted-types)는 2026년 2월부터 최신 브라우저군에서 Baseline 2026으로 표시되고 있어, 최신 프론트엔드 방어에서 중요성이 더 커짐. 다만 구형 브라우저는 별도 고려가 필요</li>
  <li>
    <p>OWASP는 CSP/WAF만으로 XSS를 막으려 하지 말고, 컨텍스트별 출력 인코딩 + 안전한 DOM API + HTML Sanitization을 기본으로 하라고 권장</p>
  </li>
  <li>
    <p>CSP (Content Security Policy)</p>

    <p>웹사이트에서 “어떤 코드만 실행해도 되는지” 정해놓는 보안 규칙</p>

    <p>= 출입 가능한 코드만을 미리 정해놓는 출입통제 시스템</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://evil.com/hack.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div>    </div>

    <p>다음과 같은 XSS 스크립트 삽입 공격이 있을 때 CSP가 있으면 <code class="language-plaintext highlighter-rouge">evil.com</code> 을 차단한다.</p>

    <p>Trusted Types</p>

    <p>위험한 HTML/스크립트를 아무 문자열로 넣지 못하게 막는 규칙</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">element</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">userInput</span><span class="p">;</span>
</code></pre></div>    </div>

    <p>만약 이 JS 코드에서 userInput이 아래와 같으면</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;script&gt;</span><span class="nf">alert</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="nt">&lt;/script&gt;</span>
</code></pre></div>    </div>

    <p>바로 XSS 발생!!</p>
  </li>
</ul>

<hr />]]></content><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><category term="Security" /><category term="Web" /><category term="Web" /><category term="웹해킹" /><category term="해킹" /><category term="XSS" /><summary type="html"><![CDATA[XSS]]></summary></entry><entry><title type="html">Github Blog 개설</title><link href="https://zevvoo.github.io/2026/03/15/my-first-post/" rel="alternate" type="text/html" title="Github Blog 개설" /><published>2026-03-15T12:00:00+09:00</published><updated>2026-03-15T12:00:00+09:00</updated><id>https://zevvoo.github.io/2026/03/15/my-first-post</id><content type="html" xml:base="https://zevvoo.github.io/2026/03/15/my-first-post/"><![CDATA[<p>안녕하세요, <strong>고려대학교 세종캠퍼스 컴퓨터소프트웨어학과 유제우</strong>입니다.</p>

<p>대규모 언어 모델(LLM)을 활용하여 <strong>AI 에이전트</strong>를 개발하는 것을 목표로 하고 있습니다.</p>

<p>최근 보안에도 관심이 생겨 해킹 연구 동아리 <strong>“KUality”</strong>에 속해있습니다.</p>

<p>웹해킹에 관심이 있어 공부 중입니다.</p>

<p>앞으로 공부한 내용과 진행 중인 프로젝트, 사소한 IT/보안 지식에 관한 정리들을 블로그에 올릴 예정입니다.</p>

<p>이 깃허브 블로그가 꽉 채워지는 그 날까지 열심히 공부하겠습니다.</p>

<p>감사합니다.</p>

<hr />

<p>Hello, I’m <strong>Jewoo Yoo</strong>, a student in the <strong>Department of Computer Software at Korea University.</strong></p>

<p>My goal is to <strong>develop AI agents</strong> using large language models (LLMs).</p>

<p>Recently, I’ve also become interested in security and I am a member of the hacking research club <strong>“KUality”.</strong></p>

<p>I’m currently studying web hacking.</p>

<p>I plan to regularly share what I learn, ongoing projects and various insights related to IT and security on this blog.</p>

<p>I’ll keep studying and working hard until this GitHub blog is filled with meaningful content.</p>

<p>Thank you.</p>]]></content><author><name>zevvoo</name><email>jewoo010204@gmail.com</email></author><category term="Blog" /><category term="About Me" /><category term="Blog" /><category term="Github" /><category term="Github-Blog" /><summary type="html"><![CDATA[안녕하세요, 고려대학교 세종캠퍼스 컴퓨터소프트웨어학과 유제우입니다.]]></summary></entry></feed>