<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>IHHH-CTF on Stacknoah</title>
    <link>https://stacknoah.com/tags/ihhh-ctf/</link>
    <description>Recent content in IHHH-CTF on Stacknoah</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Mon, 13 Jul 2026 22:19:09 +0900</lastBuildDate>
    <atom:link href="https://stacknoah.com/tags/ihhh-ctf/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>onBoard</title>
      <link>https://stacknoah.com/writeup/ctf/ihhh-ctf/web/onboard/</link>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://stacknoah.com/writeup/ctf/ihhh-ctf/web/onboard/</guid>
      <description>&lt;h2 id=&#34;한-줄&#34;&gt;한 줄&lt;/h2&gt;
&lt;p&gt;게시판 글의 avatar 필드가 &lt;code&gt;&amp;lt;img src=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt; 속성값에 박히는데, 커스텀 escaper가 따옴표를 안 막아 속성 탈출 → stored XSS. 봇 localStorage의 flag를 OOB로 유출.&lt;/p&gt;
&lt;h2 id=&#34;구조&#34;&gt;구조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;board: 게시판. &lt;code&gt;POST /api/posts&lt;/code&gt;로 글 작성. &lt;code&gt;GET /post/:id&lt;/code&gt;는 정적 셸만 주고 &lt;code&gt;post.js&lt;/code&gt;가 클라에서 렌더 → DOM XSS 지점&lt;/li&gt;
&lt;li&gt;bot: 신고된 &lt;code&gt;/post/:id&lt;/code&gt; 방문 전 &lt;code&gt;localStorage.setItem(&#39;flag&#39;, FLAG)&lt;/code&gt; (board 오리진), 6초 대기&lt;/li&gt;
&lt;li&gt;승리조건: board 오리진에서 JS 실행 → &lt;code&gt;localStorage.getItem(&#39;flag&#39;)&lt;/code&gt; → 내 서버로 유출 (blind → OOB)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;취약점&#34;&gt;취약점&lt;/h2&gt;
&lt;p&gt;post.js가 필드를 escapeHtml 거쳐 innerHTML로 렌더. escaper는 &lt;code&gt;&amp;amp; &amp;lt; &amp;gt;&lt;/code&gt;만 치환하고 따옴표(&lt;code&gt;&amp;quot;&lt;/code&gt; &lt;code&gt;&#39;&lt;/code&gt;)는 안 건드림:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Question</title>
      <link>https://stacknoah.com/writeup/ctf/ihhh-ctf/web/question/</link>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://stacknoah.com/writeup/ctf/ihhh-ctf/web/question/</guid>
      <description>&lt;h2 id=&#34;한-줄&#34;&gt;한 줄&lt;/h2&gt;
&lt;p&gt;공개 app의 URL 프리뷰(SSRF)로 내부 admin에 도달, admin의 curl에 file://를 먹여 로컬 flag 파일 읽기&lt;/p&gt;
&lt;h2 id=&#34;구조&#34;&gt;구조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;app (공개, :3000) + admin (내부, :8080, flag 보유)&lt;/li&gt;
&lt;li&gt;docker-compose에서 app만 ports로 외부 노출, admin은 expose라 내부 컨테이너에서만 접근 가능&lt;/li&gt;
&lt;li&gt;flag는 admin 컨테이너의 /flag (Dockerfile의 &lt;code&gt;COPY flag /flag&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;취약점&#34;&gt;취약점&lt;/h2&gt;
&lt;p&gt;두 지점&lt;/p&gt;
&lt;h3 id=&#34;app-ssrf&#34;&gt;app SSRF&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;request&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;args&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c678dd&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;parsed&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;scheme&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;in&lt;/span&gt; {&lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;https&amp;#34;&lt;/span&gt;}:   &lt;span style=&#34;color:#7f848e&#34;&gt;# 스킴만 검증&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#c678dd&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;bad_request&lt;/span&gt;(&lt;span style=&#34;color:#56b6c2&#34;&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;requests&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e06c75&#34;&gt;url&lt;/span&gt;, &lt;span style=&#34;color:#56b6c2&#34;&gt;...&lt;/span&gt;)                        &lt;span style=&#34;color:#7f848e&#34;&gt;# 목적지 무검증, 서버가 임의 url fetch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;목적지(host) 검증 없음 → url에 http://admin:8080 넣으면 서버가 내부 admin에 요청&lt;/li&gt;
&lt;li&gt;app은 같은 도커 네트워크라 admin에 닿고, 나는 직접 못 닿음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;admin-파일-읽기&#34;&gt;admin 파일 읽기&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;target&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;request&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;args&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;subprocess&lt;/span&gt;&lt;span style=&#34;color:#56b6c2&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#e06c75&#34;&gt;run&lt;/span&gt;([&lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;curl&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#98c379&#34;&gt;&amp;#34;--silent&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#56b6c2&#34;&gt;...&lt;/span&gt;, &lt;span style=&#34;color:#e06c75&#34;&gt;target&lt;/span&gt;])   &lt;span style=&#34;color:#7f848e&#34;&gt;# 리스트형, 스킴 검증 없음&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;리스트형 subprocess = 셸 없음 → command injection 불가, argument injection만 가능&lt;/li&gt;
&lt;li&gt;curl은 file:// 지원, admin은 스킴 무검증 → curl file:///flag로 로컬 파일 읽기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;핵심-개념&#34;&gt;핵심 개념&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SSRF: 요청 목적지가 하드코딩이 아니라 사용자 입력(데이터), 서버 권한으로 외부에서 못 닿는 내부에 접근&lt;/li&gt;
&lt;li&gt;2단 체인이 강제된 이유: 공개면은 스킴을 http/https로 제한해 file:// 직접 불가, 내부면은 스킴 무제한 → 공개면으로 내부에 닿은 뒤 내부면에서 file:// 사용, 방어의 비대칭&lt;/li&gt;
&lt;li&gt;리스트형 subprocess는 각 원소가 argv 한 칸으로 직행 → 셸 메타문자 안 먹음, command injection 아님&lt;/li&gt;
&lt;li&gt;curl 다중 스킴: file://로 로컬 파일 읽기, 사용자 입력을 curl에 그대로 넘기면 파일읽기 증폭기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;익스플로잇&#34;&gt;익스플로잇&lt;/h2&gt;
&lt;p&gt;preview 박스에:&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
