<?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>SSRF on Stacknoah</title>
    <link>https://stacknoah.com/writeup/wargame/ssrf/</link>
    <description>Recent content in SSRF on Stacknoah</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Tue, 30 Jun 2026 20:45:41 +0900</lastBuildDate>
    <atom:link href="https://stacknoah.com/writeup/wargame/ssrf/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Pathtraversal (DH, Sprout)</title>
      <link>https://stacknoah.com/writeup/wargame/ssrf/pathtraversal/</link>
      <pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://stacknoah.com/writeup/wargame/ssrf/pathtraversal/</guid>
      <description></description>
    </item>
    <item>
      <title>web-ssrf (DH, S2)</title>
      <link>https://stacknoah.com/writeup/wargame/ssrf/web-ssrf/</link>
      <pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://stacknoah.com/writeup/wargame/ssrf/web-ssrf/</guid>
      <description>&lt;!--
■ 고정 형식 규칙
1) 섹션은 항상 아래 5개. 이름·순서 고정. 해당 없으면 &#34;—&#34;로 비우되 칸은 지우지 않는다.
2) 제목에 &#39;문제의 내용&#39;(수신처·전송수단·도구명 등)을 올리지 않는다. 제목은 &#39;사고의 단계&#39;다.
3) &#39;공격&#39;은 [번호 흐름 → 페이로드 → 함정 → 핵심 당위] 순서 고정. ###로 쪼개지 않고 수단·검증은 번호 안에 녹인다.
■ 함정 = 실제로 빠진 막다른 길을 한 줄로. 당연히 먼저 해볼 법한데 막힌 것 → 왜 실패 → 어떻게 전환. 정답만 남기지 말 것. 여정 서사로 늘리지 말 것. 없으면 &#34;—&#34;.
■ 당위는 단계마다 풀지 말고 &#39;핵심 당위&#39; 1개로 수렴. 깊은 당위는 별도 학습노트로.
--&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;취약점: (어디서 무엇이 터지나 — 한 줄)&lt;/li&gt;
&lt;li&gt;핵심 트릭: (이 문제만의 급소/함정 — 다시 볼 때 가장 먼저 떠올릴 한 줄)&lt;/li&gt;
&lt;li&gt;페이로드: &lt;code&gt;(최종 한 방)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;분석&#34;&gt;분석&lt;/h2&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;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; 5
&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; 6
&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; 7
&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; 8
&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; 9
&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;10
&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;11
&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;12
&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;13
&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;14
&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;15
&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;16
&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;17
&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;18
&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;19
&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;20
&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;21
&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;22
&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;23
&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-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FLAG = open(&amp;#34;./flag.txt&amp;#34;, &amp;#34;r&amp;#34;).read()  # Flag is here!!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@app.route(&amp;#34;/img_viewer&amp;#34;, methods=[&amp;#34;GET&amp;#34;, &amp;#34;POST&amp;#34;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;def img_viewer():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    if request.method == &amp;#34;GET&amp;#34;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        return render_template(&amp;#34;img_viewer.html&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    elif request.method == &amp;#34;POST&amp;#34;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        url = request.form.get(&amp;#34;url&amp;#34;, &amp;#34;&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        urlp = urlparse(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        if url[0] == &amp;#34;/&amp;#34;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            url = &amp;#34;http://localhost:8000&amp;#34; + url
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        elif (&amp;#34;localhost&amp;#34; in urlp.netloc) or (&amp;#34;127.0.0.1&amp;#34; in urlp.netloc):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            data = open(&amp;#34;error.png&amp;#34;, &amp;#34;rb&amp;#34;).read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            img = base64.b64encode(data).decode(&amp;#34;utf8&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            return render_template(&amp;#34;img_viewer.html&amp;#34;, img=img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		try:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            data = requests.get(url, timeout=3).content
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            img = base64.b64encode(data).decode(&amp;#34;utf8&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        except:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            data = open(&amp;#34;error.png&amp;#34;, &amp;#34;rb&amp;#34;).read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            img = base64.b64encode(data).decode(&amp;#34;utf8&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        return render_template(&amp;#34;img_viewer.html&amp;#34;, img=img)
&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;ol&gt;
&lt;li&gt;항상 flag read/반환은 안 함 → 이 flag를 어떻게 내가 읽어낼 것인지가 이 문제의 핵심&lt;/li&gt;
&lt;li&gt;만약 /~처럼 상대경로로 접근 → localhost:8000 + url&lt;/li&gt;
&lt;li&gt;만약 localhost 입력 시도 -&amp;gt; 에러페이지 반환&lt;/li&gt;
&lt;li&gt;try~: 접근해서 컨텐츠 가져온 뒤 render_template으로 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;대상&lt;/th&gt;
          &lt;th&gt;동작&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;취약점&#34;&gt;취약점&lt;/h2&gt;
&lt;!-- 식별. &#34;왜 뚫리나.&#34; 어디가 왜 약한지 1~2줄 + 이 문제의 급소 한 줄. --&gt;
&lt;h2 id=&#34;공격&#34;&gt;공격&lt;/h2&gt;
&lt;!-- 설계+실행. &#34;어떻게 치나.&#34; 번호 흐름 하나로. 수단·검증은 번호 안에 녹인다(소제목 금지). --&gt;
&lt;ol&gt;
&lt;li&gt;(단계)&lt;/li&gt;
&lt;li&gt;(단계)  ← 필요할 때만 끝에 한 줄 당위&lt;/li&gt;
&lt;li&gt;(단계)&lt;/li&gt;
&lt;/ol&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;/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-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#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;blockquote&gt;
&lt;p&gt;함정: (당연히 먼저 해볼 법한데 막힌 것 → 왜 실패 → 어떻게 전환. 1&lt;del&gt;2줄. 없으면 &amp;ldquo;—&amp;rdquo;)
핵심 당위: (이 공격이 성립하는 단 하나의 이유. 1&lt;/del&gt;2줄. 당위는 여기로 모은다.)&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
