<?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>LFR on Stacknoah</title>
    <link>https://stacknoah.com/tags/lfr/</link>
    <description>Recent content in LFR on Stacknoah</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Wed, 15 Jul 2026 21:15:18 +0900</lastBuildDate>
    <atom:link href="https://stacknoah.com/tags/lfr/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>web-HTTP-CLI</title>
      <link>https://stacknoah.com/writeup/wargame/ssrf/http-web-cli/</link>
      <pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://stacknoah.com/writeup/wargame/ssrf/http-web-cli/</guid>
      <description>&lt;h2 id=&#34;승리-조건&#34;&gt;승리 조건&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;서버가 입력받은 url을 &lt;code&gt;urllib.request.urlopen(url)&lt;/code&gt;로 열어줌&lt;/li&gt;
&lt;li&gt;목표 = 로컬 &lt;code&gt;flag.txt&lt;/code&gt; 읽기 (file:// 스킴으로 로컬 파일 읽기)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;진행&#34;&gt;진행&lt;/h2&gt;
&lt;h3 id=&#34;1-구조-파악&#34;&gt;1. 구조 파악&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Flask 아님. raw TCP 소켓 서비스 → 브라우저 X, &lt;code&gt;nc host port&lt;/code&gt;로 접속&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; 프롬프트에 url 입력 → &lt;code&gt;urlopen(url)&lt;/code&gt; 결과 반환&lt;/li&gt;
&lt;li&gt;상단 &lt;code&gt;FLAG = open(&#39;./flag.txt&#39;).read()&lt;/code&gt;는 미사용 잉여코드, 진짜 타겟은 디스크의 flag.txt 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;2-스킴-관찰--lfr-가능성&#34;&gt;2. 스킴 관찰 → LFR 가능성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests&lt;/code&gt;(crawling)와 달리 &lt;code&gt;urllib&lt;/code&gt;은 file:// 스킴 지원&lt;/li&gt;
&lt;li&gt;&lt;code&gt;urlopen(&#39;file:///path&#39;)&lt;/code&gt; = 네트워크 안 타고 로컬 디스크 읽기 → HTTP SSRF가 아니라 파일 읽기(LFR) 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-필터-확인&#34;&gt;3. 필터 확인&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;host&lt;/span&gt;, &lt;span style=&#34;color:#e06c75&#34;&gt;port&lt;/span&gt;) &lt;span style=&#34;color:#56b6c2&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;get_host_port&lt;/span&gt;(&lt;span style=&#34;color:#e06c75&#34;&gt;url&lt;/span&gt;)   &lt;span style=&#34;color:#7f848e&#34;&gt;# url.split(&amp;#39;://&amp;#39;)[1].split(&amp;#39;/&amp;#39;)[0].lower().split(&amp;#39;:&amp;#39;)&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:#98c379&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;host&lt;/span&gt;: &lt;span style=&#34;color:#e06c75&#34;&gt;block&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:#98c379&#34;&gt;&amp;#39;dreamhack.io&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;host&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#98c379&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#56b6c2&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#e06c75&#34;&gt;host&lt;/span&gt;: &lt;span style=&#34;color:#e06c75&#34;&gt;block&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;urlopen&lt;/span&gt;(&lt;span style=&#34;color:#e06c75&#34;&gt;url&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;입력 url 하나가 검증(get_host_port)과 실제 fetch(urlopen)에 동시에 쓰임 → 한 문자열로 둘 다 만족해야 함&lt;/li&gt;
&lt;li&gt;제약: netloc에 콜론 1개(언패킹) + host는 localhost 아님 + 점 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;4-실패-케이스&#34;&gt;4. 실패 케이스&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file:///flag.txt&lt;/code&gt; → host가 빈 문자열 1개라 &lt;code&gt;(host,port)&lt;/code&gt; 언패킹 에러&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file://host3.dreamhack.games/...&lt;/code&gt; → host에 점 → 차단&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%20&lt;/code&gt;/&lt;code&gt;\u0020&lt;/code&gt;로 host 채우기 → urlopen이 공백을 host로 보고 non-local 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;5-핵심&#34;&gt;5. 핵심&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;file://의 &amp;ldquo;로컬&amp;rdquo; host는 loopback IP가 아니라 빈 문자열(&lt;code&gt;file:///path&lt;/code&gt;의 슬래시 3개)&lt;/li&gt;
&lt;li&gt;빈 host는 필터 통과(점 없음, localhost 아님)&lt;/li&gt;
&lt;li&gt;빈 host + 콜론 하나면 언패킹도 성립:&lt;/li&gt;
&lt;/ul&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;/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;file://:1/flag.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;→ split(&amp;#39;:&amp;#39;) = [&amp;#39;&amp;#39;, &amp;#39;1&amp;#39;] → (host=&amp;#39;&amp;#39;, port=&amp;#39;1&amp;#39;)   통과
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;→ urlopen이 빈 host라 로컬 읽기
&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;h3 id=&#34;6-경로-확정--flag&#34;&gt;6. 경로 확정 → flag&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flag.txt&lt;/code&gt;는 상대경로라 cwd 절대경로 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/proc/self/cwd/flag.txt&lt;/code&gt;로 확정(추측 불요), 또는 흔한 &lt;code&gt;/app&lt;/code&gt; 추정&lt;/li&gt;
&lt;/ul&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;file://:11944/app/flag.txt
&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;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;DH{5e2c73de0f2b273731665914bfaff022}
&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;h2 id=&#34;배운-점&#34;&gt;배운 점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;urllib&lt;/code&gt;은 file:// 스킴 지원 → 임의 파일 읽기(LFR). 요청 함수가 어떤 스킴을 여는지 항상 확인(requests=http만, urllib=file 포함)&lt;/li&gt;
&lt;li&gt;같은 입력이 &amp;ldquo;검증&amp;quot;과 &amp;ldquo;실행&amp;quot;에 동시에 쓰이면, 둘을 동시에 만족하는 단일 페이로드가 우회의 열쇠&lt;/li&gt;
&lt;li&gt;file://의 로컬 host = 빈 문자열. loopback IP로 착각 금지&lt;/li&gt;
&lt;li&gt;파일 읽기로 경로를 모를 땐 &lt;code&gt;/proc/self/cwd&lt;/code&gt;로 cwd 확정. LFR은 RCE가 아니므로 상한은 파일 읽기&lt;/li&gt;
&lt;li&gt;방어: 스킴 화이트리스트(http/https만 허용), 검증과 실제 요청의 대상 일치 보장&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
  </channel>
</rss>
